diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index e8cc0492741..d28cfd385c8 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -549,15 +549,37 @@ export class Input implements ComponentInterface { if (!this.shouldClearOnEdit()) { return; } + + /** + * The following keys do not modify the + * contents of the input. As a result, pressing + * them should not edit the input. + * + * We can't check to see if the value of the input + * was changed because we call checkClearOnEdit + * in a keydown listener, and the key has not yet + * been added to the input. + */ + const IGNORED_KEYS = ['Enter', 'Tab', 'Shift', 'Meta', 'Alt', 'Control']; + const pressedIgnoredKey = IGNORED_KEYS.includes(ev.key); + /** * Clear the input if the control has not been previously cleared during focus. * Do not clear if the user hitting enter to submit a form. */ - if (!this.didInputClearOnEdit && this.hasValue() && ev.key !== 'Enter' && ev.key !== 'Tab') { + if (!this.didInputClearOnEdit && this.hasValue() && !pressedIgnoredKey) { this.value = ''; this.emitInputChange(ev); } - this.didInputClearOnEdit = true; + + /** + * Pressing an IGNORED_KEYS first and + * then an allowed key will cause the input to not + * be cleared. + */ + if (!pressedIgnoredKey) { + this.didInputClearOnEdit = true; + } } private onCompositionStart = () => { diff --git a/core/src/components/input/test/clear-on-edit/input.e2e.ts b/core/src/components/input/test/clear-on-edit/input.e2e.ts index e6686902782..ecfbecb3c1e 100644 --- a/core/src/components/input/test/clear-on-edit/input.e2e.ts +++ b/core/src/components/input/test/clear-on-edit/input.e2e.ts @@ -1,6 +1,8 @@ import { expect } from '@playwright/test'; import { test, configs } from '@utils/test/playwright'; +const IGNORED_KEYS = ['Enter', 'Tab', 'Shift', 'Meta', 'Alt', 'Control']; + configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('input: clearOnEdit'), () => { test('should clear when typed into', async ({ page }) => { @@ -16,28 +18,57 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await expect(input).toHaveJSProperty('value', 'h'); }); - test('should not clear when enter is pressed', async ({ page }) => { - await page.setContent(``, config); + test('should not clear the input if it does not have an initial value when typing', async ({ page }) => { + await page.setContent(``, config); const input = page.locator('ion-input'); - await input.locator('input').focus(); - await page.keyboard.press('Enter'); - await page.waitForChanges(); + await input.click(); + await input.type('hello world'); - await expect(input).toHaveJSProperty('value', 'abc'); + await expect(input).toHaveJSProperty('value', 'hello world'); + }); + + IGNORED_KEYS.forEach((ignoredKey: string) => { + test(`should not clear when ${ignoredKey} is pressed`, async ({ page, skip }) => { + skip.browser( + (browserName: string) => browserName === 'firefox' && ignoredKey === 'Meta', + 'Firefox incorrectly adds "OS" to the input when pressing the Meta key on Linux' + ); + await page.setContent(``, config); + + const input = page.locator('ion-input'); + await input.locator('input').focus(); + + await page.keyboard.press(ignoredKey); + await page.waitForChanges(); + + await expect(input).toHaveJSProperty('value', 'abc'); + }); }); - test('should not clear when tab is pressed', async ({ page }) => { + test('should clear after when pressing valid key after pressing ignored key', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28633', + }); + await page.setContent(``, config); const input = page.locator('ion-input'); await input.locator('input').focus(); - await page.keyboard.press('Tab'); + // ignored + await page.keyboard.press('Shift'); await page.waitForChanges(); await expect(input).toHaveJSProperty('value', 'abc'); + + // allowed + await page.keyboard.press('a'); + await page.waitForChanges(); + + await expect(input).toHaveJSProperty('value', 'a'); }); }); }); diff --git a/core/src/components/textarea/test/clear-on-edit/textarea.e2e.ts b/core/src/components/textarea/test/clear-on-edit/textarea.e2e.ts index f085f46ee8b..898b63a2d6f 100644 --- a/core/src/components/textarea/test/clear-on-edit/textarea.e2e.ts +++ b/core/src/components/textarea/test/clear-on-edit/textarea.e2e.ts @@ -1,6 +1,8 @@ import { expect } from '@playwright/test'; import { test, configs } from '@utils/test/playwright'; +const IGNORED_KEYS = ['Tab', 'Shift', 'Meta', 'Alt', 'Control']; + configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('textarea: clearOnEdit'), () => { test('should clear when typed into', async ({ page }) => { @@ -33,5 +35,64 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await expect(textarea).toHaveJSProperty('value', 'abc'); }); + + test('should not clear the textarea if it does not have an initial value when typing', async ({ page }) => { + await page.setContent(``, config); + + const textarea = page.locator('ion-textarea'); + + await textarea.click(); + await textarea.type('hello world'); + + await expect(textarea).toHaveJSProperty('value', 'hello world'); + }); + + IGNORED_KEYS.forEach((ignoredKey: string) => { + test(`should not clear when ${ignoredKey} is pressed`, async ({ page, skip }) => { + skip.browser( + (browserName: string) => browserName === 'firefox' && ignoredKey === 'Meta', + 'Firefox incorrectly adds "OS" to the textarea when pressing the Meta key on Linux' + ); + await page.setContent( + ``, + config + ); + + const textarea = page.locator('ion-textarea'); + await textarea.locator('textarea').focus(); + + await page.keyboard.press(ignoredKey); + await page.waitForChanges(); + + await expect(textarea).toHaveJSProperty('value', 'abc'); + }); + }); + + test('should clear after when pressing valid key after pressing ignored key', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28633', + }); + + await page.setContent( + ``, + config + ); + + const textarea = page.locator('ion-textarea'); + await textarea.locator('textarea').focus(); + + // ignored + await page.keyboard.press('Shift'); + await page.waitForChanges(); + + await expect(textarea).toHaveJSProperty('value', 'abc'); + + // allowed + await page.keyboard.press('a'); + await page.waitForChanges(); + + await expect(textarea).toHaveJSProperty('value', 'a'); + }); }); }); diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index f971c8ee651..83557d3254f 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -459,15 +459,41 @@ export class Textarea implements ComponentInterface { if (!this.clearOnEdit) { return; } + + /** + * The following keys do not modify the + * contents of the input. As a result, pressing + * them should not edit the textarea. + * + * We can't check to see if the value of the textarea + * was changed because we call checkClearOnEdit + * in a keydown listener, and the key has not yet + * been added to the textarea. + * + * Unlike ion-input, the "Enter" key does modify the + * textarea by adding a new line, so "Enter" is not + * included in the IGNORED_KEYS array. + */ + const IGNORED_KEYS = ['Tab', 'Shift', 'Meta', 'Alt', 'Control']; + const pressedIgnoredKey = IGNORED_KEYS.includes(ev.key); + /** * Clear the textarea if the control has not been previously cleared * during focus. */ - if (!this.didTextareaClearOnEdit && this.hasValue() && ev.key !== 'Tab') { + if (!this.didTextareaClearOnEdit && this.hasValue() && !pressedIgnoredKey) { this.value = ''; this.emitInputChange(ev); } - this.didTextareaClearOnEdit = true; + + /** + * Pressing an IGNORED_KEYS first and + * then an allowed key will cause the input to not + * be cleared. + */ + if (!pressedIgnoredKey) { + this.didTextareaClearOnEdit = true; + } } private focusChange() {