From 2b9843f509a273171c9056492ffbd012f843cb7c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 04:30:45 +0000
Subject: [PATCH 1/5] Propagate x-input number type to inner input
Co-authored-by: PupilTong <12288479+PupilTong@users.noreply.github.com>
---
.../src/elements/XInput/InputBaseAttributes.ts | 1 +
.../web-elements/tests/web-elements.spec.ts | 10 ++++++++++
2 files changed, 11 insertions(+)
diff --git a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
index 812dc2fc85..f81525957d 100644
--- a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
+++ b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
@@ -79,6 +79,7 @@ export class InputBaseAttributes
inputMode = 'numeric';
} else if (attributeValue === 'number') {
inputMode = 'decimal';
+ inputType = 'number';
} else if (attributeValue === 'email') {
inputMode = 'email';
} else if (attributeValue === 'tel') {
diff --git a/packages/web-platform/web-elements/tests/web-elements.spec.ts b/packages/web-platform/web-elements/tests/web-elements.spec.ts
index e08433cc3c..ea1bbce51d 100644
--- a/packages/web-platform/web-elements/tests/web-elements.spec.ts
+++ b/packages/web-platform/web-elements/tests/web-elements.spec.ts
@@ -2623,6 +2623,16 @@ test.describe('web-elements test suite', () => {
},
);
+ test(
+ 'attribute-type-number-inner-input-type',
+ async ({ page }, { titlePath }) => {
+ const title = getTitle(titlePath);
+ await gotoWebComponentPage(page, title);
+ const inputType = await page.locator('input').getAttribute('type');
+ expect(inputType).toBe('number');
+ },
+ );
+
test(
'attribute-type-tel',
async ({ page }, { titlePath, title: simpleTitle }) => {
From c6841fde454dedfacb50c9e359cb9e03a6849dc5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 05:51:28 +0000
Subject: [PATCH 2/5] Co-authored-by: PupilTong
<12288479+PupilTong@users.noreply.github.com>
---
...ttribute-type-number-inner-input-type.html | 31 +++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 packages/web-platform/web-elements/tests/fixtures/x-input/attribute-type-number-inner-input-type.html
diff --git a/packages/web-platform/web-elements/tests/fixtures/x-input/attribute-type-number-inner-input-type.html b/packages/web-platform/web-elements/tests/fixtures/x-input/attribute-type-number-inner-input-type.html
new file mode 100644
index 0000000000..098e6a32f2
--- /dev/null
+++ b/packages/web-platform/web-elements/tests/fixtures/x-input/attribute-type-number-inner-input-type.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ web playground
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 72245ac8026e4424e19192a3a759e56760c581fc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 06:33:57 +0000
Subject: [PATCH 3/5] chore: add changeset for x-input fix
Co-authored-by: PupilTong <12288479+PupilTong@users.noreply.github.com>
---
.changeset/tidy-houses-smash.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/tidy-houses-smash.md
diff --git a/.changeset/tidy-houses-smash.md b/.changeset/tidy-houses-smash.md
new file mode 100644
index 0000000000..957dab4662
--- /dev/null
+++ b/.changeset/tidy-houses-smash.md
@@ -0,0 +1,5 @@
+---
+"@lynx-js/web-elements": patch
+---
+
+Fix x-input number type forwarding to the inner input element.
From edf2f9c8e81e76d1346212e9f386a2e5bf3ffc12 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:20:55 +0000
Subject: [PATCH 4/5] fix: filter invalid number input
Co-authored-by: PupilTong <12288479+PupilTong@users.noreply.github.com>
---
.github/lynx-stack.instructions.md | 1 +
.../elements/XInput/InputBaseAttributes.ts | 24 ++++++++++++++++++-
.../web-elements/tests/web-elements.spec.ts | 6 ++++-
3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/.github/lynx-stack.instructions.md b/.github/lynx-stack.instructions.md
index 4dac45fc1f..fe22e83ca5 100644
--- a/.github/lynx-stack.instructions.md
+++ b/.github/lynx-stack.instructions.md
@@ -4,3 +4,4 @@ applyTo: "packages/web-platform/web-elements/**/*"
When updating web element APIs, add targeted Playwright tests in packages/web-platform/web-elements/tests/web-elements.spec.ts and keep changes minimal.
Ensure Playwright browsers are installed (pnpm exec playwright install --with-deps ) before running web-elements tests.
+For x-input type="number" in web-elements, keep inner input type as text, set inputmode="decimal", and rely on input-filter regex to allow one dot with digits.
diff --git a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
index f81525957d..c0abf890d6 100644
--- a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
+++ b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
@@ -11,6 +11,7 @@ import {
} from '../../element-reactive/index.js';
type InputType = 'text' | 'number' | 'digit' | 'password' | 'tel' | 'email';
+const numberInputFilter = '[^0-9.]|\\.(?=.*\\.)';
/**
* shared by x-input and x-input-ng
*/
@@ -26,6 +27,7 @@ export class InputBaseAttributes
'spell-check',
];
#dom: HTMLElement;
+ #numberInputFilterOverride: string | null | undefined;
#getInputElement = genDomGetter(() => this.#dom.shadowRoot!, '#input');
@@ -62,6 +64,7 @@ export class InputBaseAttributes
@registerAttributeHandler('type', true)
_handleType(value: string | null) {
const attributeValue = value as InputType;
+ const existingInputFilter = this.#dom.getAttribute('input-filter');
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode
let inputMode:
| 'text'
@@ -79,7 +82,12 @@ export class InputBaseAttributes
inputMode = 'numeric';
} else if (attributeValue === 'number') {
inputMode = 'decimal';
- inputType = 'number';
+ if (existingInputFilter !== numberInputFilter) {
+ if (this.#numberInputFilterOverride === undefined) {
+ this.#numberInputFilterOverride = existingInputFilter;
+ }
+ this.#dom.setAttribute('input-filter', numberInputFilter);
+ }
} else if (attributeValue === 'email') {
inputMode = 'email';
} else if (attributeValue === 'tel') {
@@ -87,6 +95,20 @@ export class InputBaseAttributes
} else {
inputType = attributeValue;
}
+ if (
+ attributeValue !== 'number'
+ && this.#numberInputFilterOverride !== undefined
+ ) {
+ if (this.#numberInputFilterOverride === null) {
+ this.#dom.removeAttribute('input-filter');
+ } else {
+ this.#dom.setAttribute(
+ 'input-filter',
+ this.#numberInputFilterOverride,
+ );
+ }
+ this.#numberInputFilterOverride = undefined;
+ }
this.#setInputmode(inputMode);
this.#setType(inputType);
}
diff --git a/packages/web-platform/web-elements/tests/web-elements.spec.ts b/packages/web-platform/web-elements/tests/web-elements.spec.ts
index ea1bbce51d..6297057638 100644
--- a/packages/web-platform/web-elements/tests/web-elements.spec.ts
+++ b/packages/web-platform/web-elements/tests/web-elements.spec.ts
@@ -2629,7 +2629,11 @@ test.describe('web-elements test suite', () => {
const title = getTitle(titlePath);
await gotoWebComponentPage(page, title);
const inputType = await page.locator('input').getAttribute('type');
- expect(inputType).toBe('number');
+ expect(inputType).toBe('text');
+ const inputFilter = await page
+ .locator('x-input')
+ .getAttribute('input-filter');
+ expect(inputFilter).toBe('[^0-9.]|\\.(?=.*\\.)');
},
);
From 05b982d3b029d930bba3113bdfbd4e5dcbe46161 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 10:29:44 +0000
Subject: [PATCH 5/5] fix: internal number filtering for x-input
Co-authored-by: PupilTong <12288479+PupilTong@users.noreply.github.com>
---
.github/lynx-stack.instructions.md | 2 +-
.../elements/XInput/InputBaseAttributes.ts | 23 -------------------
.../src/elements/XInput/XInputEvents.ts | 23 ++++++++++++-------
.../web-elements/tests/web-elements.spec.ts | 8 ++-----
4 files changed, 18 insertions(+), 38 deletions(-)
diff --git a/.github/lynx-stack.instructions.md b/.github/lynx-stack.instructions.md
index fe22e83ca5..90879de3a4 100644
--- a/.github/lynx-stack.instructions.md
+++ b/.github/lynx-stack.instructions.md
@@ -4,4 +4,4 @@ applyTo: "packages/web-platform/web-elements/**/*"
When updating web element APIs, add targeted Playwright tests in packages/web-platform/web-elements/tests/web-elements.spec.ts and keep changes minimal.
Ensure Playwright browsers are installed (pnpm exec playwright install --with-deps ) before running web-elements tests.
-For x-input type="number" in web-elements, keep inner input type as text, set inputmode="decimal", and rely on input-filter regex to allow one dot with digits.
+For x-input type="number" in web-elements, keep inner input type as text, set inputmode="decimal", and filter number input internally without setting input-filter explicitly.
diff --git a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
index c0abf890d6..812dc2fc85 100644
--- a/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
+++ b/packages/web-platform/web-elements/src/elements/XInput/InputBaseAttributes.ts
@@ -11,7 +11,6 @@ import {
} from '../../element-reactive/index.js';
type InputType = 'text' | 'number' | 'digit' | 'password' | 'tel' | 'email';
-const numberInputFilter = '[^0-9.]|\\.(?=.*\\.)';
/**
* shared by x-input and x-input-ng
*/
@@ -27,7 +26,6 @@ export class InputBaseAttributes
'spell-check',
];
#dom: HTMLElement;
- #numberInputFilterOverride: string | null | undefined;
#getInputElement = genDomGetter(() => this.#dom.shadowRoot!, '#input');
@@ -64,7 +62,6 @@ export class InputBaseAttributes
@registerAttributeHandler('type', true)
_handleType(value: string | null) {
const attributeValue = value as InputType;
- const existingInputFilter = this.#dom.getAttribute('input-filter');
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode
let inputMode:
| 'text'
@@ -82,12 +79,6 @@ export class InputBaseAttributes
inputMode = 'numeric';
} else if (attributeValue === 'number') {
inputMode = 'decimal';
- if (existingInputFilter !== numberInputFilter) {
- if (this.#numberInputFilterOverride === undefined) {
- this.#numberInputFilterOverride = existingInputFilter;
- }
- this.#dom.setAttribute('input-filter', numberInputFilter);
- }
} else if (attributeValue === 'email') {
inputMode = 'email';
} else if (attributeValue === 'tel') {
@@ -95,20 +86,6 @@ export class InputBaseAttributes
} else {
inputType = attributeValue;
}
- if (
- attributeValue !== 'number'
- && this.#numberInputFilterOverride !== undefined
- ) {
- if (this.#numberInputFilterOverride === null) {
- this.#dom.removeAttribute('input-filter');
- } else {
- this.#dom.setAttribute(
- 'input-filter',
- this.#numberInputFilterOverride,
- );
- }
- this.#numberInputFilterOverride = undefined;
- }
this.#setInputmode(inputMode);
this.#setType(inputType);
}
diff --git a/packages/web-platform/web-elements/src/elements/XInput/XInputEvents.ts b/packages/web-platform/web-elements/src/elements/XInput/XInputEvents.ts
index ab7a738587..80f5c48854 100644
--- a/packages/web-platform/web-elements/src/elements/XInput/XInputEvents.ts
+++ b/packages/web-platform/web-elements/src/elements/XInput/XInputEvents.ts
@@ -19,6 +19,7 @@ export class XInputEvents
#dom: HTMLElement;
#sendComposingInput = false;
+ #numberInputFilter = /[^0-9.]|\.(?=.*\.)/g;
#getInputElement = genDomGetter(
() => this.#dom.shadowRoot!,
@@ -75,10 +76,7 @@ export class XInputEvents
#teleportInput = (event: InputEvent) => {
const input = this.#getInputElement();
- const inputFilter = this.#dom.getAttribute('input-filter');
- const filterValue = inputFilter
- ? input.value.replace(new RegExp(inputFilter, 'g'), '')
- : input.value;
+ const filterValue = this.#filterInputValue(input.value);
const isComposing = event.isComposing;
input.value = filterValue;
if (isComposing && !this.#sendComposingInput) return;
@@ -101,10 +99,7 @@ export class XInputEvents
#teleportCompositionendInput = () => {
const input = this.#getInputElement();
- const inputFilter = this.#dom.getAttribute('input-filter');
- const filterValue = inputFilter
- ? input.value.replace(new RegExp(inputFilter, 'g'), '')
- : input.value;
+ const filterValue = this.#filterInputValue(input.value);
input.value = filterValue;
// if #sendComposingInput set true, #teleportInput will send detail
if (!this.#sendComposingInput) {
@@ -126,6 +121,18 @@ export class XInputEvents
}
};
+ #filterInputValue(value: string) {
+ let filterValue = value;
+ if (this.#dom.getAttribute('type') === 'number') {
+ filterValue = filterValue.replace(this.#numberInputFilter, '');
+ }
+ const inputFilter = this.#dom.getAttribute('input-filter');
+ if (inputFilter) {
+ filterValue = filterValue.replace(new RegExp(inputFilter, 'g'), '');
+ }
+ return filterValue;
+ }
+
@registerEventEnableStatusChangeHandler('selection')
_handleEnableSelectionEvent(status: boolean) {
if (status) {
diff --git a/packages/web-platform/web-elements/tests/web-elements.spec.ts b/packages/web-platform/web-elements/tests/web-elements.spec.ts
index 6297057638..8a74efb0be 100644
--- a/packages/web-platform/web-elements/tests/web-elements.spec.ts
+++ b/packages/web-platform/web-elements/tests/web-elements.spec.ts
@@ -2630,10 +2630,6 @@ test.describe('web-elements test suite', () => {
await gotoWebComponentPage(page, title);
const inputType = await page.locator('input').getAttribute('type');
expect(inputType).toBe('text');
- const inputFilter = await page
- .locator('x-input')
- .getAttribute('input-filter');
- expect(inputFilter).toBe('[^0-9.]|\\.(?=.*\\.)');
},
);
@@ -2825,9 +2821,9 @@ test.describe('web-elements test suite', () => {
return detail;
});
await page.mouse.click(100, 25);
- await page.keyboard.type('2.');
+ await page.keyboard.type('1.2a');
await wait(200);
- expect((await confirmValue.jsonValue()).value).toBe('2.');
+ expect((await confirmValue.jsonValue()).value).toBe('1.2');
},
);