diff --git a/.changeset/plenty-hoops-brake.md b/.changeset/plenty-hoops-brake.md new file mode 100644 index 0000000000..15118dc118 --- /dev/null +++ b/.changeset/plenty-hoops-brake.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-elements": patch +--- + +feat: x-input && x-textarea add attribute input-filter, which can filter input value. diff --git a/packages/web-platform/web-elements/src/XInput/XInputEvents.ts b/packages/web-platform/web-elements/src/XInput/XInputEvents.ts index fc692b0627..04a677435b 100644 --- a/packages/web-platform/web-elements/src/XInput/XInputEvents.ts +++ b/packages/web-platform/web-elements/src/XInput/XInputEvents.ts @@ -15,7 +15,7 @@ import { registerEventEnableStatusChangeHandler } from '@lynx-js/web-elements-re export class XInputEvents implements InstanceType> { - static observedAttributes = ['send-composing-input']; + static observedAttributes = ['send-composing-input', 'input-filter']; #dom: HTMLElement; #sendComposingInput = false; @@ -29,8 +29,9 @@ export class XInputEvents '#form', ); + @registerAttributeHandler('input-filter', true) @registerEventEnableStatusChangeHandler('input') - #handleEnableInputEvent(status: boolean) { + #handleEnableInputEvent(status: boolean | string | null) { const input = this.#getInputElement(); if (status) { input.addEventListener( @@ -74,16 +75,20 @@ export class XInputEvents #teleportInput = (event: InputEvent) => { const input = this.#getInputElement(); - const value = input.value; + const inputFilter = this.#dom.getAttribute('input-filter'); + const filterValue = inputFilter + ? input.value.replace(new RegExp(inputFilter, 'g'), '') + : input.value; const isComposing = event.isComposing; + input.value = filterValue; if (isComposing && !this.#sendComposingInput) return; this.#dom.dispatchEvent( new CustomEvent('input', { ...commonComponentEventSetting, detail: { - value, + value: filterValue, /** @deprecated */ - textLength: value.length, + textLength: filterValue.length, /** @deprecated */ cursor: input.selectionStart, isComposing, @@ -96,16 +101,20 @@ export class XInputEvents #teleportCompositionendInput = () => { const input = this.#getInputElement(); - const value = input.value; + const inputFilter = this.#dom.getAttribute('input-filter'); + const filterValue = inputFilter + ? input.value.replace(new RegExp(inputFilter, 'g'), '') + : input.value; + input.value = filterValue; // if #sendComposingInput set true, #teleportInput will send detail if (!this.#sendComposingInput) { this.#dom.dispatchEvent( new CustomEvent('input', { ...commonComponentEventSetting, detail: { - value, + value: filterValue, /** @deprecated */ - textLength: value.length, + textLength: filterValue.length, /** @deprecated */ cursor: input.selectionStart, isComposing: false, diff --git a/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts b/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts index 6c65574d4c..b7ebbe80f1 100644 --- a/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts +++ b/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts @@ -15,7 +15,7 @@ import { registerEventEnableStatusChangeHandler } from '@lynx-js/web-elements-re export class XTextareaEvents implements InstanceType> { - static observedAttributes = ['send-composing-input']; + static observedAttributes = ['send-composing-input', 'input-filter']; #dom: HTMLElement; #sendComposingInput = false; @@ -29,8 +29,9 @@ export class XTextareaEvents '#form', ); + @registerAttributeHandler('input-filter', true) @registerEventEnableStatusChangeHandler('input') - #handleEnableConfirmEvent(status: boolean) { + #handleEnableConfirmEvent(status: string | boolean | null) { const textareaElement = this.#getTextareaElement(); if (status) { textareaElement.addEventListener( @@ -74,16 +75,20 @@ export class XTextareaEvents #teleportInput = (event: InputEvent) => { const input = this.#getTextareaElement(); - const value = input.value; + const inputFilter = this.#dom.getAttribute('input-filter'); + const filterValue = inputFilter + ? input.value.replace(new RegExp(inputFilter, 'g'), '') + : input.value; const isComposing = event.isComposing; + input.value = filterValue; if (isComposing && !this.#sendComposingInput) return; this.#dom.dispatchEvent( new CustomEvent('input', { ...commonComponentEventSetting, detail: { - value, + value: filterValue, /** @deprecated */ - textLength: value.length, + textLength: filterValue.length, /** @deprecated */ cursor: input.selectionStart, isComposing, @@ -96,16 +101,20 @@ export class XTextareaEvents #teleportCompositionendInput = () => { const input = this.#getTextareaElement(); - const value = input.value; + const inputFilter = this.#dom.getAttribute('input-filter'); + const filterValue = inputFilter + ? input.value.replace(new RegExp(inputFilter, 'g'), '') + : input.value; + input.value = filterValue; // if #sendComposingInput set true, #teleportInput will send detail if (!this.#sendComposingInput) { this.#dom.dispatchEvent( new CustomEvent('input', { ...commonComponentEventSetting, detail: { - value, + value: filterValue, /** @deprecated */ - textLength: value.length, + textLength: filterValue.length, /** @deprecated */ cursor: input.selectionStart, isComposing: false, diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index 1fcc40eb2b..3e1dfefa11 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -2261,6 +2261,18 @@ test.describe('reactlynx3 tests', () => { expect(result).toBe('2-5'); }, ); + test( + 'basic-element-x-input-input-filter', + async ({ page }, { title }) => { + await goto(page, title); + await page.locator('input').press('Enter'); + await wait(200); + await page.locator('input').fill('foobar!@#)'); + await wait(200); + const result = await page.locator('.result').first().innerText(); + expect(result).toBe('foobar'); + }, + ); }); test.describe('x-overlay-ng', () => { test('basic-element-x-overlay-ng-demo', async ({ page }, { title }) => { @@ -3578,6 +3590,19 @@ test.describe('reactlynx3 tests', () => { expect(result).toBe('2-5'); }, ); + + test( + 'basic-element-x-textarea-input-filter', + async ({ page }, { title }) => { + await goto(page, title); + await page.locator('textarea').press('Enter'); + await wait(200); + await page.locator('textarea').fill('foobar!@#)'); + await wait(200); + const result = await page.locator('.result').first().innerText(); + expect(result).toBe('foobar'); + }, + ); }); test.describe('x-audio-tt', () => { test('basic-element-x-audio-tt-play', async ({ page }, { title }) => { diff --git a/packages/web-platform/web-tests/tests/react/basic-element-x-input-input-filter/index.jsx b/packages/web-platform/web-tests/tests/react/basic-element-x-input-input-filter/index.jsx new file mode 100644 index 0000000000..d31ac2f960 --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-element-x-input-input-filter/index.jsx @@ -0,0 +1,37 @@ +// Copyright 2023 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { root, useState } from '@lynx-js/react'; + +function App() { + const value = 'bindinput'; + const [result, setResult] = useState(); + + const onInput = ({ detail }) => { + const { value, cursor, textLength } = detail; + + if (value.length !== textLength) { + throw new Error( + `input length not match. expect ${textLength}, got ${value.length}`, + ); + } + + setResult(value); + }; + + return ( + + + + {result} + + + ); +} + +root.render(); diff --git a/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-input-filter/index.jsx b/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-input-filter/index.jsx new file mode 100644 index 0000000000..c3136b730e --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-input-filter/index.jsx @@ -0,0 +1,37 @@ +// Copyright 2023 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { useState, root, useEffect } from '@lynx-js/react'; + +function App() { + const value = 'bindinput'; + const [result, setResult] = useState(); + + const onInput = ({ detail }) => { + const { value, cursor, textLength } = detail; + + if (value.length !== textLength) { + throw new Error( + `input length not match. expect ${textLength}, got ${value.length}`, + ); + } + + setResult(value); + }; + + return ( + + + + {result} + + + ); +} + +root.render();