Skip to content

Commit 43a466e

Browse files
feat(tray): expand API for dismissible behavior
- adds several properties and methods to tray API to support more flexibile dismissible behavior. - queries for keyboard-accessible dismiss buttons in the tray's slot content - adds a state property to track if dismiss buttons are needed - adds manual override for dismissible behavior
1 parent 7ab0fc6 commit 43a466e

File tree

1 file changed

+68
-3
lines changed

1 file changed

+68
-3
lines changed

packages/tray/src/Tray.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
import {
1414
CSSResultArray,
1515
html,
16+
nothing,
1617
PropertyValues,
1718
SpectrumElement,
1819
TemplateResult,
1920
} from '@spectrum-web-components/base';
2021
import {
2122
property,
2223
query,
24+
queryAssignedElements,
2325
} from '@spectrum-web-components/base/src/decorators.js';
2426
import '@spectrum-web-components/underlay/sp-underlay.js';
2527
import { firstFocusableIn } from '@spectrum-web-components/shared/src/first-focusable-in.js';
@@ -81,6 +83,16 @@ export class Tray extends SpectrumElement {
8183
}
8284
}
8385

86+
/**
87+
* Indicates whether the slotted content has keyboard-accessible dismiss functionality.
88+
* When set, this overrides the automatic button detection behavior.
89+
* - `true`: Content has dismiss buttons, don't render additional visually-hidden helpers
90+
* - `false`: Content lacks dismiss buttons, always render visually-hidden helpers
91+
* - `undefined` (default): Auto-detect by scanning slotted content for buttons
92+
*/
93+
@property({ type: Boolean, attribute: 'has-keyboard-dismiss' })
94+
public hasKeyboardDismissButton?: boolean;
95+
8496
/**
8597
* Returns a visually hidden dismiss button for mobile screen reader accessibility.
8698
* This button is placed before and after tray content to allow mobile screen reader
@@ -98,6 +110,59 @@ export class Tray extends SpectrumElement {
98110
`;
99111
}
100112

113+
/**
114+
* Add a state property to track if dismiss buttons are needed
115+
* Set to false if your tray content already includes keyboard-accessible dismiss buttons.
116+
*/
117+
@property({ type: Boolean, attribute: false })
118+
private needsDismissHelper = true;
119+
120+
// Track slotted content
121+
@queryAssignedElements({ flatten: true })
122+
private slottedContent!: HTMLElement[];
123+
124+
/**
125+
* Determines whether to show visually-hidden dismiss helpers.
126+
* Uses manual override if set, otherwise falls back to auto-detection.
127+
*/
128+
private get shouldShowDismissHelper(): boolean {
129+
// If explicitly set, use that value (inverted because true means "has buttons")
130+
if (this.hasKeyboardDismissButton !== undefined) {
131+
return !this.hasKeyboardDismissButton;
132+
}
133+
// Otherwise use auto-detection
134+
return this.needsDismissHelper;
135+
}
136+
137+
// Check if slotted content has keyboard-accessible dismiss buttons
138+
private checkForDismissButtons(): void {
139+
// Look for common dismiss button patterns
140+
const hasDismissButton = this.slottedContent.some((element) => {
141+
// Check for buttons at the top level
142+
if (
143+
element.tagName === 'SP-BUTTON' ||
144+
element.tagName === 'SP-CLOSE-BUTTON' ||
145+
element.tagName === 'BUTTON'
146+
)
147+
return true;
148+
149+
// Check for buttons within the slotted content
150+
const buttons = element.querySelectorAll(
151+
'sp-button, sp-close-button, button'
152+
);
153+
if (buttons.length > 0) return true;
154+
155+
return false;
156+
});
157+
158+
this.needsDismissHelper = !hasDismissButton;
159+
}
160+
161+
// Update when slot content changes
162+
private handleSlotChange(): void {
163+
this.checkForDismissButtons();
164+
}
165+
101166
private dispatchClosed(): void {
102167
this.dispatchEvent(
103168
new Event('close', {
@@ -148,9 +213,9 @@ export class Tray extends SpectrumElement {
148213
tabindex="-1"
149214
@transitionend=${this.handleTrayTransitionend}
150215
>
151-
${this.dismissHelper}
152-
<slot></slot>
153-
${this.dismissHelper}
216+
${this.shouldShowDismissHelper ? this.dismissHelper : nothing}
217+
<slot @slotchange=${this.handleSlotChange}></slot>
218+
${this.shouldShowDismissHelper ? this.dismissHelper : nothing}
154219
</div>
155220
`;
156221
}

0 commit comments

Comments
 (0)