diff --git a/.changeset/bitter-baboons-tease.md b/.changeset/bitter-baboons-tease.md new file mode 100644 index 00000000000..fe7f31e6d73 --- /dev/null +++ b/.changeset/bitter-baboons-tease.md @@ -0,0 +1,11 @@ +--- +"@hashicorp/design-system-components": major +--- + + +`Flyout` - Removed deprecated `HdsFlyoutHeader`, `HdsFlyoutBody`, `HdsFlyoutDescription`, and `HdsFlyoutFooter` subcomponents. + + + +`Modal` - Removed deprecated `HdsModalHeader`, `HdsModalBody`, and `HdsModalFooter` subcomponents. + diff --git a/.changeset/cold-worlds-notice.md b/.changeset/cold-worlds-notice.md new file mode 100644 index 00000000000..26319a386e3 --- /dev/null +++ b/.changeset/cold-worlds-notice.md @@ -0,0 +1,11 @@ +--- +"@hashicorp/design-system-components": major +--- + + + +`Dropdown` - Removed the deprecated `@text` argument from the `HdsDropdownListItemInteractive` component. + +To migrate run the codemod `v4/dropdown-list-item-interactive` (see [readme file](https://github.com/hashicorp/design-system/tree/main/packages/codemods/transforms/v4/dropdown-list-item-interactive)) + + diff --git a/.changeset/fluffy-places-ask.md b/.changeset/fluffy-places-ask.md new file mode 100644 index 00000000000..b11a17ba1c9 --- /dev/null +++ b/.changeset/fluffy-places-ask.md @@ -0,0 +1,5 @@ +--- +"@hashicorp/design-system-components": major +--- + +Removed support for Ember 3.28. New minimum support target is Ember 4.12. diff --git a/.changeset/full-showers-kiss.md b/.changeset/full-showers-kiss.md new file mode 100644 index 00000000000..d5426a68e97 --- /dev/null +++ b/.changeset/full-showers-kiss.md @@ -0,0 +1,5 @@ +--- +"@hashicorp/design-system-components": major +--- + +- Removed `sass` and `ember-cli-sass` dependencies. Consumers using `sass` in their projects should make sure it's added as a direct dependency to their project. diff --git a/.changeset/hot-owls-rescue.md b/.changeset/hot-owls-rescue.md new file mode 100644 index 00000000000..582a7dff51a --- /dev/null +++ b/.changeset/hot-owls-rescue.md @@ -0,0 +1,7 @@ +--- +"@hashicorp/design-system-components": major +--- + + +`AdvancedTable` - Removed the `@isVisuallyHidden` argument from `HdsAdvancedTableTh` component. This setting is supported via setting `isVisuallyHidden` in the corresponding `@columns` item's configuration. + diff --git a/.changeset/legal-swans-occur.md b/.changeset/legal-swans-occur.md new file mode 100644 index 00000000000..6b641130ada --- /dev/null +++ b/.changeset/legal-swans-occur.md @@ -0,0 +1,5 @@ +--- +"@hashicorp/design-system-components": major +--- + +Removed the deprecated `MenuPrimitive` component diff --git a/.changeset/spicy-ads-roll.md b/.changeset/spicy-ads-roll.md new file mode 100644 index 00000000000..b0af1b042a1 --- /dev/null +++ b/.changeset/spicy-ads-roll.md @@ -0,0 +1,10 @@ +--- +"@hashicorp/design-system-components": major +--- + + +`SideNav` - Removed deprecated features +- Removed the `@ariaLabel` argument +- Removed the `HdsSideNavHeaderIconButton` component +- Updated the deprecation removal version from `5.0.0` to `6.0.0` + diff --git a/.changeset/young-meals-knock.md b/.changeset/young-meals-knock.md new file mode 100644 index 00000000000..b46136cbbb0 --- /dev/null +++ b/.changeset/young-meals-knock.md @@ -0,0 +1,5 @@ +--- +"@hashicorp/design-system-components": major +--- + +Removed support for deprecated `ember-flight-icons` `lazyEmbed` config diff --git a/.github/workflows/ci-components.yml b/.github/workflows/ci-components.yml index 4a12cf5a824..a148994dea9 100644 --- a/.github/workflows/ci-components.yml +++ b/.github/workflows/ci-components.yml @@ -7,7 +7,7 @@ on: pull_request: {} env: - NODE_VERSION: '22.x' + NODE_VERSION: "22.x" concurrency: group: ci-components-${{ github.head_ref || github.ref }} @@ -28,7 +28,7 @@ jobs: run: ./.github/scripts/filter_changed_files.sh "packages/components" "packages/flight-icons/catalog.json" "showcase" ".github/workflows/ci-components.yml" "packages/tokens" test: - name: 'Tests' + name: "Tests" runs-on: ubuntu-latest needs: [conditional-skip] if: needs.conditional-skip.outputs.trigger-ci == 'true' @@ -63,13 +63,12 @@ jobs: try-scenarios: name: ${{ matrix.try-scenario }} runs-on: ubuntu-latest - needs: 'test' + needs: "test" strategy: fail-fast: false matrix: try-scenario: - - ember-lts-3.28 - ember-lts-4.12 # - ember-release - ember-beta diff --git a/README.md b/README.md index 4f98f324b50..609e335cfb0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + diff --git a/packages/components/README.md b/packages/components/README.md index 8f6899d81a5..c414d5a1df4 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -8,9 +8,9 @@ A package containing the components for the Helios Design System. Compatibility ------------------------------------------------------------------------------ -* Ember.js v3.28 or above -* Ember CLI v3.28 or above -* Node.js v16 or above +* Ember.js v4.12 or above +* Ember CLI v4.12 or above +* Node.js v18 or above Installation ------------------------------------------------------------------------------ diff --git a/packages/components/addon-main.cjs b/packages/components/addon-main.cjs index 7c2c6ad22d6..797c851c5f9 100644 --- a/packages/components/addon-main.cjs +++ b/packages/components/addon-main.cjs @@ -9,11 +9,8 @@ const flightIconSprite = require('@hashicorp/flight-icons/svg-sprite/svg-sprite- module.exports = { ...addonV1Shim(__dirname), contentFor(type, config) { - const legacyLazyEmbed = config?.emberFlightIcons?.lazyEmbed; - if ( !config.flightIconsSpriteLazyEmbed && - !legacyLazyEmbed && !config.__flightIconsSpriteLoaded && type === 'body-footer' ) { diff --git a/packages/components/package.json b/packages/components/package.json index 6112a99e819..857ae38cb8d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -59,7 +59,6 @@ "codemirror-lang-hcl": "^0.0.0-beta.2", "decorator-transforms": "^2.3.0", "ember-a11y-refocus": "^4.1.4", - "ember-cli-sass": "^11.0.1", "ember-concurrency": "^4.0.4", "ember-element-helper": "^0.8.6", "ember-focus-trap": "^1.1.1", @@ -71,10 +70,9 @@ "ember-truth-helpers": "^4.0.3", "luxon": "^3.4.2", "prismjs": "^1.30.0", - "sass": "^1.83.0", - "tracked-built-ins": "^4.0.0", "tabbable": "^6.2.0", - "tippy.js": "^6.3.7" + "tippy.js": "^6.3.7", + "tracked-built-ins": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.27.1", @@ -113,6 +111,7 @@ "rollup": "^4.39.0", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-scss": "^4.0.1", + "sass": "^1.89.2", "stylelint": "^16.17.0", "stylelint-config-rational-order": "^0.1.2", "stylelint-config-standard-scss": "^14.0.0", @@ -236,10 +235,6 @@ "./components/hds/dropdown/toggle/button.js": "./dist/_app_/components/hds/dropdown/toggle/button.js", "./components/hds/dropdown/toggle/chevron.js": "./dist/_app_/components/hds/dropdown/toggle/chevron.js", "./components/hds/dropdown/toggle/icon.js": "./dist/_app_/components/hds/dropdown/toggle/icon.js", - "./components/hds/flyout/body.js": "./dist/_app_/components/hds/flyout/body.js", - "./components/hds/flyout/description.js": "./dist/_app_/components/hds/flyout/description.js", - "./components/hds/flyout/footer.js": "./dist/_app_/components/hds/flyout/footer.js", - "./components/hds/flyout/header.js": "./dist/_app_/components/hds/flyout/header.js", "./components/hds/flyout.js": "./dist/_app_/components/hds/flyout.js", "./components/hds/form/character-count.js": "./dist/_app_/components/hds/form/character-count.js", "./components/hds/form/checkbox/base.js": "./dist/_app_/components/hds/form/checkbox/base.js", @@ -305,10 +300,6 @@ "./components/hds/layout/grid/item.js": "./dist/_app_/components/hds/layout/grid/item.js", "./components/hds/link/inline.js": "./dist/_app_/components/hds/link/inline.js", "./components/hds/link/standalone.js": "./dist/_app_/components/hds/link/standalone.js", - "./components/hds/menu-primitive.js": "./dist/_app_/components/hds/menu-primitive.js", - "./components/hds/modal/body.js": "./dist/_app_/components/hds/modal/body.js", - "./components/hds/modal/footer.js": "./dist/_app_/components/hds/modal/footer.js", - "./components/hds/modal/header.js": "./dist/_app_/components/hds/modal/header.js", "./components/hds/modal.js": "./dist/_app_/components/hds/modal.js", "./components/hds/page-header/actions.js": "./dist/_app_/components/hds/page-header/actions.js", "./components/hds/page-header/badges.js": "./dist/_app_/components/hds/page-header/badges.js", @@ -333,7 +324,6 @@ "./components/hds/separator.js": "./dist/_app_/components/hds/separator.js", "./components/hds/side-nav/base.js": "./dist/_app_/components/hds/side-nav/base.js", "./components/hds/side-nav/header/home-link.js": "./dist/_app_/components/hds/side-nav/header/home-link.js", - "./components/hds/side-nav/header/icon-button.js": "./dist/_app_/components/hds/side-nav/header/icon-button.js", "./components/hds/side-nav/header.js": "./dist/_app_/components/hds/side-nav/header.js", "./components/hds/side-nav.js": "./dist/_app_/components/hds/side-nav.js", "./components/hds/side-nav/list/back-link.js": "./dist/_app_/components/hds/side-nav/list/back-link.js", @@ -392,6 +382,8 @@ "./modifiers/hds-code-editor/palettes/hds-dark-palette.js": "./dist/_app_/modifiers/hds-code-editor/palettes/hds-dark-palette.js", "./modifiers/hds-code-editor/themes/hds-dark-theme.js": "./dist/_app_/modifiers/hds-code-editor/themes/hds-dark-theme.js", "./modifiers/hds-code-editor/types.js": "./dist/_app_/modifiers/hds-code-editor/types.js", + "./modifiers/hds-lifecycle.js": "./dist/_app_/modifiers/hds-lifecycle.js", + "./modifiers/hds-register-element.js": "./dist/_app_/modifiers/hds-register-element.js", "./modifiers/hds-register-event.js": "./dist/_app_/modifiers/hds-register-event.js", "./modifiers/hds-tooltip.js": "./dist/_app_/modifiers/hds-tooltip.js", "./services/hds-intl.js": "./dist/_app_/services/hds-intl.js", diff --git a/packages/components/src/components.ts b/packages/components/src/components.ts index 400dc061324..fb1b5c4d9b8 100644 --- a/packages/components/src/components.ts +++ b/packages/components/src/components.ts @@ -279,7 +279,6 @@ export { default as HdsSideNav } from './components/hds/side-nav/index.ts'; export { default as HdsSideNavBase } from './components/hds/side-nav/base.ts'; export { default as HdsSideNavHeader } from './components/hds/side-nav/header/index.ts'; export { default as HdsSideNavHeaderHomeLink } from './components/hds/side-nav/header/home-link.ts'; -export { default as HdsSideNavHeaderIconButton } from './components/hds/side-nav/header/icon-button.ts'; export { default as HdsSideNavList } from './components/hds/side-nav/list/index.ts'; export { default as HdsSideNavListBackLink } from './components/hds/side-nav/list/back-link.ts'; export { default as HdsSideNavListItem } from './components/hds/side-nav/list/item.ts'; @@ -390,8 +389,5 @@ export { default as HdsDismissButton } from './components/hds/dismiss-button/ind // Interactive export { default as HdsInteractive } from './components/hds/interactive/index.ts'; -// MenuPrimitive -export { default as HdsMenuPrimitive } from './components/hds/menu-primitive/index.ts'; - // PopoverPrimitive export { default as HdsPopoverPrimitive } from './components/hds/popover-primitive/index.ts'; diff --git a/packages/components/src/components/hds/advanced-table/index.hbs b/packages/components/src/components/hds/advanced-table/index.hbs index 8f8bae33e58..2c6e876c204 100644 --- a/packages/components/src/components/hds/advanced-table/index.hbs +++ b/packages/components/src/components/hds/advanced-table/index.hbs @@ -6,8 +6,8 @@
{{! Caption }} @@ -84,7 +84,6 @@ @isExpandable={{column.isExpandable}} @isStickyColumn={{this._isStickyColumn column}} @isStickyColumnPinned={{this.isStickyColumnPinned}} - @isVisuallyHidden={{column.isVisuallyHidden}} @tableHeight={{this._tableHeight}} @tooltip={{column.tooltip}} @onClickToggle={{this._tableModel.toggleAll}} diff --git a/packages/components/src/components/hds/advanced-table/index.ts b/packages/components/src/components/hds/advanced-table/index.ts index a5bfae87784..372f45a3ec6 100644 --- a/packages/components/src/components/hds/advanced-table/index.ts +++ b/packages/components/src/components/hds/advanced-table/index.ts @@ -10,6 +10,7 @@ import { tracked } from '@glimmer/tracking'; import { guidFor } from '@ember/object/internals'; import { service } from '@ember/service'; import { modifier } from 'ember-modifier'; +import { schedule } from '@ember/runloop'; import HdsAdvancedTableTableModel from './models/table.ts'; import type Owner from '@ember/owner'; @@ -196,6 +197,25 @@ export interface HdsAdvancedTableSignature { Element: HTMLDivElement; } +interface HdsAdvancedTableSyncTableDataModifierSignature { + Element: HdsAdvancedTableSignature['Element']; + Args: { + Named: Pick< + HdsAdvancedTableSignature['Args'], + 'columns' | 'model' | 'sortBy' | 'sortOrder' + >; + }; +} + +interface HdsAdvancedTableSyncColumnOrderModifierSignature { + Element: HdsAdvancedTableSignature['Element']; + Args: { + Named: { + columnOrder?: HdsAdvancedTableSignature['Args']['columnOrder']; + }; + }; +} + export default class HdsAdvancedTable extends Component { @service hdsIntl!: HdsIntlService; @@ -426,6 +446,50 @@ export default class HdsAdvancedTable extends Component { + let isFirstRun = true; + + return modifier( + (_element, _positional, { columns, model, sortBy, sortOrder }) => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', (): void => { + if (isFirstRun) { + isFirstRun = false; + return; + } + this._tableModel.setupData({ + columns, + model, + sortBy, + sortOrder, + }); + }); + } + ); + })(); + + private _syncColumnOrder = (() => { + let isFirstRun = true; + + return modifier( + (_element, _positional, { columnOrder }) => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', (): void => { + if (isFirstRun) { + isFirstRun = false; + return; + } + + if (columnOrder === undefined) { + return; + } + + this._tableModel.columnOrder = columnOrder; + }); + } + ); + })(); + private _registerGridElement = modifier((element: HTMLDivElement) => { this._tableModel.gridElement = element; }); @@ -613,27 +677,6 @@ export default class HdsAdvancedTable extends Component { diff --git a/packages/components/src/components/hds/advanced-table/th.ts b/packages/components/src/components/hds/advanced-table/th.ts index 04e2d84f436..d3531a443cb 100644 --- a/packages/components/src/components/hds/advanced-table/th.ts +++ b/packages/components/src/components/hds/advanced-table/th.ts @@ -44,7 +44,6 @@ export interface HdsAdvancedTableThSignature { isExpandable?: boolean; isStickyColumn?: boolean; isStickyColumnPinned?: boolean; - isVisuallyHidden?: boolean; newLabel?: string; parentId?: string; rowspan?: number; diff --git a/packages/components/src/components/hds/alert/description.hbs b/packages/components/src/components/hds/alert/description.hbs index fc97c4e9d04..a8433cfedd8 100644 --- a/packages/components/src/components/hds/alert/description.hbs +++ b/packages/components/src/components/hds/alert/description.hbs @@ -2,4 +2,8 @@ Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: MPL-2.0 }} -
{{yield}}
\ No newline at end of file +
{{yield}}
\ No newline at end of file diff --git a/packages/components/src/components/hds/alert/description.ts b/packages/components/src/components/hds/alert/description.ts index 7767669d206..e9f8b8d72fe 100644 --- a/packages/components/src/components/hds/alert/description.ts +++ b/packages/components/src/components/hds/alert/description.ts @@ -3,16 +3,23 @@ * SPDX-License-Identifier: MPL-2.0 */ -import TemplateOnlyComponent from '@ember/component/template-only'; +import Component from '@glimmer/component'; +import { modifier } from 'ember-modifier'; export interface HdsAlertDescriptionSignature { + Args: { + onInsert: (element: HdsAlertDescriptionSignature['Element']) => void; + }; Blocks: { default: []; }; Element: HTMLDivElement; } -const HdsAlertDescription = - TemplateOnlyComponent(); - -export default HdsAlertDescription; +export default class HdsAlertDescription extends Component { + private _registerElement = modifier( + (element: HdsAlertDescriptionSignature['Element']) => { + this.args.onInsert(element); + } + ); +} diff --git a/packages/components/src/components/hds/alert/index.hbs b/packages/components/src/components/hds/alert/index.hbs index e00894d8ebc..1adfb0c12e6 100644 --- a/packages/components/src/components/hds/alert/index.hbs +++ b/packages/components/src/components/hds/alert/index.hbs @@ -4,10 +4,9 @@ }}
{{#if this.icon}} @@ -16,10 +15,10 @@
{{/if}} -
+
- {{yield (hash Title=(component "hds/alert/title"))}} - {{yield (hash Description=(component "hds/alert/description"))}} + {{yield (hash Title=(component "hds/alert/title" onInsert=this._registerTitle))}} + {{yield (hash Description=(component "hds/alert/description" onInsert=this._registerDescription))}}
diff --git a/packages/components/src/components/hds/alert/index.ts b/packages/components/src/components/hds/alert/index.ts index b147b911f19..5e58aed7dfb 100644 --- a/packages/components/src/components/hds/alert/index.ts +++ b/packages/components/src/components/hds/alert/index.ts @@ -8,6 +8,7 @@ import { action } from '@ember/object'; import { assert } from '@ember/debug'; import { guidFor } from '@ember/object/internals'; import { tracked } from '@glimmer/tracking'; +import { modifier } from 'ember-modifier'; import { HdsAlertColorValues, HdsAlertTypeValues } from './types.ts'; @@ -16,6 +17,8 @@ import type HdsButtonComponent from '../button'; import type HdsLinkStandaloneComponent from '../link/standalone'; import type { HdsYieldSignature } from '../yield'; import type { HdsAlertColors, HdsAlertTypes } from './types.ts'; +import type HdsAlertTitleComponent from './title.ts'; +import type HdsAlertDescriptionComponent from './description.ts'; import type { HdsAlertTitleSignature } from './title.ts'; import type { HdsAlertDescriptionSignature } from './description.ts'; import type { HdsIconSignature } from '../icon'; @@ -25,6 +28,12 @@ export const TYPES: HdsAlertTypes[] = Object.values(HdsAlertTypeValues); export const DEFAULT_COLOR: HdsAlertColors = HdsAlertColorValues.Neutral; export const COLORS: HdsAlertColors[] = Object.values(HdsAlertColorValues); +const SEMANTIC_COLORS: HdsAlertColors[] = [ + HdsAlertColorValues.Warning, + HdsAlertColorValues.Critical, + HdsAlertColorValues.Success, +]; + export const MAPPING_COLORS_TO_ICONS = { [HdsAlertColorValues.Neutral]: 'info', [HdsAlertColorValues.Highlight]: 'info', @@ -33,10 +42,6 @@ export const MAPPING_COLORS_TO_ICONS = { [HdsAlertColorValues.Critical]: 'alert-diamond', } as const; -const CONTENT_ELEMENT_SELECTOR = '.hds-alert__content'; -const TITLE_ELEMENT_SELECTOR = '.hds-alert__title'; -const DESCRIPTION_ELEMENT_SELECTOR = '.hds-alert__description'; - export interface HdsAlertSignature { Args: { type: HdsAlertTypes; @@ -48,8 +53,11 @@ export interface HdsAlertSignature { Blocks: { default: [ { - Title?: ComponentLike; - Description?: ComponentLike; + Title?: WithBoundArgs; + Description?: WithBoundArgs< + typeof HdsAlertDescriptionComponent, + 'onInsert' + >; Generic?: ComponentLike; LinkStandalone?: WithBoundArgs< typeof HdsLinkStandaloneComponent, @@ -65,6 +73,10 @@ export interface HdsAlertSignature { export default class HdsAlert extends Component { @tracked private _role?: string; @tracked private _ariaLabelledBy?: string; + @tracked private _actions: NodeListOf | null = null; + @tracked private _titleElement?: HdsAlertTitleSignature['Element']; + @tracked + private _descriptionElement?: HdsAlertDescriptionSignature['Element']; constructor(owner: Owner, args: HdsAlertSignature['Args']) { super(owner, args); @@ -91,6 +103,11 @@ export default class HdsAlert extends Component { return color; } + // an Alert which actually alerts users (has role="alert" & aria-live="polite") as opposed to an informational or promo "alert" + get isSemantic(): boolean { + return SEMANTIC_COLORS.includes(this.color); + } + // The name of the icon to be used. get icon(): HdsIconSignature['Args']['name'] | false { const { icon } = this.args; @@ -150,33 +167,46 @@ export default class HdsAlert extends Component { return classes.join(' '); } - @action - didInsert(element: HTMLDivElement): void { - const actions = element.querySelectorAll( - `${CONTENT_ELEMENT_SELECTOR} button, ${CONTENT_ELEMENT_SELECTOR} a` - ); + get role(): 'alertdialog' | 'alert' | undefined { + if (this.isSemantic && this._actions && this._actions.length > 0) { + return 'alertdialog'; + } else if (this.isSemantic) { + return 'alert'; + } else { + return undefined; + } + } - // an Alert which actually alerts users (has role="alert" & aria-live="polite") as opposed to an informational or promo "alert" - const isSemanticAlert: boolean = - this.color === 'warning' || - this.color === 'critical' || - this.color === 'success'; + get labelElement(): HdsAlertTitleSignature['Element'] | undefined { + return this._titleElement ?? this._descriptionElement; + } - if (isSemanticAlert && actions.length) { - this._role = 'alertdialog'; - } else if (isSemanticAlert) { - this._role = 'alert'; - } + private _registerActions = modifier((element: HTMLDivElement) => { + this._actions = element.querySelectorAll('button, a'); + }); - // `alertdialog` must have an accessible name so we use either the - // title or the description as label for the alert - const label = - element.querySelector(TITLE_ELEMENT_SELECTOR) || - element.querySelector(DESCRIPTION_ELEMENT_SELECTOR); - if (label) { - const labelId = label.getAttribute('id') || guidFor(element); - label.setAttribute('id', labelId); - this._ariaLabelledBy = labelId; + private _setLabelElementId() { + if (this.labelElement === undefined) { + return; } + + const labelId = + this.labelElement.getAttribute('id') ?? guidFor(this.labelElement); + + this.labelElement.setAttribute('id', labelId); + + this._ariaLabelledBy = labelId; + } + + @action + _registerTitle(element: HdsAlertTitleSignature['Element']): void { + this._titleElement = element; + this._setLabelElementId(); + } + + @action + _registerDescription(element: HdsAlertDescriptionSignature['Element']): void { + this._descriptionElement = element; + this._setLabelElementId(); } } diff --git a/packages/components/src/components/hds/alert/title.hbs b/packages/components/src/components/hds/alert/title.hbs index 6096883f60b..27f2032e02a 100644 --- a/packages/components/src/components/hds/alert/title.hbs +++ b/packages/components/src/components/hds/alert/title.hbs @@ -5,5 +5,6 @@ {{! IMPORTANT: we removed any extra newlines before/after the `let` to reduce the issues with unexpected whitespaces (see https://github.com/hashicorp/design-system/pull/1652) }} {{#let (element this.componentTag) as |Tag|}}{{yield}}{{/let}} \ No newline at end of file diff --git a/packages/components/src/components/hds/alert/title.ts b/packages/components/src/components/hds/alert/title.ts index 4e04c2694ec..6f620dac05f 100644 --- a/packages/components/src/components/hds/alert/title.ts +++ b/packages/components/src/components/hds/alert/title.ts @@ -4,11 +4,15 @@ */ import Component from '@glimmer/component'; +import { modifier } from 'ember-modifier'; import { HdsAlertTitleTagValues } from './types.ts'; + import type { HdsAlertTitleTags } from './types'; + export interface HdsAlertTitleSignature { Args: { tag?: HdsAlertTitleTags; + onInsert: (element: HdsAlertTitleSignature['Element']) => void; }; Blocks: { default: []; @@ -20,4 +24,14 @@ export default class HdsAlertTitle extends Component { get componentTag(): HdsAlertTitleTags { return this.args.tag ?? HdsAlertTitleTagValues.Div; } + + private _registerElement = modifier( + (element: HdsAlertTitleSignature['Element']) => { + const { onInsert } = this.args; + + if (typeof onInsert === 'function') { + onInsert(element); + } + } + ); } diff --git a/packages/components/src/components/hds/app-side-nav/list/index.hbs b/packages/components/src/components/hds/app-side-nav/list/index.hbs index adb1c30f885..1184cf605c7 100644 --- a/packages/components/src/components/hds/app-side-nav/list/index.hbs +++ b/packages/components/src/components/hds/app-side-nav/list/index.hbs @@ -14,7 +14,7 @@ (hash Item=(component "hds/app-side-nav/list/item") BackLink=(component "hds/app-side-nav/list/back-link") - Title=(component "hds/app-side-nav/list/title" didInsertTitle=this.didInsertTitle) + Title=(component "hds/app-side-nav/list/title" registerTitleId=this._registerTitleId) Link=(component "hds/app-side-nav/list/link") ) }} diff --git a/packages/components/src/components/hds/app-side-nav/list/index.ts b/packages/components/src/components/hds/app-side-nav/list/index.ts index a67d6084748..d01df698dfd 100644 --- a/packages/components/src/components/hds/app-side-nav/list/index.ts +++ b/packages/components/src/components/hds/app-side-nav/list/index.ts @@ -4,8 +4,10 @@ */ import Component from '@glimmer/component'; -import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; +import { modifier } from 'ember-modifier'; +import { schedule } from '@ember/runloop'; + import type { ComponentLike } from '@glint/template'; import type { HdsYieldSignature } from '../../yield'; import type { HdsAppSideNavListItemSignature } from './item'; @@ -29,6 +31,10 @@ export interface HdsAppSideNavListSignature { Element: HTMLElement; } +export interface HdsAppSideNavListRegisterTitleIdModifierSignature { + Element: HdsAppSideNavListSignature['Element']; +} + export default class HdsAppSideNavList extends Component { @tracked private _titleIds: string[] = []; @@ -36,8 +42,12 @@ export default class HdsAppSideNavList extends Component((element) => { + // eslint-disable-next-line ember/no-runloop + schedule( + 'afterRender', + () => (this._titleIds = [...this._titleIds, element.id]) + ); + }); } diff --git a/packages/components/src/components/hds/app-side-nav/list/title.hbs b/packages/components/src/components/hds/app-side-nav/list/title.hbs index c588194b66a..4498ab843d6 100644 --- a/packages/components/src/components/hds/app-side-nav/list/title.hbs +++ b/packages/components/src/components/hds/app-side-nav/list/title.hbs @@ -7,7 +7,7 @@
{{~yield~}}
\ No newline at end of file diff --git a/packages/components/src/components/hds/app-side-nav/list/title.ts b/packages/components/src/components/hds/app-side-nav/list/title.ts index 75b013a9fad..1529c0a6c40 100644 --- a/packages/components/src/components/hds/app-side-nav/list/title.ts +++ b/packages/components/src/components/hds/app-side-nav/list/title.ts @@ -4,12 +4,14 @@ */ import { guidFor } from '@ember/object/internals'; -import { action } from '@ember/object'; import Component from '@glimmer/component'; +import type { ModifierLike } from '@glint/template'; +import type { HdsAppSideNavListRegisterTitleIdModifierSignature } from './index.ts'; + export interface HdsAppSideNavListTitleSignature { Args: { - didInsertTitle?: (titleId: string) => void; + registerTitleId?: ModifierLike; }; Blocks: { default: []; @@ -20,13 +22,4 @@ export interface HdsAppSideNavListTitleSignature { export default class HdsAppSideNavListTitle extends Component { /* Generate a unique ID for each Title */ private _titleId = 'title-' + guidFor(this); - - @action - didInsertTitle(element: HTMLElement): void { - const { didInsertTitle } = this.args; - - if (typeof didInsertTitle === 'function') { - didInsertTitle(element.id); - } - } } diff --git a/packages/components/src/components/hds/app-side-nav/portal/target.hbs b/packages/components/src/components/hds/app-side-nav/portal/target.hbs index 1e55cc26865..397590447e8 100644 --- a/packages/components/src/components/hds/app-side-nav/portal/target.hbs +++ b/packages/components/src/components/hds/app-side-nav/portal/target.hbs @@ -9,6 +9,6 @@ @onChange={{this.panelsChanged}} @name={{if @targetName @targetName "hds-app-side-nav-portal-target"}} class="hds-app-side-nav__content-panels" - {{did-update this.didUpdateSubnav this._numSubnavs}} + {{this._animateSubnav this._numSubnavs}} />
\ No newline at end of file diff --git a/packages/components/src/components/hds/app-side-nav/portal/target.ts b/packages/components/src/components/hds/app-side-nav/portal/target.ts index c6ec46fea37..2a17f570c3c 100644 --- a/packages/components/src/components/hds/app-side-nav/portal/target.ts +++ b/packages/components/src/components/hds/app-side-nav/portal/target.ts @@ -4,10 +4,12 @@ */ import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; +import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { macroCondition, isTesting } from '@embroider/macros'; +import { modifier } from 'ember-modifier'; +import { next } from '@ember/runloop'; import type { HdsAppSideNavPortalSignature } from './index'; import type { Registry as Services } from '@ember/service'; @@ -40,16 +42,18 @@ export default class HdsAppSideNavPortalTarget extends Component { + // eslint-disable-next-line ember/no-runloop + next(() => this.animateSubnav(element, [count])); + } + ); + @action panelsChanged(portalCount: number): void { this._numSubnavs = portalCount; } - @action - didUpdateSubnav(element: HTMLElement, [count]: [number]): void { - this.animateSubnav(element, [count]); - } - @action animateSubnav(element: HTMLElement, [count]: [number]): void { /* diff --git a/packages/components/src/components/hds/breadcrumb/index.hbs b/packages/components/src/components/hds/breadcrumb/index.hbs index a52f892205b..5207c02c768 100644 --- a/packages/components/src/components/hds/breadcrumb/index.hbs +++ b/packages/components/src/components/hds/breadcrumb/index.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: MPL-2.0 }} \ No newline at end of file diff --git a/packages/components/src/components/hds/breadcrumb/index.ts b/packages/components/src/components/hds/breadcrumb/index.ts index 66ff86ba41e..cb2543a2618 100644 --- a/packages/components/src/components/hds/breadcrumb/index.ts +++ b/packages/components/src/components/hds/breadcrumb/index.ts @@ -4,6 +4,7 @@ */ import Component from '@glimmer/component'; +import { modifier } from 'ember-modifier'; export interface HdsBreadcrumbSignature { Args: { @@ -17,47 +18,15 @@ export interface HdsBreadcrumbSignature { Element: HTMLElement; } -const NOOP = () => {}; - export default class HdsBreadcrumb extends Component { - /** - * @param onDidInsert - * @type {function} - * @default () => {} - */ - get didInsert(): () => void { - const { didInsert } = this.args; - - if (typeof didInsert === 'function') { - return didInsert; - } else { - return NOOP; - } - } - - /** - * @param itemsCanWrap - * @type {boolean} - * @default true - */ get itemsCanWrap(): boolean { return this.args.itemsCanWrap ?? true; } - /** - * @param ariaLabel - * @type {string} - * @default 'breadcrumbs' - */ get ariaLabel(): string { return this.args.ariaLabel ?? 'breadcrumbs'; } - /** - * Get the class names to apply to the component. - * @method Breadcrumb#classNames - * @return {string} The "class" attribute to apply to the component. - */ get classNames(): string { const classes = ['hds-breadcrumb']; @@ -68,4 +37,12 @@ export default class HdsBreadcrumb extends Component { return classes.join(' '); } + + private _callDidInsert = modifier(() => { + const { didInsert } = this.args; + + if (typeof didInsert === 'function') { + didInsert(); + } + }); } diff --git a/packages/components/src/components/hds/code-editor/description.hbs b/packages/components/src/components/hds/code-editor/description.hbs index a1a817352ac..5ab05b0a563 100644 --- a/packages/components/src/components/hds/code-editor/description.hbs +++ b/packages/components/src/components/hds/code-editor/description.hbs @@ -8,7 +8,7 @@ class="hds-code-editor__description" @tag="p" @size="100" - {{did-insert @onInsert}} + {{this._callOnInsert}} ...attributes > {{yield}} diff --git a/packages/components/src/components/hds/code-editor/description.ts b/packages/components/src/components/hds/code-editor/description.ts index cd83383f1d9..cce44c962d6 100644 --- a/packages/components/src/components/hds/code-editor/description.ts +++ b/packages/components/src/components/hds/code-editor/description.ts @@ -4,6 +4,7 @@ */ import Component from '@glimmer/component'; +import { modifier } from 'ember-modifier'; import type { HdsTextBodySignature } from '../text/body'; @@ -21,4 +22,14 @@ export interface HdsCodeEditorDescriptionSignature { export default class HdsCodeEditorDescription extends Component { private _id = `${this.args.editorId}-description`; + + private _callOnInsert = modifier( + (element: HdsCodeEditorDescriptionElement) => { + const { onInsert } = this.args; + + if (typeof onInsert === 'function') { + onInsert(element); + } + } + ); } diff --git a/packages/components/src/components/hds/code-editor/title.hbs b/packages/components/src/components/hds/code-editor/title.hbs index 38e83ecc0dc..4b06b60521a 100644 --- a/packages/components/src/components/hds/code-editor/title.hbs +++ b/packages/components/src/components/hds/code-editor/title.hbs @@ -9,7 +9,7 @@ @tag={{this.tag}} @size="200" @weight="semibold" - {{did-insert @onInsert}} + {{hds-lifecycle didInsert=@onInsert}} ...attributes > {{yield}} diff --git a/packages/components/src/components/hds/disclosure-primitive/index.hbs b/packages/components/src/components/hds/disclosure-primitive/index.hbs index 0867c02a868..c90a73e00d3 100644 --- a/packages/components/src/components/hds/disclosure-primitive/index.hbs +++ b/packages/components/src/components/hds/disclosure-primitive/index.hbs @@ -2,7 +2,7 @@ Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: MPL-2.0 }} -
+
{{yield (hash onClickToggle=this.onClickToggle isOpen=this.isOpen contentId=this._contentId) to="toggle"}}
diff --git a/packages/components/src/components/hds/disclosure-primitive/index.ts b/packages/components/src/components/hds/disclosure-primitive/index.ts index d475d664d12..f5ac2a64459 100644 --- a/packages/components/src/components/hds/disclosure-primitive/index.ts +++ b/packages/components/src/components/hds/disclosure-primitive/index.ts @@ -8,6 +8,7 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { schedule } from '@ember/runloop'; import { guidFor } from '@ember/object/internals'; +import { modifier } from 'ember-modifier'; export interface HdsDisclosurePrimitiveSignature { Args: { @@ -55,6 +56,23 @@ export default class HdsDisclosurePrimitive extends Component { + let isFirstRun = true; + + return modifier((_element, [isOpen]: [boolean | undefined]) => { + if (isFirstRun) { + isFirstRun = false; + return; + } + + if (isOpen !== undefined) { + this.isOpen = isOpen; + } + + this._isControlled = true; + }); + })(); + @action onClickToggle(): void { this.isOpen = !this.isOpen; @@ -68,14 +86,6 @@ export default class HdsDisclosurePrimitive extends Component {{#if (or PP.isOpen @preserveContentInDom)}} {{yield (hash Header=(component "hds/dropdown/header") close=PP.hidePopover)}} -
    +
      {{yield (hash close=PP.hidePopover diff --git a/packages/components/src/components/hds/dropdown/index.ts b/packages/components/src/components/hds/dropdown/index.ts index 12bccfcd4ce..4b08275fbd0 100644 --- a/packages/components/src/components/hds/dropdown/index.ts +++ b/packages/components/src/components/hds/dropdown/index.ts @@ -4,8 +4,8 @@ */ import Component from '@glimmer/component'; -import { action } from '@ember/object'; import { assert } from '@ember/debug'; +import { modifier } from 'ember-modifier'; import { // map Dropdown's `listPosition` values to PopoverPrimitive's `placement` values @@ -15,7 +15,7 @@ import { } from './types.ts'; import type { ComponentLike } from '@glint/template'; -import type { MenuPrimitiveSignature } from '../menu-primitive'; +import type { HdsPopoverPrimitiveSignature } from '../popover-primitive/index.ts'; import type { HdsDropdownFooterSignature } from './footer'; import type { HdsDropdownHeaderSignature } from './header'; import type { HdsDropdownListItemCheckboxSignature } from './list-item/checkbox'; @@ -39,15 +39,16 @@ export const POSITIONS: HdsDropdownPositions[] = Object.values( ); export interface HdsDropdownSignature { - Args: MenuPrimitiveSignature['Args'] & { + Args: { height?: string; isInline?: boolean; - isOpen?: boolean; + isOpen?: HdsPopoverPrimitiveSignature['Args']['isOpen']; listPosition?: HdsDropdownPositions; width?: string; enableCollisionDetection?: HdsAnchoredPositionOptions['enableCollisionDetection']; preserveContentInDom?: boolean; matchToggleWidth?: boolean; + onClose?: HdsPopoverPrimitiveSignature['Args']['onClose']; }; Blocks: { default: [ @@ -69,7 +70,7 @@ export interface HdsDropdownSignature { }, ]; }; - Element: MenuPrimitiveSignature['Element']; + Element: HTMLDivElement; } export default class HdsDropdown extends Component { @@ -153,9 +154,9 @@ export default class HdsDropdown extends Component { return classes.join(' '); } - @action - didInsertList(element: HTMLUListElement): void { + private _setupCheckmarkList = modifier((element: HTMLUListElement) => { const checkmarkItems = element.querySelectorAll(`[role="option"]`); + if (checkmarkItems.length) { const toggleButtonId = element .closest('.hds-dropdown') @@ -168,5 +169,5 @@ export default class HdsDropdown extends Component { element.setAttribute('aria-labelledby', toggleButtonId); } } - } + }); } diff --git a/packages/components/src/components/hds/dropdown/list-item/interactive.hbs b/packages/components/src/components/hds/dropdown/list-item/interactive.hbs index 53fc34e1b9f..f62914dc95f 100644 --- a/packages/components/src/components/hds/dropdown/list-item/interactive.hbs +++ b/packages/components/src/components/hds/dropdown/list-item/interactive.hbs @@ -9,11 +9,7 @@
- {{#if (has-block)}} - {{yield (hash Badge=(component "hds/badge" size="small"))}} - {{else}} - {{this.text}} - {{/if}} + {{yield (hash Badge=(component "hds/badge" size="small"))}}
{{else}} @@ -34,11 +30,7 @@ {{/if}} - {{#if (has-block)}} - {{yield (hash Badge=(component "hds/badge" size="small"))}} - {{else}} - {{this.text}} - {{/if}} + {{yield (hash Badge=(component "hds/badge" size="small"))}} {{#if @trailingIcon}} diff --git a/packages/components/src/components/hds/dropdown/list-item/interactive.ts b/packages/components/src/components/hds/dropdown/list-item/interactive.ts index 323942938b0..722866f4bc0 100644 --- a/packages/components/src/components/hds/dropdown/list-item/interactive.ts +++ b/packages/components/src/components/hds/dropdown/list-item/interactive.ts @@ -4,7 +4,7 @@ */ import Component from '@glimmer/component'; -import { assert, deprecate } from '@ember/debug'; +import { assert } from '@ember/debug'; import { HdsDropdownListItemInteractiveColorValues } from './types.ts'; @@ -13,7 +13,6 @@ import type { HdsInteractiveSignature } from '../../interactive'; import type { HdsDropdownListItemInteractiveColors } from './types.ts'; import type { ComponentLike } from '@glint/template'; import type { HdsBadgeSignature } from '../../badge/index.ts'; -import type Owner from '@ember/owner'; export const DEFAULT_COLOR = HdsDropdownListItemInteractiveColorValues.Action; export const COLORS: HdsDropdownListItemInteractiveColors[] = Object.values( @@ -25,10 +24,6 @@ export interface HdsDropdownListItemInteractiveSignature { color?: HdsDropdownListItemInteractiveColors; icon?: HdsIconSignature['Args']['name']; isLoading?: boolean; - /** - * @deprecated The `@text` argument for "Hds::Dropdown::ListItem::Interactive" has been deprecated. Please put text in the yielded block. See: https://helios.hashicorp.design/components/dropdown?tab=version%20history#4100 - */ - text?: string; trailingIcon?: HdsIconSignature['Args']['name']; }; Blocks: { @@ -42,41 +37,6 @@ export interface HdsDropdownListItemInteractiveSignature { } export default class HdsDropdownListItemInteractive extends Component { - constructor( - owner: Owner, - args: HdsDropdownListItemInteractiveSignature['Args'] - ) { - super(owner, args); - - if (args.text !== undefined) { - deprecate( - 'The `@text` argument for "Hds::Dropdown::ListItem::Interactive" has been deprecated. Please put text in the yielded block.', - false, - { - id: 'hds.dropdown.list-item.interactive', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/dropdown?tab=version%20history#4100', - for: '@hashicorp/design-system-components', - since: { - available: '4.10.0', - enabled: '5.0.0', - }, - } - ); - } - } - - get text(): string { - const { text } = this.args; - - assert( - '@text for "Hds::Dropdown::ListItem::Interactive" must have a valid value', - text !== undefined - ); - - return text; - } - get color(): HdsDropdownListItemInteractiveColors { const { color = DEFAULT_COLOR } = this.args; diff --git a/packages/components/src/components/hds/dropdown/toggle/icon.hbs b/packages/components/src/components/hds/dropdown/toggle/icon.hbs index 4b894a5ea09..a27e7ff2660 100644 --- a/packages/components/src/components/hds/dropdown/toggle/icon.hbs +++ b/packages/components/src/components/hds/dropdown/toggle/icon.hbs @@ -8,7 +8,7 @@ ...attributes aria-expanded={{if @isOpen "true" "false"}} {{@setupPrimitiveToggle}} - {{did-update this.onDidUpdateImageSrc @imageSrc}} + {{this._updateImagePresence @imageSrc}} type="button" >
diff --git a/packages/components/src/components/hds/dropdown/toggle/icon.ts b/packages/components/src/components/hds/dropdown/toggle/icon.ts index b1e0d69e85e..c47e915d9ab 100644 --- a/packages/components/src/components/hds/dropdown/toggle/icon.ts +++ b/packages/components/src/components/hds/dropdown/toggle/icon.ts @@ -7,6 +7,7 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { assert } from '@ember/debug'; import { tracked } from '@glimmer/tracking'; +import { modifier } from 'ember-modifier'; import { HdsDropdownToggleIconSizeValues } from './types.ts'; import type { HdsIconSignature } from '../../icon'; @@ -45,21 +46,17 @@ export default class HdsDropdownToggleIcon extends Component { + this._hasImage = !!_imageSrc; + } + ); @action onImageLoadError(): void { this._hasImage = false; } - /** - * @param text - * @type {string} - * @description The text of the `aria-label` applied to the toggle - */ get text(): string { const { text } = this.args; @@ -71,12 +68,6 @@ export default class HdsDropdownToggleIcon extends Component - {{yield}} -
\ No newline at end of file diff --git a/packages/components/src/components/hds/flyout/body.ts b/packages/components/src/components/hds/flyout/body.ts deleted file mode 100644 index 72f070a5ed5..00000000000 --- a/packages/components/src/components/hds/flyout/body.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; - -import type Owner from '@ember/owner'; - -export interface HdsFlyoutBodySignature { - // when component has no args, but constructor still needs to be defined, use `never` - // see: https://github.com/hashicorp/design-system/pull/2511/files/f2146e5243d0431892a62d2fbf2889f1cbd3e525#r1815255004 - Args: never; - Blocks: { - default: []; - }; - Element: HTMLDivElement; -} - -export default class HdsFlyoutBody extends Component { - constructor(owner: Owner, args: HdsFlyoutBodySignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Flyout::Body` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Body` as one-to-one replacement.', - false, - { - id: 'hds.components.flyout.body', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - enabled: '4.6.0', - available: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/flyout/description.hbs b/packages/components/src/components/hds/flyout/description.hbs deleted file mode 100644 index 92ce65b7b43..00000000000 --- a/packages/components/src/components/hds/flyout/description.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} - - {{yield}} - \ No newline at end of file diff --git a/packages/components/src/components/hds/flyout/description.ts b/packages/components/src/components/hds/flyout/description.ts deleted file mode 100644 index e04a663fe82..00000000000 --- a/packages/components/src/components/hds/flyout/description.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type { HdsTextBodySignature } from '../text/body'; -import type Owner from '@ember/owner'; - -export interface HdsFlyoutDescriptionSignature { - Args: never; - Blocks: { - default: []; - }; - Element: HdsTextBodySignature['Element']; -} - -export default class HdsFlyoutDescription extends Component { - constructor(owner: Owner, args: HdsFlyoutDescriptionSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Flyout::Description` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Description` as one-to-one replacement.', - false, - { - id: 'hds.components.flyout.description', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - available: '4.6.0', - enabled: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/flyout/footer.hbs b/packages/components/src/components/hds/flyout/footer.hbs deleted file mode 100644 index 448b9041755..00000000000 --- a/packages/components/src/components/hds/flyout/footer.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} - \ No newline at end of file diff --git a/packages/components/src/components/hds/flyout/footer.ts b/packages/components/src/components/hds/flyout/footer.ts deleted file mode 100644 index 98e237553e9..00000000000 --- a/packages/components/src/components/hds/flyout/footer.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type Owner from '@ember/owner'; - -export interface HdsFlyoutFooterSignature { - Args: { - onDismiss?: (event: MouseEvent) => void; - }; - Blocks: { - default: [{ close?: (event: MouseEvent) => void }]; - }; - Element: HTMLDivElement; -} - -export default class HdsFlyoutFooter extends Component { - constructor(owner: Owner, args: HdsFlyoutFooterSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Flyout::Footer` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Footer` as one-to-one replacement.', - false, - { - id: 'hds.components.flyout.footer', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - enabled: '4.6.0', - available: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/flyout/header.hbs b/packages/components/src/components/hds/flyout/header.hbs deleted file mode 100644 index e46e9e2c03e..00000000000 --- a/packages/components/src/components/hds/flyout/header.hbs +++ /dev/null @@ -1,21 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} -
- {{#if @icon}} - - {{/if}} - - {{#if @tagline}} - - {{@tagline}} - - {{/if}} - {{yield}} - - -
\ No newline at end of file diff --git a/packages/components/src/components/hds/flyout/header.ts b/packages/components/src/components/hds/flyout/header.ts deleted file mode 100644 index 7ff4f297fdf..00000000000 --- a/packages/components/src/components/hds/flyout/header.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type { HdsIconSignature } from '../icon'; -import type Owner from '@ember/owner'; - -export interface HdsFlyoutHeaderSignature { - Args: { - id?: string; - tagline?: string; - onDismiss: (event: MouseEvent) => void; - icon?: HdsIconSignature['Args']['name']; - }; - Blocks: { - default: []; - }; - Element: HTMLDivElement; -} - -export default class HdsFlyoutHeader extends Component { - constructor(owner: Owner, args: HdsFlyoutHeaderSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Flyout::Header` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Header` as one-to-one replacement.', - false, - { - id: 'hds.components.flyout.header', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - enabled: '4.6.0', - available: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/flyout/index.hbs b/packages/components/src/components/hds/flyout/index.hbs index 949ca208a42..99b42eaeb67 100644 --- a/packages/components/src/components/hds/flyout/index.hbs +++ b/packages/components/src/components/hds/flyout/index.hbs @@ -6,8 +6,7 @@ class={{this.classNames}} ...attributes aria-labelledby={{this.id}} - {{did-insert this.didInsert}} - {{will-destroy this.willDestroyNode}} + {{this._initElement}} {{! @glint-expect-error - https://github.com/josemarluedke/ember-focus-trap/issues/86 }} {{focus-trap isActive=this._isOpen focusTrapOptions=(hash onDeactivate=this.onDismiss clickOutsideDeactivates=true)}} > diff --git a/packages/components/src/components/hds/flyout/index.ts b/packages/components/src/components/hds/flyout/index.ts index 4861fe385c6..7e4a1f76ba1 100644 --- a/packages/components/src/components/hds/flyout/index.ts +++ b/packages/components/src/components/hds/flyout/index.ts @@ -9,8 +9,10 @@ import { action } from '@ember/object'; import { assert } from '@ember/debug'; import { getElementId } from '../../../utils/hds-get-element-id.ts'; import { buildWaiter } from '@ember/test-waiters'; -import type { WithBoundArgs } from '@glint/template'; +import { modifier } from 'ember-modifier'; +import type { WithBoundArgs } from '@glint/template'; +import type Owner from '@ember/owner'; import type { HdsFlyoutSizes } from './types.ts'; import { HdsFlyoutSizesValues } from './types.ts'; @@ -62,17 +64,8 @@ export default class HdsFlyout extends Component { // TODO: make this property private; currently blocked by our consumers relying on it despite not being part of the public API: https://github.com/hashicorp/cloud-ui/blob/main/engines/waypoint/addon/components/preview-pane.ts#L15 // private _element!: HTMLDialogElement; _element!: HTMLDialogElement; - private _body!: HTMLElement; private _bodyInitialOverflowValue = ''; - /** - * Sets the size of the flyout - * Accepted values: medium, large - * - * @param size - * @type {string} - * @default 'medium' - */ get size(): HdsFlyoutSizes { const { size = DEFAULT_SIZE } = this.args; @@ -93,11 +86,6 @@ export default class HdsFlyout extends Component { return getElementId(this); } - /** - * Get the class names to apply to the component. - * @method classNames - * @return {string} The "class" attribute to apply to the component. - */ get classNames(): string { const classes = ['hds-flyout']; @@ -107,6 +95,13 @@ export default class HdsFlyout extends Component { return classes.join(' '); } + constructor(owner: Owner, args: HdsFlyoutSignature['Args']) { + super(owner, args); + + this._bodyInitialOverflowValue = + document.body.style.getPropertyValue('overflow'); + } + @action registerOnCloseCallback(event: Event) { if (this.args.onClose && typeof this.args.onClose === 'function') { this.args.onClose(event); @@ -115,17 +110,8 @@ export default class HdsFlyout extends Component { this._isOpen = false; } - @action - didInsert(element: HTMLDialogElement): void { - // Store references of `` and `` elements + private _initElement = modifier((element: HdsFlyoutSignature['Element']) => { this._element = element; - this._body = document.body; - - if (this._body) { - // Store the initial `overflow` value of `` so we can reset to it - this._bodyInitialOverflowValue = - this._body.style.getPropertyValue('overflow'); - } // Register "onClose" callback function to be called when a native 'close' event is dispatched // eslint-disable-next-line @typescript-eslint/unbound-method @@ -135,19 +121,16 @@ export default class HdsFlyout extends Component { if (!this._element.open) { this.open(); } - } - @action - willDestroyNode(): void { - if (this._element) { + return () => { this._element.removeEventListener( 'close', // eslint-disable-next-line @typescript-eslint/unbound-method this.registerOnCloseCallback, true ); - } - } + }; + }); @action open(): void { @@ -156,7 +139,7 @@ export default class HdsFlyout extends Component { this._isOpen = true; // Prevent page from scrolling when the dialog is open - if (this._body) this._body.style.setProperty('overflow', 'hidden'); + document.body.style.setProperty('overflow', 'hidden'); // Call "onOpen" callback function if (this.args.onOpen && typeof this.args.onOpen === 'function') { @@ -183,18 +166,17 @@ export default class HdsFlyout extends Component { this._element.close(); // Reset page `overflow` property - if (this._body) { - this._body.style.removeProperty('overflow'); - if (this._bodyInitialOverflowValue === '') { - if (this._body.style.length === 0) { - this._body.removeAttribute('style'); - } - } else { - this._body.style.setProperty( - 'overflow', - this._bodyInitialOverflowValue - ); + document.body.style.removeProperty('overflow'); + + if (this._bodyInitialOverflowValue === '') { + if (document.body.style.length === 0) { + document.body.removeAttribute('style'); } + } else { + document.body.style.setProperty( + 'overflow', + this._bodyInitialOverflowValue + ); } // Return focus to a specific element (if provided) diff --git a/packages/components/src/components/hds/form/character-count/index.hbs b/packages/components/src/components/hds/form/character-count/index.hbs index 71a0d37efae..2da562321f1 100644 --- a/packages/components/src/components/hds/form/character-count/index.hbs +++ b/packages/components/src/components/hds/form/character-count/index.hbs @@ -7,7 +7,7 @@ @size="100" class={{this.classNames}} id={{this.id}} - {{did-insert this.onInsert}} + {{hds-lifecycle didInsert=@onInsert}} ...attributes aria-live="polite" > diff --git a/packages/components/src/components/hds/form/character-count/index.ts b/packages/components/src/components/hds/form/character-count/index.ts index cd7769d0f5b..1beaecda630 100644 --- a/packages/components/src/components/hds/form/character-count/index.ts +++ b/packages/components/src/components/hds/form/character-count/index.ts @@ -4,10 +4,10 @@ */ import Component from '@glimmer/component'; + import type { HdsTextBodySignature } from '../../text/body'; const ID_PREFIX = 'character-count-'; -const NOOP = (): void => {}; export interface HdsFormCharacterCountSignature { Args: { @@ -52,12 +52,6 @@ export default class HdsFormCharacterCount extends Component {} - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get onInsert(): (element: HTMLElement, ...args: any[]) => void { - const { onInsert } = this.args; - // notice: this is a guard used to prevent triggering an error when the component is used as standalone element - if (typeof onInsert === 'function') { - return onInsert; - } else { - return NOOP; - } - } - /** - * Get the class names to apply to the component. - * @method classNames - * @return {string} The "class" attribute to apply to the component. - */ get classNames(): string { const classes = ['hds-form-character-count']; diff --git a/packages/components/src/components/hds/form/error/index.hbs b/packages/components/src/components/hds/form/error/index.hbs index d00d5fd1657..18f81ad1504 100644 --- a/packages/components/src/components/hds/form/error/index.hbs +++ b/packages/components/src/components/hds/form/error/index.hbs @@ -2,7 +2,7 @@ Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: MPL-2.0 }} -
+
{{yield (hash Message=(component "hds/form/error/message"))}} diff --git a/packages/components/src/components/hds/form/error/index.ts b/packages/components/src/components/hds/form/error/index.ts index c6400a1a046..94b3a79b939 100644 --- a/packages/components/src/components/hds/form/error/index.ts +++ b/packages/components/src/components/hds/form/error/index.ts @@ -9,16 +9,12 @@ import type { HdsFormErrorMessageSignature } from './message'; export const ID_PREFIX = 'error-'; -const NOOP = (): void => {}; - export interface HdsFormErrorSignature { Args: { contextualClass?: string; controlId?: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onInsert?: (element: HTMLElement, ...args: any[]) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onRemove?: (element: HTMLElement, ...args: any[]) => void; + onInsert?: (element: HTMLElement, ...args: unknown[]) => void; + onRemove?: (element: HTMLElement, ...args: unknown[]) => void; }; Blocks: { default: [ @@ -31,57 +27,16 @@ export interface HdsFormErrorSignature { } export default class HdsFormError extends Component { - /** - * Determines the unique ID to assign to the element - * @method id - * @return {(string|null)} The "id" attribute to apply to the element or null, if no controlId is provided - */ get id(): string | null { const { controlId } = this.args; + if (controlId) { return `${ID_PREFIX}${controlId}`; } - return null; - } - - /** - * @param onInsert - * @type {function} - * @default () => {} - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get onInsert(): (element: HTMLElement, ...args: any[]) => void { - const { onInsert } = this.args; - // notice: this is a guard used to prevent triggering an error when the component is used as standalone element - if (typeof onInsert === 'function') { - return onInsert; - } else { - return NOOP; - } + return null; } - /** - * @param onRemove - * @type {function} - * @default () => {} - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get onRemove(): (element: HTMLElement, ...args: any[]) => void { - const { onRemove } = this.args; - - // notice: this is a guard used to prevent triggering an error when the component is used as standalone element - if (typeof onRemove === 'function') { - return onRemove; - } else { - return NOOP; - } - } - /** - * Get the class names to apply to the component. - * @method classNames - * @return {string} The "class" attribute to apply to the component. - */ get classNames(): string { const classes = ['hds-form-error']; diff --git a/packages/components/src/components/hds/form/helper-text/index.hbs b/packages/components/src/components/hds/form/helper-text/index.hbs index a20c74000d9..29bc12ff411 100644 --- a/packages/components/src/components/hds/form/helper-text/index.hbs +++ b/packages/components/src/components/hds/form/helper-text/index.hbs @@ -8,7 +8,7 @@ @size="100" @weight="regular" id={{this.id}} - {{did-insert this.onInsert}} + {{hds-lifecycle didInsert=@onInsert}} ...attributes > {{yield}} diff --git a/packages/components/src/components/hds/menu-primitive/index.hbs b/packages/components/src/components/hds/menu-primitive/index.hbs deleted file mode 100644 index 29a6c04850b..00000000000 --- a/packages/components/src/components/hds/menu-primitive/index.hbs +++ /dev/null @@ -1,25 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS COMPONENT IS NOW DEPRECATED -}} -{{! template-lint-disable no-invalid-interactive }} -
-
- {{yield (hash onClickToggle=this.onClickToggle isOpen=this.isOpen) to="toggle"}} -
- {{#if this.isOpen}} -
- {{yield (hash close=this.close) to="content"}} -
- {{/if}} -
-{{! template-lint-enable no-invalid-interactive }} \ No newline at end of file diff --git a/packages/components/src/components/hds/menu-primitive/index.ts b/packages/components/src/components/hds/menu-primitive/index.ts deleted file mode 100644 index db655a604da..00000000000 --- a/packages/components/src/components/hds/menu-primitive/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import { tracked } from '@glimmer/tracking'; -import { action } from '@ember/object'; -import { schedule } from '@ember/runloop'; -import type Owner from '@ember/owner'; - -export interface MenuPrimitiveSignature { - Args: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onClose?: (...args: any[]) => void; - }; - Blocks: { - toggle?: [ - { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onClickToggle: (event: MouseEvent, ...args: any[]) => void; - isOpen?: boolean; - }, - ]; - content?: [ - { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - close: (...args: any[]) => void; - }, - ]; - }; - Element: HTMLDivElement; -} - -export default class MenuPrimitive extends Component { - @tracked isOpen: boolean | undefined; // notice: if in the future we need to add a "@isOpen" prop to control the status from outside (eg to have the MenuPrimitive opened on render) just add "this.args.isOpen" here to initalize the variable - @tracked toggleRef: HTMLElement | undefined; - @tracked _element!: HTMLElement; - - constructor(owner: Owner, args: MenuPrimitiveSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::MenuPrimitive` component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`.', - false, - { - id: 'hds.components.menu-primitive', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/menu-primitive?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - enabled: '4.10.0', - available: '4.10.0', - }, - } - ); - } - - @action - didInsert(element: HTMLElement): void { - this._element = element; - } - - @action - onClickToggle(event: MouseEvent): void { - // we store a reference to the DOM node that has the "onClickToggle" event associated with it - if (!this.toggleRef) { - this.toggleRef = event.currentTarget as HTMLElement; - } - this.isOpen = !this.isOpen; - // we explicitly apply a focus state to the toggle element to overcome a bug in WebKit (see https://github.com/hashicorp/design-system/commit/40cd7f6b3cb15c45f9a1235fafd0fb3ed58e6e62) - this.toggleRef?.focus(); - } - - @action - onFocusOut(event: FocusEvent): void { - // due to inconsistent implementation of relatedTarget across browsers we use the activeElement as a fallback - // if the related target is not part of the disclosed content we close the disclosed container - if ( - !this._element.contains( - (event.relatedTarget as Node) || (document.activeElement as Node) - ) - ) { - this.close(); - } - } - - @action - onKeyUp(event: KeyboardEvent): void { - if (event.key === 'Escape') { - this.close(); - this.toggleRef?.focus(); - } - } - - @action - close(): void { - // we schedule this afterRender to avoid an error in tests caused by updating `isOpen` multiple times in the same computation - // eslint-disable-next-line ember/no-runloop - schedule('afterRender', (): void => { - this.isOpen = false; - // we call the "onClose" callback if it exists (and is a function) - if (this.args.onClose && typeof this.args.onClose === 'function') { - this.args.onClose(); - } - }); - } -} diff --git a/packages/components/src/components/hds/modal/body.hbs b/packages/components/src/components/hds/modal/body.hbs deleted file mode 100644 index 5cffdb61039..00000000000 --- a/packages/components/src/components/hds/modal/body.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} -
- {{yield}} -
\ No newline at end of file diff --git a/packages/components/src/components/hds/modal/body.ts b/packages/components/src/components/hds/modal/body.ts deleted file mode 100644 index 325268fafa2..00000000000 --- a/packages/components/src/components/hds/modal/body.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type Owner from '@ember/owner'; - -export interface HdsModalBodySignature { - // when component has no args, but constructor still needs to be defined, use `never` - // see: https://github.com/hashicorp/design-system/pull/2511/files/f2146e5243d0431892a62d2fbf2889f1cbd3e525#r1815255004 - Args: never; - Blocks: { - default: []; - }; - Element: HTMLDivElement; -} - -export default class HdsModalBody extends Component { - constructor(owner: Owner, args: HdsModalBodySignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Modal::Body` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Body` as one-to-one replacement.', - false, - { - id: 'hds.components.modal.body', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - available: '4.6.0', - enabled: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/modal/footer.hbs b/packages/components/src/components/hds/modal/footer.hbs deleted file mode 100644 index ee3d71f9d53..00000000000 --- a/packages/components/src/components/hds/modal/footer.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} - \ No newline at end of file diff --git a/packages/components/src/components/hds/modal/footer.ts b/packages/components/src/components/hds/modal/footer.ts deleted file mode 100644 index f78ed43686a..00000000000 --- a/packages/components/src/components/hds/modal/footer.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type Owner from '@ember/owner'; - -export interface HdsModalFooterSignature { - Args: { - onDismiss?: (event: MouseEvent) => void; - }; - Blocks: { - default: [{ close?: (event: MouseEvent) => void }]; - }; - Element: HTMLDivElement; -} - -export default class HdsModalFooter extends Component { - constructor(owner: Owner, args: HdsModalFooterSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Modal::Footer` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Footer` as one-to-one replacement.', - false, - { - id: 'hds.components.modal.footer', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - enabled: '4.6.0', - available: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/modal/header.hbs b/packages/components/src/components/hds/modal/header.hbs deleted file mode 100644 index 4426455cd6c..00000000000 --- a/packages/components/src/components/hds/modal/header.hbs +++ /dev/null @@ -1,21 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} -
- {{#if @icon}} - - {{/if}} - - {{#if @tagline}} - - {{@tagline}} - - {{/if}} - {{yield}} - - -
\ No newline at end of file diff --git a/packages/components/src/components/hds/modal/header.ts b/packages/components/src/components/hds/modal/header.ts deleted file mode 100644 index f0d5df59b3c..00000000000 --- a/packages/components/src/components/hds/modal/header.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { deprecate } from '@ember/debug'; -import type { HdsIconSignature } from '../icon'; -import type Owner from '@ember/owner'; - -export interface HdsModalHeaderSignature { - Args: { - id?: string; - tagline?: string; - onDismiss: (event: MouseEvent) => void; - icon?: HdsIconSignature['Args']['name']; - }; - Blocks: { - default: []; - }; - Element: HTMLDivElement; -} - -export default class HdsModalHeader extends Component { - constructor(owner: Owner, args: HdsModalHeaderSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::Modal::Header` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::DialogPrimitive::Header` as one-to-one replacement.', - false, - { - id: 'hds.components.modal.header', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/flyout?tab=version%20history#460', - for: '@hashicorp/design-system-components', - since: { - available: '4.6.0', - enabled: '4.6.0', - }, - } - ); - } -} diff --git a/packages/components/src/components/hds/modal/index.hbs b/packages/components/src/components/hds/modal/index.hbs index ea32f64eb5b..94b25cf3bd8 100644 --- a/packages/components/src/components/hds/modal/index.hbs +++ b/packages/components/src/components/hds/modal/index.hbs @@ -6,8 +6,7 @@ class={{this.classNames}} ...attributes aria-labelledby={{this.id}} - {{did-insert this.didInsert}} - {{will-destroy this.willDestroyNode}} + {{this._initElement}} {{! @glint-expect-error - https://github.com/josemarluedke/ember-focus-trap/issues/86 }} {{focus-trap isActive=this._isOpen focusTrapOptions=(hash onDeactivate=this.onDismiss clickOutsideDeactivates=true)}} > diff --git a/packages/components/src/components/hds/modal/index.ts b/packages/components/src/components/hds/modal/index.ts index 33d3e27ee63..faf49931f8e 100644 --- a/packages/components/src/components/hds/modal/index.ts +++ b/packages/components/src/components/hds/modal/index.ts @@ -9,8 +9,10 @@ import { action } from '@ember/object'; import { assert } from '@ember/debug'; import { getElementId } from '../../../utils/hds-get-element-id.ts'; import { buildWaiter } from '@ember/test-waiters'; +import { modifier } from 'ember-modifier'; import type { WithBoundArgs } from '@glint/template'; +import type Owner from '@ember/owner'; import type { HdsModalSizes, HdsModalColors } from './types.ts'; import HdsDialogPrimitiveHeaderComponent from '../dialog-primitive/header.ts'; @@ -59,7 +61,6 @@ export interface HdsModalSignature { export default class HdsModal extends Component { @tracked private _isOpen = false; private _element!: HTMLDialogElement; - private _body!: HTMLElement; private _bodyInitialOverflowValue = ''; get isDismissDisabled(): boolean { @@ -108,40 +109,15 @@ export default class HdsModal extends Component { return classes.join(' '); } - @action registerOnCloseCallback(event: Event): void { - if ( - !this.isDismissDisabled && - this.args.onClose && - typeof this.args.onClose === 'function' - ) { - this.args.onClose(event); - } + constructor(owner: Owner, args: HdsModalSignature['Args']) { + super(owner, args); - // If the dismissal of the modal is disabled, we keep the modal open/visible otherwise we mark it as closed - if (this.isDismissDisabled) { - // If, in a chain of events, the element is not attached to the DOM, the `showModal` would fail - // so we add this safeguard condition that checks for the `` to have a parent - if (this._element.parentElement) { - // As there is no way to `preventDefault` on `close` events, we call the `showModal` function - // preserving the state of the modal dialog - this._element.showModal(); - } - } else { - this._isOpen = false; - } + this._bodyInitialOverflowValue = + document.body.style.getPropertyValue('overflow'); } - @action - didInsert(element: HTMLDialogElement): void { - // Store references of `` and `` elements + private _initElement = modifier((element: HdsModalSignature['Element']) => { this._element = element; - this._body = document.body; - - if (this._body) { - // Store the initial `overflow` value of `` so we can reset to it - this._bodyInitialOverflowValue = - this._body.style.getPropertyValue('overflow'); - } // Register "onClose" callback function to be called when a native 'close' event is dispatched // eslint-disable-next-line @typescript-eslint/unbound-method @@ -151,17 +127,37 @@ export default class HdsModal extends Component { if (!this._element.open) { this.open(); } - } - @action - willDestroyNode(): void { - if (this._element) { + return () => { this._element.removeEventListener( 'close', // eslint-disable-next-line @typescript-eslint/unbound-method this.registerOnCloseCallback, true ); + }; + }); + + @action registerOnCloseCallback(event: Event): void { + if ( + !this.isDismissDisabled && + this.args.onClose && + typeof this.args.onClose === 'function' + ) { + this.args.onClose(event); + } + + // If the dismissal of the modal is disabled, we keep the modal open/visible otherwise we mark it as closed + if (this.isDismissDisabled) { + // If, in a chain of events, the element is not attached to the DOM, the `showModal` would fail + // so we add this safeguard condition that checks for the `` to have a parent + if (this._element.parentElement) { + // As there is no way to `preventDefault` on `close` events, we call the `showModal` function + // preserving the state of the modal dialog + this._element.showModal(); + } + } else { + this._isOpen = false; } } @@ -172,7 +168,7 @@ export default class HdsModal extends Component { this._isOpen = true; // Prevent page from scrolling when the dialog is open - if (this._body) this._body.style.setProperty('overflow', 'hidden'); + document.body.style.setProperty('overflow', 'hidden'); // Call "onOpen" callback function if (this.args.onOpen && typeof this.args.onOpen === 'function') { @@ -199,18 +195,17 @@ export default class HdsModal extends Component { this._element.close(); // Reset page `overflow` property - if (this._body) { - this._body.style.removeProperty('overflow'); - if (this._bodyInitialOverflowValue === '') { - if (this._body.style.length === 0) { - this._body.removeAttribute('style'); - } - } else { - this._body.style.setProperty( - 'overflow', - this._bodyInitialOverflowValue - ); + document.body.style.removeProperty('overflow'); + + if (this._bodyInitialOverflowValue === '') { + if (document.body.style.length === 0) { + document.body.removeAttribute('style'); } + } else { + document.body.style.setProperty( + 'overflow', + this._bodyInitialOverflowValue + ); } // Return focus to a specific element (if provided) diff --git a/packages/components/src/components/hds/side-nav/header/icon-button.hbs b/packages/components/src/components/hds/side-nav/header/icon-button.hbs deleted file mode 100644 index 0a53016f1cd..00000000000 --- a/packages/components/src/components/hds/side-nav/header/icon-button.hbs +++ /dev/null @@ -1,22 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: MPL-2.0 -}} -{{! - THIS SUBCOMPONENT IS NOW DEPRECATED -}} - - - \ No newline at end of file diff --git a/packages/components/src/components/hds/side-nav/header/icon-button.ts b/packages/components/src/components/hds/side-nav/header/icon-button.ts deleted file mode 100644 index 7645e31be45..00000000000 --- a/packages/components/src/components/hds/side-nav/header/icon-button.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Component from '@glimmer/component'; -import { assert, deprecate } from '@ember/debug'; - -import type { HdsIconSignature } from '../../icon'; -import type { HdsInteractiveSignature } from '../../interactive/'; -import type Owner from '@ember/owner'; - -export interface HdsSideNavHeaderIconButtonSignature { - Args: HdsInteractiveSignature['Args'] & { - icon: HdsIconSignature['Args']['name']; - ariaLabel: string; - }; - Element: HdsInteractiveSignature['Element']; -} - -export default class HdsSideNavHeaderIconButton extends Component { - constructor(owner: Owner, args: HdsSideNavHeaderIconButtonSignature['Args']) { - super(owner, args); - - deprecate( - 'The `Hds::SideNav::Header::IconButton` sub-component is now deprecated and will be removed in the next major version of `@hashicorp/design-system-components`. Use `Hds::Button` with the `isIconOnly` variant instead.', - false, - { - id: 'hds.components.sidenav.header.iconbutton', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/side-nav?tab=version%20history#4100', - for: '@hashicorp/design-system-components', - since: { - available: '4.10.0', - enabled: '4.10.0', - }, - } - ); - } - - get ariaLabel(): string { - const { ariaLabel } = this.args; - - assert( - '@ariaLabel for "Hds::SideNav::Header::IconButton" must have a valid value', - ariaLabel !== undefined - ); - - return ariaLabel; - } -} diff --git a/packages/components/src/components/hds/side-nav/index.hbs b/packages/components/src/components/hds/side-nav/index.hbs index 56c8550dfd1..997acde7818 100644 --- a/packages/components/src/components/hds/side-nav/index.hbs +++ b/packages/components/src/components/hds/side-nav/index.hbs @@ -16,7 +16,7 @@ {{on "transitionend" (fn this.setTransition "end")}} {{! @glint-expect-error - https://github.com/josemarluedke/ember-focus-trap/issues/86 }} {{focus-trap isActive=this.shouldTrapFocus}} - {{did-insert this.didInsert}} + {{this._registerContainersToHide}} > <:root> {{#if this.hasA11yRefocus}} @@ -34,8 +34,7 @@
{{! template-lint-enable no-invalid-interactive}} void; @@ -84,7 +81,7 @@ export default class HdsSideNav extends Component { false, { id: 'hds.components.sidenav', - until: '5.0.0', + until: '6.0.0', url: 'https://helios.hashicorp.design/components/side-nav?tab=version%20history#4140', for: '@hashicorp/design-system-components', since: { @@ -93,23 +90,6 @@ export default class HdsSideNav extends Component { }, } ); - - if (args.ariaLabel !== undefined) { - deprecate( - 'The `@ariaLabel` argument for "Hds::SideNav" has been deprecated. It is replaced by aria-labelledby and aria-expanded on the toggle button', - false, - { - id: 'hds.sidenav', - until: '5.0.0', - url: 'https://helios.hashicorp.design/components/side-nav?tab=version%20history#4140', - for: '@hashicorp/design-system-components', - since: { - available: '4.14.0', - enabled: '5.0.0', - }, - } - ); - } } addEventListeners(): void { @@ -157,13 +137,6 @@ export default class HdsSideNav extends Component { return (this.isResponsive && !this.isDesktop) || this.isCollapsible; } - /** - * @deprecated The `@ariaLabel` argument for "Hds::SideNav" has been deprecated. It is replaced by aria-labelledby and aria-expanded on the toggle button - */ - get ariaLabel(): string | undefined { - return this.args.ariaLabel; - } - get classNames(): string { const classes = []; // `hds-side-nav` is already set by the "Hds::SideNav::Base" component @@ -198,6 +171,14 @@ export default class HdsSideNav extends Component { }); } + private _registerContainersToHide = modifier( + (element: HdsSideNavSignature['Element']): void => { + this.containersToHide = element.querySelectorAll( + '.hds-side-nav-hide-when-minimized' + ); + } + ); + @action escapePress(event: KeyboardEvent): void { if (event.key === 'Escape' && !this.isMinimized && !this.isDesktop) { @@ -219,13 +200,6 @@ export default class HdsSideNav extends Component { } } - @action - didInsert(element: HTMLElement): void { - this.containersToHide = element.querySelectorAll( - '.hds-side-nav-hide-when-minimized' - ); - } - @action setTransition(phase: string, event: TransitionEvent): void { // we only want to respond to `width` animation/transitions diff --git a/packages/components/src/components/hds/side-nav/list/index.hbs b/packages/components/src/components/hds/side-nav/list/index.hbs index 9b547bf8f87..7fe9d1eca06 100644 --- a/packages/components/src/components/hds/side-nav/list/index.hbs +++ b/packages/components/src/components/hds/side-nav/list/index.hbs @@ -10,7 +10,7 @@ (hash Item=(component "hds/side-nav/list/item") BackLink=(component "hds/side-nav/list/back-link") - Title=(component "hds/side-nav/list/title" didInsertTitle=this.didInsertTitle) + Title=(component "hds/side-nav/list/title" registerTitleId=this._registerTitleId) Link=(component "hds/side-nav/list/link") ) }} diff --git a/packages/components/src/components/hds/side-nav/list/index.ts b/packages/components/src/components/hds/side-nav/list/index.ts index a24e97f0b55..b8d481971ff 100644 --- a/packages/components/src/components/hds/side-nav/list/index.ts +++ b/packages/components/src/components/hds/side-nav/list/index.ts @@ -6,6 +6,9 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; +import { modifier } from 'ember-modifier'; +import { schedule } from '@ember/runloop'; + import type { ComponentLike } from '@glint/template'; import type { HdsYieldSignature } from '../../yield'; import type { HdsSideNavListItemSignature } from './item'; @@ -29,6 +32,10 @@ export interface HdsSideNavListSignature { Element: HTMLElement; } +export interface HdsSideNavListRegisterTitleIdModifierSignature { + Element: HdsSideNavListSignature['Element']; +} + export default class HdsSideNavList extends Component { @tracked _titleIds: string[] = []; @@ -40,4 +47,13 @@ export default class HdsSideNavList extends Component { didInsertTitle(titleId: string): void { this._titleIds = [...this._titleIds, titleId]; } + + private _registerTitleId = + modifier((element) => { + // eslint-disable-next-line ember/no-runloop + schedule( + 'afterRender', + () => (this._titleIds = [...this._titleIds, element.id]) + ); + }); } diff --git a/packages/components/src/components/hds/side-nav/list/title.hbs b/packages/components/src/components/hds/side-nav/list/title.hbs index 53c7a8a6fde..4a8a7f3dbbc 100644 --- a/packages/components/src/components/hds/side-nav/list/title.hbs +++ b/packages/components/src/components/hds/side-nav/list/title.hbs @@ -7,7 +7,7 @@
{{~yield~}}
\ No newline at end of file diff --git a/packages/components/src/components/hds/side-nav/list/title.ts b/packages/components/src/components/hds/side-nav/list/title.ts index 205df3fe043..a1b6217a9d6 100644 --- a/packages/components/src/components/hds/side-nav/list/title.ts +++ b/packages/components/src/components/hds/side-nav/list/title.ts @@ -4,12 +4,14 @@ */ import { guidFor } from '@ember/object/internals'; -import { action } from '@ember/object'; import Component from '@glimmer/component'; +import type { ModifierLike } from '@glint/template'; +import type { HdsSideNavListRegisterTitleIdModifierSignature } from './index.ts'; + export interface HdsSideNavListTitleSignature { Args: { - didInsertTitle?: (titleId: string) => void; + registerTitleId?: ModifierLike; }; Blocks: { default: []; @@ -20,13 +22,4 @@ export interface HdsSideNavListTitleSignature { export default class HdsSideNavListTitle extends Component { /* Generate a unique ID for each Title */ titleId = 'title-' + guidFor(this); - - @action - didInsertTitle(element: HTMLElement): void { - const { didInsertTitle } = this.args; - - if (typeof didInsertTitle === 'function') { - didInsertTitle(element.id); - } - } } diff --git a/packages/components/src/components/hds/side-nav/portal/target.hbs b/packages/components/src/components/hds/side-nav/portal/target.hbs index 000fa99b686..5a5725875e8 100644 --- a/packages/components/src/components/hds/side-nav/portal/target.hbs +++ b/packages/components/src/components/hds/side-nav/portal/target.hbs @@ -9,6 +9,6 @@ @onChange={{this.panelsChanged}} @name={{if @targetName @targetName "hds-side-nav-portal-target"}} class="hds-side-nav__content-panels hds-side-nav-hide-when-minimized" - {{did-update this.didUpdateSubnav this.numSubnavs}} + {{this._animateSubnav this.numSubnavs}} />
\ No newline at end of file diff --git a/packages/components/src/components/hds/side-nav/portal/target.ts b/packages/components/src/components/hds/side-nav/portal/target.ts index 076fb5e9eab..96ccf567b18 100644 --- a/packages/components/src/components/hds/side-nav/portal/target.ts +++ b/packages/components/src/components/hds/side-nav/portal/target.ts @@ -4,9 +4,11 @@ */ import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; +import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; +import { modifier } from 'ember-modifier'; +import { next } from '@ember/runloop'; import { macroCondition, isTesting } from '@embroider/macros'; import type { HdsSideNavPortalSignature } from './index'; @@ -41,16 +43,18 @@ export default class HdsSideNavPortalTarget extends Component { + // eslint-disable-next-line ember/no-runloop + next(() => this.animateSubnav(element, [count])); + } + ); + @action panelsChanged(portalCount: number): void { this.numSubnavs = portalCount; } - @action - didUpdateSubnav(element: HTMLElement, [count]: [number]): void { - this.animateSubnav(element, [count]); - } - @action animateSubnav(element: HTMLElement, [count]: [number]): void { /* diff --git a/packages/components/src/components/hds/table/th-selectable.hbs b/packages/components/src/components/hds/table/th-selectable.hbs index 1e57c3eab94..daddaefe789 100644 --- a/packages/components/src/components/hds/table/th-selectable.hbs +++ b/packages/components/src/components/hds/table/th-selectable.hbs @@ -15,8 +15,7 @@ class="hds-table__checkbox" checked={{@isSelected}} aria-label={{this.ariaLabel}} - {{did-insert this.didInsert}} - {{will-destroy this.willDestroyNode}} + {{this._registerCheckbox}} {{on "change" this.onSelectionChange}} /> {{#if this.isSortable}} diff --git a/packages/components/src/components/hds/table/th-selectable.ts b/packages/components/src/components/hds/table/th-selectable.ts index 1b7681f1e9d..2ef15297408 100644 --- a/packages/components/src/components/hds/table/th-selectable.ts +++ b/packages/components/src/components/hds/table/th-selectable.ts @@ -7,6 +7,7 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { guidFor } from '@ember/object/internals'; import { tracked } from '@glimmer/tracking'; +import { modifier } from 'ember-modifier'; import { HdsTableThSortOrderValues, HdsTableThSortOrderLabelValues, @@ -74,22 +75,23 @@ export default class HdsTableThSelectable extends Component { + const { didInsert } = this.args; - @action - willDestroyNode(): void { - super.willDestroy(); - const { willDestroy } = this.args; - if (typeof willDestroy === 'function') { - willDestroy(this.args.selectionKey); + if (typeof didInsert === 'function') { + didInsert(element, this.args.selectionKey); + } + + return () => { + const { willDestroy } = this.args; + + if (typeof willDestroy === 'function') { + willDestroy(this.args.selectionKey); + } + }; } - } + ); @action onSelectionChange(event: Event): void { diff --git a/packages/components/src/components/hds/tabs/index.hbs b/packages/components/src/components/hds/tabs/index.hbs index 8077c541385..07fb8702f6b 100644 --- a/packages/components/src/components/hds/tabs/index.hbs +++ b/packages/components/src/components/hds/tabs/index.hbs @@ -5,10 +5,10 @@ {{! template-lint-disable no-invalid-role }}
diff --git a/packages/components/src/components/hds/tabs/index.ts b/packages/components/src/components/hds/tabs/index.ts index 30cf6a0c6d7..a08c4aea221 100644 --- a/packages/components/src/components/hds/tabs/index.ts +++ b/packages/components/src/components/hds/tabs/index.ts @@ -9,6 +9,8 @@ import { action } from '@ember/object'; import { assert, warn } from '@ember/debug'; import { next, schedule } from '@ember/runloop'; import { HdsTabsSizeValues } from './types.ts'; +import { modifier } from 'ember-modifier'; + import type { ComponentLike } from '@glint/template'; import type { HdsTabsTabSignature } from './tab'; import type { HdsTabsPanelSignature } from './panel'; @@ -89,11 +91,6 @@ export default class HdsTabs extends Component { } } - /** - * Get the class names to apply to the component. - * @method classNames - * @return {string} The "class" attribute to apply to the component. - */ get classNames(): string { const classes = ['hds-tabs']; @@ -103,50 +100,58 @@ export default class HdsTabs extends Component { return classes.join(' '); } - @action - didInsert(): void { - assert( - 'The number of Tabs must be equal to the number of Panels', - this._tabNodes.length === this._panelNodes.length - ); - - if (this._selectedTabId) { - this.selectedTabIndex = this._tabIds.indexOf(this._selectedTabId); - } - + private _handleInsert = modifier(() => { // eslint-disable-next-line ember/no-runloop schedule('afterRender', (): void => { - this.setTabIndicator(); - }); - } + assert( + 'The number of Tabs must be equal to the number of Panels', + this._tabNodes.length === this._panelNodes.length + ); - @action - didUpdateSelectedTabIndex(): void { - // eslint-disable-next-line ember/no-runloop - schedule('afterRender', (): void => { - this.setTabIndicator(); - }); - } - - @action - didUpdateSelectedTabId(): void { - // if the selected tab is set dynamically (eg. in a `each` loop) - // the `Tab` nodes will be re-inserted/rendered, which means the `this.selectedTabId` variable changes - // but the parent `Tabs` component has already been rendered/inserted but doesn't re-render - // so the value of the `selectedTabIndex` is not updated, unless we trigger a recalculation - // using the `did-update` modifier that checks for changes in the `this.selectedTabId` variable - if (this._selectedTabId) { - this.selectedTabIndex = this._tabIds.indexOf(this._selectedTabId); - } - } + if (this._selectedTabId) { + this.selectedTabIndex = this._tabIds.indexOf(this._selectedTabId); + } - @action - didUpdateParentVisibility(): void { - // eslint-disable-next-line ember/no-runloop - schedule('afterRender', (): void => { this.setTabIndicator(); }); - } + }); + + private _updateSelectedTabIndex = modifier( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_element, [_selectedTabIndex]): void => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', (): void => { + this.setTabIndicator(); + }); + } + ); + + private _updateSelectedTabId = modifier( + (_element, [_selectedTabId]: [string | undefined]): void => { + // if the selected tab is set dynamically (eg. in a `each` loop) + // the `Tab` nodes will be re-inserted/rendered, which means the `this.selectedTabId` variable changes + // but the parent `Tabs` component has already been rendered/inserted but doesn't re-render + // so the value of the `selectedTabIndex` is not updated, unless we trigger a recalculation + // using the `did-update` modifier that checks for changes in the `this.selectedTabId` variable + if (_selectedTabId !== undefined) { + this.selectedTabIndex = this._tabIds.indexOf(_selectedTabId); + } + } + ); + + private _updateParentVisibility = modifier( + ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _element, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + [_isParentVisible]: [HdsTabsSignature['Args']['isParentVisible']] + ): void => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', (): void => { + this.setTabIndicator(); + }); + } + ); @action didInsertTab(element: HTMLButtonElement, isSelected?: boolean): void { diff --git a/packages/components/src/components/hds/tabs/panel.hbs b/packages/components/src/components/hds/tabs/panel.hbs index 5d49c7e38ac..3c7231db95d 100644 --- a/packages/components/src/components/hds/tabs/panel.hbs +++ b/packages/components/src/components/hds/tabs/panel.hbs @@ -9,8 +9,7 @@ id={{this._panelId}} hidden={{not this.isVisible}} aria-labelledby={{this.coupledTabId}} - {{did-insert this.didInsertNode}} - {{will-destroy this.willDestroyNode}} + {{this._handleLifecycle}} > {{yield (hash isVisible=this.isVisible)}} \ No newline at end of file diff --git a/packages/components/src/components/hds/tabs/panel.ts b/packages/components/src/components/hds/tabs/panel.ts index 4328d72efc7..44eb263afa6 100644 --- a/packages/components/src/components/hds/tabs/panel.ts +++ b/packages/components/src/components/hds/tabs/panel.ts @@ -5,9 +5,11 @@ import Component from '@glimmer/component'; import { guidFor } from '@ember/object/internals'; -import { action } from '@ember/object'; +import { modifier } from 'ember-modifier'; + import type { HdsTabsTabSignature } from './tab'; import type { HdsTabsPanelIds, HdsTabsTabIds } from './types'; +import { schedule } from '@ember/runloop'; export interface HdsTabsPanelSignature { Args: { @@ -61,22 +63,24 @@ export default class HdsTabsPanel extends Component { : undefined; } - @action - didInsertNode(element: HTMLElement): void { - const { didInsertNode } = this.args; + private _handleLifecycle = modifier((element: HTMLElement) => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', () => { + const { didInsertNode } = this.args; - if (typeof didInsertNode === 'function') { - this._elementId = element.id; - didInsertNode(element, this._elementId); - } - } + if (typeof didInsertNode === 'function') { + this._elementId = element.id; - @action - willDestroyNode(element: HTMLElement): void { - const { willDestroyNode } = this.args; + didInsertNode(element, this._elementId); + } + }); - if (typeof willDestroyNode === 'function') { - willDestroyNode(element); - } - } + return () => { + const { willDestroyNode } = this.args; + + if (typeof willDestroyNode === 'function') { + willDestroyNode(element); + } + }; + }); } diff --git a/packages/components/src/components/hds/tabs/tab.hbs b/packages/components/src/components/hds/tabs/tab.hbs index 4fe78914232..59df6ad845e 100644 --- a/packages/components/src/components/hds/tabs/tab.hbs +++ b/packages/components/src/components/hds/tabs/tab.hbs @@ -12,9 +12,8 @@ aria-controls={{this.coupledPanelId}} aria-selected={{if this.isSelected "true" "false"}} tabindex={{unless this.isSelected "-1"}} - {{did-insert this.didInsertNode @isSelected}} - {{did-update this.didUpdateNode @count @isSelected}} - {{will-destroy this.willDestroyNode}} + {{this._handleLifecycle}} + {{this._handleUpdates @count @isSelected}} {{on "click" this.onClick}} {{on "keyup" this.onKeyUp}} > diff --git a/packages/components/src/components/hds/tabs/tab.ts b/packages/components/src/components/hds/tabs/tab.ts index 25a302f7a69..2e20438b7d3 100644 --- a/packages/components/src/components/hds/tabs/tab.ts +++ b/packages/components/src/components/hds/tabs/tab.ts @@ -6,6 +6,9 @@ import Component from '@glimmer/component'; import { guidFor } from '@ember/object/internals'; import { action } from '@ember/object'; +import { modifier } from 'ember-modifier'; +import { schedule } from '@ember/runloop'; + import type { IconName } from '@hashicorp/flight-icons/svg'; import type { HdsTabsTabIds, HdsTabsPanelIds } from './types'; @@ -63,34 +66,41 @@ export default class HdsTabsTab extends Component { : undefined; } - @action - didInsertNode(element: HTMLButtonElement, positional: [boolean?]): void { - const { didInsertNode } = this.args; - - const isSelected = positional[0]; - - if (typeof didInsertNode === 'function') { - didInsertNode(element, isSelected); + private _handleLifecycle = modifier((element: HTMLButtonElement) => { + // eslint-disable-next-line ember/no-runloop + schedule('afterRender', () => { + const { isSelected, didInsertNode } = this.args; + + if (typeof didInsertNode === 'function') { + didInsertNode(element, isSelected); + } + }); + + return () => { + const { willDestroyNode } = this.args; + + if (typeof willDestroyNode === 'function') { + willDestroyNode(element); + } + }; + }); + + private _handleUpdates = modifier( + ( + _element, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + [_count, isSelected]: [ + HdsTabsTabSignature['Args']['count'], + HdsTabsTabSignature['Args']['isSelected'], + ] + ): void => { + const { didUpdateNode } = this.args; + + if (typeof didUpdateNode === 'function' && this.nodeIndex !== undefined) { + didUpdateNode(this.nodeIndex, isSelected); + } } - } - - @action - didUpdateNode(): void { - const { didUpdateNode } = this.args; - - if (typeof didUpdateNode === 'function' && this.nodeIndex !== undefined) { - didUpdateNode(this.nodeIndex, this.args.isSelected); - } - } - - @action - willDestroyNode(element: HTMLButtonElement): void { - const { willDestroyNode } = this.args; - - if (typeof willDestroyNode === 'function') { - willDestroyNode(element); - } - } + ); @action onClick(event: MouseEvent): false | undefined { diff --git a/packages/components/src/components/hds/time/index.ts b/packages/components/src/components/hds/time/index.ts index 3f35278fe0e..8a54306f9c3 100644 --- a/packages/components/src/components/hds/time/index.ts +++ b/packages/components/src/components/hds/time/index.ts @@ -5,7 +5,7 @@ import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; -import { inject as service } from '@ember/service'; +import { service } from '@ember/service'; import { action } from '@ember/object'; import type { DisplayType } from '../../../services/hds-time-types.ts'; diff --git a/packages/components/src/components/hds/time/range.ts b/packages/components/src/components/hds/time/range.ts index 1a0081f5efd..7198e56713e 100644 --- a/packages/components/src/components/hds/time/range.ts +++ b/packages/components/src/components/hds/time/range.ts @@ -4,7 +4,7 @@ */ import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; +import { service } from '@ember/service'; import type TimeService from '../../../services/hds-time'; export interface HdsTimeRangeSignature { diff --git a/packages/components/src/components/hds/time/single.hbs b/packages/components/src/components/hds/time/single.hbs index 1484eaa202e..b1de49dab98 100644 --- a/packages/components/src/components/hds/time/single.hbs +++ b/packages/components/src/components/hds/time/single.hbs @@ -7,8 +7,7 @@ class="hds-time hds-time--single" datetime={{@isoUtcString}} ...attributes - {{did-insert @register}} - {{will-destroy @unregister}} + {{hds-lifecycle didInsert=@register willDestroy=@unregister passElement=false}} > {{~#if @display.options.showFriendly~}} {{~#if @display.options.displayFormat~}} diff --git a/packages/components/src/instance-initializers/load-sprite.ts b/packages/components/src/instance-initializers/load-sprite.ts index 8d5cf982217..a88ecd9eccb 100644 --- a/packages/components/src/instance-initializers/load-sprite.ts +++ b/packages/components/src/instance-initializers/load-sprite.ts @@ -6,11 +6,8 @@ import config from 'ember-get-config'; export async function initialize() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment - const legacyLazyEmbed = config?.emberFlightIcons?.lazyEmbed; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (config?.flightIconsSpriteLazyEmbed || legacyLazyEmbed) { + if (config?.flightIconsSpriteLazyEmbed) { const { default: svgSprite } = await import( '@hashicorp/flight-icons/svg-sprite/svg-sprite-module' ); diff --git a/packages/components/src/modifiers/hds-lifecycle.ts b/packages/components/src/modifiers/hds-lifecycle.ts new file mode 100644 index 00000000000..cf6d0734836 --- /dev/null +++ b/packages/components/src/modifiers/hds-lifecycle.ts @@ -0,0 +1,38 @@ +import { modifier } from 'ember-modifier'; + +type HdsLifecycleCallback = ((element: HTMLElement) => void) | (() => void); + +export interface HdsLifecycleModifierSignature { + Element: HTMLElement; + Args: { + Named: { + didInsert?: HdsLifecycleCallback; + willDestroy?: HdsLifecycleCallback; + passElement?: boolean; + }; + }; +} + +const hdsLifecycleModifier = modifier( + (element, _positional, { didInsert, willDestroy, passElement = true }) => { + if (typeof didInsert === 'function') { + if (passElement) { + (didInsert as (element: HTMLElement) => void)(element); + } else { + (didInsert as () => void)(); + } + } + + return () => { + if (typeof willDestroy === 'function') { + if (passElement) { + (willDestroy as (element: HTMLElement) => void)(element); + } else { + (willDestroy as () => void)(); + } + } + }; + } +); + +export default hdsLifecycleModifier; diff --git a/packages/components/src/modifiers/hds-register-element.ts b/packages/components/src/modifiers/hds-register-element.ts new file mode 100644 index 00000000000..812065f09b2 --- /dev/null +++ b/packages/components/src/modifiers/hds-register-element.ts @@ -0,0 +1,34 @@ +import { modifier } from 'ember-modifier'; +import { assert } from '@ember/debug'; + +type HdsRegisterElementCallback = (element: HTMLElement) => void; + +export interface HdsRegisterElementModifierSignature { + Element: HTMLElement; + Args: { + Positional: [ + onInsert: HdsRegisterElementCallback, + onDestroy?: HdsRegisterElementCallback, + ]; + }; +} + +const hdsRegisterElementModifier = + modifier( + (element, [onInsert, onDestroy]) => { + assert( + 'The `onInsert` argument for "hds-register-element" must be a function.', + typeof onInsert === 'function' + ); + + onInsert(element); + + return () => { + if (typeof onDestroy === 'function') { + onDestroy(element); + } + }; + } + ); + +export default hdsRegisterElementModifier; diff --git a/packages/components/src/styles/@hashicorp/design-system-components.scss b/packages/components/src/styles/@hashicorp/design-system-components.scss index 00c6008b820..c74eaa36f4b 100644 --- a/packages/components/src/styles/@hashicorp/design-system-components.scss +++ b/packages/components/src/styles/@hashicorp/design-system-components.scss @@ -39,7 +39,6 @@ @use "../components/icon-tile"; @use "../components/layout"; // multiple components @use "../components/link"; // multiple components -@use "../components/menu-primitive"; @use "../components/modal"; @use "../components/page-header"; @use "../components/pagination"; diff --git a/packages/components/src/styles/components/dropdown.scss b/packages/components/src/styles/components/dropdown.scss index 0c35374cd8b..014c6146f74 100644 --- a/packages/components/src/styles/components/dropdown.scss +++ b/packages/components/src/styles/components/dropdown.scss @@ -207,7 +207,7 @@ $hds-dropdown-toggle-border-radius: $hds-button-border-radius; // LIST // UL ELEMENT -// GOES INSIDE HDS::MenuPrimitive's :content block +// GOES INSIDE HDS::PopoverPrimitive's popover element .hds-dropdown__content { position: relative; diff --git a/packages/components/src/styles/components/menu-primitive.scss b/packages/components/src/styles/components/menu-primitive.scss deleted file mode 100644 index 0ed5a234395..00000000000 --- a/packages/components/src/styles/components/menu-primitive.scss +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -// -// MenuPrimitive COMPONENT -// - -.hds-menu-primitive { - position: relative; - width: fit-content; -} diff --git a/packages/components/src/styles/components/side-nav/header.scss b/packages/components/src/styles/components/side-nav/header.scss index e79eb3d3add..a20703abd02 100644 --- a/packages/components/src/styles/components/side-nav/header.scss +++ b/packages/components/src/styles/components/side-nav/header.scss @@ -94,29 +94,3 @@ } } } - -// generic "icon-button" - -// TODO: Replace this component with Hds::Button -.hds-side-nav__icon-button { - @include hds-interactive-dark-theme(); - display: flex; - align-items: center; - justify-content: center; - width: 36px; // same height as the dropdown "toggle" - height: 36px; - padding: 5px; // we take in account the transparent border - - // disabled state: - &:disabled, - &[disabled], - &.mock-disabled, - &:disabled:focus, - &[disabled]:focus, - &.mock-disabled:focus, - &:disabled:hover, - &[disabled]:hover, - &.mock-disabled:hover { - @include hds-interactive-dark-theme-state-disabled(); - } -} diff --git a/packages/components/src/template-registry.ts b/packages/components/src/template-registry.ts index 66183aa31c6..8f066b17b6d 100644 --- a/packages/components/src/template-registry.ts +++ b/packages/components/src/template-registry.ts @@ -97,10 +97,6 @@ import type HdsDropdownListItemTitleComponent from './components/hds/dropdown/li import type HdsDropdownToggleButtonComponent from './components/hds/dropdown/toggle/button'; import type HdsDropdownToggleChevronComponent from './components/hds/dropdown/toggle/chevron'; import type HdsDropdownToggleIconComponent from './components/hds/dropdown/toggle/icon'; -import type HdsFlyoutBodyComponent from './components/hds/flyout/body'; -import type HdsFlyoutDescriptionComponent from './components/hds/flyout/description'; -import type HdsFlyoutFooterComponent from './components/hds/flyout/footer'; -import type HdsFlyoutHeaderComponent from './components/hds/flyout/header'; import type HdsFlyoutComponent from './components/hds/flyout'; import type HdsFormComponent from './components/hds/form'; @@ -168,10 +164,6 @@ import type HdsLayoutGridComponent from './components/hds/layout/grid'; import type HdsLayoutGridItemComponent from './components/hds/layout/grid/item'; import type HdsLinkInlineComponent from './components/hds/link/inline'; import type HdsLinkStandaloneComponent from './components/hds/link/standalone'; -import type HdsMenuPrimitiveComponent from './components/hds/menu-primitive'; -import type HdsModalBodyComponent from './components/hds/modal/body'; -import type HdsModalFooterComponent from './components/hds/modal/footer'; -import type HdsModalHeaderComponent from './components/hds/modal/header'; import type HdsModalComponent from './components/hds/modal/'; import type HdsPageHeaderComponent from './components/hds/page-header'; import type HdsPageHeaderActionsComponent from './components/hds/page-header/actions'; @@ -201,7 +193,6 @@ import type HdsSideNavPortalComponent from './components/hds/side-nav/portal'; import type HdsSideNavPortalTargetComponent from './components/hds/side-nav/portal/target'; import type HdsSideNavHeaderComponent from './components/hds/side-nav/header'; import type HdsSideNavHeaderHomeLinkComponent from './components/hds/side-nav/header/home-link'; -import type HdsSideNavHeaderIconButtonComponent from './components/hds/side-nav/header/icon-button'; import type HdsSideNavListComponent from './components/hds/side-nav/list'; import type HdsSideNavListBackLinkComponent from './components/hds/side-nav/list/back-link'; import type HdsSideNavListItemComponent from './components/hds/side-nav/list/item'; @@ -251,6 +242,8 @@ import type HdsClipboardModifier from './modifiers/hds-clipboard.ts'; import type HdsRegisterEventModifier from './modifiers/hds-register-event.ts'; import type HdsTooltipModifier from './modifiers/hds-tooltip.ts'; import type HdsAdvancedTableCellModifier from './modifiers/hds-advanced-table-cell.ts'; +import type HdsLifecycleModifier from './modifiers/hds-lifecycle.ts'; +import type HdsRegisterElementModifier from './modifiers/hds-register-element.ts'; export default interface HdsComponentsRegistry { // ----- COMPONENTS --------------------------------------------------- @@ -570,18 +563,6 @@ export default interface HdsComponentsRegistry { 'Hds::Flyout': typeof HdsFlyoutComponent; 'hds/flyout': typeof HdsFlyoutComponent; - 'Hds::Flyout::Body': typeof HdsFlyoutBodyComponent; - 'hds/flyout/body': typeof HdsFlyoutBodyComponent; - - 'Hds::Flyout::Description': typeof HdsFlyoutDescriptionComponent; - 'hds/flyout/description': typeof HdsFlyoutDescriptionComponent; - - 'Hds::Flyout::Footer': typeof HdsFlyoutFooterComponent; - 'hds/flyout/footer': typeof HdsFlyoutFooterComponent; - - 'Hds::Flyout::Header': typeof HdsFlyoutHeaderComponent; - 'hds/flyout/header': typeof HdsFlyoutHeaderComponent; - // FORM 'Hds::Form': typeof HdsFormComponent; 'hds/form': typeof HdsFormComponent; @@ -806,23 +787,10 @@ export default interface HdsComponentsRegistry { 'Hds::Link::Standalone': typeof HdsLinkStandaloneComponent; 'hds/link/standalone': typeof HdsLinkStandaloneComponent; - // MenuPrimitive - 'Hds::MenuPrimitive': typeof HdsMenuPrimitiveComponent; - 'hds/menu-primitive': typeof HdsMenuPrimitiveComponent; - // Modal 'Hds::Modal': typeof HdsModalComponent; 'hds/modal': typeof HdsModalComponent; - 'Hds::ModalBody': typeof HdsModalBodyComponent; - 'hds/modal/body': typeof HdsModalBodyComponent; - - 'Hds::ModalFooter': typeof HdsModalFooterComponent; - 'hds/modal/footer': typeof HdsModalFooterComponent; - - 'Hds::ModalHeader': typeof HdsModalHeaderComponent; - 'hds/modal/header': typeof HdsModalHeaderComponent; - // PageHeader 'Hds::PageHeader': typeof HdsPageHeaderComponent; 'hds/page-header': typeof HdsPageHeaderComponent; @@ -925,10 +893,6 @@ export default interface HdsComponentsRegistry { 'hds/side-nav/header/home-link': typeof HdsSideNavHeaderHomeLinkComponent; HdsSideNavHeaderHomeLink: typeof HdsSideNavHeaderHomeLinkComponent; - 'Hds::SideNav::Header::IconButton': typeof HdsSideNavHeaderIconButtonComponent; - 'hds/side-nav/header/icon-button': typeof HdsSideNavHeaderIconButtonComponent; - HdsSideNavHeaderIconButton: typeof HdsSideNavHeaderIconButtonComponent; - 'Hds::SideNav::List': typeof HdsSideNavListComponent; 'hds/side-nav/list': typeof HdsSideNavListComponent; HdsSideNavList: typeof HdsSideNavListComponent; @@ -1071,4 +1035,10 @@ export default interface HdsComponentsRegistry { // hds-advanced-table-cell 'hds-advanced-table-cell': typeof HdsAdvancedTableCellModifier; + + // hds-register-element + 'hds-register-element': typeof HdsRegisterElementModifier; + + // hds-lifecycle + 'hds-lifecycle': typeof HdsLifecycleModifier; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86152a1465d..ebf5c722479 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,9 +141,6 @@ importers: ember-a11y-refocus: specifier: ^4.1.4 version: 4.1.4 - ember-cli-sass: - specifier: ^11.0.1 - version: 11.0.1 ember-concurrency: specifier: ^4.0.4 version: 4.0.4(@babel/core@7.28.0)(@glint/template@1.5.2) @@ -177,9 +174,6 @@ importers: prismjs: specifier: ^1.30.0 version: 1.30.0 - sass: - specifier: ^1.83.0 - version: 1.89.2 tabbable: specifier: ^6.2.0 version: 6.2.0 @@ -298,6 +292,9 @@ importers: rollup-plugin-scss: specifier: ^4.0.1 version: 4.0.1 + sass: + specifier: ^1.89.2 + version: 1.89.2 stylelint: specifier: ^16.17.0 version: 16.23.0(typescript@5.9.2) @@ -14200,7 +14197,6 @@ snapshots: codemirror-lang-hcl: 0.0.0-beta.2 decorator-transforms: 2.3.0(@babel/core@7.28.0) ember-a11y-refocus: 4.1.4 - ember-cli-sass: 11.0.1 ember-concurrency: 4.0.4(@babel/core@7.28.0)(@glint/template@1.5.2) ember-element-helper: 0.8.8 ember-focus-trap: 1.1.1(ember-source@6.5.0(@glimmer/component@1.1.2(@babel/core@7.28.0))(rsvp@4.8.5)) @@ -14212,7 +14208,6 @@ snapshots: ember-truth-helpers: 4.0.3(ember-source@6.5.0(@glimmer/component@1.1.2(@babel/core@7.28.0))(rsvp@4.8.5)) luxon: 3.7.1 prismjs: 1.30.0 - sass: 1.89.2 tabbable: 6.2.0 tippy.js: 6.3.7 tracked-built-ins: 4.0.0(@babel/core@7.28.0) @@ -14258,7 +14253,6 @@ snapshots: codemirror-lang-hcl: 0.0.0-beta.2 decorator-transforms: 2.3.0(@babel/core@7.28.0) ember-a11y-refocus: 4.1.4 - ember-cli-sass: 11.0.1 ember-concurrency: 4.0.4(@babel/core@7.28.0)(@glint/template@1.5.2) ember-element-helper: 0.8.8 ember-focus-trap: 1.1.1(ember-source@6.4.0(@glimmer/component@1.1.2(@babel/core@7.28.0))(rsvp@4.8.5)) @@ -14270,7 +14264,6 @@ snapshots: ember-truth-helpers: 4.0.3(ember-source@6.4.0(@glimmer/component@1.1.2(@babel/core@7.28.0))(rsvp@4.8.5)) luxon: 3.7.1 prismjs: 1.30.0 - sass: 1.89.2 tabbable: 6.2.0 tippy.js: 6.3.7 tracked-built-ins: 4.0.0(@babel/core@7.28.0) diff --git a/showcase/app/components/mock/app/header/app-header.gts b/showcase/app/components/mock/app/header/app-header.gts index 8f9d9f66ad4..226e101691b 100644 --- a/showcase/app/components/mock/app/header/app-header.gts +++ b/showcase/app/components/mock/app/header/app-header.gts @@ -96,7 +96,9 @@ export default class MockAppHeaderAppHeader extends Component - + + Account Settings + diff --git a/showcase/app/components/page-components/accordion/sub-sections/base-elements.gts b/showcase/app/components/page-components/accordion/sub-sections/base-elements.gts index ab9b61a50d9..bf5dd33492d 100644 --- a/showcase/app/components/page-components/accordion/sub-sections/base-elements.gts +++ b/showcase/app/components/page-components/accordion/sub-sections/base-elements.gts @@ -161,6 +161,7 @@ const SubSectionBaseElements: TemplateOnlyComponent =