fix(input): prevent layout shift when hiding password toggle (#30533)

Issue number: resolves #29562 

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## 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?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
- 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

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

This solution was suggested by @piotr-cz in the bug report.
<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Colin Bares
2025-07-09 15:53:48 -05:00
committed by GitHub
parent a4dea39179
commit f1defba2ac
2 changed files with 65 additions and 1 deletions

View File

@ -618,5 +618,5 @@
*/
:host([disabled]) ::slotted(ion-input-password-toggle),
:host([readonly]) ::slotted(ion-input-password-toggle) {
display: none;
visibility: hidden;
}

View File

@ -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(
`
<ion-input label="Password" type="password" value="password123">
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
</ion-input>
`,
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(
`
<ion-input label="Password" type="password" value="password123">
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
</ion-input>
`,
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);
});
});
});