From 52404a937d3b93bc4dac089656193c17bf13d3b0 Mon Sep 17 00:00:00 2001 From: Kieran Nichols Date: Wed, 13 Dec 2023 09:18:51 -0500 Subject: [PATCH] [@next] general cleanup, fixes, and organization (#444) --- src/dev/pages/card/card.ts | 1 - src/dev/pages/chips/chips.ts | 1 - .../button-toggle-group-constants.ts | 5 +++ .../button-toggle-group.ts | 22 ++++++---- src/lib/button-toggle/button-toggle.test.ts | 15 +++++++ .../button-toggle/button-toggle-constants.ts | 5 +++ .../button-toggle/button-toggle.ts | 18 +++++--- src/lib/button/base/base-button.ts | 30 ++++++------- src/lib/constants.ts | 3 ++ .../base-element-internals-component.test.ts | 0 .../base-form-associated-component.test.ts | 0 .../base/base-label-aware-component.test.ts | 0 src/lib/core/base/index.ts | 4 -- .../focus/with-focusable.test.ts} | 6 +-- .../focus/with-focusable.ts} | 4 +- .../form/with-form-associated.ts} | 6 +-- ...with-validity.ts => with-form-validity.ts} | 0 .../internals/with-default-aria.ts} | 44 +++++++------------ .../internals/with-element-internals.ts | 36 +++++++++++++++ .../label/with-label-aware.ts} | 4 +- src/lib/core/styles/tokens/_token-utils.scss | 11 +++++ .../button-toggle-group/_tokens.scss | 4 +- .../button-toggle/button-toggle/_tokens.scss | 10 ++--- .../core/styles/tokens/button/_tokens.scss | 8 ++-- .../core/styles/tokens/checkbox/_tokens.scss | 12 ++--- .../floating-action-button/_tokens.scss | 2 +- .../styles/tokens/icon-button/_tokens.scss | 4 +- .../styles/tokens/profile-card/_tokens.scss | 2 +- src/lib/core/styles/tokens/radio/_tokens.scss | 2 +- .../core/styles/tokens/switch/_tokens.scss | 6 +-- .../core/styles/tokens/tabs/tab/_tokens.scss | 10 ++--- .../tokens/theme/_tokens.utilities.scss | 6 ++- src/lib/radio/radio-group/radio-group.ts | 13 ++++-- src/lib/radio/radio/radio.ts | 25 +++++------ 34 files changed, 194 insertions(+), 125 deletions(-) delete mode 100644 src/lib/core/base/base-element-internals-component.test.ts delete mode 100644 src/lib/core/base/base-form-associated-component.test.ts delete mode 100644 src/lib/core/base/base-label-aware-component.test.ts rename src/lib/core/{base/base-focusable-component.test.ts => mixins/focus/with-focusable.test.ts} (93%) rename src/lib/core/{base/base-focusable-component.ts => mixins/focus/with-focusable.ts} (98%) rename src/lib/core/{base/base-form-associated-component.ts => mixins/form/with-form-associated.ts} (97%) rename src/lib/core/mixins/form/{with-validity.ts => with-form-validity.ts} (100%) rename src/lib/core/{base/base-element-internals-component.ts => mixins/internals/with-default-aria.ts} (61%) create mode 100644 src/lib/core/mixins/internals/with-element-internals.ts rename src/lib/core/{base/base-label-aware-component.ts => mixins/label/with-label-aware.ts} (90%) diff --git a/src/dev/pages/card/card.ts b/src/dev/pages/card/card.ts index 55d78d827..5c869cbd2 100644 --- a/src/dev/pages/card/card.ts +++ b/src/dev/pages/card/card.ts @@ -3,7 +3,6 @@ import '@tylertech/forge/card'; import '@tylertech/forge/scaffold'; import '@tylertech/forge/button'; import '@tylertech/forge/icon-button'; -import '@tylertech/forge/icon-button/forge-icon-button.scss'; import './card.scss'; import type { ICardComponent, ISwitchComponent } from '@tylertech/forge'; diff --git a/src/dev/pages/chips/chips.ts b/src/dev/pages/chips/chips.ts index b0fa40c3c..f6a9af05e 100644 --- a/src/dev/pages/chips/chips.ts +++ b/src/dev/pages/chips/chips.ts @@ -1,7 +1,6 @@ import '$src/shared'; import '@tylertech/forge/chips'; import '@tylertech/forge/icon-button'; -import '@tylertech/forge/icon-button/forge-icon-button.scss'; import { IChipSetComponent, IconRegistry, ISwitchComponent } from '@tylertech/forge'; import type { IChipComponent } from '@tylertech/forge'; import { tylIconAlarm, tylIconBookmark, tylIconDirections, tylIconEvent, tylIconFace, tylIconPlace, tylIconRefresh } from '@tylertech/tyler-icons/standard'; diff --git a/src/lib/button-toggle/button-toggle-group/button-toggle-group-constants.ts b/src/lib/button-toggle/button-toggle-group/button-toggle-group-constants.ts index 2fe3a5b88..8f975d3cb 100644 --- a/src/lib/button-toggle/button-toggle-group/button-toggle-group-constants.ts +++ b/src/lib/button-toggle/button-toggle-group/button-toggle-group-constants.ts @@ -1,4 +1,6 @@ import { COMPONENT_NAME_PREFIX, Theme } from '../../constants'; +import { supportsElementInternalsAria } from '../../core'; +import { ARIAAttribute } from '../../core/utils/a11y-utils'; const elementName: keyof HTMLElementTagNameMap = `${COMPONENT_NAME_PREFIX}button-toggle-group`; @@ -16,6 +18,8 @@ const observedAttributes = { THEME: 'theme' }; +const observedAriaAttributes: ARIAAttribute[] = supportsElementInternalsAria() ? [] : ['role', 'aria-label']; + const attributes = { ...observedAttributes }; @@ -36,6 +40,7 @@ const events = { export const BUTTON_TOGGLE_GROUP_CONSTANTS = { elementName, observedAttributes, + observedAriaAttributes, attributes, classes, selectors, diff --git a/src/lib/button-toggle/button-toggle-group/button-toggle-group.ts b/src/lib/button-toggle/button-toggle-group/button-toggle-group.ts index c5ac21be1..b37efe659 100644 --- a/src/lib/button-toggle/button-toggle-group/button-toggle-group.ts +++ b/src/lib/button-toggle/button-toggle-group/button-toggle-group.ts @@ -1,20 +1,21 @@ import { attachShadowTemplate, coerceBoolean, CustomElement, FoundationProperty } from '@tylertech/forge-core'; -import { IWithFormAssociation, WithFormAssociation } from '../../core/base/base-form-associated-component'; -import { IWithLabelAwareness, WithLabelAwareness } from '../../core/base/base-label-aware-component'; -import { IWithElementInternals, WithElementInternals } from '../../core/base/base-element-internals-component'; -import { IWithFormValidity, WithFormValidity } from '../../core/mixins/form/with-validity'; +import { IWithFormAssociation, WithFormAssociation } from '../../core/mixins/form/with-form-associated'; +import { IWithLabelAwareness, WithLabelAwareness } from '../../core/mixins/label/with-label-aware'; +import { IWithElementInternals, WithElementInternals } from '../../core/mixins/internals/with-element-internals'; +import { IWithFormValidity, WithFormValidity } from '../../core/mixins/form/with-form-validity'; import { BaseComponent } from '../../core/base/base-component'; import { ButtonToggleComponent } from '../button-toggle/button-toggle'; import { ButtonToggleGroupAdapter } from './button-toggle-group-adapter'; import { ButtonToggleGroupTheme, BUTTON_TOGGLE_GROUP_CONSTANTS, IButtonToggleGroupChangeEventData } from './button-toggle-group-constants'; import { ButtonToggleGroupFoundation } from './button-toggle-group-foundation'; -import { getFormState, getFormValue, inputType, setDefaultAria } from '../../constants'; +import { getFormState, getFormValue, inputType, observedDefaultAriaAttributes, setDefaultAria } from '../../constants'; import { FormValue, FormRestoreState, FormRestoreReason } from '../../core/utils/form-utils'; +import { IWithDefaultAria, WithDefaultAria } from '../../core/mixins/internals/with-default-aria'; import template from './button-toggle-group.html'; import styles from './button-toggle-group.scss'; -export interface IButtonToggleGroupComponent extends IWithLabelAwareness, IWithFormAssociation, IWithFormValidity, IWithElementInternals { +export interface IButtonToggleGroupComponent extends IWithLabelAwareness, IWithFormAssociation, IWithFormValidity, IWithElementInternals, IWithDefaultAria { value: any; outlined: boolean; multiple: boolean; @@ -37,7 +38,7 @@ declare global { } } -const BaseButtonToggleGroupClass = WithLabelAwareness(WithFormAssociation(WithFormValidity(WithElementInternals(BaseComponent)))); +const BaseButtonToggleGroupClass = WithLabelAwareness(WithFormAssociation(WithFormValidity(WithDefaultAria(WithElementInternals(BaseComponent))))); /** * @tag forge-button-toggle-group @@ -99,9 +100,14 @@ const BaseButtonToggleGroupClass = WithLabelAwareness(WithFormAssociation(WithFo }) export class ButtonToggleGroupComponent extends BaseButtonToggleGroupClass implements IButtonToggleGroupComponent { public static get observedAttributes(): string[] { - return Object.values(BUTTON_TOGGLE_GROUP_CONSTANTS.observedAttributes); + return [ + ...Object.values(BUTTON_TOGGLE_GROUP_CONSTANTS.observedAttributes), + ...Object.values(BUTTON_TOGGLE_GROUP_CONSTANTS.observedAriaAttributes) + ]; } + public readonly [observedDefaultAriaAttributes] = BUTTON_TOGGLE_GROUP_CONSTANTS.observedAriaAttributes; + private _foundation: ButtonToggleGroupFoundation; constructor() { diff --git a/src/lib/button-toggle/button-toggle.test.ts b/src/lib/button-toggle/button-toggle.test.ts index 71f2399f2..e2abbde60 100644 --- a/src/lib/button-toggle/button-toggle.test.ts +++ b/src/lib/button-toggle/button-toggle.test.ts @@ -23,6 +23,21 @@ describe('Button Toggle', () => { expect(harness.buttonToggles.every(toggle => toggle.getAttribute('aria-pressed') === 'false')).to.be.true; }); + ['primary', 'secondary', 'danger', 'success', 'warning', 'info'].forEach((theme: ButtonToggleGroupTheme) => { + it(`should be accessible with selected values for theme ${theme}`, async () => { + const harness = await createFixture({ theme, value: 'two' }); + + // Primary is the default + if (theme !== 'primary') { + expect(harness.element.getAttribute(BUTTON_TOGGLE_GROUP_CONSTANTS.attributes.THEME)).to.equal(theme); + } + + expect(harness.buttonToggles[1].selected).to.be.true; + expect(harness.buttonToggles[1].getAttribute('aria-pressed')).to.equal('true'); + await expect(harness.element).to.be.accessible(); + }); + }); + it('should not have value by default', async () => { const harness = await createFixture(); diff --git a/src/lib/button-toggle/button-toggle/button-toggle-constants.ts b/src/lib/button-toggle/button-toggle/button-toggle-constants.ts index bfb04f183..dd8c22238 100644 --- a/src/lib/button-toggle/button-toggle/button-toggle-constants.ts +++ b/src/lib/button-toggle/button-toggle/button-toggle-constants.ts @@ -1,4 +1,6 @@ import { COMPONENT_NAME_PREFIX } from '../../constants'; +import { supportsElementInternalsAria } from '../../core'; +import { ARIAAttribute } from '../../core/utils/a11y-utils'; const elementName: keyof HTMLElementTagNameMap = `${COMPONENT_NAME_PREFIX}button-toggle`; @@ -10,6 +12,8 @@ const observedAttributes = { TABINDEX: 'tabindex' // Need this to support the focusable mixin }; +const observedAriaAttributes: ARIAAttribute[] = supportsElementInternalsAria() ? [] : ['role', 'aria-pressed', 'aria-disabled']; + const attributes = { ...observedAttributes }; @@ -21,6 +25,7 @@ const events = { export const BUTTON_TOGGLE_CONSTANTS = { elementName, observedAttributes, + observedAriaAttributes, attributes, events }; diff --git a/src/lib/button-toggle/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle/button-toggle.ts index e8b87a1f5..500d39308 100644 --- a/src/lib/button-toggle/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle/button-toggle.ts @@ -1,18 +1,19 @@ import { attachShadowTemplate, coerceBoolean, CustomElement, FoundationProperty } from '@tylertech/forge-core'; -import { ExperimentalFocusOptions } from '../../constants'; -import { IWithFocusable, WithFocusable } from '../../core/base/base-focusable-component'; -import { IWithElementInternals, WithElementInternals } from '../../core/base/base-element-internals-component'; +import { ExperimentalFocusOptions, observedDefaultAriaAttributes } from '../../constants'; +import { IWithFocusable, WithFocusable } from '../../core/mixins/focus/with-focusable'; +import { IWithElementInternals, WithElementInternals } from '../../core/mixins/internals/with-element-internals'; import { FocusIndicatorComponent } from '../../focus-indicator'; import { StateLayerComponent } from '../../state-layer'; import { BaseComponent } from '../../core/base/base-component'; import { ButtonToggleAdapter } from './button-toggle-adapter'; import { BUTTON_TOGGLE_CONSTANTS, IButtonToggleSelectEventData } from './button-toggle-constants'; import { ButtonToggleFoundation } from './button-toggle-foundation'; +import { IWithDefaultAria, WithDefaultAria } from '../../core/mixins/internals/with-default-aria'; import template from './button-toggle.html'; import styles from './button-toggle.scss'; -export interface IButtonToggleComponent extends IWithElementInternals, IWithFocusable { +export interface IButtonToggleComponent extends IWithElementInternals, IWithDefaultAria, IWithFocusable { value: T; selected: boolean; disabled: boolean; @@ -29,7 +30,7 @@ declare global { } } -const BaseButtonToggleClass = WithElementInternals(WithFocusable(BaseComponent)); +const BaseButtonToggleClass = WithDefaultAria(WithElementInternals(WithFocusable(BaseComponent))); /** * @tag forge-button-toggle @@ -94,9 +95,14 @@ const BaseButtonToggleClass = WithElementInternals(WithFocusable(BaseComponent)) }) export class ButtonToggleComponent extends BaseButtonToggleClass implements IButtonToggleComponent { public static get observedAttributes(): string[] { - return Object.values(BUTTON_TOGGLE_CONSTANTS.observedAttributes); + return [ + ...Object.values(BUTTON_TOGGLE_CONSTANTS.observedAttributes), + ...Object.values(BUTTON_TOGGLE_CONSTANTS.observedAriaAttributes) + ]; } + public readonly [observedDefaultAriaAttributes] = BUTTON_TOGGLE_CONSTANTS.observedAriaAttributes; + private _foundation: ButtonToggleFoundation; constructor() { diff --git a/src/lib/button/base/base-button.ts b/src/lib/button/base/base-button.ts index f0e0bfbe0..aff892fce 100644 --- a/src/lib/button/base/base-button.ts +++ b/src/lib/button/base/base-button.ts @@ -1,15 +1,16 @@ import { coerceBoolean, FoundationProperty } from '@tylertech/forge-core'; import { tylIconArrowDropDown } from '@tylertech/tyler-icons/standard'; import { IconRegistry } from '../../icon/icon-registry'; -import { WithFocusable, IWithFocusable } from '../../core/base/base-focusable-component'; +import { WithFocusable, IWithFocusable } from '../../core/mixins/focus/with-focusable'; import { BaseComponent } from '../../core/base/base-component'; import { ExperimentalFocusOptions, internals } from '../../constants'; import { IBaseButtonAdapter } from './base-button-adapter'; import { BASE_BUTTON_CONSTANTS, ButtonType } from './base-button-constants'; import { BaseButtonFoundation } from './base-button-foundation'; -import { WithLabelAwareness, IWithLabelAwareness } from '../../core/base/base-label-aware-component'; +import { WithLabelAwareness, IWithLabelAwareness } from '../../core/mixins/label/with-label-aware'; +import { IWithElementInternals, WithElementInternals } from '../../core/mixins/internals/with-element-internals'; -export interface IBaseButton extends IWithFocusable, IWithLabelAwareness { +export interface IBaseButton extends IWithFocusable, IWithLabelAwareness, IWithElementInternals { type: ButtonType; disabled: boolean; popoverIcon: boolean; @@ -25,19 +26,16 @@ export interface IBaseButton extends IWithFocusable, IWithLabelAwareness { focus(options?: ExperimentalFocusOptions): void; } -const BaseButtonClass = WithLabelAwareness(WithFocusable(BaseComponent)); +const BaseButtonClass = WithElementInternals(WithLabelAwareness(WithFocusable(BaseComponent))); export abstract class BaseButton> extends BaseButtonClass implements IBaseButton { public static readonly formAssociated = true; - public [internals]: ElementInternals; - protected abstract _foundation: T; constructor() { super(); IconRegistry.define(tylIconArrowDropDown); - this[internals] = this.attachInternals(); } public override connectedCallback(): void { @@ -49,31 +47,31 @@ export abstract class BaseButton { diff --git a/src/lib/core/base/base-focusable-component.ts b/src/lib/core/mixins/focus/with-focusable.ts similarity index 98% rename from src/lib/core/base/base-focusable-component.ts rename to src/lib/core/mixins/focus/with-focusable.ts index 33e8c8b17..15a3d3276 100644 --- a/src/lib/core/base/base-focusable-component.ts +++ b/src/lib/core/mixins/focus/with-focusable.ts @@ -7,8 +7,8 @@ * The original source code can be found at: [GitHub](https://github.com/material-components/material-web/blob/main/labs/behaviors/focusable.ts) */ -import { AbstractConstructor, isFocusable, MixinBase } from '../../constants'; -import { IBaseComponent } from './base-component'; +import { AbstractConstructor, isFocusable, MixinBase } from '../../../constants'; +import { IBaseComponent } from '../../base/base-component'; /** * An element that can enable and disable `tabindex` focusability. diff --git a/src/lib/core/base/base-form-associated-component.ts b/src/lib/core/mixins/form/with-form-associated.ts similarity index 97% rename from src/lib/core/base/base-form-associated-component.ts rename to src/lib/core/mixins/form/with-form-associated.ts index ffaa52624..afdfa4df4 100644 --- a/src/lib/core/base/base-form-associated-component.ts +++ b/src/lib/core/mixins/form/with-form-associated.ts @@ -8,9 +8,9 @@ */ import { toggleAttribute } from '@tylertech/forge-core'; -import { MixinBase, getFormState, getFormValue, getValidationMessage, inputType, internals, AbstractConstructor } from '../../constants'; -import { FormRestoreReason, FormRestoreState, FormValue, InputType, InputValidationProps } from '../utils/form-utils'; -import { IBaseComponent } from './base-component'; +import { MixinBase, getFormState, getFormValue, getValidationMessage, inputType, internals, AbstractConstructor } from '../../../constants'; +import { FormRestoreReason, FormRestoreState, FormValue, InputType, InputValidationProps } from '../../utils/form-utils'; +import { IBaseComponent } from '../../base/base-component'; /** * A component that can be associated with a form. diff --git a/src/lib/core/mixins/form/with-validity.ts b/src/lib/core/mixins/form/with-form-validity.ts similarity index 100% rename from src/lib/core/mixins/form/with-validity.ts rename to src/lib/core/mixins/form/with-form-validity.ts diff --git a/src/lib/core/base/base-element-internals-component.ts b/src/lib/core/mixins/internals/with-default-aria.ts similarity index 61% rename from src/lib/core/base/base-element-internals-component.ts rename to src/lib/core/mixins/internals/with-default-aria.ts index 0f91c1aa0..b4c92c6be 100644 --- a/src/lib/core/base/base-element-internals-component.ts +++ b/src/lib/core/mixins/internals/with-default-aria.ts @@ -1,4 +1,4 @@ -import { internals, MixinBase, setDefaultAria, AbstractConstructor } from '../../constants'; +import { internals, MixinBase, setDefaultAria, AbstractConstructor, observedDefaultAriaAttributes } from '../../../constants'; import { ARIAAttribute, ariaAttributeToProperty, @@ -6,19 +6,14 @@ import { DefaultAriaOptions, restoreDefaultAria as restoreDefaultAriaUtil, setDefaultAria as setDefaultAriaUtil -} from '../utils/a11y-utils'; -import { supportsElementInternalsAria } from '../utils/feature-detection'; -import { IBaseComponent } from './base-component'; +} from '../../utils/a11y-utils'; +import { supportsElementInternalsAria } from '../../utils/feature-detection'; +import { IBaseComponent } from '../../base/base-component'; /** - * A component with attached Element Internals. + * A component with support for setting default ARIA. */ -export interface IWithElementInternals extends IBaseComponent { - /** - * The Element Internals of the component. - */ - readonly [internals]: ElementInternals; - +export interface IWithDefaultAria extends IBaseComponent { /** * Sets the default ARIA of the component using Element Internals if supported or sprouting * ARIA attributes if not. @@ -30,8 +25,8 @@ export interface IWithElementInternals extends IBaseComponent { [setDefaultAria](properties: Partial, options?: DefaultAriaOptions): void; } -export declare abstract class WithElementInternalsContract { - public readonly [internals]: ElementInternals; +export declare abstract class WithDefaultAriaContract { + public abstract readonly [observedDefaultAriaAttributes]: ARIAAttribute[]; public attributeChangedCallback(name: string, oldValue: string, newValue: string): void; public [setDefaultAria](properties: Partial, options?: DefaultAriaOptions): void; } @@ -46,39 +41,30 @@ export declare abstract class WithElementInternalsContract { * @returns The mixed-in base component. */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function WithElementInternals(base: TBase, observedAria?: ARIAAttribute[]) { - abstract class ElementInternalsComponent extends base implements IWithElementInternals { - public readonly [internals]: ElementInternals; - - constructor(...args: any[]) { - super(...args); - this[internals] = this.attachInternals(); - } +export function WithDefaultAria(base: TBase) { + abstract class DefaultAria extends base implements IWithDefaultAria { + public abstract readonly [observedDefaultAriaAttributes]: ARIAAttribute[]; public override attributeChangedCallback(name: string, oldValue: string, newValue: string): void { super.attributeChangedCallback?.(name, oldValue, newValue); // If Element Internals is supported our default ARIA is never set as an attribute, so // there's nothing to do here. - if (!observedAria || supportsElementInternalsAria()) { + if (!this[observedDefaultAriaAttributes] || supportsElementInternalsAria()) { return; } // If the observed attribute is removed, restore the default ARIA. - if (observedAria.includes(name as ARIAAttribute) && !newValue) { + if (this[observedDefaultAriaAttributes].includes(name as ARIAAttribute) && !newValue) { const ariaPropertyName = ariaAttributeToProperty(name as ARIAAttribute); - this._restoreDefaultAria(ariaPropertyName); + restoreDefaultAriaUtil(this, ariaPropertyName); } } public [setDefaultAria](properties: Partial, options?: DefaultAriaOptions): void { setDefaultAriaUtil(this, this[internals], properties, options); } - - private _restoreDefaultAria(name: keyof ARIAMixinStrict): void { - restoreDefaultAriaUtil(this, name); - } } - return ElementInternalsComponent as AbstractConstructor & TBase; + return DefaultAria as AbstractConstructor & TBase; } diff --git a/src/lib/core/mixins/internals/with-element-internals.ts b/src/lib/core/mixins/internals/with-element-internals.ts new file mode 100644 index 000000000..aa1b31e5b --- /dev/null +++ b/src/lib/core/mixins/internals/with-element-internals.ts @@ -0,0 +1,36 @@ +import { AbstractConstructor, internals, MixinBase } from '../../../constants'; +import { IBaseComponent } from '../../base/base-component'; + +/** + * A component with attached Element Internals. + */ +export interface IWithElementInternals extends IBaseComponent { + /** + * The Element Internals of the component. + */ + readonly [internals]: ElementInternals; +} + +export declare abstract class WithElementInternalsContract { + public readonly [internals]: ElementInternals; +} + +/** + * Mixes in Element Internals functionality into a base component. + * + * @param base The base component to mix into. + * @returns The mixed-in base component. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function WithElementInternals(base: TBase) { + abstract class ElementInternalsComponent extends base implements IWithElementInternals { + public readonly [internals]: ElementInternals; + + constructor(...args: any[]) { + super(...args); + this[internals] = this.attachInternals(); + } + } + + return ElementInternalsComponent as AbstractConstructor & TBase; +} diff --git a/src/lib/core/base/base-label-aware-component.ts b/src/lib/core/mixins/label/with-label-aware.ts similarity index 90% rename from src/lib/core/base/base-label-aware-component.ts rename to src/lib/core/mixins/label/with-label-aware.ts index 18f931a6a..807b048b3 100644 --- a/src/lib/core/base/base-label-aware-component.ts +++ b/src/lib/core/mixins/label/with-label-aware.ts @@ -1,5 +1,5 @@ -import { AbstractConstructor, MixinBase } from '../../constants'; -import { IBaseComponent } from './base-component'; +import { AbstractConstructor, MixinBase } from '../../../constants'; +import { IBaseComponent } from '../../base/base-component'; /** * An element that can be associated with a Forge label component. diff --git a/src/lib/core/styles/tokens/_token-utils.scss b/src/lib/core/styles/tokens/_token-utils.scss index c73184fb7..f036fd60f 100644 --- a/src/lib/core/styles/tokens/_token-utils.scss +++ b/src/lib/core/styles/tokens/_token-utils.scss @@ -1,5 +1,7 @@ @use 'sass:map'; +@use 'sass:string'; @use '../utils'; +@use '../core/config'; /// /// Mixin for providing theme tokens to the module using external names. @@ -84,6 +86,15 @@ /// @param {List} $excludes - An optional list of token names to exclude from `$tokens` /// @mixin tokens($module, $tokens, $includes: null, $excludes: null) { + // Validate that all tokens are generated with matching keys + @each $key, $var in $tokens { + // The token value is always expected to start with this string per our naming convention: `var(----, )` + $expected: 'var(--#{config.$prefix}-#{$module}-#{$key},'; + @if string.index($var, $expected) != 1 { + @error 'Mismatching token key "#{$key}" found for module "#{$module}" with value "#{$var}"'; + } + } + @if ($includes) { // We are including only specific tokens @each $token-name in $includes { diff --git a/src/lib/core/styles/tokens/button-toggle/button-toggle-group/_tokens.scss b/src/lib/core/styles/tokens/button-toggle/button-toggle-group/_tokens.scss index e6eaa24d2..ef3085be7 100644 --- a/src/lib/core/styles/tokens/button-toggle/button-toggle-group/_tokens.scss +++ b/src/lib/core/styles/tokens/button-toggle/button-toggle-group/_tokens.scss @@ -23,8 +23,8 @@ $tokens: ( // Outline outline-width: utils.module-val(button-toggle-group, outline-width, border.variable(thin)), outline-style: utils.module-val(button-toggle-group, outline-style, solid), - outline-color: utils.module-val(button-toggle-group, outline-color, theme.variable(outline)), - outline-color-active: utils.module-val(button-toggle-group, active-outline-color, theme.variable(outline-high)), + outline-color: utils.module-val(button-toggle-group, outline-color, theme.variable(outline-medium)), + outline-color-active: utils.module-val(button-toggle-group, outline-color-active, theme.variable(outline-high)), // Shape shape: utils.module-val(button-toggle-group, shape, shape.variable(medium)), diff --git a/src/lib/core/styles/tokens/button-toggle/button-toggle/_tokens.scss b/src/lib/core/styles/tokens/button-toggle/button-toggle/_tokens.scss index 532f1aa63..f12caa7d7 100644 --- a/src/lib/core/styles/tokens/button-toggle/button-toggle/_tokens.scss +++ b/src/lib/core/styles/tokens/button-toggle/button-toggle/_tokens.scss @@ -14,10 +14,10 @@ $tokens: ( spacing: utils.module-val(button-toggle, spacing, spacing.variable(xsmall)), padding-block: utils.module-val(button-toggle, padding-block, 2px), padding-inline: utils.module-val(button-toggle, padding-inline, 8px), - icon-size: utils.module-val(button-toggle-group, icon-size, button.get(icon-size)), + icon-size: utils.module-val(button-toggle, icon-size, button.get(icon-size)), // Theme - color: utils.module-val(button-toggle, text-color, theme.variable(text-medium)), + color: utils.module-val(button-toggle, color, theme.variable(text-medium)), background: utils.module-val(button-toggle, background, transparent), hover-background: utils.module-ref(button-toggle, hover-background, background), active-background: utils.module-ref(button-toggle, active-background, background), @@ -45,9 +45,9 @@ $tokens: ( // Disabled disabled-opacity: utils.module-val(button-toggle, disabled-opacity, theme.emphasis(medium-low)), - disabled-cursor: utils.module-val(button, disabled-cursor, not-allowed), - disabled-color: utils.module-val(button, disabled-text-color, theme.variable(text-low)), - disabled-background: utils.module-val(button, disabled-background, transparent), + disabled-cursor: utils.module-val(button-toggle, disabled-cursor, not-allowed), + disabled-color: utils.module-val(button-toggle, disabled-color, theme.variable(text-low)), + disabled-background: utils.module-val(button-toggle, disabled-background, transparent), // Animation transition-duration: utils.module-val(button-toggle, transition-duration, animation.variable(duration-short3)), diff --git a/src/lib/core/styles/tokens/button/_tokens.scss b/src/lib/core/styles/tokens/button/_tokens.scss index bb6c6c586..31e1de537 100644 --- a/src/lib/core/styles/tokens/button/_tokens.scss +++ b/src/lib/core/styles/tokens/button/_tokens.scss @@ -12,7 +12,7 @@ $tokens: ( // Shared primary-color: utils.module-val(button, primary-color, theme.variable(primary)), text-color: utils.module-ref(button, text-color, primary-color), - disabled-color: utils.module-val(button, disabled, theme.variable(surface-container)), + disabled-color: utils.module-val(button, disabled-color, theme.variable(surface-container)), padding: utils.module-val(button, padding, 8px), display: utils.module-val(button, display, inline-flex), justify: utils.module-val(button, justify, center), @@ -53,7 +53,7 @@ $tokens: ( // Tonal tonal-background: utils.module-val(button, tonal-background, theme.variable(primary-container)), tonal-disabled-background: utils.module-ref(button, tonal-disabled-background, disabled-color), - tonal-color: utils.module-val(button, flat-color, theme.variable(on-primary-container)), + tonal-color: utils.module-val(button, tonal-color, theme.variable(on-primary-container)), tonal-disabled-color: utils.module-ref(button, tonal-disabled-color, disabled-text-color), // Filled @@ -96,8 +96,8 @@ $tokens: ( // Focus indicator focus-indicator-offset: utils.module-val(button, focus-indicator-offset, 4px), - text-focus-indicator-offset: utils.module-val(button, focus-indicator-offset, 0px), - link-focus-indicator-offset: utils.module-val(button, focus-indicator-offset, 2px), + text-focus-indicator-offset: utils.module-val(button, text-focus-indicator-offset, 0px), + link-focus-indicator-offset: utils.module-val(button, link-focus-indicator-offset, 2px), // Popover icon popover-icon-transition-duration: utils.module-val(button, popover-icon-transition-duration, animation.variable(duration-short3)), diff --git a/src/lib/core/styles/tokens/checkbox/_tokens.scss b/src/lib/core/styles/tokens/checkbox/_tokens.scss index 6a7cc4790..80cde018c 100644 --- a/src/lib/core/styles/tokens/checkbox/_tokens.scss +++ b/src/lib/core/styles/tokens/checkbox/_tokens.scss @@ -13,7 +13,7 @@ $tokens: ( border-width: utils.module-val(checkbox, border-width, border.variable(medium)), icon-color: utils.module-val(checkbox, icon-color, theme.variable(on-tertiary)), state-layer-size: utils.module-val(checkbox, state-layer-size, 40px), - state-layer-dense-size: utils.module-val(checkbox, state-layer-size, 24px), + state-layer-dense-size: utils.module-val(checkbox, state-layer-dense-size, 24px), // Checkbox background-color: utils.module-val(checkbox, background-color, theme.variable(surface)), @@ -36,14 +36,14 @@ $tokens: ( // Label gap: utils.module-val(checkbox, gap, 0), - justify: utils.module-val(switch, justify, space-between), - direction: utils.module-val(switch, direction, initial), + justify: utils.module-val(checkbox, justify, space-between), + direction: utils.module-val(checkbox, direction, initial), // State layer state-layer-width: utils.module-ref(checkbox, state-layer-width, state-layer-size), state-layer-height: utils.module-ref(checkbox, state-layer-height, state-layer-size), state-layer-checked-color: utils.module-ref(checkbox, state-layer-checked-color, primary-color), - state-layer-unchecked-color: utils.module-ref(state-layer, state-layer-unchecked-color, color), + state-layer-unchecked-color: utils.module-ref(checkbox, state-layer-unchecked-color, color), state-layer-shape: utils.module-val(checkbox, state-layer-shape, shape.variable(full)), // Dense @@ -55,8 +55,8 @@ $tokens: ( // Animation animation-duration: utils.module-val(checkbox, animation-duration, animation.variable(duration-short2)), - background-animation-timing: utils.module-val(checkbox, animation-duration, animation.variable(easing-standard)), - icon-animation-timing: utils.module-val(checkbox, animation-duration, animation.variable(easing-emphasized-accelerate)) + background-animation-timing: utils.module-val(checkbox, background-animation-timing, animation.variable(easing-standard)), + icon-animation-timing: utils.module-val(checkbox, icon-animation-timing, animation.variable(easing-emphasized-accelerate)) ) !default; @function get($key) { diff --git a/src/lib/core/styles/tokens/floating-action-button/_tokens.scss b/src/lib/core/styles/tokens/floating-action-button/_tokens.scss index 90a06e449..0a8cd056e 100644 --- a/src/lib/core/styles/tokens/floating-action-button/_tokens.scss +++ b/src/lib/core/styles/tokens/floating-action-button/_tokens.scss @@ -9,7 +9,7 @@ $tokens: ( // General display: utils.module-val(fab, display, inline-flex), - gap: utils.module-val(fab, spacing, spacing.variable(xsmall)), + gap: utils.module-val(fab, gap, spacing.variable(xsmall)), background: utils.module-val(fab, background, theme.variable(secondary)), color: utils.module-val(fab, color, theme.variable(on-secondary)), size: utils.module-val(fab, size, 56px), diff --git a/src/lib/core/styles/tokens/icon-button/_tokens.scss b/src/lib/core/styles/tokens/icon-button/_tokens.scss index d77307aab..3cf4daccd 100644 --- a/src/lib/core/styles/tokens/icon-button/_tokens.scss +++ b/src/lib/core/styles/tokens/icon-button/_tokens.scss @@ -11,7 +11,7 @@ $tokens: ( // General display: utils.module-val(icon-button, display, inline-flex), size: utils.module-val(icon-button, size, 48px), - gap: utils.module-val(icon-button, spacing, 0), + gap: utils.module-val(icon-button, gap, 0), icon-color: utils.module-val(icon-button, icon-color, currentColor), background-color: utils.module-val(icon-button, background-color, none), icon-size: utils.module-val(icon-button, icon-size, typography.font-size-relative('1500')), @@ -79,7 +79,7 @@ $tokens: ( // Toggle filled filled-toggle-background-color: utils.module-val(icon-button, filled-toggle-background-color, theme.variable(surface-container-low)), - filled-toggle-icon-color: utils.module-val(icon-button, filled-toggle-background-color, theme.variable(primary)), + filled-toggle-icon-color: utils.module-val(icon-button, filled-toggle-icon-color, theme.variable(primary)), // Toggle (on) filled filled-toggle-on-background-color: utils.module-val(icon-button, filled-toggle-on-background-color, theme.variable(primary)), diff --git a/src/lib/core/styles/tokens/profile-card/_tokens.scss b/src/lib/core/styles/tokens/profile-card/_tokens.scss index 48425b56c..b273e0436 100644 --- a/src/lib/core/styles/tokens/profile-card/_tokens.scss +++ b/src/lib/core/styles/tokens/profile-card/_tokens.scss @@ -5,7 +5,7 @@ $tokens: ( min-width: utils.module-val(profile-card, min-width, 360px), - shape: utils.module-val(profile-card, border-radius, shape.variable(medium)), + shape: utils.module-val(profile-card, shape, shape.variable(medium)), avatar-size: utils.module-val(profile-card, avatar-size, 56px) ) !default; diff --git a/src/lib/core/styles/tokens/radio/_tokens.scss b/src/lib/core/styles/tokens/radio/_tokens.scss index 541ed364a..f6f242a8a 100644 --- a/src/lib/core/styles/tokens/radio/_tokens.scss +++ b/src/lib/core/styles/tokens/radio/_tokens.scss @@ -39,7 +39,7 @@ $tokens: ( // State layer state-layer-width: utils.module-ref(radio, state-layer-width, state-layer-size), state-layer-height: utils.module-ref(radio, state-layer-height, state-layer-size), - state-layer-unchecked-color: utils.module-ref(state-layer, state-layer-unchecked-color, color), + state-layer-unchecked-color: utils.module-ref(radio, state-layer-unchecked-color, color), state-layer-checked-color: utils.module-ref(radio, state-layer-checked-color, primary-color), state-layer-shape: utils.module-ref(radio, state-layer-shape, shape), diff --git a/src/lib/core/styles/tokens/switch/_tokens.scss b/src/lib/core/styles/tokens/switch/_tokens.scss index 65ed59a24..597d01042 100644 --- a/src/lib/core/styles/tokens/switch/_tokens.scss +++ b/src/lib/core/styles/tokens/switch/_tokens.scss @@ -19,7 +19,7 @@ $tokens: ( icon-scale: utils.module-val(switch, icon-scale, 1), state-layer-size: utils.module-val(switch, state-layer-size, 40px), - state-layer-dense-size: utils.module-val(switch, handle-dense-size, 28px), + state-layer-dense-size: utils.module-val(switch, state-layer-dense-size, 28px), // Handle handle-on-color: utils.module-val(switch, handle-on-color, theme.variable(tertiary)), @@ -60,7 +60,7 @@ $tokens: ( state-layer-width: utils.module-ref(switch, state-layer-width, state-layer-size), state-layer-height: utils.module-ref(switch, state-layer-height, state-layer-size), state-layer-on-color: utils.module-ref(switch, state-layer-on-color, handle-on-color), - state-layer-off-color: utils.module-ref(state-layer, state-layer-off-color, color), + state-layer-off-color: utils.module-ref(switch, state-layer-off-color, color), // Dense state-layer-dense-width: utils.module-ref(switch, state-layer-dense-width, state-layer-dense-size), @@ -77,7 +77,7 @@ $tokens: ( handle-active-off-scale: utils.module-ref(switch, handle-active-off-scale, handle-active-scale), handle-active-elevation: utils.module-ref(switch, handle-active-elevation, handle-elevation), handle-active-on-elevation: utils.module-ref(switch, handle-active-on-elevation, handle-active-elevation), - handle-active-off-elevation: utils.module-ref(switch, handle-active-on-elevation, handle-active-elevation), + handle-active-off-elevation: utils.module-ref(switch, handle-active-off-elevation, handle-active-elevation), track-active-on-color: utils.module-ref(switch, track-active-on-color, track-on-color), track-active-off-color: utils.module-ref(switch, track-active-off-color, track-off-color), track-active-on-border-width: utils.module-ref(switch, track-active-on-border-width, track-on-border-width), diff --git a/src/lib/core/styles/tokens/tabs/tab/_tokens.scss b/src/lib/core/styles/tokens/tabs/tab/_tokens.scss index 60581895e..d9c4367b7 100644 --- a/src/lib/core/styles/tokens/tabs/tab/_tokens.scss +++ b/src/lib/core/styles/tokens/tabs/tab/_tokens.scss @@ -19,15 +19,15 @@ $tokens: ( indicator-shape: utils.module-val(tab, indicator-shape, 3px 3px 0 0), // Indicator (vertical) - vertical-indicator-shape: utils.module-val(tab, indicator-shape, 3px 0 0 3px), + vertical-indicator-shape: utils.module-val(tab, vertical-indicator-shape, 3px 0 0 3px), // Indicator (secondary) - secondary-indicator-height: utils.module-val(tab, indicator-height, 2px), - secondary-indicator-shape: utils.module-val(tab, indicator-shape, 0), + secondary-indicator-height: utils.module-val(tab, secondary-indicator-height, 2px), + secondary-indicator-shape: utils.module-val(tab, secondary-indicator-shape, 0), // Indicator (inverted) - inverted-indicator-shape: utils.module-val(tab, indicator-shape, 0 0 3px 3px), - vertical-inverted-indicator-shape: utils.module-val(tab, indicator-shape, 0 3px 3px 0), + inverted-indicator-shape: utils.module-val(tab, inverted-indicator-shape, 0 0 3px 3px), + vertical-inverted-indicator-shape: utils.module-val(tab, vertical-inverted-indicator-shape, 0 3px 3px 0), // Container container-color: utils.module-val(tab, container-color, transparent), diff --git a/src/lib/core/styles/tokens/theme/_tokens.utilities.scss b/src/lib/core/styles/tokens/theme/_tokens.utilities.scss index a837a83bf..6a191e215 100644 --- a/src/lib/core/styles/tokens/theme/_tokens.utilities.scss +++ b/src/lib/core/styles/tokens/theme/_tokens.utilities.scss @@ -14,8 +14,10 @@ $on-surface: map.get($surface-theme, on-surface); @return ( - outline: theme-utils.hexify($on-surface, $surface, color-emphasis.value(lower)), - outline-high: theme-utils.hexify($on-surface, $surface, color-emphasis.value(medium)) + outline-high: theme-utils.hexify($on-surface, $surface, color-emphasis.value(highest)), + outline-medium: theme-utils.hexify($on-surface, $surface, color-emphasis.value(medium)), + outline-low: theme-utils.hexify($on-surface, $surface, color-emphasis.value(medium-low)), + outline: theme-utils.hexify($on-surface, $surface, color-emphasis.value(lower)) ); } diff --git a/src/lib/radio/radio-group/radio-group.ts b/src/lib/radio/radio-group/radio-group.ts index 8129ace89..b1012196c 100644 --- a/src/lib/radio/radio-group/radio-group.ts +++ b/src/lib/radio/radio-group/radio-group.ts @@ -1,13 +1,16 @@ import { CustomElement, FoundationProperty, attachShadowTemplate, coerceBoolean, toggleAttribute } from '@tylertech/forge-core'; -import { internals, setDefaultAria } from '../../constants'; -import { BaseComponent, IWithElementInternals, IWithLabelAwareness, WithElementInternals, WithLabelAwareness } from '../../core/base'; +import { internals, observedDefaultAriaAttributes, setDefaultAria } from '../../constants'; +import { BaseComponent } from '../../core/base/base-component'; +import { IWithDefaultAria, WithDefaultAria } from '../../core/mixins/internals/with-default-aria'; +import { IWithElementInternals, WithElementInternals } from '../../core/mixins/internals/with-element-internals'; +import { IWithLabelAwareness, WithLabelAwareness } from '../../core/mixins/label/with-label-aware'; import { RadioGroupAdapter } from './radio-group-adapter'; import { RADIO_GROUP_CONSTANTS } from './radio-group-constants'; import { RadioGroupFoundation } from './radio-group-foundation'; import template from './radio-group.html'; -export interface IRadioGroupComponent extends IWithLabelAwareness, IWithElementInternals { +export interface IRadioGroupComponent extends IWithLabelAwareness, IWithElementInternals, IWithDefaultAria { readonly form: HTMLFormElement | null; readonly labels: NodeList; name: string; @@ -21,7 +24,7 @@ declare global { } } -const BaseRadioGroupClass = WithLabelAwareness(WithElementInternals(BaseComponent, RADIO_GROUP_CONSTANTS.observedAriaAttributes)); +const BaseRadioGroupClass = WithLabelAwareness(WithDefaultAria(WithElementInternals(BaseComponent))); /** * @tag forge-radio-group @@ -38,6 +41,8 @@ const BaseRadioGroupClass = WithLabelAwareness(WithElementInternals(BaseComponen export class RadioGroupComponent extends BaseRadioGroupClass implements IRadioGroupComponent { public static readonly formAssociated = true; + public readonly [observedDefaultAriaAttributes] = RADIO_GROUP_CONSTANTS.observedAriaAttributes; + public get form(): HTMLFormElement | null { return this[internals].form; } diff --git a/src/lib/radio/radio/radio.ts b/src/lib/radio/radio/radio.ts index 70e61e3fd..1f523caa7 100644 --- a/src/lib/radio/radio/radio.ts +++ b/src/lib/radio/radio/radio.ts @@ -1,16 +1,10 @@ import { CustomElement, FoundationProperty, attachShadowTemplate, coerceBoolean } from '@tylertech/forge-core'; -import { getFormState, getFormValue, inputType, internals, setDefaultAria } from '../../constants'; -import { - BaseComponent, - IWithElementInternals, - IWithFocusable, - IWithFormAssociation, - IWithLabelAwareness, - WithElementInternals, - WithFocusable, - WithFormAssociation, - WithLabelAwareness -} from '../../core/base'; +import { getFormState, getFormValue, inputType, internals, observedDefaultAriaAttributes, setDefaultAria } from '../../constants'; +import { BaseComponent } from '../../core/base/base-component'; +import { IWithFocusable, WithFocusable } from '../../core/mixins/focus/with-focusable'; +import { IWithElementInternals, WithElementInternals } from '../../core/mixins/internals/with-element-internals'; +import { IWithFormAssociation, WithFormAssociation } from '../../core/mixins/form/with-form-associated'; +import { IWithLabelAwareness, WithLabelAwareness } from '../../core/mixins/label/with-label-aware'; import { FormValue } from '../../core/utils/form-utils'; import { FocusIndicatorComponent } from '../../focus-indicator'; import { StateLayerComponent } from '../../state-layer'; @@ -18,11 +12,12 @@ import { RadioGroupManager } from '../core/radio-group-manager'; import { RadioAdapter } from './radio-adapter'; import { RADIO_CONSTANTS, RadioLabelPosition, RadioState, tryCheck } from './radio-constants'; import { RadioFoundation } from './radio-foundation'; +import { IWithDefaultAria, WithDefaultAria } from '../../core/mixins/internals/with-default-aria'; import template from './radio.html'; import style from './radio.scss'; -export interface IRadioComponent extends IWithFormAssociation, IWithFocusable, IWithLabelAwareness, IWithElementInternals { +export interface IRadioComponent extends IWithFormAssociation, IWithFocusable, IWithLabelAwareness, IWithElementInternals, IWithDefaultAria { checked: boolean; defaultChecked: boolean; required: boolean; @@ -36,7 +31,7 @@ declare global { } } -const BaseRadioClass = WithFormAssociation(WithLabelAwareness(WithFocusable(WithElementInternals(BaseComponent, RADIO_CONSTANTS.observedAriaAttributes)))); +const BaseRadioClass = WithFormAssociation(WithLabelAwareness(WithFocusable(WithDefaultAria(WithElementInternals(BaseComponent))))); /** * @tag forge-radio @@ -126,6 +121,8 @@ export class RadioComponent extends BaseRadioClass implements IRadioComponent { ]; } + public readonly [observedDefaultAriaAttributes] = RADIO_CONSTANTS.observedAriaAttributes; + private _foundation: RadioFoundation; constructor() {