From f110f9e0e2055960213012d8e75b6b9e1b9a29c7 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Wed, 10 Apr 2024 11:15:40 +0100 Subject: [PATCH 001/127] added `PopoverPrimitive` component --- packages/components/package.json | 1 + .../hds/popover-primitive/index.hbs | 20 + .../components/hds/popover-primitive/index.js | 346 ++++++++++++++++++ .../@hashicorp/design-system-components.scss | 1 + .../styles/components/popover-primitive.scss | 105 ++++++ 5 files changed, 473 insertions(+) create mode 100644 packages/components/src/components/hds/popover-primitive/index.hbs create mode 100644 packages/components/src/components/hds/popover-primitive/index.js create mode 100644 packages/components/src/styles/components/popover-primitive.scss diff --git a/packages/components/package.json b/packages/components/package.json index 00366acd18..1eca9a23c8 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -217,6 +217,7 @@ "./components/hds/pagination/nav/number.js": "./dist/_app_/components/hds/pagination/nav/number.js", "./components/hds/pagination/numbered/index.js": "./dist/_app_/components/hds/pagination/numbered/index.js", "./components/hds/pagination/size-selector/index.js": "./dist/_app_/components/hds/pagination/size-selector/index.js", + "./components/hds/popover-primitive/index.js": "./dist/_app_/components/hds/popover-primitive/index.js", "./components/hds/reveal/index.js": "./dist/_app_/components/hds/reveal/index.js", "./components/hds/reveal/toggle/button.js": "./dist/_app_/components/hds/reveal/toggle/button.js", "./components/hds/segmented-group/index.js": "./dist/_app_/components/hds/segmented-group/index.js", diff --git a/packages/components/src/components/hds/popover-primitive/index.hbs b/packages/components/src/components/hds/popover-primitive/index.hbs new file mode 100644 index 0000000000..6a3b918399 --- /dev/null +++ b/packages/components/src/components/hds/popover-primitive/index.hbs @@ -0,0 +1,20 @@ +{{! @glint-nocheck: not typesafe yet }} +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} +
+ {{#let (element (if this.containsInteractive "div" "button")) as |Tag|}} + + {{yield (hash popoverElement=this.popoverElement isOpen=this.isOpen) to="toggle"}} + + {{/let}} + \ No newline at end of file diff --git a/packages/components/src/components/hds/popover-primitive/index.js b/packages/components/src/components/hds/popover-primitive/index.js new file mode 100644 index 0000000000..9bdb07d9e0 --- /dev/null +++ b/packages/components/src/components/hds/popover-primitive/index.js @@ -0,0 +1,346 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { assert, warn } from '@ember/debug'; +import { next } from '@ember/runloop'; +import { guidFor } from '@ember/object/internals'; +import { modifier } from 'ember-modifier'; + +import registerEvent from '../../../modifiers/hds-register-event'; +import anchoredPositionModifier from '../../../modifiers/hds-anchored-position'; + +// https://github.com/oddbird/popover-polyfill?tab=readme-ov-file#with-npm +// this is needed until Firefox officially supports the Popover API +// see: https://wiki.mozilla.org/Release_Management/Release_owners +import { + // this call polyfills some of the browser methods to emulate the Popover API + apply as applyPopoverApiPolyfill, + // we'll use these two flags to overwrite the popover positioning strategy + // this is specifically done for Firefox: currently it doesn't support it, but will soon (we need Firefox 127 to support the last 2 versions) + // see: https://whattrainisitnow.com/release/?version=127 + isSupported as isPopoverApiSupported, + isPolyfilled as isPopoverApiPolyfilled, +} from '@oddbird/popover-polyfill/fn'; + +// we use this to re-export the values +export { + PLACEMENTS, + DEFAULT_PLACEMENT, +} from '../../../modifiers/hds-anchored-position'; + +export default class HdsPopoverPrimitiveComponent extends Component { + @tracked toggleElement; + @tracked popoverElement; + @tracked arrowElement; + @tracked isOpen = this.args.isOpen ?? false; + @tracked isClosing = false; + @tracked containsInteractive = false; + // this will enable "soft" events for the toggle ("hover" and "focus") + @tracked enableSoftEvents = this.args.enableSoftEvents ?? false; + // this will enable "click" events for the toggle + @tracked enableClickEvents = this.args.enableClickEvents ?? false; + + constructor() { + super(...arguments); + + // if the Popover API is not supported we need to polyfill it + if (!isPopoverApiSupported()) { + warn( + "The browser used does not support the Popover API so it's been emulated and some functionalities may not work as expected.", + { + id: 'hds-popover.no-popover-api-support.polyfill-applied', + } + ); + // this function polyfills quite a few DOM methods and adds emulation for the Popover API + // see: https://github.com/oddbird/popover-polyfill/blob/main/src/popover.ts#L123 + applyPopoverApiPolyfill(); + } + } + + /** + * Generates a unique ID for the "popover" (will be used in the `popovertarget` attribute of the toggle button) + * + * @param popoverId + */ + popoverId = 'popover-' + guidFor(this); + + setupPrimitiveContainer = modifier( + (element) => { + this.containerElement = element; + + // we register the "soft" events + if (this.enableSoftEvents) { + registerEvent(this.containerElement, ['mouseenter', this.onMouseEnter]); + registerEvent(this.containerElement, ['mouseleave', this.onMouseLeave]); + registerEvent(this.containerElement, ['focusin', this.onFocusIn]); + } + // we always want the focusOut event + registerEvent(this.containerElement, ['focusout', this.onFocusOut]); + }, + { eager: false } + ); + + setupPrimitiveToggle = modifier( + (element) => { + this.toggleElement = element; + + // check if it contains interactive elements + this.containsInteractive = + element.querySelector( + 'a, button, input, select, textarea, details, dialog, menuitem, option' + ) !== null; + + if (this.args.toggleAriaLabel) { + this.toggleElement.setAttribute( + 'aria-label', + this.args.toggleAriaLabel + ); + } + + this.toggleElement.setAttribute( + 'aria-expanded', + // don't use this.isOpen here because it will cause a re-rendering + this.args.isOpen ? 'true' : 'false' + ); + + // for the click events we don't use `onclick` event listeners, but we rely on the `popovertarget` attribute + // provided by the Popover API which does all the magic for us without needing JS code + // (important: to work it needs to be applied to a button, so we can't safely support if the toggle contains interactive elements) + if (this.enableClickEvents) { + if (this.containsInteractive) { + assert( + 'Hds::PopoverPrimitive - You have assigned `onClick` events to the "toggle" element, but it contains interactive elements: this may result in unexpected behaviours or non accessible code' + ); + } else { + this.toggleElement.setAttribute('popovertarget', this.popoverId); + } + } + }, + { eager: false } + ); + + setupPrimitiveArrow = modifier( + (element) => { + this.arrowElement = element; + }, + { eager: false } + ); + + setupPrimitivePopover = modifier( + (element) => { + this.popoverElement = element; + + this.popoverElement.id = this.popoverId; + + // this should be an extremely edge case, but in the case the popover needs to be initially forced to be open + // we need to use the "manual" state to support the case of multiple "menus" opened at the same time + // IMPORTANT! if a "popover" is set to "open" with a "manual" state, then it can't be closed via `esc` and `click outside` + if (this.args.isOpen) { + this.popoverElement.popover = 'manual'; + this.popoverElement.showPopover(); + } else { + this.popoverElement.popover = 'auto'; + } + + // Register "onBeforeToggle" + "onToggle" callback functions to be called when a native 'toggle' event is dispatched + registerEvent(this.popoverElement, [ + 'beforetoggle', + this.onBeforeTogglePopover, + ]); + registerEvent(this.popoverElement, ['toggle', this.onTogglePopover]); + + // Apply the "float-popover" modifier to the "popover" element + // (notice: this function runs the first time when the element the modifier was applied to is inserted into the DOM, and it autotracks while running. + // Any tracked values that it accesses will be tracked, including the arguments it receives, and if any of them changes, the function will run again) + // This modifiers uses the Floating UI library to provide: + // - positioning of the "popover" in relation to the "toggle" + // - collision detection (optional) + next(() => { + anchoredPositionModifier( + this.popoverElement, // element the modifier is attached to + [this.toggleElement], // positional arguments + this.popoverOptions // named arguments + ); + }); + }, + { eager: false } + ); + + get popoverOptions() { + // we need to spread the argument because if it's set via `{{ hash … }}` Ember complains when we overwrite one of its values + const popoverOptions = this.args.popoverOptions + ? { ...this.args.popoverOptions } + : {}; + + // we overwrite the "strategy" if the Popover API is not supported (polyfill applied for the first time) of it's already been polyfilled (see above) + // this is specifically done for Firefox: currently it doesn't support it, but will soon (we need Firefox 127 to support the last 2 versions) + // see: https://wiki.mozilla.org/Release_Management/Release_owners + if (!isPopoverApiSupported() || isPopoverApiPolyfilled()) { + // when using the "absolute" strategy, the presence of a parent with "relative" position leads to wrong layout rendering (known issue in the polyfill library) + // see: https://github.com/oddbird/popover-polyfill/tree/main?tab=readme-ov-file#caveats + popoverOptions.strategy = 'fixed'; + } + + if (this.arrowElement) { + popoverOptions.arrowOptions = { + // we assign the `arrowElement` to the `element` key + element: this.arrowElement, + // we use the padding value from the provided options (if assigned) or use a default + padding: popoverOptions?.arrowOptions?.padding ?? 0, + }; + } + + return popoverOptions; + } + + /** + * @param isInline + * @type {boolean} + * @default true + * @description sets display for the container and toggle elements + */ + get isInline() { + let { isInline = true } = this.args; + return isInline; + } + + /** + * Get the class names to apply to the element + * @method classNames + * @return {string} The "class" attribute to apply to the root element + */ + get classNames() { + let classes = ['hds-popover-primitive']; + + // add a class based on the @isInline argument + if (this.isInline) { + classes.push('hds-popover-primitive--is-inline'); + } else { + classes.push('hds-popover-primitive--is-block'); + } + + return classes.join(' '); + } + + /** + * Get the class names to apply to the toggle + * @method classNamesContent + * @return {string} The "class" attribute to apply to the toggle + */ + get classNamesToggle() { + let classes = ['hds-popover-primitive__toggle']; + + // add a class based on the @isInline argument + if (this.isInline) { + classes.push('hds-popover-primitive__toggle--is-inline'); + } else { + classes.push('hds-popover-primitive__toggle--is-block'); + } + + return classes.join(' '); + } + + @action + showPopover() { + this.popoverElement.showPopover(); + } + + @action + hidePopover() { + this.popoverElement.hidePopover(); + } + + @action + togglePopover() { + this.popoverElement.togglePopover(); + } + + // fired just _before_ the "popover" is shown or hidden + @action + onBeforeTogglePopover(event) { + if (event.newState === 'closed') { + // we need this flag to check if it's in the "closing" process, + // because the browser automatically returns the focus to the "trigger" button + // and this would re-open immediately the popover because of the `focusin` event + this.isClosing = true; + } + } + + // fired just _after_ the "popover" is shown or hidden + @action + onTogglePopover(event) { + if (event.newState === 'open') { + this.isOpen = true; + this.toggleElement.setAttribute('aria-expanded', 'true'); + + // we call the "onOpen" callback if it exists (and is a function) + let { onOpen } = this.args; + if (typeof onOpen === 'function') { + onOpen(); + } + } else { + // if the popover was initially forced to be open (using the "manual" state) then revert its status to `auto` once the user interacts with it + if (this.args.isOpen) { + this.popoverElement.popover = 'auto'; + } + this.isOpen = false; + // reset the "isClosing" flag (the `toggle` event is fired _after_ the popover is closed) + this.isClosing = false; + this.toggleElement.setAttribute('aria-expanded', 'false'); + // we call the "onClose" callback if it exists (and is a function) + let { onClose } = this.args; + if (typeof onClose === 'function') { + onClose(); + } + } + } + + @action + onMouseEnter() { + if (this.timer) { + clearTimeout(this.timer); + } + this.showPopover(); + } + + @action + onFocusIn() { + // don't re-open the popover if the focus is returned because the closing + if (!this.isClosing) { + if (this.timer) { + clearTimeout(this.timer); + } + this.showPopover(); + } + } + + @action + onMouseLeave() { + this.timer = setTimeout(this.hidePopover.bind(this), 500); + } + + @action + onFocusOut(event) { + // TODO! discuss with Alex if/why we need this check here + // 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.containerElement.contains( + event.relatedTarget || document.activeElement + ) + ) { + this.hidePopover(); + } + } + + // this is exposed to the consumers to programmatically "close" the popover + @action + close() { + this.hidePopover(); + } +} diff --git a/packages/components/src/styles/@hashicorp/design-system-components.scss b/packages/components/src/styles/@hashicorp/design-system-components.scss index 450bc533e7..c902e8bf67 100644 --- a/packages/components/src/styles/@hashicorp/design-system-components.scss +++ b/packages/components/src/styles/@hashicorp/design-system-components.scss @@ -39,6 +39,7 @@ @use "../components/modal"; @use "../components/page-header"; @use "../components/pagination"; +@use "../components/popover-primitive"; @use "../components/reveal"; @use "../components/segmented-group"; @use "../components/separator"; diff --git a/packages/components/src/styles/components/popover-primitive.scss b/packages/components/src/styles/components/popover-primitive.scss new file mode 100644 index 0000000000..e79fbcb2eb --- /dev/null +++ b/packages/components/src/styles/components/popover-primitive.scss @@ -0,0 +1,105 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +// +// POPOVER-PRIMITIVE +// + +.hds-popover-primitive { + position: relative; + width: fit-content; + height: fit-content; +} + +:where(.hds-popover-primitive--is-inline) { + display: inline-block; +} + +:where(.hds-popover-primitive--is-block) { + display: block; +} + + +// TOGGLE + +.hds-popover-primitive__toggle { + width: fit-content; + height: fit-content; + + &:where(button) { + margin: 0; + padding: 0; + color: inherit; + font: inherit; + text-align: inherit; + background-color: inherit; + border: none; + cursor: pointer; + } +} + +:where(.hds-popover-primitive__toggle--is-inline) { + display: inline-block; +} + +:where(.hds-popover-primitive__toggle--is-block) { + display: block; +} + + +// ARROW + +.hds-popover-primitive__arrow { + --arrow-size: var(--hds-popover-primitive-arrow-size, 16px); + --arrow-shift: calc(-1 * var(--arrow-size)); + --arrow-background: var(--hds-popover-primitive-arrow-background, none); + position: absolute; + z-index: 1; + display: block; + // notice: floating-ui assumes the "arrow" container is square + width: var(--arrow-size); + height: var(--arrow-size); + background: var(--arrow-background); + pointer-events: none; + + &[data-hds-anchored-arrow-placement^="top"] { + bottom: var(--arrow-shift); + transform: rotate(180deg); + } + + &[data-hds-anchored-arrow-placement^="right"] { + left: var(--arrow-shift); + transform: rotate(-90deg); + } + + &[data-hds-anchored-arrow-placement^="bottom"] { + top: var(--arrow-shift); + transform: rotate(0deg); // this fixes a rendering issue in Safari + } + + &[data-hds-anchored-arrow-placement^="left"] { + right: var(--arrow-shift); + transform: rotate(90deg); + } +} + + +// CONTENT + +.hds-popover-primitive__content { + // the "popover" attributes comes with pre-defined styling so we need to override it + :where(&[popover]) { + margin: 0; + padding: 0; + overflow: visible; + color: inherit; + background: none; + border: none; + // TODO do we need this? + // width: fit-content; + // height: fit-content; + inset: 0; + } +} \ No newline at end of file From 07c537552ee3deef6236afebda08c883c1314954 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Thu, 9 May 2024 18:18:09 +0100 Subject: [PATCH 002/127] added `@oddbird/popover-polyfill` as dependency --- packages/components/package.json | 1 + yarn.lock | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/packages/components/package.json b/packages/components/package.json index 1eca9a23c8..55b381a442 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,6 +40,7 @@ "@floating-ui/dom": "^1.6.3", "@hashicorp/design-system-tokens": "^2.1.0", "@hashicorp/ember-flight-icons": "^5.0.1", + "@oddbird/popover-polyfill": "^0.4.1", "decorator-transforms": "^1.1.0", "ember-a11y-refocus": "^3.0.2", "ember-cli-sass": "^11.0.1", diff --git a/yarn.lock b/yarn.lock index 75e2aa9093..dd210facac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3548,6 +3548,7 @@ __metadata: "@glint/template": "npm:^1.4.0" "@hashicorp/design-system-tokens": "npm:^2.1.0" "@hashicorp/ember-flight-icons": "npm:^5.0.1" + "@oddbird/popover-polyfill": "npm:^0.4.1" "@rollup/plugin-babel": "npm:^6.0.4" "@tsconfig/ember": "npm:^3.0.6" "@types/ember-qunit": "npm:^6.1.1" @@ -4197,6 +4198,13 @@ __metadata: languageName: node linkType: hard +"@oddbird/popover-polyfill@npm:^0.4.1": + version: 0.4.1 + resolution: "@oddbird/popover-polyfill@npm:0.4.1" + checksum: 08d5a065a3e4b2173006c6666bfc9ae6a8928b2e12eba5bb6db5cf871108687db5abe9bdfb107073c3f948dbd64cd986b7d628226d7523f30e9f89c8fc6b83b5 + languageName: node + linkType: hard + "@percy/cli-app@npm:1.27.4": version: 1.27.4 resolution: "@percy/cli-app@npm:1.27.4" From 3e7cafbb7f4ec286d8f557246bbdd0945b136926 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Tue, 9 Apr 2024 20:27:50 +0100 Subject: [PATCH 003/127] added showcase for `PopoverPrimitive` --- .../utilities/popover-primitive.js | 46 ++ showcase/app/router.js | 1 + .../app/routes/utilities/popover-primitive.js | 14 + showcase/app/styles/app.scss | 1 + .../showcase-pages/popover-primitive.scss | 128 +++++ showcase/app/templates/index.hbs | 5 + .../templates/utilities/popover-primitive.hbs | 519 ++++++++++++++++++ 7 files changed, 714 insertions(+) create mode 100644 showcase/app/controllers/utilities/popover-primitive.js create mode 100644 showcase/app/routes/utilities/popover-primitive.js create mode 100644 showcase/app/styles/showcase-pages/popover-primitive.scss create mode 100644 showcase/app/templates/utilities/popover-primitive.hbs diff --git a/showcase/app/controllers/utilities/popover-primitive.js b/showcase/app/controllers/utilities/popover-primitive.js new file mode 100644 index 0000000000..877ed613ef --- /dev/null +++ b/showcase/app/controllers/utilities/popover-primitive.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { scheduleOnce } from '@ember/runloop'; + +function centerScrollableArea() { + document.querySelectorAll('[center-scrollable-area]').forEach((element) => { + const direction = + element.attributes['center-scrollable-area'].value ?? 'both'; + if (direction === 'both' || direction === 'x') { + element.scrollLeft = (element.scrollWidth - element.offsetWidth) / 2; + } + if (direction === 'both' || direction === 'y') { + element.scrollTop = + 20 + (element.scrollHeight - element.offsetHeight) / 2; + } + }); +} + +export default class PopoverPrimitiveController extends Controller { + @service router; + + constructor() { + super(...arguments); + this.router.on('routeDidChange', this, 'routeDidChange'); + } + + routeDidChange() { + if (this.router.currentRoute.name === 'utilities.popover-primitive') { + scheduleOnce('afterRender', this, centerScrollableArea); + } + } + + @action + noop() {} + + @action + onClickButton() { + window.alert('The button has been clicked!'); + } +} diff --git a/showcase/app/router.js b/showcase/app/router.js index 4b6945dde0..75965055ee 100644 --- a/showcase/app/router.js +++ b/showcase/app/router.js @@ -76,6 +76,7 @@ Router.map(function () { this.route('dismiss-button'); this.route('interactive'); this.route('menu-primitive'); + this.route('popover-primitive'); }); this.route('overrides', function () { this.route('power-select'); diff --git a/showcase/app/routes/utilities/popover-primitive.js b/showcase/app/routes/utilities/popover-primitive.js new file mode 100644 index 0000000000..b60f0e1605 --- /dev/null +++ b/showcase/app/routes/utilities/popover-primitive.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Route from '@ember/routing/route'; + +import { PLACEMENTS } from '@hashicorp/design-system-components/components/hds/popover-primitive'; + +export default class ComponentsPopoverRoute extends Route { + model() { + return { PLACEMENTS }; + } +} diff --git a/showcase/app/styles/app.scss b/showcase/app/styles/app.scss index 277a1a4658..8fc1b44e15 100644 --- a/showcase/app/styles/app.scss +++ b/showcase/app/styles/app.scss @@ -53,6 +53,7 @@ @import "./showcase-pages/menu-primitive"; @import "./showcase-pages/modal"; @import "./showcase-pages/pagination"; +@import "./showcase-pages/popover-primitive"; @import "./showcase-pages/power-select"; @import "./showcase-pages/reveal"; @import "./showcase-pages/separator"; diff --git a/showcase/app/styles/showcase-pages/popover-primitive.scss b/showcase/app/styles/showcase-pages/popover-primitive.scss new file mode 100644 index 0000000000..23f16b3900 --- /dev/null +++ b/showcase/app/styles/showcase-pages/popover-primitive.scss @@ -0,0 +1,128 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +// PopoverPrimitive + +body.utilities-popover-primitive { + + // basic styling for the "arrow" element + .hds-popover-primitive__arrow { + --hds-popover-primitive-arrow-size: 16px; + --hds-popover-primitive-arrow-background: url('data:image/svg+xml,') no-repeat 0 0 / 16px 16px; + outline: 1px dotted red; + } + + .shw-utilities-popover-primitive-base-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 100px; + height: 40px; + outline: 1px dotted #d3d3d3; + } + + .shw-utilities-popover-primitive-placement-grid { + grid-gap: 10px; + grid-template-rows: repeat(5, 80px) !important; + grid-template-columns: repeat(5, 160px) !important; + padding: 5rem; + + .shw-utilities-popover-primitive-placement-grid__item--top-start { grid-area: 1 / 2; } + .shw-utilities-popover-primitive-placement-grid__item--top { grid-area: 1 / 3; } + .shw-utilities-popover-primitive-placement-grid__item--top-end { grid-area: 1 / 4; } + .shw-utilities-popover-primitive-placement-grid__item--right-start { grid-area: 2 / 5; } + .shw-utilities-popover-primitive-placement-grid__item--right { grid-area: 3 / 5; } + .shw-utilities-popover-primitive-placement-grid__item--right-end { grid-area: 4 / 5; } + .shw-utilities-popover-primitive-placement-grid__item--bottom-start { grid-area: 5 / 2; } + .shw-utilities-popover-primitive-placement-grid__item--bottom { grid-area: 5 / 3; } + .shw-utilities-popover-primitive-placement-grid__item--bottom-end { grid-area: 5 / 4; } + .shw-utilities-popover-primitive-placement-grid__item--left-start { grid-area: 2 / 1; } + .shw-utilities-popover-primitive-placement-grid__item--left { grid-area: 3 / 1; } + .shw-utilities-popover-primitive-placement-grid__item--left-end { grid-area: 4 / 1; } + + .shw-utilities-popover-primitive-placement-grid__target { + display: flex; + align-items: center; + justify-content: center; + width: 160px; + height: 80px; + outline: 1px dotted #d3d3d3; + } + + // we highlight the main axes (for visual reference) + .shw-utilities-popover-primitive-placement-grid__item--top, + .shw-utilities-popover-primitive-placement-grid__item--right, + .shw-utilities-popover-primitive-placement-grid__item--bottom, + .shw-utilities-popover-primitive-placement-grid__item--left { + .shw-utilities-popover-primitive-placement-grid__target { + font-weight: bold; + } + } + } + + .shw-utilities-popover-primitive-collision-detection-container { + position: relative; + display: grid; + align-items: center; + justify-items: center; + width: 100%; + min-height: 200px; + aspect-ratio: 1; + overflow-x: auto; + overflow-y: auto; + border: 1px dotted #d3d3d3; + overscroll-behavior: contain; + + } + + .shw-utilities-popover-primitive-collision-detection-content { + display: flex; + align-items: center; + justify-content: center; + width: 520px; + height: 480px; + } + + .shw-component-popover-primitive-interaction-bubble { + position: relative; + padding: 16px; + background: var(--token-color-surface-primary); + box-shadow: var(--token-surface-higher-box-shadow); + + > button { + position: absolute; + top: 8px; + right: 8px; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + font-size: 16px; + } + + > ul { + margin: 0; + padding: 0 48px 0 0; + list-style: none; + + li { + margin: 0; + padding: 0; + + & + li { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dotted #ccc; + } + } + + a { + display: block; + } + } + } +} diff --git a/showcase/app/templates/index.hbs b/showcase/app/templates/index.hbs index 51bdbec80f..39d11a4c2c 100644 --- a/showcase/app/templates/index.hbs +++ b/showcase/app/templates/index.hbs @@ -283,6 +283,11 @@ SPDX-License-Identifier: MPL-2.0 MenuPrimitive +
  • + + PopoverPrimitive + +
  • \ No newline at end of file diff --git a/showcase/app/templates/utilities/popover-primitive.hbs b/showcase/app/templates/utilities/popover-primitive.hbs new file mode 100644 index 0000000000..f56cb7b367 --- /dev/null +++ b/showcase/app/templates/utilities/popover-primitive.hbs @@ -0,0 +1,519 @@ +{{! @glint-nocheck: not typesafe yet }} +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} + +{{page-title "PopoverPrimitive"}} + +PopoverPrimitive + +
    + + Base elements + + + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    +
    + + + + Content + + + + + <:toggle> +

    Lorem ipsum dolor

    + + <:content> + + +
    +
    + + + <:toggle> + + + <:content> + + + + +
    + + + + + + + <:toggle> + + + <:content> + + + + + + + <:toggle> +

    Lorem ipsum dolor

    + + <:content> + + +
    +
    + + + <:toggle> + + + <:content> + + + + + <:toggle> + + + <:content> + + + + + + + <:toggle> + + + <:content> + + + + + <:toggle> + + + <:content> + + + + + + + <:toggle> + + + <:content> + + + + +
    + + + + Display + + + +

    + Lorem ipsum dolor sit amet consectetur + + <:toggle> + + + <:content> + + + + adipisicing elit. Doloremque blanditiis sapiente iste beatae voluptates voluptatum. +

    +
    + +

    + Lorem ipsum dolor sit amet consectetur + + <:toggle> + + + <:content> + + + + adipisicing elit. Doloremque blanditiis sapiente iste beatae voluptates voluptatum. +

    +
    + +

    + Lorem ipsum dolor sit amet consectetur + + <:toggle> + toggle + + <:content> + + + + adipisicing elit. Doloremque blanditiis sapiente iste beatae voluptates voluptatum. +

    +
    + +

    + Lorem ipsum dolor sit amet consectetur + + <:toggle> + toggle + + <:content> + + + + adipisicing elit. Doloremque blanditiis sapiente iste beatae voluptates voluptatum. +

    +
    +
    + + + + Options + + Placement + + Base + + + {{#each @model.PLACEMENTS as |place|}} + + + <:toggle> +
    {{place}}
    + + <:content> + + +
    +
    + {{/each}} +
    + + With arrow + + + {{#each @model.PLACEMENTS as |place|}} + + + <:toggle> +
    {{place}}
    + + <:content> + + +
    +
    + {{/each}} +
    + + + + Offset + + offsetOptions + + {{#let (array false true) as |booleans|}} + {{#each booleans as |hasArrow|}} + + + {{#let + (array + (hash label="not set" offset=0) + (hash label="16" offset=16) + (hash label="{ mainAxis: 40, crossAxis: 0 }" offset=(hash mainAxis=40 crossAxis=0)) + (hash label="{ mainAxis: 16, crossAxis: 100 }" offset=(hash mainAxis=16 crossAxis=100)) + ) + as |offsetOptionsVariants| + }} + {{#each offsetOptionsVariants as |offsetOptionsVariant|}} + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + {{/each}} + {{/let}} +
    + + {{/each}} + {{/let}} + + arrowPadding + + + {{#let (array (hash label="not set") (hash label="24" arrowPadding=24)) as |arrowPaddingVariants|}} + {{#each arrowPaddingVariants as |arrowPaddingVariant|}} + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + {{/each}} + {{/let}} +
    + + + + Strategy + + + {{#let (array "absolute" "fixed") as |strategies|}} + {{#each strategies as |strategy|}} + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + {{/each}} + {{/let}} +
    + + + + Collision detection + + Scroll within the boxes to see the collision detection in action + + + {{#let (array false true "flip" "shift") as |detections|}} + {{#each detections as |detection|}} + +
    + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + +
    + {{/each}} + {{/let}} +
    + + + + Interaction + + {{#let (array "soft" "click") as |interactionVariants|}} + {{#each interactionVariants as |interaction|}} + + + On + {{#if (eq interaction "soft")}} + Hover/Focus + ("Soft") + {{else if (eq interaction "click")}} + Click + {{/if}} + + + {{#let (array "non-interactive" "interactive") as |triggerTypes|}} + {{#each triggerTypes as |triggerType|}} + + {{#if (not (and (eq interaction "click") (eq triggerType "interactive")))}} + + Toggle with + {{triggerType}} + content + + + + {{#let + (hash + non-interactive=(array "text" "icon" "tag" "generic") + interactive=(array "button" "link" "tag" "input") + ) + as |triggerVariants| + }} + {{#each (get triggerVariants triggerType) as |triggerVariant|}} + + + + <:toggle> + {{#if (eq triggerVariant "text")}} + Lorem ipsum dolor + {{/if}} + {{#if (eq triggerVariant "icon")}} + + {{/if}} + {{#if (eq triggerVariant "tag")}} + {{#if (eq triggerType "non-interactive")}} + + {{/if}} + {{#if (eq triggerType "interactive")}} + + {{/if}} + {{/if}} + {{#if (eq triggerVariant "button")}} + + {{/if}} + {{#if (eq triggerVariant "link")}} + + {{/if}} + {{#if (eq triggerVariant "input")}} + + {{/if}} + {{#if (eq triggerVariant "generic")}} + + {{/if}} + + <:content as |c|> +
    + +
      +
    • + Lorem ipsum dolor sit amet consectetur +
    • +
    • + Link to Google +
    • +
    • + +
    • +
    +
    + +
    +
    + + {{/each}} + {{/let}} + +
    + + {{/if}} + + {{/each}} + {{/let}} + + {{#if (eq interaction "soft")}} + + {{/if}} + + {{/each}} + {{/let}} + + + + In combination with @isOpen + + {{#let (array false true) as |booleans|}} + {{#each booleans as |isOpen|}} + + {{#let (array "none" "soft" "click") as |interactionVariants|}} + {{#each interactionVariants as |interaction|}} + + + <:toggle> +
    Toggle
    + + <:content> + + +
    +
    + {{/each}} + {{/let}} +
    + {{/each}} + {{/let}} + +
    \ No newline at end of file From bc164434054753857db707dbf7369f08843c638e Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Tue, 9 Apr 2024 20:28:10 +0100 Subject: [PATCH 004/127] added integration tests for `PopoverPrimitive` --- .../hds/popover-primitive/index-test.js | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 showcase/tests/integration/components/hds/popover-primitive/index-test.js diff --git a/showcase/tests/integration/components/hds/popover-primitive/index-test.js b/showcase/tests/integration/components/hds/popover-primitive/index-test.js new file mode 100644 index 0000000000..2ffceca748 --- /dev/null +++ b/showcase/tests/integration/components/hds/popover-primitive/index-test.js @@ -0,0 +1,327 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, click, focus, blur, setupOnerror } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module( + 'Integration | Component | hds/popover-primitive/index', + function (hooks) { + setupRenderingTest(hooks); + + test('it should render the component with a CSS class that matches the component name', async function (assert) { + await render(hbs``); + assert.dom('#test-popover-primitive').hasClass('hds-popover-primitive'); + }); + + // BASE ELEMENTS + CONTENT VISIBILITY + IS-OPEN + + test('it should not render the "arrow" by default', async function (assert) { + await render(hbs` + + `); + assert.dom('.hds-popover-primitive__arrow').doesNotExist(); + }); + test('it should render the "arrow" element if `@hasArrow` is `true`', async function (assert) { + await render(hbs` + + `); + assert.dom('.hds-popover-primitive__arrow').exists(); + }); + + test('it should render the elements yielded to the :toggle and :content slots and toggle their visibility correctly', async function (assert) { + await render(hbs` + + <:toggle> +
    Toggle
    + + <:content> +
    Content
    + +
    + `); + assert.dom('.hds-popover-primitive__toggle').exists(); + assert.dom('#test-popover-primitive-toggle').exists(); + assert.dom('.hds-popover-primitive__arrow').exists().isNotVisible(); + assert.dom('.hds-popover-primitive__content').exists().isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + + // toggle the visibility + await click('button.hds-popover-primitive__toggle'); + + assert.dom('.hds-popover-primitive__arrow').isVisible(); + assert.dom('.hds-popover-primitive__content').isVisible(); + assert.dom('#test-popover-primitive-content').exists().isVisible(); + }); + + test('the arrow and the content should be rendered and visible if `@isOpen` is `true`', async function (assert) { + await render(hbs` + + <:toggle>Toggle + <:content>Content + + `); + assert.dom('.hds-popover-primitive__arrow').isVisible(); + assert.dom('.hds-popover-primitive__content').isVisible(); + // we use the Popover API selector to do an extra check + assert.dom('.hds-popover-primitive__content:popover-open').exists(); + }); + + // DISPLAY + + test('it should render the container and toggle as inline by default', async function (assert) { + await render(hbs` + + `); + assert.dom('.hds-popover-primitive--is-inline').exists(); + assert.dom('.hds-popover-primitive__toggle--is-inline').exists(); + }); + test('it should render the container and toggle as block if `@isInline` is `false`', async function (assert) { + await render(hbs` + + `); + assert.dom('.hds-popover-primitive--is-block').exists(); + assert.dom('.hds-popover-primitive__toggle--is-block').exists(); + }); + + // CONTAINS INTERACTIVE ELEMENTS + + test('it should render the toggle container as ` + + <:content>Content + + `); + assert.dom('div.hds-popover-primitive__toggle').exists(); + assert.dom('div.hds-popover-primitive__toggle > button').exists(); + }); + + // INTERACTIONS + + test('it should toggle the content visibility on focus in/out', async function (assert) { + await render(hbs` + + <:toggle>Toggle + <:content> +
    Content
    + +
    + `); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + // focus the toggle to show the content + await focus('button.hds-popover-primitive__toggle'); + // now it should be visible + assert.dom('.hds-popover-primitive__content').isVisible(); + assert.dom('#test-popover-primitive-content').exists().isVisible(); + // extra test to check that the the content goes on the top layer + assert.strictEqual( + document.querySelectorAll('[popover]:popover-open').length, + 1 + ); + // unfocus the toggle to hide the content + await blur('button.hds-popover-primitive__toggle'); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + }); + test('it should toggle the content visibility on click', async function (assert) { + await render(hbs` + + <:toggle>Toggle + <:content> +
    Content
    + +
    + `); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + // click the toggle to show the content + await click('button.hds-popover-primitive__toggle'); + // now it should be visible + assert.dom('.hds-popover-primitive__content').isVisible(); + assert.dom('#test-popover-primitive-content').exists().isVisible(); + // click again the toggle to hide the content + await click('button.hds-popover-primitive__toggle'); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + }); + + test('it should toggle the content visibility on focus in/out when containinig interactive elements', async function (assert) { + await render(hbs` + + <:toggle> + + + <:content> +
    Content
    + +
    + `); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + // focus the toggle to show the content + await focus('.hds-popover-primitive__toggle > button'); + // now it should be visible + assert.dom('#test-popover-primitive-content').isVisible(); + assert.dom('#test-popover-primitive-content').exists().isVisible(); + // unfocus the toggle to hide the content + await blur('.hds-popover-primitive__toggle > button'); + // it's hidden when closed + assert.dom('.hds-popover-primitive__content').isNotVisible(); + assert.dom('#test-popover-primitive-content').doesNotExist(); + }); + + // CALLBACKS + + test('it should invoke the `onOpen/onClose` callbacks', async function (assert) { + let status; + this.set('onOpen', () => (status = 'opened')); + this.set('onClose', () => (status = 'closed')); + await render(hbs` + + <:toggle> +
    Toggle
    + + <:content> +
    Content
    + +
    + `); + // toggle the visibility + await click('button.hds-popover-primitive__toggle'); + assert.strictEqual(status, 'opened'); + // toggle it again + await click('button.hds-popover-primitive__toggle'); + assert.strictEqual(status, 'closed'); + }); + + // POPOVER OPTIONS + + // notice: since these options are forwarded to the `hds-anchored-position` modifier and there are specific tests for it, we're not going to test them here + // plus, since the content elements are moved to the top layer, thery're not scaled like the `ember-testing` container, all the sizes/positions are out of sync + + // POPOVER API (HTML ATTRIBUTES) + + test('the toggle does not have a `popovertarget` attribute by default', async function (assert) { + await render(hbs``); + assert + .dom('.hds-popover-primitive__toggle') + .doesNotHaveAttribute('popovertarget'); + }); + test('the toggle has a `popovertarget` attribute if `@enableClickEvents` is `true`', async function (assert) { + await render(hbs``); + // the target ID is dynamically generated + let target = this.element.querySelector( + '.hds-popover-primitive__content' + ); + let targetId = target.id; + assert + .dom('.hds-popover-primitive__toggle') + .hasAttribute('popovertarget', targetId); + }); + test('the content has a `popover` attribute by default', async function (assert) { + await render(hbs``); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('popover', 'auto'); + }); + test('the content has a `popover` attribute set to `manual` if `@isOpen` is `true` and it reverts to `auto` after "soft" interacting with it', async function (assert) { + await render( + hbs`` + ); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('popover', 'manual'); + await focus('.hds-popover-primitive__toggle'); + // this will change back the `popover` attribute to `auto` + await blur('.hds-popover-primitive__toggle'); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('popover', 'auto'); + }); + test('the content has a `popover` attribute set to `manual` if `@isOpen` is `true` and it reverts to `auto` after "click" interacting with it', async function (assert) { + await render( + hbs`` + ); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('popover', 'manual'); + // this will change back the `popover` attribute to `auto` + await click('.hds-popover-primitive__toggle'); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('popover', 'auto'); + }); + + // A11Y + + test('it displays the correct aria and role attributes', async function (assert) { + await render(hbs` + + `); + // the target ID is dynamically generated + let target = this.element.querySelector( + '.hds-popover-primitive__content' + ); + let targetId = target.id; + assert + .dom('.hds-popover-primitive__toggle') + .hasAria('controls', targetId); + assert.dom('.hds-popover-primitive__toggle').hasAria('label', 'test123'); + assert.dom('.hds-popover-primitive__toggle').hasAria('expanded', 'false'); + await click('.hds-popover-primitive__toggle'); + assert.dom('.hds-popover-primitive__toggle').hasAria('expanded', 'true'); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('role', 'tooltip'); + assert + .dom('.hds-popover-primitive__content') + .hasAttribute('tabindex', '-1'); + }); + + // ASSERTIONS + + test('it should throw an assertion if `@enableClickEvents` is `true` and the toggle contains interactive elements', async function (assert) { + const errorMessage = + 'Hds::PopoverPrimitive - You have assigned `onClick` events to the "toggle" element, but it contains interactive elements: this may result in unexpected behaviours or non accessible code'; + assert.expect(2); + setupOnerror(function (error) { + assert.strictEqual(error.message, `Assertion Failed: ${errorMessage}`); + }); + await render(hbs` + + <:toggle> + + + <:content>Content + + `); + assert.throws(function () { + throw new Error(errorMessage); + }); + }); + } +); From 90004859fc5afee24c2e65b3aa67c15f864a5896 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Thu, 11 Apr 2024 14:11:43 +0100 Subject: [PATCH 005/127] generalized autoscroll to `Shw::Autoscrollable` showcase component so it can be reused --- .../components/shw/autoscrollable/index.hbs | 8 ++++ .../components/shw/autoscrollable/index.js | 38 +++++++++++++++++++ .../utilities/popover-primitive.js | 29 -------------- showcase/app/styles/app.scss | 1 + .../showcase-components/autoscrollable.scss | 17 +++++++++ .../showcase-pages/popover-primitive.scss | 17 +-------- .../templates/utilities/popover-primitive.hbs | 6 +-- 7 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 showcase/app/components/shw/autoscrollable/index.hbs create mode 100644 showcase/app/components/shw/autoscrollable/index.js create mode 100644 showcase/app/styles/showcase-components/autoscrollable.scss diff --git a/showcase/app/components/shw/autoscrollable/index.hbs b/showcase/app/components/shw/autoscrollable/index.hbs new file mode 100644 index 0000000000..e81e599926 --- /dev/null +++ b/showcase/app/components/shw/autoscrollable/index.hbs @@ -0,0 +1,8 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} + +
    + {{yield}} +
    \ No newline at end of file diff --git a/showcase/app/components/shw/autoscrollable/index.js b/showcase/app/components/shw/autoscrollable/index.js new file mode 100644 index 0000000000..d210902576 --- /dev/null +++ b/showcase/app/components/shw/autoscrollable/index.js @@ -0,0 +1,38 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Component from '@glimmer/component'; +import { modifier } from 'ember-modifier'; +import { scheduleOnce } from '@ember/runloop'; + +function centerScrollableArea({ + element, + direction, + horizontalShift, + verticalShift, +}) { + if (direction === 'both' || direction === 'x') { + element.scrollLeft = + horizontalShift + (element.scrollWidth - element.offsetWidth) / 2; + } + if (direction === 'both' || direction === 'y') { + element.scrollTop = + verticalShift + (element.scrollHeight - element.offsetHeight) / 2; + } +} + +export default class AutoscrollableIndexComponent extends Component { + autoscroll = modifier( + (element) => { + scheduleOnce('afterRender', this, centerScrollableArea, { + element: element, + direction: this.args.direction ?? 'both', + horizontalShift: this.args.horizontalShift ?? 0, + verticalShift: this.args.verticalShift ?? 0, + }); + }, + { eager: false } + ); +} diff --git a/showcase/app/controllers/utilities/popover-primitive.js b/showcase/app/controllers/utilities/popover-primitive.js index 877ed613ef..f8cdf0eb02 100644 --- a/showcase/app/controllers/utilities/popover-primitive.js +++ b/showcase/app/controllers/utilities/popover-primitive.js @@ -5,37 +5,8 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; -import { inject as service } from '@ember/service'; -import { scheduleOnce } from '@ember/runloop'; - -function centerScrollableArea() { - document.querySelectorAll('[center-scrollable-area]').forEach((element) => { - const direction = - element.attributes['center-scrollable-area'].value ?? 'both'; - if (direction === 'both' || direction === 'x') { - element.scrollLeft = (element.scrollWidth - element.offsetWidth) / 2; - } - if (direction === 'both' || direction === 'y') { - element.scrollTop = - 20 + (element.scrollHeight - element.offsetHeight) / 2; - } - }); -} export default class PopoverPrimitiveController extends Controller { - @service router; - - constructor() { - super(...arguments); - this.router.on('routeDidChange', this, 'routeDidChange'); - } - - routeDidChange() { - if (this.router.currentRoute.name === 'utilities.popover-primitive') { - scheduleOnce('afterRender', this, centerScrollableArea); - } - } - @action noop() {} diff --git a/showcase/app/styles/app.scss b/showcase/app/styles/app.scss index 8fc1b44e15..d867fe43d2 100644 --- a/showcase/app/styles/app.scss +++ b/showcase/app/styles/app.scss @@ -16,6 +16,7 @@ // local components +@import "./showcase-components/autoscrollable"; @import "./showcase-components/divider"; @import "./showcase-components/flex"; @import "./showcase-components/grid"; diff --git a/showcase/app/styles/showcase-components/autoscrollable.scss b/showcase/app/styles/showcase-components/autoscrollable.scss new file mode 100644 index 0000000000..28eeb8cd64 --- /dev/null +++ b/showcase/app/styles/showcase-components/autoscrollable.scss @@ -0,0 +1,17 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +.shw-autoscrollable__container { + position: relative; + display: grid; + align-items: center; + justify-items: center; + width: 100%; + aspect-ratio: 1; + overflow-x: auto; + overflow-y: auto; + border: 1px dotted #d3d3d3; + overscroll-behavior: contain; +} diff --git a/showcase/app/styles/showcase-pages/popover-primitive.scss b/showcase/app/styles/showcase-pages/popover-primitive.scss index 23f16b3900..a5c55d1c5b 100644 --- a/showcase/app/styles/showcase-pages/popover-primitive.scss +++ b/showcase/app/styles/showcase-pages/popover-primitive.scss @@ -62,22 +62,7 @@ body.utilities-popover-primitive { } } - .shw-utilities-popover-primitive-collision-detection-container { - position: relative; - display: grid; - align-items: center; - justify-items: center; - width: 100%; - min-height: 200px; - aspect-ratio: 1; - overflow-x: auto; - overflow-y: auto; - border: 1px dotted #d3d3d3; - overscroll-behavior: contain; - - } - - .shw-utilities-popover-primitive-collision-detection-content { + .shw-utilities-popover-primitive-collision-detection-wrapper { display: flex; align-items: center; justify-content: center; diff --git a/showcase/app/templates/utilities/popover-primitive.hbs b/showcase/app/templates/utilities/popover-primitive.hbs index f56cb7b367..3a3a288d7d 100644 --- a/showcase/app/templates/utilities/popover-primitive.hbs +++ b/showcase/app/templates/utilities/popover-primitive.hbs @@ -342,8 +342,8 @@ {{#let (array false true "flip" "shift") as |detections|}} {{#each detections as |detection|}} -
    + +
    -
    +
    {{/each}} {{/let}} From 58bd98edc8f3ac6ea2eec2eddce2f796869b573f Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Mon, 15 Apr 2024 14:34:00 +0100 Subject: [PATCH 006/127] =?UTF-8?q?changed=20default=20layout=20to=20?= =?UTF-8?q?=E2=80=9Cblock=E2=80=9D=20instead=20of=20=E2=80=9Cinline?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/components/hds/popover-primitive/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/components/hds/popover-primitive/index.js b/packages/components/src/components/hds/popover-primitive/index.js index 9bdb07d9e0..25cd3d740b 100644 --- a/packages/components/src/components/hds/popover-primitive/index.js +++ b/packages/components/src/components/hds/popover-primitive/index.js @@ -205,7 +205,7 @@ export default class HdsPopoverPrimitiveComponent extends Component { * @description sets display for the container and toggle elements */ get isInline() { - let { isInline = true } = this.args; + let { isInline = false } = this.args; return isInline; } From 384b7972115e18560a5d02e45e7bef648b064ecf Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Mon, 15 Apr 2024 14:35:09 +0100 Subject: [PATCH 007/127] changed `eager` parameter for the modifiers from `false` to `true` https://github.com/ember-modifier/ember-modifier/blob/main/MIGRATIONS.md#when-is-this-a-breaking-change --- .../src/components/hds/popover-primitive/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/src/components/hds/popover-primitive/index.js b/packages/components/src/components/hds/popover-primitive/index.js index 25cd3d740b..259dbc04ac 100644 --- a/packages/components/src/components/hds/popover-primitive/index.js +++ b/packages/components/src/components/hds/popover-primitive/index.js @@ -82,7 +82,7 @@ export default class HdsPopoverPrimitiveComponent extends Component { // we always want the focusOut event registerEvent(this.containerElement, ['focusout', this.onFocusOut]); }, - { eager: false } + { eager: true } ); setupPrimitiveToggle = modifier( @@ -121,14 +121,14 @@ export default class HdsPopoverPrimitiveComponent extends Component { } } }, - { eager: false } + { eager: true } ); setupPrimitiveArrow = modifier( (element) => { this.arrowElement = element; }, - { eager: false } + { eager: true } ); setupPrimitivePopover = modifier( @@ -168,7 +168,7 @@ export default class HdsPopoverPrimitiveComponent extends Component { ); }); }, - { eager: false } + { eager: true } ); get popoverOptions() { From 466bf0d72ecfa1c85d26d87bec6748b2400a029d Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Mon, 15 Apr 2024 14:35:14 +0100 Subject: [PATCH 008/127] updated showcase --- .../templates/utilities/popover-primitive.hbs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/showcase/app/templates/utilities/popover-primitive.hbs b/showcase/app/templates/utilities/popover-primitive.hbs index 3a3a288d7d..ffc1fe4973 100644 --- a/showcase/app/templates/utilities/popover-primitive.hbs +++ b/showcase/app/templates/utilities/popover-primitive.hbs @@ -51,7 +51,7 @@
    - + <:toggle> @@ -94,6 +94,7 @@ +
    <:toggle> @@ -112,6 +113,7 @@ +
    <:toggle> @@ -135,15 +137,17 @@ - Display + Options + + Display - +

    Lorem ipsum dolor sit amet consectetur <:toggle> - + <:content> @@ -152,12 +156,12 @@ adipisicing elit. Doloremque blanditiis sapiente iste beatae voluptates voluptatum.

    - +

    Lorem ipsum dolor sit amet consectetur - + <:toggle> - + <:content> @@ -183,7 +187,7 @@

    Lorem ipsum dolor sit amet consectetur - + <:toggle> toggle @@ -196,9 +200,7 @@ - - - Options + Placement @@ -348,6 +350,8 @@ @popoverOptions={{(hash offsetOptions=16 enableCollisionDetection=detection)}} @hasArrow={{true}} @isOpen={{true}} + @enableSoftEvents={{false}} + @enableClickEvents={{true}} > <:toggle>

    Toggle
    From 6a45f13628fcb5a8e9c9438d0ffa38d9c6046bf5 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Mon, 15 Apr 2024 17:17:36 +0100 Subject: [PATCH 009/127] fixed integration tests for `block` (new default) vs `inline` --- .../components/hds/popover-primitive/index-test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/showcase/tests/integration/components/hds/popover-primitive/index-test.js b/showcase/tests/integration/components/hds/popover-primitive/index-test.js index 2ffceca748..6156f636ec 100644 --- a/showcase/tests/integration/components/hds/popover-primitive/index-test.js +++ b/showcase/tests/integration/components/hds/popover-primitive/index-test.js @@ -73,19 +73,19 @@ module( // DISPLAY - test('it should render the container and toggle as inline by default', async function (assert) { + test('it should render the container and toggle as block by default', async function (assert) { await render(hbs` `); - assert.dom('.hds-popover-primitive--is-inline').exists(); - assert.dom('.hds-popover-primitive__toggle--is-inline').exists(); + assert.dom('.hds-popover-primitive--is-block').exists(); + assert.dom('.hds-popover-primitive__toggle--is-block').exists(); }); - test('it should render the container and toggle as block if `@isInline` is `false`', async function (assert) { + test('it should render the container and toggle as inline if `@isInline` is `true`', async function (assert) { await render(hbs` - + `); - assert.dom('.hds-popover-primitive--is-block').exists(); - assert.dom('.hds-popover-primitive__toggle--is-block').exists(); + assert.dom('.hds-popover-primitive--is-inline').exists(); + assert.dom('.hds-popover-primitive__toggle--is-inline').exists(); }); // CONTAINS INTERACTIVE ELEMENTS From 0ebcec1ef484a33b288af43ebc4867c159082e61 Mon Sep 17 00:00:00 2001 From: Cristiano Rastelli Date: Tue, 16 Apr 2024 11:26:20 +0100 Subject: [PATCH 010/127] updated how `aria` atttributes are applied to the toggle and popover (plus other accessibility tweaks) --- .../hds/popover-primitive/index.hbs | 18 ++++++++++-- .../components/hds/popover-primitive/index.js | 29 ++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/components/src/components/hds/popover-primitive/index.hbs b/packages/components/src/components/hds/popover-primitive/index.hbs index 6a3b918399..d3c022beb1 100644 --- a/packages/components/src/components/hds/popover-primitive/index.hbs +++ b/packages/components/src/components/hds/popover-primitive/index.hbs @@ -5,11 +5,25 @@ }}
    {{#let (element (if this.containsInteractive "div" "button")) as |Tag|}} - + {{yield (hash popoverElement=this.popoverElement isOpen=this.isOpen) to="toggle"}} {{/let}} -