From 289fc8ff43284c7311e3d0260cb2c5bed111b818 Mon Sep 17 00:00:00 2001 From: Alexander Shabunevich Date: Fri, 9 Dec 2022 13:32:46 +0300 Subject: [PATCH] Optimize directive work --- src/directive.ts | 20 ++++++++--- src/mask-input.ts | 25 ++++++++++--- test/components/Custom.vue | 13 +++++++ test/components/CustomInput.vue | 11 ++++++ test/components/Model.vue | 11 ++++++ test/components/Multiple.vue | 44 +++++++++++++++++++++++ test/directive.test.ts | 64 +++++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 test/components/Custom.vue create mode 100644 test/components/CustomInput.vue create mode 100644 test/components/Model.vue create mode 100644 test/components/Multiple.vue diff --git a/src/directive.ts b/src/directive.ts index cb0ef07..526471a 100644 --- a/src/directive.ts +++ b/src/directive.ts @@ -7,13 +7,18 @@ const masks = new WeakMap() export const vMaska: MaskaDirective = (el, binding) => { const input = el instanceof HTMLInputElement ? el : el.querySelector('input') + const opts = { ...(binding.arg as MaskInputOptions) } ?? {} + if (input == null) return - if (masks.get(input) != null) { - masks.get(input)?.destroy() - } + const existed = masks.get(input) + if (existed != null) { + if (!existed.needUpdate(input, opts)) { + return + } - const opts = { ...(binding.arg as MaskInputOptions) } ?? {} + existed.destroy() + } if (binding.value != null) { const binded = binding.value @@ -32,4 +37,11 @@ export const vMaska: MaskaDirective = (el, binding) => { } masks.set(input, new MaskInput(input, opts)) + + // check initial value for v-model + setTimeout(() => { + if (input.value !== '') { + input.dispatchEvent(new InputEvent('input')) + } + }) } diff --git a/src/mask-input.ts b/src/mask-input.ts index c858c4e..a287bfc 100644 --- a/src/mask-input.ts +++ b/src/mask-input.ts @@ -22,12 +22,16 @@ export class MaskInput { target: string | NodeListOf | HTMLInputElement, readonly options: MaskInputOptions = {} ) { - const { onMaska, preProcess, postProcess, ...opts } = options - if (typeof target === 'string') { - this.init(Array.from(document.querySelectorAll(target)), opts) + this.init( + Array.from(document.querySelectorAll(target)), + this.getMaskOpts(options) + ) } else { - this.init('length' in target ? Array.from(target) : [target], opts) + this.init( + 'length' in target ? Array.from(target) : [target], + this.getMaskOpts(options) + ) } } @@ -39,6 +43,19 @@ export class MaskInput { this.items.clear() } + needUpdate (input: HTMLInputElement, opts: MaskInputOptions): boolean { + const mask = this.items.get(input) as Mask + const maskNew = new Mask(parseInput(input, this.getMaskOpts(opts))) + + return JSON.stringify(mask.opts) !== JSON.stringify(maskNew.opts) + } + + private getMaskOpts (options: MaskInputOptions): MaskOptions { + const { onMaska, preProcess, postProcess, ...opts } = options + + return opts + } + private init (inputs: HTMLInputElement[], defaults: MaskOptions): void { for (const input of inputs) { const mask = new Mask(parseInput(input, defaults)) diff --git a/test/components/Custom.vue b/test/components/Custom.vue new file mode 100644 index 0000000..1ada741 --- /dev/null +++ b/test/components/Custom.vue @@ -0,0 +1,13 @@ + + + diff --git a/test/components/CustomInput.vue b/test/components/CustomInput.vue new file mode 100644 index 0000000..a6b91ea --- /dev/null +++ b/test/components/CustomInput.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/components/Model.vue b/test/components/Model.vue new file mode 100644 index 0000000..d7f3203 --- /dev/null +++ b/test/components/Model.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/components/Multiple.vue b/test/components/Multiple.vue new file mode 100644 index 0000000..6c13e6c --- /dev/null +++ b/test/components/Multiple.vue @@ -0,0 +1,44 @@ + + + diff --git a/test/directive.test.ts b/test/directive.test.ts index 0870459..6f638f8 100644 --- a/test/directive.test.ts +++ b/test/directive.test.ts @@ -8,10 +8,13 @@ import BindUnmasked from './components/BindUnmasked.vue' import Callbacks from './components/Callbacks.vue' import Completed from './components/Completed.vue' import Config from './components/Config.vue' +import Custom from './components/Custom.vue' import DataAttr from './components/DataAttr.vue' import Dynamic from './components/Dynamic.vue' import Events from './components/Events.vue' import Hooks from './components/Hooks.vue' +import Model from './components/Model.vue' +import Multiple from './components/Multiple.vue' import Options from './components/Options.vue' import Parent from './components/Parent.vue' import Simple from './components/Simple.vue' @@ -103,6 +106,67 @@ test('bind completed', async () => { expect(wrapper.get('div').element.textContent).toBe('Completed') }) +test('v-model', async () => { + const wrapper = mount(Model) + const input = wrapper.get('input') + + await new Promise((r) => setTimeout(r)) + + expect(input.element.value).toBe('1-2') + expect(wrapper.get('div').element.textContent).toBe('1-2') + + await input.setValue('1') + expect(input.element.value).toBe('1-') + expect(wrapper.get('div').element.textContent).toBe('1-') + + await input.setValue('123') + expect(input.element.value).toBe('1-2') + expect(wrapper.get('div').element.textContent).toBe('1-2') +}) + +test('custom component', async () => { + const wrapper = mount(Custom) + const input = wrapper.get('input') + + await input.setValue('1') + expect(input.element.value).toBe('1-') + expect(wrapper.get('div').element.textContent).toBe('1-') + + await input.setValue('123') + expect(input.element.value).toBe('1-2') + expect(wrapper.get('div').element.textContent).toBe('1-2') +}) + +test('multiple inputs', async () => { + const wrapper = mount(Multiple) + const input = wrapper.get('#input1') + const checkbox = wrapper.get('#checkbox') + + await new Promise((r) => setTimeout(r)) + + expect(wrapper.get('#value1').element.textContent).toBe('1-2') + expect(wrapper.get('#value2').element.textContent).toBe('3-2') + + expect(wrapper.emitted()).toHaveProperty('mask1') + expect(wrapper.emitted()).toHaveProperty('mask2') + expect(wrapper.emitted('mask1')).toHaveLength(1) + expect(wrapper.emitted('mask2')).toHaveLength(1) + + await input.setValue('1') + expect(input.element.value).toBe('1') + expect(wrapper.get('#value1').element.textContent).toBe('1') + expect(wrapper.emitted()).toHaveProperty('mask1') + expect(wrapper.emitted('mask1')).toHaveLength(2) + expect(wrapper.emitted('mask2')).toHaveLength(1) + + await checkbox.setValue() + expect(checkbox.element).toBeChecked() + + expect(input.element.value).toBe('1-') + expect(wrapper.emitted('mask1')).toHaveLength(3) + expect(wrapper.emitted('mask2')).toHaveLength(1) +}) + test('config and bind', async () => { const wrapper = mount(Config) const input = wrapper.get('input')