diff --git a/.changeset/plenty-moose-jump.md b/.changeset/plenty-moose-jump.md new file mode 100644 index 0000000000..de10bd1398 --- /dev/null +++ b/.changeset/plenty-moose-jump.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-elements": patch +--- + +feat: x-input and x-textarea support bindselection event, the returned type structure is `{ selectionStart: number; selectionEnd: number }`. diff --git a/packages/web-platform/web-elements/src/XInput/XInputEvents.ts b/packages/web-platform/web-elements/src/XInput/XInputEvents.ts index bad9fbaf82..610ca90593 100644 --- a/packages/web-platform/web-elements/src/XInput/XInputEvents.ts +++ b/packages/web-platform/web-elements/src/XInput/XInputEvents.ts @@ -108,6 +108,37 @@ export class XInputEvents } }; + @registerEventEnableStatusChangeHandler('selection') + #handleEnableSelectionEvent(status: boolean) { + if (status) { + this.#getInputElement().addEventListener( + 'select', + this.#selectEvent, + { + passive: true, + }, + ); + } else { + this.#getInputElement().removeEventListener( + 'select', + this.#selectEvent, + ); + } + } + + #selectEvent = () => { + const input = this.#getInputElement(); + this.#dom.dispatchEvent( + new CustomEvent('selection', { + ...commonComponentEventSetting, + detail: { + selectionStart: input.selectionStart, + selectionEnd: input.selectionEnd, + }, + }), + ); + }; + #blockHtmlEvent = (event: InputEvent) => { if ( event.target === this.#getInputElement() diff --git a/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts b/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts index 72378e5739..5c3c29bb5c 100644 --- a/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts +++ b/packages/web-platform/web-elements/src/XTextarea/XTextareaEvents.ts @@ -108,6 +108,37 @@ export class XTextareaEvents } }; + @registerEventEnableStatusChangeHandler('selection') + #handleEnableSelectionEvent(status: boolean) { + if (status) { + this.#getTextareaElement().addEventListener( + 'select', + this.#selectEvent, + { + passive: true, + }, + ); + } else { + this.#getTextareaElement().removeEventListener( + 'select', + this.#selectEvent, + ); + } + } + + #selectEvent = () => { + const input = this.#getTextareaElement(); + this.#dom.dispatchEvent( + new CustomEvent('selection', { + ...commonComponentEventSetting, + detail: { + selectionStart: input.selectionStart, + selectionEnd: input.selectionEnd, + }, + }), + ); + }; + #blockHtmlEvent = (event: FocusEvent | InputEvent) => { if ( event.target === this.#getTextareaElement() diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index ec8e5b39f3..a9ed8de5ea 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -2237,6 +2237,21 @@ test.describe('reactlynx3 tests', () => { expect(selectionEnd).toBe(true); }, ); + test( + 'basic-element-x-input-bindselection', + async ({ page }, { title }) => { + await goto(page, title); + await wait(200); + await page.evaluate(() => { + const inputDom = document.querySelector('lynx-view')?.shadowRoot + ?.querySelector('x-input')?.shadowRoot?.querySelector('input'); + inputDom?.focus(); + inputDom?.setSelectionRange(2, 5); + }); + const result = await page.locator('.result').first().innerText(); + expect(result).toBe('2-5'); + }, + ); }); test.describe('x-overlay-ng', () => { test('basic-element-x-overlay-ng-demo', async ({ page }, { title }) => { @@ -3535,6 +3550,23 @@ test.describe('reactlynx3 tests', () => { expect(selectionEnd).toBe(true); }, ); + test( + 'basic-element-x-textarea-bindselection', + async ({ page }, { title }) => { + await goto(page, title); + await wait(200); + await page.evaluate(() => { + const textareaDom = document.querySelector('lynx-view')?.shadowRoot + ?.querySelector('x-textarea')?.shadowRoot?.querySelector( + 'textarea', + ); + textareaDom?.focus(); + textareaDom?.setSelectionRange(2, 5); + }); + const result = await page.locator('.result').first().innerText(); + expect(result).toBe('2-5'); + }, + ); }); 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-bindselection/index.jsx b/packages/web-platform/web-tests/tests/react/basic-element-x-input-bindselection/index.jsx new file mode 100644 index 0000000000..87e49ef9df --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-element-x-input-bindselection/index.jsx @@ -0,0 +1,28 @@ +// 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 = 'bindselection'; + const [result, setResult] = useState(); + + const onSelection = ({ detail: { selectionStart, selectionEnd } }) => { + setResult(`${selectionStart}-${selectionEnd}`); + }; + + return ( + + + + {result} + + + ); +} + +root.render(); diff --git a/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-bindselection/index.jsx b/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-bindselection/index.jsx new file mode 100644 index 0000000000..93e5b8212a --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-element-x-textarea-bindselection/index.jsx @@ -0,0 +1,28 @@ +// 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 = 'bindselection'; + const [result, setResult] = useState(); + + const onSelection = ({ detail: { selectionStart, selectionEnd } }) => { + setResult(`${selectionStart}-${selectionEnd}`); + }; + + return ( + + + + {result} + + + ); +} + +root.render();