From f1defba2acb417c6f243b2902923d85efbb6f879 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Wed, 9 Jul 2025 15:53:48 -0500 Subject: [PATCH] fix(input): prevent layout shift when hiding password toggle (#30533) Issue number: resolves #29562 --------- ## What is the current behavior? When an input with a password toggle is given `disabled` or `readonly`, hiding the password toggle causes a layout shift as it shrinks the height of the input component. ## What is the new behavior? - Password toggle is given `visibility: hidden` instead of removing it from the DOM with `display: none` so it retains it's space but is still hidden and still removed from the accessibility tree. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information This solution was suggested by @piotr-cz in the bug report. --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> --- core/src/components/input/input.scss | 2 +- .../components/input/test/states/input.e2e.ts | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 57d438eb2d..2161cc3dfb 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -618,5 +618,5 @@ */ :host([disabled]) ::slotted(ion-input-password-toggle), :host([readonly]) ::slotted(ion-input-password-toggle) { - display: none; + visibility: hidden; } diff --git a/core/src/components/input/test/states/input.e2e.ts b/core/src/components/input/test/states/input.e2e.ts index eb51f76096..33d33d91cf 100644 --- a/core/src/components/input/test/states/input.e2e.ts +++ b/core/src/components/input/test/states/input.e2e.ts @@ -26,5 +26,69 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { const input = page.locator('ion-input'); await expect(input).toHaveScreenshot(screenshot(`input-disabled`)); }); + + test('should maintain consistent height when password toggle is hidden on disabled input', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29562', + }); + await page.setContent( + ` + + + + `, + config + ); + + const input = page.locator('ion-input'); + + // Get the height when input is enabled + const enabledHeight = await input.boundingBox().then((box) => box?.height); + + // Disable the input + await input.evaluate((el) => el.setAttribute('disabled', 'true')); + await page.waitForChanges(); + + // Get the height when input is disabled + const disabledHeight = await input.boundingBox().then((box) => box?.height); + + // Verify heights are the same + expect(enabledHeight).toBe(disabledHeight); + }); + + test('should maintain consistent height when password toggle is hidden on readonly input', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29562', + }); + await page.setContent( + ` + + + + `, + config + ); + + const input = page.locator('ion-input'); + + // Get the height when input is enabled + const enabledHeight = await input.boundingBox().then((box) => box?.height); + + // Make the input readonly + await input.evaluate((el) => el.setAttribute('readonly', 'true')); + await page.waitForChanges(); + + // Get the height when input is readonly + const readonlyHeight = await input.boundingBox().then((box) => box?.height); + + // Verify heights are the same + expect(enabledHeight).toBe(readonlyHeight); + }); }); });