diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 56bdb89087e..2a8d8580223 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1862,6 +1862,10 @@ export namespace Components { */ "numericInput": boolean; "scrollActiveItemIntoView": () => Promise; + /** + * Sets the value prop and fires the ionChange event. This is used when we need to fire ionChange from user-generated events that cannot be caught with normal input/change event listeners. + */ + "setValue": (value?: string | number | undefined) => Promise; /** * The selected option in the picker. */ diff --git a/core/src/components/picker-column-internal/picker-column-internal.tsx b/core/src/components/picker-column-internal/picker-column-internal.tsx index 77f473e146a..1b0e709cdf4 100644 --- a/core/src/components/picker-column-internal/picker-column-internal.tsx +++ b/core/src/components/picker-column-internal/picker-column-internal.tsx @@ -144,7 +144,15 @@ export class PickerColumnInternal implements ComponentInterface { } } - private setValue(value?: string | number) { + /** + * Sets the value prop and fires the ionChange event. + * This is used when we need to fire ionChange from + * user-generated events that cannot be caught with normal + * input/change event listeners. + * @internal + */ + @Method() + async setValue(value?: string | number) { const { items } = this; this.value = value; const findItem = items.find((item) => item.value === value); diff --git a/core/src/components/picker-internal/picker-internal.tsx b/core/src/components/picker-internal/picker-internal.tsx index c13bdef8d8c..bda6ea9336e 100644 --- a/core/src/components/picker-internal/picker-internal.tsx +++ b/core/src/components/picker-internal/picker-internal.tsx @@ -347,7 +347,7 @@ export class PickerInternal implements ComponentInterface { */ const findItemFromCompleteValue = values.find(({ text }) => text.replace(/^0+/, '') === inputEl.value); if (findItemFromCompleteValue) { - inputModeColumn.value = findItemFromCompleteValue.value; + inputModeColumn.setValue(findItemFromCompleteValue.value); return; } @@ -377,7 +377,7 @@ export class PickerInternal implements ComponentInterface { const item = colEl.items.find(({ text }) => text.replace(behavior, '') === value); if (item) { - colEl.value = item.value; + colEl.setValue(item.value); } }; diff --git a/core/src/components/picker-internal/test/keyboard-entry/picker-internal.e2e.ts b/core/src/components/picker-internal/test/keyboard-entry/picker-internal.e2e.ts new file mode 100644 index 00000000000..24be31b39d6 --- /dev/null +++ b/core/src/components/picker-internal/test/keyboard-entry/picker-internal.e2e.ts @@ -0,0 +1,92 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; +import type { E2ELocator } from '@utils/test/playwright/page/utils/locator'; + +test.describe('picker-internal: keyboard entry', () => { + test('should scroll to and update the value prop for a single column', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const column = page.locator('ion-picker-column-internal'); + const ionChange = await page.spyOnEvent('ionChange'); + await column.focus(); + + await page.keyboard.press('Digit2'); + + await expect(ionChange).toHaveReceivedEventDetail({ text: '02', value: 2 }); + await expect(column).toHaveJSProperty('value', 2); + }); + + test('should scroll to and update the value prop for multiple columns', async ({ page }) => { + await page.setContent(` + + + + + + + `); + + const firstColumn = page.locator('ion-picker-column-internal#first'); + const secondColumn = page.locator('ion-picker-column-internal#second'); + const highlight = page.locator('ion-picker-internal .picker-highlight'); + const firstIonChange = await (firstColumn as E2ELocator).spyOnEvent('ionChange'); + const secondIonChange = await (secondColumn as E2ELocator).spyOnEvent('ionChange'); + + const box = await highlight.boundingBox(); + if (box !== null) { + await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2); + } + + await expect(firstColumn).toHaveClass(/picker-column-active/); + await expect(secondColumn).toHaveClass(/picker-column-active/); + + await page.keyboard.press('Digit2'); + + await expect(firstIonChange).toHaveReceivedEventDetail({ text: '02', value: 2 }); + await expect(firstColumn).toHaveJSProperty('value', 2); + + await page.keyboard.press('Digit2+Digit4'); + + await expect(secondIonChange).toHaveReceivedEventDetail({ text: '24', value: 24 }); + await expect(secondColumn).toHaveJSProperty('value', 24); + }); +}); diff --git a/core/src/utils/test/playwright/page/utils/locator.ts b/core/src/utils/test/playwright/page/utils/locator.ts index 9088d7ca7f1..0fe632e843a 100644 --- a/core/src/utils/test/playwright/page/utils/locator.ts +++ b/core/src/utils/test/playwright/page/utils/locator.ts @@ -21,7 +21,7 @@ export interface E2ELocator extends Locator { * ... * await ionChange.next(); */ - spyOnEvent: (eventName: string) => void; + spyOnEvent: (eventName: string) => Promise; } export const locator = (