Skip to content

Commit a19cbe3

Browse files
fix(overlay-trigger): update overlay-trigger interactions type (#5806)
1 parent 9eb6d0b commit a19cbe3

File tree

6 files changed

+164
-67
lines changed

6 files changed

+164
-67
lines changed

.changeset/weak-streets-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@spectrum-web-components/overlay': patch
3+
---
4+
5+
- **Fixed**: Expanded `<overlay-trigger>` `type` property to accept all overlay types ('auto', 'hint', 'manual', 'modal', 'page') instead of the incorrect, previous restricted subset.

packages/overlay/README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,42 @@ Some Overlays will always be passed focus (e.g. modal or page Overlays). When th
267267

268268
#### trigger
269269

270-
The `trigger` option accepts an `HTMLElement` or a `VirtualTrigger` from which to position the Overlay.
270+
The `trigger` attribute accepts a string ID reference for the trigger element combined with the interaction type.
271+
272+
The format is `"elementId@interaction"`, where:
273+
274+
- `elementId` is the ID of the HTML element to use as the trigger
275+
- `interaction` is required and can be `click`, `hover`, or `longpress`
276+
277+
Examples:
278+
279+
```html
280+
<sp-button id="my-button">Open Overlay</sp-button>
281+
282+
<!-- Explicit click interaction -->
283+
<sp-overlay trigger="my-button@click" placement="top-start">
284+
<sp-popover>Click popover</sp-popover>
285+
</sp-overlay>
286+
287+
<!-- Explicit hover interaction -->
288+
<sp-overlay trigger="my-button@hover" placement="right-start">
289+
<sp-popover>Hover popover</sp-popover>
290+
</sp-overlay>
291+
292+
<!-- Explicit longpress interaction -->
293+
<sp-overlay trigger="my-button@longpress" placement="bottom-start">
294+
<sp-popover>Longpress popover</sp-popover>
295+
</sp-overlay>
296+
```
297+
298+
#### triggerElement
299+
300+
The `triggerElement` property accepts an `HTMLElement` or a `VirtualTrigger` from which to position the Overlay.
271301

272302
- You can import the `VirtualTrigger` class from the overlay package to create a virtual trigger that can be used to position an Overlay. This is useful when you want to position an Overlay relative to a point on the screen that is not an element in the DOM, like the mouse cursor.
273303

304+
#### type
305+
274306
The `type` of an Overlay outlines a number of things about the interaction model within which it works:
275307

276308
<sp-tabs selected="modal" auto label="Type attribute options">
@@ -529,7 +561,7 @@ This means that in both cases, if the transition is meant to be a part of the op
529561
placement=${Placement}
530562
receives-focus=${'true' | 'false' | 'auto' (default)
531563
trigger=${string | ${string}@${string}}
532-
.triggerElement=${HTMLElement}
564+
.triggerElement=${HTMLElement | VirtualTrigger}
533565
.triggerInteraction=${'click' | 'longpress' | 'hover'}
534566
type=${'auto' | 'hint' | 'manual' | 'modal' | 'page'}
535567
?allow-outside-click=${boolean}

packages/overlay/overlay-trigger.md

Lines changed: 103 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -77,78 +77,129 @@ When using the `placement` attribute of an `<overlay-trigger>` (`"top" |"top-sta
7777

7878
#### Type
7979

80-
<sp-tabs selected="inline" auto label="Type attribute options">
81-
<sp-tab value="inline">Inline</sp-tab>
82-
<sp-tab-panel value="inline">
80+
The `type` of an Overlay outlines a number of things about the interaction model within which it works:
8381

84-
`'inline'` type inserts the overlay after the trigger in the tab order. This creates a natural flow where:
82+
**Note:** The `type` attribute only affects click-triggered overlays. Hover overlays always use `hint` type behavior, and longpress overlays always use `auto` type behavior. For more control over hover and longpress overlay types, use `<sp-overlay>` directly.
8583

86-
- Forward tab: Goes to the next logical element
87-
- Backward tab (shift): Returns to the trigger
84+
<sp-tabs selected="modal" auto label="Type attribute options">
85+
<sp-tab value="modal">Modal</sp-tab>
86+
<sp-tab-panel value="modal">
87+
88+
`'modal'` Overlays create a modal context that traps focus within the content and prevents interaction with the rest of the page. The overlay manages focus trapping and accessibility features like `aria-modal="true"` to ensure proper screen reader behavior.
89+
90+
They should be used when you need to ensure that the user has interacted with the content of the Overlay before continuing with their work. This is commonly used for dialogs that require a user to confirm or cancel an action before continuing.
8891

8992
```html
90-
<overlay-trigger type="inline" placement="bottom">
91-
<sp-button slot="trigger">Open Menu</sp-button>
92-
<sp-popover slot="click-content">
93-
<sp-menu>
94-
<sp-menu-item>Option 1</sp-menu-item>
95-
<sp-menu-item>Option 2</sp-menu-item>
96-
</sp-menu>
97-
</sp-popover>
93+
<overlay-trigger type="modal" triggered-by="click">
94+
<sp-button slot="trigger">Open modal</sp-button>
95+
<sp-dialog-wrapper
96+
slot="click-content"
97+
headline="Signin form"
98+
dismissable
99+
underlay
100+
>
101+
<p>I am a modal type overlay.</p>
102+
<sp-field-label>Enter your email</sp-field-label>
103+
<sp-textfield placeholder="test@gmail.com"></sp-textfield>
104+
<sp-action-button
105+
onClick="
106+
this.dispatchEvent(
107+
new Event('close', {
108+
bubbles: true,
109+
composed: true,
110+
})
111+
);
112+
"
113+
>
114+
Sign in
115+
</sp-action-button>
116+
</sp-dialog-wrapper>
98117
</overlay-trigger>
99118
```
100119

101120
</sp-tab-panel>
102-
<sp-tab value="replace">Replace</sp-tab>
103-
<sp-tab-panel value="replace">
121+
<sp-tab value="page">Page</sp-tab>
122+
<sp-tab-panel value="page">
104123

105-
`'replace'` type inserts the overlay as if it were the trigger itself in the tab order. This means:
124+
`'page'` Overlays behave similarly to `'modal'` Overlays by creating a modal context and trapping focus, but they will not be allowed to close via the "light dismiss" algorithm (e.g. the Escape key).
106125

107-
- Forward tab: Goes to the next logical element
108-
- Backward tab (shift): Goes to the element before the trigger
126+
A page overlay could be used for a full-screen menu on a mobile website. When the user clicks on the menu button, the entire screen is covered with the menu options.
109127

110128
```html
111-
<overlay-trigger type="replace" placement="bottom">
112-
<sp-button slot="trigger">Show Details</sp-button>
113-
<sp-popover slot="click-content">
114-
<sp-dialog>
115-
<p>Details panel that replaces trigger in tab order</p>
116-
<sp-button
117-
onclick="this.dispatchEvent(new Event('close', { bubbles: true, composed: true }))"
118-
>
119-
Close
120-
</sp-button>
121-
</sp-dialog>
129+
<overlay-trigger type="page" triggered-by="click">
130+
<sp-button slot="trigger">Open page</sp-button>
131+
<sp-dialog-wrapper
132+
slot="click-content"
133+
headline="Full screen menu"
134+
mode="fullscreenTakeover"
135+
cancel-label="Close"
136+
>
137+
<p>I am a page type overlay.</p>
138+
</sp-dialog-wrapper>
139+
</overlay-trigger>
140+
```
141+
142+
</sp-tab-panel>
143+
<sp-tab value="hint">Hint</sp-tab>
144+
<sp-tab-panel value="hint">
145+
146+
`'hint'` Overlays are much like tooltips so they are not just ephemeral, but they are delivered primarily as a visual helper and exist outside of the tab order. In this way, be sure _not_ to place interactive content within this type of Overlay.
147+
148+
This overlay type does not accept focus and does not interfere with the user's interaction with the rest of the page.
149+
150+
```html
151+
<overlay-trigger type="hint" triggered-by="hover">
152+
<sp-button slot="trigger">Open hint</sp-button>
153+
<sp-tooltip slot="click-content">
154+
I am a hint type overlay. I am not interactive and will close when the
155+
user interacts with the page.
156+
</sp-tooltip>
157+
</overlay-trigger>
158+
```
159+
160+
</sp-tab-panel>
161+
<sp-tab value="auto">Auto</sp-tab>
162+
<sp-tab-panel value="auto">
163+
164+
`'auto'` Overlays provide a place for content that is ephemeral _and_ interactive. These Overlays can accept focus and remain open while interacting with their content. They will close when focus moves outside the overlay or when clicking elsewhere on the page.
165+
166+
```html
167+
<overlay-trigger type="auto" triggered-by="click" placement="bottom">
168+
<sp-button slot="trigger">Open overlay</sp-button>
169+
<sp-popover slot="click-content" dialog>
170+
<p>
171+
My slider in overlay element:
172+
<sp-slider label="Slider Label - Editable" editable></sp-slider>
173+
</p>
122174
</sp-popover>
123175
</overlay-trigger>
124176
```
125177

126178
</sp-tab-panel>
127-
<sp-tab value="modal">Modal</sp-tab>
128-
<sp-tab-panel value="modal">
179+
<sp-tab value="manual">Manual</sp-tab>
180+
<sp-tab-panel value="manual">
181+
182+
`'manual'` Overlays act much like `'auto'` Overlays, but do not close when losing focus or interacting with other parts of the page.
129183

130-
`'modal'` type creates a separate tab order and traps focus within the overlay content until the required interaction is complete. This is ideal for important interactions that need user attention.
184+
Note: When a `'manual'` Overlay is at the top of the "overlay stack", it will still respond to the Escape key and close.
131185

132186
```html
133-
<overlay-trigger type="modal">
134-
<sp-button slot="trigger">Open Settings</sp-button>
135-
<sp-dialog-wrapper
136-
slot="click-content"
137-
headline="Settings"
138-
dismissable
139-
underlay
140-
>
141-
<sp-field-label>Theme</sp-field-label>
142-
<sp-picker>
143-
<sp-menu-item>Light</sp-menu-item>
144-
<sp-menu-item>Dark</sp-menu-item>
145-
</sp-picker>
146-
<sp-button
147-
onclick="this.dispatchEvent(new Event('close', { bubbles: true, composed: true }))"
148-
>
149-
Save
150-
</sp-button>
151-
</sp-dialog-wrapper>
187+
<style>
188+
.chat-container {
189+
position: fixed;
190+
bottom: 1em;
191+
left: 1em;
192+
}
193+
</style>
194+
<overlay-trigger type="manual" triggered-by="click">
195+
<sp-button slot="trigger">Open manual</sp-button>
196+
<sp-popover slot="click-content" class="chat-container">
197+
<sp-dialog dismissable>
198+
<span slot="heading">Chat Window</span>
199+
<sp-textfield placeholder="Enter your message"></sp-textfield>
200+
<sp-action-button>Send</sp-action-button>
201+
</sp-dialog>
202+
</sp-popover>
152203
</overlay-trigger>
153204
```
154205

packages/overlay/src/OverlayTrigger.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import type { Placement } from '@floating-ui/dom';
2626

2727
import type { BeforetoggleOpenEvent } from './events.js';
2828
import type { Overlay } from './Overlay.js';
29-
import type { OverlayTriggerInteractions } from './overlay-types';
29+
import type { OverlayTypes } from './overlay-types';
30+
// eslint-disable-next-line import/no-extraneous-dependencies
3031
import '@spectrum-web-components/overlay/sp-overlay.js';
3132

3233
import overlayTriggerStyles from './overlay-trigger.css.js';
@@ -90,7 +91,7 @@ export class OverlayTrigger extends SpectrumElement {
9091
public placement?: Placement;
9192

9293
@property()
93-
public type?: OverlayTriggerInteractions;
94+
public type?: OverlayTypes;
9495

9596
@property({ type: Number })
9697
public offset = 6;
@@ -225,7 +226,7 @@ export class OverlayTrigger extends SpectrumElement {
225226
.placement=${this.clickPlacement || this.placement}
226227
.triggerElement=${this.targetContent[0]}
227228
.triggerInteraction=${'click'}
228-
.type=${this.type !== 'modal' ? 'auto' : 'modal'}
229+
.type=${this.type || 'auto'}
229230
@beforetoggle=${this.handleBeforetoggle}
230231
.receivesFocus=${this.receivesFocus}
231232
>

packages/overlay/src/overlay-types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ export { Placement };
2323

2424
export type OverlayTypes = 'auto' | 'hint' | 'manual' | 'modal' | 'page';
2525

26+
// Constant array for runtime use (tests, validation, etc.)
27+
export const OVERLAY_TYPES = [
28+
'auto',
29+
'hint',
30+
'manual',
31+
'modal',
32+
'page',
33+
] as const satisfies readonly OverlayTypes[];
34+
2635
export type TriggerInteraction = 'click' | 'longpress' | 'hover';
2736

2837
export type TriggerInteractions = OverlayTypes;

packages/overlay/test/index.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import { Button } from '@spectrum-web-components/button';
2424
import '@spectrum-web-components/button/sp-button.js';
2525
import '@spectrum-web-components/dialog/sp-dialog.js';
2626
import {
27+
OVERLAY_TYPES,
2728
OverlayTrigger,
28-
TriggerInteractions,
2929
} from '@spectrum-web-components/overlay';
3030
import { Popover } from '@spectrum-web-components/popover';
3131
import '@spectrum-web-components/popover/sp-popover.js';
@@ -113,10 +113,10 @@ export const runOverlayTriggerTests = (type: string): void => {
113113

114114
this.innerTrigger = this.testDiv.querySelector(
115115
'#inner-trigger'
116-
) as OverlayTrigger;
116+
)! as OverlayTrigger;
117117
this.outerTrigger = this.testDiv.querySelector(
118118
'#trigger'
119-
) as OverlayTrigger;
119+
)! as OverlayTrigger;
120120
this.innerButton = this.testDiv.querySelector(
121121
'#inner-button'
122122
) as Button;
@@ -213,20 +213,19 @@ export const runOverlayTriggerTests = (type: string): void => {
213213
).to.be.true;
214214
});
215215

216-
['modal', 'replace', 'inline'].map((type: string) => {
216+
OVERLAY_TYPES.map((type) => {
217217
it(`opens a popover - [type="${type}"]`, async function () {
218-
this.outerTrigger.type = type as Extract<
219-
TriggerInteractions,
220-
'inline' | 'modal' | 'replace'
221-
>;
222-
await elementUpdated(this.outerTrigger);
218+
const outerTrigger = this.outerTrigger as OverlayTrigger;
219+
220+
outerTrigger.type = type;
221+
await elementUpdated(outerTrigger);
223222
expect(
224223
await isOnTopLayer(this.outerClickContent),
225224
'popover not available at point'
226225
).to.be.false;
227226

228227
expect(this.outerButton).to.exist;
229-
const opened = oneEvent(this.outerTrigger, 'sp-opened');
228+
const opened = oneEvent(outerTrigger, 'sp-opened');
230229
this.outerButton.click();
231230
await opened;
232231
expect(

0 commit comments

Comments
 (0)