From de89a75c37f0fa069e671f04aaef1abd9e8541b2 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 4 Dec 2024 14:50:37 -0500 Subject: [PATCH 01/42] feat(dialog): use native `` element Related to #1865. --- elements/rh-dialog/demo/rh-dialog.html | 2 +- elements/rh-dialog/docs/40-accessibility.md | 18 +- elements/rh-dialog/rh-dialog.css | 199 +++++++++++--------- elements/rh-dialog/rh-dialog.ts | 192 ++++++++++++------- 4 files changed, 255 insertions(+), 156 deletions(-) diff --git a/elements/rh-dialog/demo/rh-dialog.html b/elements/rh-dialog/demo/rh-dialog.html index 54dc315a9d..c4d5fd47db 100644 --- a/elements/rh-dialog/demo/rh-dialog.html +++ b/elements/rh-dialog/demo/rh-dialog.html @@ -6,7 +6,7 @@

Modal dialog with a header

aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- + Learn more diff --git a/elements/rh-dialog/docs/40-accessibility.md b/elements/rh-dialog/docs/40-accessibility.md index 5b023d234e..655b5de9c6 100644 --- a/elements/rh-dialog/docs/40-accessibility.md +++ b/elements/rh-dialog/docs/40-accessibility.md @@ -63,9 +63,25 @@ Only the close button and any interactive elements are selectable. ### Backdrop -A dialog will not close by users clicking or tapping the backdrop or outside of the container. +A dialog will close by users clicking or tapping the backdrop or outside of the container. +## Accessible labels + +Each dialog needs an accessible name. If a dialog has a heading tag in the `header` or default slot, this component will automatically apply an appropriate ID to the heading tag and an `aria-labelledby` attribute to the ``. + +Users can optionally provide an `accessible-label` attribute which overrides the built-in `aria-labelledby` functionality: + +```html + + ... + +``` + +The `accessible-label` attribute assigns an `aria-label` to the `` element. + +If neither an `accessible-label` nor any headings exist, the `aria-label` on the `` will default to the text of the dialog's trigger. + ## Additional guidelines diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index f12ce32234..8448f39bb3 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -1,56 +1,60 @@ :host { + --rh-dialog-backdrop-background-color: rgba(3, 3, 3, 0.62); + --rh-dialog-overlay-background-color: transparent; + display: block; position: relative; - - --_spacer-align-top: var(--rh-space-md, 8px); - --_height-offset: min(var(--_spacer-align-top), var(--rh-space-3xl, 48px)); } [hidden] { display: none !important; } -section { - display: flex; - position: fixed; - height: 100%; - width: 100%; - top: 0; - left: 0; - align-items: center; - justify-content: center; - z-index: 500; -} - -#container { - position: relative; - max-height: inherit; +.visually-hidden { + block-size: 1px; + border: 0; + clip: rect(0, 0, 0, 0); + inline-size: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; } +/* NOTE: @deprecated, use `::backdrop` instead. Remove for RHDS v3. */ [part='overlay'] { + background-color: + var(--rh-dialog-overlay-background-color, + var(--rh-dialog-backdrop-background-color)); + block-size: 100%; + inset-block-start: 0; + inset-inline-start: 0; position: fixed; - height: 100%; - width: 100%; - top: 0; - left: 0; - background-color: rgba(3, 3, 3, 0.62); + inline-size: 100%; +} + +::backdrop { + background-color: var(--rh-dialog-backdrop-background-color); } [part='dialog'] { - position: relative; - margin: 0 auto; - width: var(--_box-width, calc(100% - var(--rh-space-2xl, 32px))); - max-height: var(--_box-max-height, calc(100% - var(--rh-space-3xl, 48px))); - box-shadow: - 0 1rem 2rem 0 rgba(3, 3, 3, 0.16), - 0 0 0.5rem 0 rgba(3, 3, 3, 0.1); - padding: var(--rh-space-xl, 24px); - margin-inline: var(--rh-space-lg, 16px); background-color: var(--rh-color-surface-lightest, #ffffff); - max-width: min(90%, 1140px); border-radius: var(--rh-border-radius-default, 3px); + border: 0; + box-shadow: var(--rh-box-shadow-xl, 0 8px 24px 3px rgba(21, 21, 21, 0.35)); + box-sizing: border-box; color: var(--rh-color-text-primary-on-light, #151515); font-family: inherit; + inline-size: 100%; + margin-inline: auto; + max-block-size: var(--_box-max-block-size, calc(100vh - var(--rh-space-3xl, 48px))); + max-inline-size: var(--_box-width, min(90%, 1140px)); + overflow-y: auto; + overscroll-behavior: contain; + padding: var(--rh-space-xl, 24px); + padding-block-end: var(--rh-space-2xl, 32px); /* NOTE: don't cut off box-shadow at the bottom */ + position: relative; } :host([width]) [part='dialog'], @@ -73,122 +77,137 @@ section { --_box-width: 70rem; } -[part='content'] { - overflow-y: auto; - overscroll-behavior: contain; - max-height: var(--_box-max-height, calc(100vh - var(--rh-space-3xl, 48px))); - box-sizing: border-box; - border-radius: var(--rh-border-radius-default, 3px); +[part='header'] { + background-color: var(--rh-color-surface-lightest, #ffffff); + padding-block-end: var(--rh-space-sm, 6px); + position: sticky; + top: calc(-1 * var(--rh-space-xl, 24px)); } -[part='content'] ::slotted([slot='header']) { - margin-top: 0 !important; +[part='header'][hidden] + [part='body'] { + max-width: calc(100% - var(--rh-space-xl, 24px)); } -header { - position: sticky; - top: 0; - background-color: var(--rh-color-surface-lightest, #ffffff); +@media (min-width: 1200px) { + [part='dialog'] { + padding: var(--rh-space-2xl, 32px); + } + + [part='header'] { + top: calc(-1 * var(--rh-space-2xl, 32px)); + } + + [part='header'][hidden] + [part='body'] { + max-width: calc(100% - var(--rh-space-2xl, 32px)); + } +} + +[part='content'] ::slotted([slot='header']) { + margin-top: 0 !important; } -header ::slotted(:is(h1,h2,h3,h4,h5,h6)[slot='header']) { +[part='header'] ::slotted(:is(h1,h2,h3,h4,h5,h6)[slot='header']) { + font-family: var(--rh-font-family-heading, RedHatDisplay, 'Red Hat Display', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Malayalam', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans Thai', Helvetica, Arial, sans-serif); font-size: var(--rh-font-size-heading-sm, 1.5rem); font-weight: var(--rh-font-weight-body-text-regular, 400); - font-family: 'Red Hat Display', RedHatDisplay, Overpass, Helvetica, sans-serif; } [part='close-button'] { color: var(--rh-color-icon-subtle, #707070); - background-color: transparent; - border: none; - margin: 0; - padding: 0; - text-align: left; - position: absolute; cursor: pointer; - line-height: 24px; - padding-block: 0.375rem; - padding-inline: var(--rh-space-lg, 16px); - top: 0; - right: calc(var(--rh-space-xl, 24px) / -3); + display: block; + inset-block-start: var(--rh-length-xs, 4px); + inset-inline-end: var(--rh-length-xs, 4px); + position: absolute; + z-index: 500; } -[part='close-button'] > svg { - font-size: 16px; - width: var(--rh-space-lg, 16px); - aspect-ratio: 1/1; +[part='close-button']::part(button) { + padding: var(--rh-space-xl, 24px); } -[part='close-button']:is(:hover, :focus-within, :focus-visible) svg:is(svg, :hover) { - fill: var(--rh-color-icon-secondary-on-light, #151515); +@media (min-width: 1200px) { + [part='close-button']::part(button) { + padding: var(--rh-space-2xl, 32px); + } } :host([position='top']) #dialog { - align-self: start; - margin-block: var(--rh-space-2xl, 32px); - margin-inline: var(--rh-space-lg, 16px); - width: 100%; - max-width: calc(100% - min(var(--rh-space-2xl, 32px) * 2, var(--rh-space-2xl, 32px))); - max-height: calc(100% - var(--_height-offset) - var(--_spacer-align-top)); + margin-block-start: 0; + max-inline-size: none; + inline-size: 100%; } -footer { - display: flex; +:host([position='top']) #content { + margin-inline: 0; + max-inline-size: calc(100% - min(var(--rh-space-2xl, 32px) * 2, var(--rh-space-2xl, 32px))); +} + +[part='footer'] { align-items: center; + display: flex; gap: var(--rh-space-md, 8px); } #rhds-wrapper { - display: contents; - font-family: 'Red Hat Text', RedHatText, Overpass, Helvetica, sans-serif; - --offset: var(--rh-space-md, 8px); --offset-top: var(--offset); --offset-right: var(--offset); -} -:host([type='video']) { - --rh-dialog-close-button-color: var(--rh-color-icon-secondary-on-dark, #ffffff); + display: contents; + font-family: var(--rh-font-family-body-text, RedHatText, 'Red Hat Text', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Malayalam', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans Thai', Helvetica, Arial, sans-serif); } :host([type='video']) [part='close-button'] { - top: var(--offset-top); - right: var(--offset-right); - padding: var(--rh-space-sm, 6px); color: var(--rh-color-icon-secondary-on-dark, #ffffff); + inset-inline-end: var(--offset-right); + inset-block-start: var(--offset-top); +} + +:host([type='video']) #close-button::part(button) { + padding-inline: var(--rh-space-md, 8px); + padding-block: var(--rh-space-lg, 16px); +} + +:host([type='video']) #close-button::part(icon) { + filter: drop-shadow(1px 0 1px var(--rh-dialog-backdrop-background-color)); } :host([type='video']) [part='content'] { + background-color: var(--rh-color-surface-darkest, #151515); overflow: hidden; + padding: 0; } -:host([type='video'][open]) [part='overlay'] { +:host([type='video']) { --_gray-90-rgb: var(--rh-color-gray-90-rgb, 31 31 31); + --rh-dialog-backdrop-background-color: rgb(var(--_gray-90-rgb) / var(--rh-opacity-60, 60%)); - background-color: rgb(var(--_gray-90-rgb) / var(--rh-opacity-60, 60%)); + background-color: + var(--rh-dialog-overlay-background-color, + var(--rh-dialog-backdrop-background-color)); } :host([type='video'][open]) [part='dialog'] { --_aspect-ratio: var(--rh-dialog-video-aspect-ratio, 16/9); aspect-ratio: var(--_aspect-ratio); - max-width: min(90%, calc(90vh * var(--_aspect-ratio) + var(--offset-top))); + max-inline-size: min(90%, calc(90vh * var(--_aspect-ratio) + var(--offset-top))); padding: 0; - margin: 0; } :host([type='video']) #rhds-wrapper.mobile [part='close-button'] { --offset-right: var(--rh-space-sm, 6px); } -:host([type='video']) #container, :host([type='video']) [part='content'], :host([type='video']) ::slotted(:not([slot])) { aspect-ratio: var(--rh-dialog-video-aspect-ratio, 16/9); + max-inline-size: none; + inline-size: 100%; +} - /* account for a 1px descrepency between dialog and container aspect ratio */ - width: calc(100% + 1px); - position: absolute; +:host([type='video']) ::slotted(:not([slot])) { inset: 0; - max-height: none; + position: absolute; } diff --git a/elements/rh-dialog/rh-dialog.ts b/elements/rh-dialog/rh-dialog.ts index bb09ebd1d5..189c39d3fe 100644 --- a/elements/rh-dialog/rh-dialog.ts +++ b/elements/rh-dialog/rh-dialog.ts @@ -14,6 +14,7 @@ import { query } from 'lit/decorators/query.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import '@rhds/elements/rh-surface/rh-surface.js'; +import '@rhds/elements/rh-button/rh-button.js'; export class DialogCancelEvent extends Event { constructor() { @@ -60,11 +61,13 @@ async function pauseYoutube(iframe: HTMLIFrameElement) { * @cssprop {} --rh-dialog-video-aspect-ratio * @cssprop {} [--rh-dialog-close-button-color=var(--rh-color-icon-secondary-on-dark, #ffffff)] * Sets the dialog close button color. + * @cssprop {} [--rh-dialog-backdrop-background-color=rgba(3, 3, 3, 0.62)] + * Sets the background color for the native HTML dialog element's `backdrop` pseudo-element + * @cssprop {} [--rh-dialog-overlay-background-color=transparent] + * Deprecated. Sets the background color for the `#overlay` `
`. Use `--rh-dialog-backdrop-background-color` instead. */ @customElement('rh-dialog') export class RhDialog extends LitElement { - static readonly version = '{{version}}'; - static readonly styles = [styles]; protected static closeOnOutsideClick = true; @@ -80,11 +83,19 @@ export class RhDialog extends LitElement { */ @property({ reflect: true }) position?: 'top'; + /** + * Use `accessible-label="My custom label"` to add an `aria-label` to the `` element. + * Defaults to the name of the dialog trigger if no attribute is set and no headings exist in the modal. + * See Dialog's Accessibility page for more info. + */ + @property({ attribute: 'accessible-label' }) accessibleLabel?: string; + @property({ type: Boolean, reflect: true }) open = false; /** Optional ID of the trigger element */ @property() trigger?: string; + /** Use `type="video"` to embed a video player into a dialog. */ @property({ reflect: true }) type?: 'video'; /** @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/returnValue */ @@ -92,9 +103,13 @@ export class RhDialog extends LitElement { #screenSize = new ScreenSizeController(this); - @query('#overlay') private overlay?: HTMLElement | null; - @query('#dialog') private dialog?: HTMLElement | null; - @query('#close-button') private closeButton?: HTMLElement | null; + @query('#dialog') private dialog!: HTMLDialogElement; + @query('#content') private content!: HTMLElement; + @query('#close-button') private closeButton!: HTMLElement; + + get videoColorPalette() { + return this.type === 'video' ? 'dark' : 'lightest'; + } #headerId = getRandomId(); #triggerElement: HTMLElement | null = null; @@ -102,71 +117,65 @@ export class RhDialog extends LitElement { #body: Element[] = []; #headings: Element[] = []; #cancelling = false; + #lastTabbable: HTMLElement = this.closeButton; #slots = new SlotController(this, null, 'header', 'description', 'footer'); connectedCallback() { super.connectedCallback(); - this.addEventListener('keydown', this.onKeydown); + this.addEventListener('keydown', this.#onKeyDown); this.addEventListener('click', this.onClick); } + disconnectedCallback() { + super.disconnectedCallback(); + this.removeEventListener('keydown', this.#onKeyDown); + this.#triggerElement?.removeEventListener('click', this.onTriggerClick); + } + render() { const headerId = (this.#header || this.#headings.length) ? this.#headerId : undefined; - const headerLabel = this.#triggerElement ? this.#triggerElement.innerText : undefined; + const triggerLabel = this.#triggerElement ? this.#triggerElement.innerText : undefined; const hasHeader = this.#slots.hasSlotted('header'); const hasDescription = this.#slots.hasSlotted('description'); const hasFooter = this.#slots.hasSlotted('footer'); - const { mobile } = this.#screenSize; return html` -
-
-
+
`; } - disconnectedCallback() { - super.disconnectedCallback(); - this.removeEventListener('keydown', this.onKeydown); - this.#triggerElement?.removeEventListener('click', this.onTriggerClick); - } - @initializer() protected async _init() { await this.updateComplete; @@ -207,8 +216,8 @@ export class RhDialog extends LitElement { // This prevents background scroll document.body.style.overflow = 'hidden'; await this.updateComplete; - // Set the focus to the container - this.dialog?.focus(); + // 's automatically set focus to the first focusable element in the modal, + // no need to set it here. this.dispatchEvent(new DialogOpenEvent(this.#triggerElement)); } else { // Return scrollability @@ -218,10 +227,6 @@ export class RhDialog extends LitElement { await this.updateComplete; - if (this.#triggerElement) { - this.#triggerElement.focus(); - } - this.dispatchEvent(event); } } @@ -241,29 +246,73 @@ export class RhDialog extends LitElement { } @bound private onClick(event: MouseEvent) { - const { open, overlay, dialog } = this; + const { open, content } = this; if (open) { const path = event.composedPath(); const { closeOnOutsideClick } = this.constructor as typeof RhDialog; - if (closeOnOutsideClick && path.includes(overlay!) && !path.includes(dialog!)) { + if (closeOnOutsideClick && !path.includes(content!)) { event.preventDefault(); this.cancel(); } } } - @bound private onKeydown(event: KeyboardEvent) { + #trapFocus() { + // https://github.com/KittyGiraudel/focusable-selectors + // TODO: Add RHDS focusable elements (?) + const notInert = ':not([inert]):not([inert] *)'; + const notNegTabIndex = ':not([tabindex^="-"])'; + const notDisabled = ':not(:disabled)'; + const focusableSelectorList = [ + `a[href]${notInert}${notNegTabIndex}`, + `area[href]${notInert}${notNegTabIndex}`, + `input:not([type="hidden"]):not([type="radio"])${notInert}${notNegTabIndex}${notDisabled}`, + `input[type="radio"]${notInert}${notNegTabIndex}${notDisabled}`, + `select${notInert}${notNegTabIndex}${notDisabled}`, + `textarea${notInert}${notNegTabIndex}${notDisabled}`, + `button${notInert}${notNegTabIndex}${notDisabled}`, + `details${notInert} > summary:first-of-type${notNegTabIndex}`, + `details:not(:has(> summary))${notInert}${notNegTabIndex}`, + `iframe${notInert}${notNegTabIndex}`, + `audio[controls]${notInert}${notNegTabIndex}`, + `video[controls]${notInert}${notNegTabIndex}`, + `[contenteditable]${notInert}${notNegTabIndex}`, + `[tabindex]${notInert}${notNegTabIndex}`, + ]; + + const focusableSlottedElements = + this.querySelectorAll(focusableSelectorList.join(',')); + const hasLastElement = focusableSlottedElements.length > 0; + this.#lastTabbable = hasLastElement ? + focusableSlottedElements[focusableSlottedElements.length - 1] : this.closeButton; + } + + #handleTab(event: KeyboardEvent) { + // No focusable elements except close button: + if (this.#lastTabbable === this.closeButton) { + event.preventDefault(); + this.closeButton.focus(); + return; + } + // With focusable elements in dialog: + if (document.activeElement === this.#lastTabbable) { + event.preventDefault(); + this.closeButton.focus(); + } + } + + #handleShiftTab(event: KeyboardEvent) { + if (document.activeElement === this && this.shadowRoot?.activeElement === this.closeButton) { + event.preventDefault(); + this.#lastTabbable.focus(); + } + } + + #onKeyDown(event: KeyboardEvent) { switch (event.key) { - case 'Tab': - if (event.target === this.closeButton) { - event.preventDefault(); - this.dialog?.focus(); - } - return; case 'Escape': case 'Esc': - event.preventDefault(); this.cancel(); return; case 'Enter': @@ -272,11 +321,18 @@ export class RhDialog extends LitElement { this.showModal(); } return; + case 'Tab': + if (event.shiftKey) { + this.#handleShiftTab(event); + return; + } + this.#handleTab(event); } } private async cancel() { this.#cancelling = true; + this.close(); this.open = false; await this.updateComplete; this.#cancelling = false; @@ -294,7 +350,12 @@ export class RhDialog extends LitElement { * ``` */ @bound toggle() { - this.open = !this.open; + if (!this.open) { + this.showModal(); + this.open = true; + } else { + this.close(); + } } /** @@ -304,6 +365,8 @@ export class RhDialog extends LitElement { * ``` */ @bound show() { + this.dialog?.showModal(); + this.#trapFocus(); this.open = true; } @@ -324,6 +387,7 @@ export class RhDialog extends LitElement { this.returnValue = returnValue; } + this.dialog?.close(); this.open = false; } } From 4a5f0661284e9dfd3463b21ca2fa4c436a9cb59d Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 4 Dec 2024 14:51:36 -0500 Subject: [PATCH 02/42] chore(dialog): add changeset --- .changeset/loud-coins-shake.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .changeset/loud-coins-shake.md diff --git a/.changeset/loud-coins-shake.md b/.changeset/loud-coins-shake.md new file mode 100644 index 0000000000..888e2b3c14 --- /dev/null +++ b/.changeset/loud-coins-shake.md @@ -0,0 +1,21 @@ +--- +"@rhds/elements": minor +--- + +``: Dialog now uses the native HTML `` element internally. + +Note: `#overlay` is now deprecated in favor of a public variable on the CSS psuedo-element `::backdrop`: + +Before: + +```css +rh-dialog::part(overlay) { ... } +``` + +After: + +```css +rh-dialog { + --rh-dialog-backdrop-background-color: ghostwhite; +} +``` From 6832da62e9afd3e59db4d507781d777e62442848 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 11:33:05 -0500 Subject: [PATCH 03/42] fix(dialog): update color values to use new semantic tokens --- elements/rh-dialog/rh-dialog.css | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index 8448f39bb3..56424ca5f1 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -39,12 +39,12 @@ } [part='dialog'] { - background-color: var(--rh-color-surface-lightest, #ffffff); + background-color: var(--rh-color-surface); border-radius: var(--rh-border-radius-default, 3px); border: 0; box-shadow: var(--rh-box-shadow-xl, 0 8px 24px 3px rgba(21, 21, 21, 0.35)); box-sizing: border-box; - color: var(--rh-color-text-primary-on-light, #151515); + color: var(--rh-color-text-primary); font-family: inherit; inline-size: 100%; margin-inline: auto; @@ -78,7 +78,7 @@ } [part='header'] { - background-color: var(--rh-color-surface-lightest, #ffffff); + background-color: var(--rh-color-surface); padding-block-end: var(--rh-space-sm, 6px); position: sticky; top: calc(-1 * var(--rh-space-xl, 24px)); @@ -113,7 +113,7 @@ } [part='close-button'] { - color: var(--rh-color-icon-subtle, #707070); + color: var(--rh-color-icon-secondary); cursor: pointer; display: block; inset-block-start: var(--rh-length-xs, 4px); @@ -159,7 +159,6 @@ } :host([type='video']) [part='close-button'] { - color: var(--rh-color-icon-secondary-on-dark, #ffffff); inset-inline-end: var(--offset-right); inset-block-start: var(--offset-top); } @@ -174,7 +173,6 @@ } :host([type='video']) [part='content'] { - background-color: var(--rh-color-surface-darkest, #151515); overflow: hidden; padding: 0; } From 9e1b5524d7342ea1f7f7c39ec8377cb4df0cf4d2 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 12:51:06 -0500 Subject: [PATCH 04/42] fix(dialog): make surface wrap only dialog/modal --- elements/rh-dialog/rh-dialog.css | 9 +++-- elements/rh-dialog/rh-dialog.ts | 59 ++++++++++++++++---------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index 56424ca5f1..c909043e45 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -52,8 +52,7 @@ max-inline-size: var(--_box-width, min(90%, 1140px)); overflow-y: auto; overscroll-behavior: contain; - padding: var(--rh-space-xl, 24px); - padding-block-end: var(--rh-space-2xl, 32px); /* NOTE: don't cut off box-shadow at the bottom */ + padding: 0; position: relative; } @@ -77,6 +76,10 @@ --_box-width: 70rem; } +[part='content'] { + padding: var(--rh-space-xl, 24px); +} + [part='header'] { background-color: var(--rh-color-surface); padding-block-end: var(--rh-space-sm, 6px); @@ -89,7 +92,7 @@ } @media (min-width: 1200px) { - [part='dialog'] { + [part='content'] { padding: var(--rh-space-2xl, 32px); } diff --git a/elements/rh-dialog/rh-dialog.ts b/elements/rh-dialog/rh-dialog.ts index 189c39d3fe..be8e67bd31 100644 --- a/elements/rh-dialog/rh-dialog.ts +++ b/elements/rh-dialog/rh-dialog.ts @@ -107,10 +107,6 @@ export class RhDialog extends LitElement { @query('#content') private content!: HTMLElement; @query('#close-button') private closeButton!: HTMLElement; - get videoColorPalette() { - return this.type === 'video' ? 'dark' : 'lightest'; - } - #headerId = getRandomId(); #triggerElement: HTMLElement | null = null; #header: HTMLElement | null = null; @@ -141,16 +137,19 @@ export class RhDialog extends LitElement { const hasFooter = this.#slots.hasSlotted('footer'); const { mobile } = this.#screenSize; return html` - +
-
- -
+
+
+ + Close Dialog -
- -
- +
+
+ +
+ +
+
+
+ +
+
+
-
- -
-
- -
-
-
-
+
+ +
`; } @@ -216,8 +217,6 @@ export class RhDialog extends LitElement { // This prevents background scroll document.body.style.overflow = 'hidden'; await this.updateComplete; - // 's automatically set focus to the first focusable element in the modal, - // no need to set it here. this.dispatchEvent(new DialogOpenEvent(this.#triggerElement)); } else { // Return scrollability @@ -240,9 +239,11 @@ export class RhDialog extends LitElement { } } - @bound private onTriggerClick(event: MouseEvent) { + @bound private async onTriggerClick(event: MouseEvent) { event.preventDefault(); this.showModal(); + await this.updateComplete; + this.closeButton?.focus(); } @bound private onClick(event: MouseEvent) { From fb0dfda9499359f882776f779d10f19e7a9ef104 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 14:38:21 -0500 Subject: [PATCH 05/42] docs(dialog): update a11y docs around focus and focus order --- elements/rh-dialog/docs/40-accessibility.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/elements/rh-dialog/docs/40-accessibility.md b/elements/rh-dialog/docs/40-accessibility.md index 655b5de9c6..734edab738 100644 --- a/elements/rh-dialog/docs/40-accessibility.md +++ b/elements/rh-dialog/docs/40-accessibility.md @@ -37,17 +37,17 @@ A dialog can be opened by pressing `Enter` when the dialog trigger has focus. Wh ## Focus order +When a dialog opens, the close dialog button recieves focus by default. Users have the ability to tab through each focusable element in the dialog. When the last focusable element in the dialog is reached, focus returns to the close dialog button—effectively trapping focus inside the dialog. + +### Changing focus order depending on content + When a dialog opens, the element that should receive focus depends on the content and size of the modal. To help you decide where to place focus, follow these guidelines: -- If the dialog contains semantic elements like lists or tables that are necessary to perceive in order to better understand dialog content, place focus on a static element at the start of the content - - The element that receives focus in this way must have `tabindex=“-1”` - If the dialog includes an irreversible action like deleting data, place focus on the least destructive action - If the dialog includes actions that simply provide additional information like `OK` or `Continue` buttons, place focus on the action that is likely to be most frequently used -- If none of the above apply, place focus on the first focusable element -- If placing focus on an element causes the beginning of dialog content to scroll out of view, place focus on a static element at the top instead - - The element that receives focus in this way must have `tabindex=“-1”` - +- If none of the above apply, focus is automatically placed on the close dialog button +To move focus away from the close dialog button, listen for the [`open` event](https://ux.redhat.com/elements/dialog/code/#rh-dialog-apis) and move focus to the appropriate element in the lightdom via the [`focus()` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus). ## Touch targets From 3f8d621694e88a357453a679cff9b58c8338183d Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 14:39:21 -0500 Subject: [PATCH 06/42] fix(dialog): add `accessible-label` to the `no-headings.html` demo --- elements/rh-dialog/demo/no-headings.html | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/elements/rh-dialog/demo/no-headings.html b/elements/rh-dialog/demo/no-headings.html index 28b666d63d..06498e1008 100644 --- a/elements/rh-dialog/demo/no-headings.html +++ b/elements/rh-dialog/demo/no-headings.html @@ -1,11 +1,12 @@ Open - -

This modal doesn't have any headings. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco - laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui - officia deserunt mollit anim id est laborum.

- + +

This dialog uses the accessible-label attribute to define its aria-label. + If the dialog includes a heading, the heading automatically becomes its accessible name—as long as the + accessible-label attribute does not exist. If neither a heading nor the accessible-label + attribute are provided, the dialog's accessible name defaults to the text content of its trigger element. + Learn more about these attributes in the rh-dialog docs. +

+ Call-to-action
From 236f8c11d3aa2e5ed9d73a24be2df6917db66ef5 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 14:39:51 -0500 Subject: [PATCH 07/42] fix(dialog): update content in `no-header-content.html` demo --- elements/rh-dialog/demo/no-header-content.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elements/rh-dialog/demo/no-header-content.html b/elements/rh-dialog/demo/no-header-content.html index 120bf766ff..487e4197fe 100644 --- a/elements/rh-dialog/demo/no-header-content.html +++ b/elements/rh-dialog/demo/no-header-content.html @@ -1,7 +1,7 @@ Open -

A modal with no slotted header content

-

This has no header content

+

This dialog has no slotted header content

+

All this content exists in the default slot.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla From 74ee1a608a85ac471553f5feff24e4fb95563e4c Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 5 Dec 2024 17:05:41 -0500 Subject: [PATCH 08/42] fix(dialog): horizontally center dialogs with `variant`/`width` attributes --- elements/rh-dialog/rh-dialog.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index c909043e45..a831f1182a 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -56,11 +56,6 @@ position: relative; } -:host([width]) [part='dialog'], -:host([variant]) [part='dialog'] { - margin-inline: 0; -} - :host([width='small']) [part='dialog'], :host([variant='small']) [part='dialog'] { --_box-width: 35rem; From bf34916cb7fdb394855aa144ef9f7847996148ee Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 6 Dec 2024 14:17:36 -0500 Subject: [PATCH 09/42] fix(dialog): position top / modal width variants sizing --- elements/rh-dialog/rh-dialog.css | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index a831f1182a..ad8ff9ed08 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -131,14 +131,9 @@ } :host([position='top']) #dialog { - margin-block-start: 0; - max-inline-size: none; inline-size: 100%; -} - -:host([position='top']) #content { - margin-inline: 0; - max-inline-size: calc(100% - min(var(--rh-space-2xl, 32px) * 2, var(--rh-space-2xl, 32px))); + margin-block-start: var(--rh-space-2xl, 32px); + max-inline-size: var(--_box-width, calc(100% - var(--rh-space-2xl, 32px))); } [part='footer'] { From e8ef16ab71197cb478364d3caa539c529caa5b91 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 6 Dec 2024 14:18:24 -0500 Subject: [PATCH 10/42] fix(dialog): add `rh-*` elements to list of focusable selectors. --- elements/rh-dialog/rh-dialog.ts | 47 ++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/elements/rh-dialog/rh-dialog.ts b/elements/rh-dialog/rh-dialog.ts index be8e67bd31..2f67dab2b8 100644 --- a/elements/rh-dialog/rh-dialog.ts +++ b/elements/rh-dialog/rh-dialog.ts @@ -261,7 +261,6 @@ export class RhDialog extends LitElement { #trapFocus() { // https://github.com/KittyGiraudel/focusable-selectors - // TODO: Add RHDS focusable elements (?) const notInert = ':not([inert]):not([inert] *)'; const notNegTabIndex = ':not([tabindex^="-"])'; const notDisabled = ':not(:disabled)'; @@ -280,6 +279,52 @@ export class RhDialog extends LitElement { `video[controls]${notInert}${notNegTabIndex}`, `[contenteditable]${notInert}${notNegTabIndex}`, `[tabindex]${notInert}${notNegTabIndex}`, + `rh-accordion${notInert}${notNegTabIndex}${notDisabled}`, + `rh-accordion-header${notInert}${notNegTabIndex}${notDisabled}`, + `rh-accordion-panel${notInert}${notNegTabIndex}${notDisabled}`, + `rh-alert${notInert}${notNegTabIndex}${notDisabled}`, + `rh-audio-player${notInert}${notNegTabIndex}${notDisabled}`, + `rh-avatar${notInert}${notNegTabIndex}${notDisabled}`, + `rh-back-to-top${notInert}${notNegTabIndex}${notDisabled}`, + // rh-badge not interactive + // rh-blockquote not interactive + `rh-breadcrumb${notInert}${notNegTabIndex}${notDisabled}`, + `rh-button${notInert}${notNegTabIndex}${notDisabled}`, + `rh-card${notInert}${notNegTabIndex}${notDisabled}`, + `rh-code-block${notInert}${notNegTabIndex}${notDisabled}`, + `rh-cta${notInert}${notNegTabIndex}${notDisabled}`, + `rh-dialog${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer-block${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer-copyright${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer-links${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer-social-link${notInert}${notNegTabIndex}${notDisabled}`, + `rh-footer-universal${notInert}${notNegTabIndex}${notDisabled}`, + // rh-health-index not interactive + // rh-icon not interactive + `rh-navigation-secondary${notInert}${notNegTabIndex}${notDisabled}`, + `rh-navigation-secondary-dropdown${notInert}${notNegTabIndex}${notDisabled}`, + `rh-navigation-secondary-menu-section${notInert}${notNegTabIndex}${notDisabled}`, + `rh-navigation-secondary-menu${notInert}${notNegTabIndex}${notDisabled}`, + `rh-navigation-secondary-overlay${notInert}${notNegTabIndex}${notDisabled}`, + `rh-pagination${notInert}${notNegTabIndex}${notDisabled}`, + `rh-site-status${notInert}${notNegTabIndex}${notDisabled}`, + `rh-skip-link${notInert}${notNegTabIndex}${notDisabled}`, + // rh-spinner not interactive + // rh-stat not interactive + `rh-subnav${notInert}${notNegTabIndex}${notDisabled}`, + // rh-surface not interactive + `rh-switch${notInert}${notNegTabIndex}${notDisabled}`, + `rh-table${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tabs${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tab${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tab-panel${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tag${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tile${notInert}${notNegTabIndex}${notDisabled}`, + `rh-tile-group${notInert}${notNegTabIndex}${notDisabled}`, + // rh-timestamp not interactive + `rh-tooltip${notInert}${notNegTabIndex}${notDisabled}`, + `rh-video-embed${notInert}${notNegTabIndex}${notDisabled}`, ]; const focusableSlottedElements = From 4eff705aab4dfefa2b74df19cb74cb52ea0a7617 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 9 Dec 2024 16:53:47 -0500 Subject: [PATCH 11/42] fix(dialog): update close button spacing/height/width. --- elements/rh-dialog/rh-dialog.css | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index ad8ff9ed08..3c0a4fa30e 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -121,13 +121,8 @@ } [part='close-button']::part(button) { - padding: var(--rh-space-xl, 24px); -} - -@media (min-width: 1200px) { - [part='close-button']::part(button) { - padding: var(--rh-space-2xl, 32px); - } + block-size: var(--rh-length-xl, 24px); + inline-size: var(--rh-length-xl, 24px); } :host([position='top']) #dialog { @@ -156,11 +151,6 @@ inset-block-start: var(--offset-top); } -:host([type='video']) #close-button::part(button) { - padding-inline: var(--rh-space-md, 8px); - padding-block: var(--rh-space-lg, 16px); -} - :host([type='video']) #close-button::part(icon) { filter: drop-shadow(1px 0 1px var(--rh-dialog-backdrop-background-color)); } From accb3f6bb4036a54b04fd8fc346ec16f43b6cc18 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 14:03:08 -0500 Subject: [PATCH 12/42] fix(dialog): re-add `--rh-dialog-close-button-color` --- elements/rh-dialog/rh-dialog.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index 3c0a4fa30e..7d391b0551 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -111,7 +111,7 @@ } [part='close-button'] { - color: var(--rh-color-icon-secondary); + color: var(--rh-dialog-close-button-color, var(--rh-color-icon-secondary)); cursor: pointer; display: block; inset-block-start: var(--rh-length-xs, 4px); From 4ded108caad7e9ae93438e6f17931ad63ef7dd0d Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 16:51:25 -0500 Subject: [PATCH 13/42] fix(dialog): decrease close button size, fix video modal close button color --- elements/rh-dialog/rh-dialog.css | 99 +++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index 7d391b0551..1d60f296f8 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -22,6 +22,15 @@ white-space: nowrap; } +#rhds-wrapper { + --offset: var(--rh-space-xl, 24px); + --offset-top: var(--offset); + --offset-right: var(--offset); + + display: contents; + font-family: var(--rh-font-family-body-text, RedHatText, 'Red Hat Text', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Malayalam', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans Thai', Helvetica, Arial, sans-serif); +} + /* NOTE: @deprecated, use `::backdrop` instead. Remove for RHDS v3. */ [part='overlay'] { background-color: @@ -73,30 +82,48 @@ [part='content'] { padding: var(--rh-space-xl, 24px); + margin-block-start: calc(-1 * var(--offset-top)); } [part='header'] { background-color: var(--rh-color-surface); - padding-block-end: var(--rh-space-sm, 6px); position: sticky; - top: calc(-1 * var(--rh-space-xl, 24px)); + inset-block-start: calc(-1 * var(--offset-top)); } [part='header'][hidden] + [part='body'] { - max-width: calc(100% - var(--rh-space-xl, 24px)); + max-inline-size: calc(100% - var(--rh-space-xl, 24px)); +} + +[part='header'], +[part='header'][hidden] + [part='body'] { + padding-inline-end: var(--rh-space-3xl, 48px); } @media (min-width: 1200px) { + #rhds-wrapper { + --offset: var(--rh-space-2xl, 32px); + } + + :host(:not[type='video']) [part='dialog']:has([part='header'][hidden]) [part='close-button'] { + --offset-top: var(--rh-space-xl, 24px); + } + [part='content'] { padding: var(--rh-space-2xl, 32px); } - [part='header'] { - top: calc(-1 * var(--rh-space-2xl, 32px)); + [part='content']:has([part='header'][hidden]) { + --offset-top: var(--rh-space-xl, 24px); } [part='header'][hidden] + [part='body'] { - max-width: calc(100% - var(--rh-space-2xl, 32px)); + max-inline-size: calc(100% - var(--rh-space-2xl, 32px)); + } + + [part='header'], + [part='body'] { + padding-inline-end: calc(var(--offset-right) * 2); } } @@ -108,15 +135,21 @@ font-family: var(--rh-font-family-heading, RedHatDisplay, 'Red Hat Display', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Malayalam', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans Thai', Helvetica, Arial, sans-serif); font-size: var(--rh-font-size-heading-sm, 1.5rem); font-weight: var(--rh-font-weight-body-text-regular, 400); + margin-block-end: var(--rh-space-lg, 16px) !important; +} + +[part='body'] ::slotted(p) { + margin-block: 0 var(--rh-space-xl, 24px) !important; } [part='close-button'] { color: var(--rh-dialog-close-button-color, var(--rh-color-icon-secondary)); cursor: pointer; - display: block; - inset-block-start: var(--rh-length-xs, 4px); - inset-inline-end: var(--rh-length-xs, 4px); - position: absolute; + display: flex; + justify-content: end; + margin-inline-end: var(--offset-right); + inset-block-start: var(--offset-top); + position: sticky; z-index: 500; } @@ -137,29 +170,6 @@ gap: var(--rh-space-md, 8px); } -#rhds-wrapper { - --offset: var(--rh-space-md, 8px); - --offset-top: var(--offset); - --offset-right: var(--offset); - - display: contents; - font-family: var(--rh-font-family-body-text, RedHatText, 'Red Hat Text', 'Noto Sans Arabic', 'Noto Sans Hebrew', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Malayalam', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans Thai', Helvetica, Arial, sans-serif); -} - -:host([type='video']) [part='close-button'] { - inset-inline-end: var(--offset-right); - inset-block-start: var(--offset-top); -} - -:host([type='video']) #close-button::part(icon) { - filter: drop-shadow(1px 0 1px var(--rh-dialog-backdrop-background-color)); -} - -:host([type='video']) [part='content'] { - overflow: hidden; - padding: 0; -} - :host([type='video']) { --_gray-90-rgb: var(--rh-color-gray-90-rgb, 31 31 31); --rh-dialog-backdrop-background-color: rgb(var(--_gray-90-rgb) / var(--rh-opacity-60, 60%)); @@ -169,18 +179,41 @@ var(--rh-dialog-backdrop-background-color)); } +:host([type='video']) #rhds-wrapper { + --offset: var(--rh-space-md, 8px); +} + :host([type='video'][open]) [part='dialog'] { --_aspect-ratio: var(--rh-dialog-video-aspect-ratio, 16/9); aspect-ratio: var(--_aspect-ratio); + background-color: var(--rh-dialog-overlay-background-color); max-inline-size: min(90%, calc(90vh * var(--_aspect-ratio) + var(--offset-top))); padding: 0; } +:host([type='video']) [part='close-button'] { + inset-inline-end: var(--offset-right); + inset-block-start: var(--offset-top); + margin-inline-end: 0; + position: absolute; +} + :host([type='video']) #rhds-wrapper.mobile [part='close-button'] { --offset-right: var(--rh-space-sm, 6px); } +:host([type='video']) #close-button::part(icon) { + color: var(--rh-color-surface); + filter: drop-shadow(1px 0 1px var(--rh-dialog-backdrop-background-color)); +} + +:host([type='video']) [part='content'] { + margin-block-start: 0; /* Safari fix */ + overflow: hidden; + padding: 0; +} + :host([type='video']) [part='content'], :host([type='video']) ::slotted(:not([slot])) { aspect-ratio: var(--rh-dialog-video-aspect-ratio, 16/9); From 0947d167ac2d8f6749dfa13100c25beea3d275ee Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 17:06:27 -0500 Subject: [PATCH 14/42] fix(dialog): add close button color variable to icon declaration --- elements/rh-dialog/rh-dialog.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/rh-dialog/rh-dialog.css b/elements/rh-dialog/rh-dialog.css index 1d60f296f8..551547d9b2 100644 --- a/elements/rh-dialog/rh-dialog.css +++ b/elements/rh-dialog/rh-dialog.css @@ -204,7 +204,7 @@ } :host([type='video']) #close-button::part(icon) { - color: var(--rh-color-surface); + color: var(--rh-dialog-close-button-color, var(--rh-color-surface)); filter: drop-shadow(1px 0 1px var(--rh-dialog-backdrop-background-color)); } From e275b6f1a08bd7b86ebd7d3271a0aad90190876b Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 17:17:26 -0500 Subject: [PATCH 15/42] fix(dialog): remove `trapFocus();` function See: https://github.com/RedHat-UX/red-hat-design-system/pull/2078/files#r1877655958 --- elements/rh-dialog/rh-dialog.ts | 76 --------------------------------- 1 file changed, 76 deletions(-) diff --git a/elements/rh-dialog/rh-dialog.ts b/elements/rh-dialog/rh-dialog.ts index 2f67dab2b8..e112128853 100644 --- a/elements/rh-dialog/rh-dialog.ts +++ b/elements/rh-dialog/rh-dialog.ts @@ -259,81 +259,6 @@ export class RhDialog extends LitElement { } } - #trapFocus() { - // https://github.com/KittyGiraudel/focusable-selectors - const notInert = ':not([inert]):not([inert] *)'; - const notNegTabIndex = ':not([tabindex^="-"])'; - const notDisabled = ':not(:disabled)'; - const focusableSelectorList = [ - `a[href]${notInert}${notNegTabIndex}`, - `area[href]${notInert}${notNegTabIndex}`, - `input:not([type="hidden"]):not([type="radio"])${notInert}${notNegTabIndex}${notDisabled}`, - `input[type="radio"]${notInert}${notNegTabIndex}${notDisabled}`, - `select${notInert}${notNegTabIndex}${notDisabled}`, - `textarea${notInert}${notNegTabIndex}${notDisabled}`, - `button${notInert}${notNegTabIndex}${notDisabled}`, - `details${notInert} > summary:first-of-type${notNegTabIndex}`, - `details:not(:has(> summary))${notInert}${notNegTabIndex}`, - `iframe${notInert}${notNegTabIndex}`, - `audio[controls]${notInert}${notNegTabIndex}`, - `video[controls]${notInert}${notNegTabIndex}`, - `[contenteditable]${notInert}${notNegTabIndex}`, - `[tabindex]${notInert}${notNegTabIndex}`, - `rh-accordion${notInert}${notNegTabIndex}${notDisabled}`, - `rh-accordion-header${notInert}${notNegTabIndex}${notDisabled}`, - `rh-accordion-panel${notInert}${notNegTabIndex}${notDisabled}`, - `rh-alert${notInert}${notNegTabIndex}${notDisabled}`, - `rh-audio-player${notInert}${notNegTabIndex}${notDisabled}`, - `rh-avatar${notInert}${notNegTabIndex}${notDisabled}`, - `rh-back-to-top${notInert}${notNegTabIndex}${notDisabled}`, - // rh-badge not interactive - // rh-blockquote not interactive - `rh-breadcrumb${notInert}${notNegTabIndex}${notDisabled}`, - `rh-button${notInert}${notNegTabIndex}${notDisabled}`, - `rh-card${notInert}${notNegTabIndex}${notDisabled}`, - `rh-code-block${notInert}${notNegTabIndex}${notDisabled}`, - `rh-cta${notInert}${notNegTabIndex}${notDisabled}`, - `rh-dialog${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer-block${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer-copyright${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer-links${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer-social-link${notInert}${notNegTabIndex}${notDisabled}`, - `rh-footer-universal${notInert}${notNegTabIndex}${notDisabled}`, - // rh-health-index not interactive - // rh-icon not interactive - `rh-navigation-secondary${notInert}${notNegTabIndex}${notDisabled}`, - `rh-navigation-secondary-dropdown${notInert}${notNegTabIndex}${notDisabled}`, - `rh-navigation-secondary-menu-section${notInert}${notNegTabIndex}${notDisabled}`, - `rh-navigation-secondary-menu${notInert}${notNegTabIndex}${notDisabled}`, - `rh-navigation-secondary-overlay${notInert}${notNegTabIndex}${notDisabled}`, - `rh-pagination${notInert}${notNegTabIndex}${notDisabled}`, - `rh-site-status${notInert}${notNegTabIndex}${notDisabled}`, - `rh-skip-link${notInert}${notNegTabIndex}${notDisabled}`, - // rh-spinner not interactive - // rh-stat not interactive - `rh-subnav${notInert}${notNegTabIndex}${notDisabled}`, - // rh-surface not interactive - `rh-switch${notInert}${notNegTabIndex}${notDisabled}`, - `rh-table${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tabs${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tab${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tab-panel${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tag${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tile${notInert}${notNegTabIndex}${notDisabled}`, - `rh-tile-group${notInert}${notNegTabIndex}${notDisabled}`, - // rh-timestamp not interactive - `rh-tooltip${notInert}${notNegTabIndex}${notDisabled}`, - `rh-video-embed${notInert}${notNegTabIndex}${notDisabled}`, - ]; - - const focusableSlottedElements = - this.querySelectorAll(focusableSelectorList.join(',')); - const hasLastElement = focusableSlottedElements.length > 0; - this.#lastTabbable = hasLastElement ? - focusableSlottedElements[focusableSlottedElements.length - 1] : this.closeButton; - } - #handleTab(event: KeyboardEvent) { // No focusable elements except close button: if (this.#lastTabbable === this.closeButton) { @@ -412,7 +337,6 @@ export class RhDialog extends LitElement { */ @bound show() { this.dialog?.showModal(); - this.#trapFocus(); this.open = true; } From 03fef8d79f9c5509cf6cd7e9b35953431a9d3cf1 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 17:22:18 -0500 Subject: [PATCH 16/42] chore(dialog): update changeset wording about overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benny Powers - עם ישראל חי! --- .changeset/loud-coins-shake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/loud-coins-shake.md b/.changeset/loud-coins-shake.md index 888e2b3c14..9e1d71e1a2 100644 --- a/.changeset/loud-coins-shake.md +++ b/.changeset/loud-coins-shake.md @@ -4,7 +4,7 @@ ``: Dialog now uses the native HTML `

` element internally. -Note: `#overlay` is now deprecated in favor of a public variable on the CSS psuedo-element `::backdrop`: +Note: the `overlay` CSS shadow part is now deprecated in favor of the `--rh-dialog-backdrop-background-color` CSS custom property. Before: From d1c59fc3c0bca20004acd67f603db89565bf098e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Dec 2024 17:24:49 -0500 Subject: [PATCH 17/42] fix(dialog): add `href` to `rh-cta` host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benny Powers - עם ישראל חי! --- elements/rh-dialog/demo/rh-dialog.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/elements/rh-dialog/demo/rh-dialog.html b/elements/rh-dialog/demo/rh-dialog.html index c4d5fd47db..8975de9632 100644 --- a/elements/rh-dialog/demo/rh-dialog.html +++ b/elements/rh-dialog/demo/rh-dialog.html @@ -6,9 +6,7 @@

Modal dialog with a header

aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- - Learn more - + Learn more