From 74dddd5d4397ff79698e540cfb465ab41035e2de Mon Sep 17 00:00:00 2001 From: Nikita Barsukov Date: Wed, 10 Jan 2024 13:25:46 +0300 Subject: [PATCH] feat(core): new built-in `maskitoStrictCompositionPlugin` (#881) --- projects/core/src/index.ts | 1 + projects/core/src/lib/utils/index.ts | 1 + .../lib/utils/strict-composition-plugin.ts | 33 +++++++++++++++++ .../3-strict-composition/component.ts | 21 +++++++++++ .../examples/3-strict-composition/mask.ts | 6 +++ .../plugins/plugins.component.ts | 6 +++ .../documentation/plugins/plugins.module.ts | 8 +++- .../plugins/plugins.template.html | 37 +++++++++++++++++++ 8 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 projects/core/src/lib/utils/strict-composition-plugin.ts create mode 100644 projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/component.ts create mode 100644 projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/mask.ts diff --git a/projects/core/src/index.ts b/projects/core/src/index.ts index 7bb40d606..c4c2b586c 100644 --- a/projects/core/src/index.ts +++ b/projects/core/src/index.ts @@ -15,6 +15,7 @@ export { export { maskitoInitialCalibrationPlugin, maskitoPipe, + maskitoStrictCompositionPlugin, maskitoTransform, maskitoUpdateElement, } from './lib/utils'; diff --git a/projects/core/src/lib/utils/index.ts b/projects/core/src/lib/utils/index.ts index 4b79adb6e..caed4fe26 100644 --- a/projects/core/src/lib/utils/index.ts +++ b/projects/core/src/lib/utils/index.ts @@ -7,4 +7,5 @@ export * from './get-not-empty-selection'; export * from './get-word-selection'; export * from './initial-calibration-plugin'; export * from './pipe'; +export * from './strict-composition-plugin'; export * from './transform'; diff --git a/projects/core/src/lib/utils/strict-composition-plugin.ts b/projects/core/src/lib/utils/strict-composition-plugin.ts new file mode 100644 index 000000000..47238fde2 --- /dev/null +++ b/projects/core/src/lib/utils/strict-composition-plugin.ts @@ -0,0 +1,33 @@ +import {ElementState, MaskitoPlugin, TypedInputEvent} from '../types'; +import {maskitoUpdateElement} from './dom/update-element'; +import {areElementStatesEqual} from './element-states-equality'; +import {maskitoTransform} from './transform'; + +export function maskitoStrictCompositionPlugin(): MaskitoPlugin { + return (element, maskitoOptions) => { + const listener = (event: TypedInputEvent): void => { + if (event.inputType !== 'insertCompositionText') { + return; + } + + const selection = [ + element.selectionStart || 0, + element.selectionEnd || 0, + ] as const; + const elementState: ElementState = { + selection, + value: element.value, + }; + const validatedState = maskitoTransform(elementState, maskitoOptions); + + if (!areElementStatesEqual(elementState, validatedState)) { + event.preventDefault(); + maskitoUpdateElement(element, validatedState); + } + }; + + element.addEventListener('input', listener as EventListener); + + return () => element.removeEventListener('input', listener as EventListener); + }; +} diff --git a/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/component.ts b/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/component.ts new file mode 100644 index 000000000..244f67a6f --- /dev/null +++ b/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/component.ts @@ -0,0 +1,21 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; + +import mask from './mask'; + +@Component({ + selector: 'plugins-strict-composition-doc-example-3', + template: ` + + Enter number + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PluginsDocExample3 { + readonly maskitoOptions = mask; + value = ''; +} diff --git a/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/mask.ts b/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/mask.ts new file mode 100644 index 000000000..6e0baa169 --- /dev/null +++ b/projects/demo/src/pages/documentation/plugins/examples/3-strict-composition/mask.ts @@ -0,0 +1,6 @@ +import {MaskitoOptions, maskitoStrictCompositionPlugin} from '@maskito/core'; + +export default { + mask: /^[0-90-9]*$/, + plugins: [maskitoStrictCompositionPlugin()], +} as MaskitoOptions; diff --git a/projects/demo/src/pages/documentation/plugins/plugins.component.ts b/projects/demo/src/pages/documentation/plugins/plugins.component.ts index 54b4edfc8..4a353699a 100644 --- a/projects/demo/src/pages/documentation/plugins/plugins.component.ts +++ b/projects/demo/src/pages/documentation/plugins/plugins.component.ts @@ -22,4 +22,10 @@ export class PluginsDocPageComponent { ), 'index.ts': import('./examples/2-initial-calibration/index.ts?raw'), }; + + readonly strictCompositionExample: TuiDocExample = { + [DocExamplePrimaryTab.MaskitoOptions]: import( + './examples/3-strict-composition/mask.ts?raw' + ), + }; } diff --git a/projects/demo/src/pages/documentation/plugins/plugins.module.ts b/projects/demo/src/pages/documentation/plugins/plugins.module.ts index 9b745d700..578d16909 100644 --- a/projects/demo/src/pages/documentation/plugins/plugins.module.ts +++ b/projects/demo/src/pages/documentation/plugins/plugins.module.ts @@ -10,6 +10,7 @@ import {TuiInputModule} from '@taiga-ui/kit'; import {NextStepsModule} from '../next-steps/next-steps.module'; import {PluginsDocExample1} from './examples/1-reject/component'; import {PluginsDocExample2} from './examples/2-initial-calibration/component'; +import {PluginsDocExample3} from './examples/3-strict-composition/component'; import {PluginsDocPageComponent} from './plugins.component'; @NgModule({ @@ -24,7 +25,12 @@ import {PluginsDocPageComponent} from './plugins.component'; TuiAddonDocModule, RouterModule.forChild(tuiGenerateRoutes(PluginsDocPageComponent)), ], - declarations: [PluginsDocPageComponent, PluginsDocExample1, PluginsDocExample2], + declarations: [ + PluginsDocPageComponent, + PluginsDocExample1, + PluginsDocExample2, + PluginsDocExample3, + ], exports: [PluginsDocPageComponent], }) export class PluginsDocPageModule {} diff --git a/projects/demo/src/pages/documentation/plugins/plugins.template.html b/projects/demo/src/pages/documentation/plugins/plugins.template.html index 036a9615c..1399f587b 100644 --- a/projects/demo/src/pages/documentation/plugins/plugins.template.html +++ b/projects/demo/src/pages/documentation/plugins/plugins.template.html @@ -45,5 +45,42 @@ + + +

+ By default, + Maskito + does not break IME Composition and waits until + + compositionend + + fires to begin calibration of the textfield's value. It is especially important for East Asian languages + such as Chinese, Japanese, Korean, and other languages with complex characters. +

+ +

+ However, sometimes this behaviour is not desired and you can want to enable mask validation on every + keystroke (to be like a classic not-composition input). For example, some Android devices with enabled + system autocomplete can interpret user's input as part of composition event – waiting for + compositionend + can be not required for some cases (e.g. entering of numbers or your application is not used by East + Asian clients). For this cases, you can use + maskitoStrictCompositionPlugin + . It applies mask's constraints on ANY intermediate value of IME composition. +

+
+ +
+