From 4e387005663b4e8425cb28e41608bb4f924b3864 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 27 May 2025 09:08:24 -0700 Subject: [PATCH] fix(input-password-toggle, button): force update aria attributes (#30411) Issue number: internal --------- ## What is the current behavior? The `ion-input-password-toggle` has aria attributes that are updated based on the value visibility. However, those values do not reflect on the native button. This leads to the screen readers to not announce correctly. ## What is the new behavior? - The aria attributes now reflects correctly within the native button. - The `aria-label` has been updated to indicate the state of visibility. - Added tests. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-fw-6525-ionic1.vercel.app/src/components/input-password-toggle/test/basic) --- core/src/components/button/button.tsx | 22 +++++++++++- .../input-password-toggle.tsx | 2 +- .../test/a11y/input-password-toggle.e2e.ts | 34 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index 4e63b8413e..47326abfea 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -1,5 +1,5 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Host, Prop, Watch, State, h } from '@stencil/core'; +import { Component, Element, Event, Host, Prop, Watch, State, forceUpdate, h } from '@stencil/core'; import type { AnchorInterface, ButtonInterface } from '@utils/element-interface'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, hasShadowDom } from '@utils/helpers'; @@ -158,6 +158,26 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf */ @Event() ionBlur!: EventEmitter; + /** + * This component is used within the `ion-input-password-toggle` component + * to toggle the visibility of the password input. + * These attributes need to update based on the state of the password input. + * Otherwise, the values will be stale. + * + * @param newValue + * @param _oldValue + * @param propName + */ + @Watch('aria-checked') + @Watch('aria-label') + onAriaChanged(newValue: string, _oldValue: string, propName: string) { + this.inheritedAttributes = { + ...this.inheritedAttributes, + [propName]: newValue, + }; + forceUpdate(this); + } + /** * This is responsible for rendering a hidden native * button element inside the associated form. This allows diff --git a/core/src/components/input-password-toggle/input-password-toggle.tsx b/core/src/components/input-password-toggle/input-password-toggle.tsx index 44b8e0828a..90b743b41e 100644 --- a/core/src/components/input-password-toggle/input-password-toggle.tsx +++ b/core/src/components/input-password-toggle/input-password-toggle.tsx @@ -127,7 +127,7 @@ export class InputPasswordToggle implements ComponentInterface { fill="clear" shape="round" aria-checked={isPasswordVisible ? 'true' : 'false'} - aria-label="show password" + aria-label={isPasswordVisible ? 'Hide password' : 'Show password'} role="switch" type="button" onPointerDown={(ev) => { diff --git a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts index 76b9f3d283..0493509dc2 100644 --- a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts +++ b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts @@ -20,4 +20,38 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { expect(results.violations).toEqual([]); }); }); + + test.describe(title('input password toggle: aria attributes'), () => { + test('should inherit aria attributes to inner button on load', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + const nativeButton = page.locator('ion-input-password-toggle button'); + + await expect(nativeButton).toHaveAttribute('aria-label', 'Show password'); + await expect(nativeButton).toHaveAttribute('aria-checked', 'false'); + }); + test('should inherit aria attributes to inner button after toggle', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + const nativeButton = page.locator('ion-input-password-toggle button'); + await nativeButton.click(); + + await expect(nativeButton).toHaveAttribute('aria-label', 'Hide password'); + await expect(nativeButton).toHaveAttribute('aria-checked', 'true'); + }); + }); });