From bb1cbfdb72103cc072f0857abc2b4e37881bf84e Mon Sep 17 00:00:00 2001 From: Mat Harris Date: Fri, 8 Dec 2023 15:15:16 -0500 Subject: [PATCH 1/5] chore: merge upstream Squashed commit of the following: commit 9ef1e186ab5659917ccdfb8930f34c8de14ff93c Author: Mat Harris Date: Fri Dec 8 12:06:10 2023 -0500 A11y revamp: Pharos buttons (non-breaking change) (#628) * docs: add @sirrah-tam as a contributor * feat(button): add a11y naming convention for aria * feat(button): add aria-description support * chore: add changset * Update packages/pharos/src/components/button/pharos-button.ts Co-authored-by: Dane Hillard * Update packages/pharos/src/components/button/pharos-button.ts Co-authored-by: Dane Hillard * feat(button): move property deprecated flag * fix(button): remove ts-ignore after lit upgrade * feat(button): add a11y state typing * fix: add TODO for future reference Co-authored-by: Dane Hillard * fix(a11y attributes): update AriaHiddenState name --------- Co-authored-by: Dane Hillard --- .changeset/large-frogs-smile.md | 5 ++ .../src/components/button/pharos-button.ts | 68 ++++++++++++++++--- .../sidenav/pharos-sidenav-button.ts | 4 +- .../components/toast/pharos-toast-button.ts | 4 +- .../pharos-toggle-button.ts | 10 +-- .../pharos/src/typings/a11y-attributes.d.ts | 17 +++++ 6 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 .changeset/large-frogs-smile.md create mode 100644 packages/pharos/src/typings/a11y-attributes.d.ts diff --git a/.changeset/large-frogs-smile.md b/.changeset/large-frogs-smile.md new file mode 100644 index 000000000..b068b2190 --- /dev/null +++ b/.changeset/large-frogs-smile.md @@ -0,0 +1,5 @@ +--- +'@ithaka/pharos': minor +--- + +Update PharosButton to use a11y naming convention and include wider ARIA support diff --git a/packages/pharos/src/components/button/pharos-button.ts b/packages/pharos/src/components/button/pharos-button.ts index dedef5bf6..6831db979 100644 --- a/packages/pharos/src/components/button/pharos-button.ts +++ b/packages/pharos/src/components/button/pharos-button.ts @@ -17,9 +17,6 @@ export type ButtonType = 'button' | 'submit' | 'reset'; export type ButtonVariant = 'primary' | 'secondary' | 'subtle' | 'overlay'; -// undefined means no state has been expressed at all and won't render; 'undefined' is an explicit state -export type PressedState = 'false' | 'true' | 'mixed' | 'undefined' | undefined; - const TYPES = ['button', 'submit', 'reset'] as ButtonType[]; const VARIANTS = ['primary', 'secondary', 'subtle', 'overlay'] as ButtonVariant[]; @@ -112,12 +109,41 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) public large = false; /** + * @deprecated * Indicates the aria label to apply to the button. * @attr label */ @property({ type: String, reflect: true }) public label?: string; + /** + * Indicates the aria label to apply to the button. + * @attr a11y-label + */ + @property({ type: String, reflect: true, attribute: 'a11y-label' }) + public a11yLabel?: string; + + /** + * Indicates the aria description to apply to the button. + * @attr a11y-description + */ + @property({ type: String, reflect: true, attribute: 'a11y-description' }) + public a11yDescription?: string; + + /** + * Indicates the aria expanded state to apply to the button. + * @attr a11y-expanded + */ + @property({ type: String, reflect: true, attribute: 'a11y-expanded' }) + public a11yExpanded: AriaExpandedState = undefined; + + /** + * Indicates the aria expanded state to apply to the button. + * @attr a11y-haspopup + */ + @property({ type: String, reflect: true, attribute: 'a11y-haspopup' }) + public a11yHaspopup: AriaPopupState = undefined; + /** * Indicates the button's width should match its container. * @attr full-width @@ -140,11 +166,19 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) public value?: string; /** + * @deprecated * Indicates this button is a toggle button and whether it is pressed or not. * @attr value */ @property({ type: String, reflect: true }) - public pressed: PressedState = undefined; + public pressed: AriaPressedState = undefined; + + /** + * Indicates this button is a toggle button and whether it is pressed or not. + * @attr value + */ + @property({ type: String, reflect: true, attribute: 'a11y-pressed' }) + public a11yPressed: AriaPressedState = undefined; @query('#button-element') private _button!: HTMLButtonElement | HTMLAnchorElement; @@ -178,6 +212,14 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) `${this.variant} is not a valid variant. Valid variants are: ${VARIANTS.join(', ')}` ); } + + if (this.label) { + console.warn("The 'label' attribute is deprecated. Use 'a11y-label' instead."); + } + + if (this.pressed) { + console.warn("The 'pressed' attribute is deprecated. Use 'a11y-pressed' instead."); + } } override connectedCallback(): void { @@ -243,6 +285,10 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) } protected override render(): TemplateResult { + // TODO: Remove in future release once sufficient time elapsed to update naming convention + const a11yLabel = this.a11yLabel ?? this.label; + const a11yPressed = this.a11yPressed ?? this.pressed; + return this.href ? html` ${this.buttonContent} @@ -269,8 +318,11 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) ?autofocus=${this.autofocus} ?disabled=${this.disabled} type="${ifDefined(this.type)}" - aria-label=${ifDefined(this.label)} - aria-pressed=${ifDefined(this.pressed)} + aria-label=${ifDefined(a11yLabel)} + aria-description=${ifDefined(this.a11yDescription)} + aria-pressed=${ifDefined(a11yPressed)} + aria-expanded=${ifDefined(this.a11yExpanded)} + aria-haspopup=${ifDefined(this.a11yHaspopup)} > ${this.buttonContent} diff --git a/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts b/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts index 6dcab2e42..174826cc6 100644 --- a/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts +++ b/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts @@ -4,8 +4,8 @@ import { PharosButton } from '../button/pharos-button'; import type { PharosSidenav } from './pharos-sidenav'; import type { LinkTarget } from '../base/anchor-element'; -import type { ButtonType, IconName, ButtonVariant, PressedState } from '../button/pharos-button'; -export type { LinkTarget, ButtonType, IconName, ButtonVariant, PressedState }; +import type { ButtonType, IconName, ButtonVariant } from '../button/pharos-button'; +export type { LinkTarget, ButtonType, IconName, ButtonVariant }; /** * Pharos sidenav button component. diff --git a/packages/pharos/src/components/toast/pharos-toast-button.ts b/packages/pharos/src/components/toast/pharos-toast-button.ts index 7cdb2eab4..3908b43a5 100644 --- a/packages/pharos/src/components/toast/pharos-toast-button.ts +++ b/packages/pharos/src/components/toast/pharos-toast-button.ts @@ -3,9 +3,9 @@ import { toastButtonStyles } from './pharos-toast-button.css'; import { PharosButton } from '../button/pharos-button'; import type { LinkTarget } from '../base/anchor-element'; -import type { ButtonType, IconName, ButtonVariant, PressedState } from '../button/pharos-button'; +import type { ButtonType, IconName, ButtonVariant } from '../button/pharos-button'; -export type { LinkTarget, ButtonType, IconName, ButtonVariant, PressedState }; +export type { LinkTarget, ButtonType, IconName, ButtonVariant }; /** * Pharos toast button component. diff --git a/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts b/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts index 1952fc9ab..004cba931 100644 --- a/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts +++ b/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts @@ -3,14 +3,8 @@ import type { CSSResultArray, PropertyValues } from 'lit'; import { toggleButtonStyles } from './pharos-toggle-button.css'; import { PharosButton } from '../button/pharos-button'; -import type { - ButtonType, - LinkTarget, - IconName, - ButtonVariant, - PressedState, -} from '../button/pharos-button'; -export type { ButtonType, LinkTarget, IconName, ButtonVariant, PressedState }; +import type { ButtonType, LinkTarget, IconName, ButtonVariant } from '../button/pharos-button'; +export type { ButtonType, LinkTarget, IconName, ButtonVariant }; /** * Pharos toggle button component. diff --git a/packages/pharos/src/typings/a11y-attributes.d.ts b/packages/pharos/src/typings/a11y-attributes.d.ts new file mode 100644 index 000000000..0876b0ee3 --- /dev/null +++ b/packages/pharos/src/typings/a11y-attributes.d.ts @@ -0,0 +1,17 @@ +export {}; + +declare global { + type AriaHiddenState = 'false' | 'true' | 'undefined' | undefined; + type AriaPressedState = 'false' | 'true' | 'mixed' | 'undefined' | undefined; + type AriaExpandedState = 'false' | 'true' | 'undefined' | undefined; + type AriaDisabledState = 'false' | 'true' | undefined; + type AriaPopupState = + | 'false' + | 'true' + | 'menu' + | 'tree' + | 'grid' + | 'listbox' + | 'dialog' + | undefined; +} From c00068937c2cc4966ec8f1fd9520c5fec2d20d7a Mon Sep 17 00:00:00 2001 From: Mat Harris Date: Mon, 22 Jan 2024 11:55:37 -0500 Subject: [PATCH 2/5] feat: update label to a11y-label Added the a11y-label attribute to replace the label attribute when needing to update a components aria-label --- .../pharos-dropdown-menu-nav.ts | 22 ++++++++++++++-- .../pharos/src/components/link/pharos-link.ts | 25 ++++++++++++++++--- .../src/components/popover/pharos-popover.ts | 17 ++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/packages/pharos/src/components/dropdown-menu-nav/pharos-dropdown-menu-nav.ts b/packages/pharos/src/components/dropdown-menu-nav/pharos-dropdown-menu-nav.ts index 8737df045..b2628dc52 100644 --- a/packages/pharos/src/components/dropdown-menu-nav/pharos-dropdown-menu-nav.ts +++ b/packages/pharos/src/components/dropdown-menu-nav/pharos-dropdown-menu-nav.ts @@ -1,7 +1,7 @@ import { PharosElement } from '../base/pharos-element'; import { html } from 'lit'; import { property, queryAssignedElements } from 'lit/decorators.js'; -import type { TemplateResult, CSSResultArray } from 'lit'; +import type { TemplateResult, CSSResultArray, PropertyValues } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { dropdownMenuNavStyles } from './pharos-dropdown-menu-nav.css'; import type { PharosDropdownMenuNavLink } from './pharos-dropdown-menu-nav-link'; @@ -21,10 +21,18 @@ export class PharosDropdownMenuNav extends FocusMixin(PharosElement) { /** * Indicates the aria label to apply to the nav. * @attr label + * @deprecated */ @property({ type: String, reflect: true }) public label?: string; + /** + * Indicates the aria label to apply to the button. + * @attr a11y-label + */ + @property({ type: String, reflect: true, attribute: 'a11y-label' }) + public a11yLabel?: string; + @queryAssignedElements({ selector: '[data-pharos-component="PharosDropdownMenuNavLink"]' }) private _allLinks!: NodeListOf; @@ -39,6 +47,14 @@ export class PharosDropdownMenuNav extends FocusMixin(PharosElement) { this.addEventListener('focus', () => this._closeAllMenus()); } + protected override update(changedProperties: PropertyValues): void { + super.update && super.update(changedProperties); + + if (this.label) { + console.warn("The 'label' attribute is deprecated. Use 'a11y-label' instead."); + } + } + private _closeAllMenus(link: PharosDropdownMenuNavLink | undefined = undefined) { const menu: PharosDropdownMenu | null = this.querySelector( '[data-pharos-component="PharosDropdownMenu"][open]' @@ -69,8 +85,10 @@ export class PharosDropdownMenuNav extends FocusMixin(PharosElement) { } protected override render(): TemplateResult { + // TODO: Remove in future release once sufficient time elapsed to update naming convention + const a11yLabel = this.a11yLabel ?? this.label; return html` -