From bd6b5a83093d65bf7e2b493804d87ab90bffd30f Mon Sep 17 00:00:00 2001 From: Nikita Barsukov Date: Thu, 7 Mar 2024 16:52:56 +0300 Subject: [PATCH] feat: add multi-line support + documentation example --- projects/core/src/lib/mask.ts | 11 ++++-- .../core/src/lib/utils/content-editable.ts | 5 +-- .../content-editable-doc.component.ts | 21 +++++++++-- .../content-editable-doc.template.html | 14 ++++++++ .../examples/2-multi-line/component.ts | 35 +++++++++++++++++++ .../examples/2-multi-line/mask.ts | 5 +++ .../vanilla-js.ts => vanilla-js-tab.md} | 7 ++-- 7 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/component.ts create mode 100644 projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/mask.ts rename projects/demo/src/pages/recipes/content-editable/examples/{1-time/vanilla-js.ts => vanilla-js-tab.md} (71%) diff --git a/projects/core/src/lib/mask.ts b/projects/core/src/lib/mask.ts index 45a05a8cb..a42ad675b 100644 --- a/projects/core/src/lib/mask.ts +++ b/projects/core/src/lib/mask.ts @@ -237,7 +237,11 @@ export class Maskito extends MaskHistory { initialState.value.slice(0, initialFrom) + initialState.value.slice(initialTo); - if (newPossibleValue === newElementState.value && !force) { + if ( + newPossibleValue === newElementState.value && + !force && + !this.element.isContentEditable + ) { return; } @@ -285,7 +289,10 @@ export class Maskito extends MaskHistory { return event.preventDefault(); } - if (newPossibleValue !== newElementState.value) { + if ( + newPossibleValue !== newElementState.value || + this.element.isContentEditable + ) { event.preventDefault(); this.updateElementState(newElementState, { diff --git a/projects/core/src/lib/utils/content-editable.ts b/projects/core/src/lib/utils/content-editable.ts index 53c1903c3..bfe5617dc 100644 --- a/projects/core/src/lib/utils/content-editable.ts +++ b/projects/core/src/lib/utils/content-editable.ts @@ -7,11 +7,12 @@ class ContentEditableAdapter implements TextfieldLike { constructor(private readonly element: HTMLElement) {} public get value(): string { - return this.element.textContent || ''; + return this.element.innerText.replace(/\n\n$/, '\n'); } public set value(value) { - this.element.textContent = value; + // Setting into innerHTML of element with `white-space: pre;` style + this.element.innerHTML = value.replace(/\n$/, '\n\n'); } public get selectionStart(): number | null { diff --git a/projects/demo/src/pages/recipes/content-editable/content-editable-doc.component.ts b/projects/demo/src/pages/recipes/content-editable/content-editable-doc.component.ts index 0c24c9db8..ef7dbe741 100644 --- a/projects/demo/src/pages/recipes/content-editable/content-editable-doc.component.ts +++ b/projects/demo/src/pages/recipes/content-editable/content-editable-doc.component.ts @@ -6,11 +6,18 @@ import {TuiAddonDocModule} from '@taiga-ui/addon-doc'; import {TuiLinkModule} from '@taiga-ui/core'; import {ContentEditableDocExample1} from './examples/1-time/component'; +import {ContentEditableDocExample2} from './examples/2-multi-line/component'; @Component({ standalone: true, selector: 'content-editable-doc', - imports: [TuiAddonDocModule, TuiLinkModule, RouterLink, ContentEditableDocExample1], + imports: [ + TuiAddonDocModule, + TuiLinkModule, + RouterLink, + ContentEditableDocExample1, + ContentEditableDocExample2, + ], templateUrl: './content-editable-doc.template.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -23,7 +30,17 @@ export default class ContentEditableDocComponent { protected readonly contentEditableExample1: TuiDocExample = { [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-time/mask.ts?raw'), - [DocExamplePrimaryTab.JavaScript]: import('./examples/1-time/vanilla-js.ts?raw'), + [DocExamplePrimaryTab.JavaScript]: import('./examples/vanilla-js-tab.md?raw'), [DocExamplePrimaryTab.Angular]: import('./examples/1-time/component.ts?raw'), }; + + protected readonly contentEditableExample2: TuiDocExample = { + [DocExamplePrimaryTab.MaskitoOptions]: import( + './examples/2-multi-line/mask.ts?raw' + ), + [DocExamplePrimaryTab.JavaScript]: import('./examples/vanilla-js-tab.md?raw'), + [DocExamplePrimaryTab.Angular]: import( + './examples/2-multi-line/component.ts?raw' + ), + }; } diff --git a/projects/demo/src/pages/recipes/content-editable/content-editable-doc.template.html b/projects/demo/src/pages/recipes/content-editable/content-editable-doc.template.html index 5a5f19b9a..f92418644 100644 --- a/projects/demo/src/pages/recipes/content-editable/content-editable-doc.template.html +++ b/projects/demo/src/pages/recipes/content-editable/content-editable-doc.template.html @@ -58,4 +58,18 @@ + + + + Use + white-space: pre + for multi-line mode + + + diff --git a/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/component.ts b/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/component.ts new file mode 100644 index 000000000..44d7b1a7a --- /dev/null +++ b/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/component.ts @@ -0,0 +1,35 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MaskitoDirective} from '@maskito/angular'; + +import mask from './mask'; + +@Component({ + standalone: true, + selector: 'content-editable-doc-example-2', + imports: [MaskitoDirective], + template: ` + Enter message: +

+ `, + styles: [ + ` + [contenteditable] { + white-space: pre; + border: 3px dashed lightgray; + max-width: 30rem; + padding: 1rem; + } + `, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ContentEditableDocExample2 { + protected readonly mask = mask; + protected initialText = `Hello, world! +How are you today? +Do not forget to read description of this example!`; +} diff --git a/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/mask.ts b/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/mask.ts new file mode 100644 index 000000000..42c172149 --- /dev/null +++ b/projects/demo/src/pages/recipes/content-editable/examples/2-multi-line/mask.ts @@ -0,0 +1,5 @@ +import type {MaskitoOptions} from '@maskito/core'; + +export default { + mask: /^[a-z\s.,/!?]+$/i, +} as MaskitoOptions; diff --git a/projects/demo/src/pages/recipes/content-editable/examples/1-time/vanilla-js.ts b/projects/demo/src/pages/recipes/content-editable/examples/vanilla-js-tab.md similarity index 71% rename from projects/demo/src/pages/recipes/content-editable/examples/1-time/vanilla-js.ts rename to projects/demo/src/pages/recipes/content-editable/examples/vanilla-js-tab.md index d140bc7ef..e6ce712db 100644 --- a/projects/demo/src/pages/recipes/content-editable/examples/1-time/vanilla-js.ts +++ b/projects/demo/src/pages/recipes/content-editable/examples/vanilla-js-tab.md @@ -1,3 +1,4 @@ +```ts import {Maskito, maskitoAdaptContentEditable} from '@maskito/core'; import maskitoOptions from './mask'; @@ -6,7 +7,5 @@ const element = document.querySelector('[contenteditable]')!; const maskedInput = new Maskito(maskitoAdaptContentEditable(element), maskitoOptions); -console.info( - 'Call this function when the element is detached from DOM', - maskedInput.destroy, -); +console.info('Call this function when the element is detached from DOM', maskedInput.destroy); +```