@fusion.dev/helix-headless 0.157.0 → 0.159.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +851 -56
- package/dist/app-header/root/app-header-root.d.ts +59 -12
- package/dist/app-header/root/app-header-root.js +68 -29
- package/dist/button/root/button-root.d.ts +8 -0
- package/dist/button/root/button-root.js +4 -0
- package/dist/button-group/root/button-group-root.d.ts +20 -7
- package/dist/button-group/root/button-group-root.js +30 -5
- package/dist/command/root/command-root.d.ts +19 -5
- package/dist/command/root/command-root.js +14 -4
- package/dist/data-table/root/data-table-root.js +5 -2
- package/dist/element-events.generated.js +7 -1
- package/dist/lightbox/item/lightbox-item.d.ts +7 -1
- package/dist/lightbox/item/lightbox-item.js +15 -2
- package/dist/lightbox/root/lightbox-root.d.ts +8 -4
- package/dist/lightbox/root/lightbox-root.js +9 -1
- package/dist/lightbox/zoom-controller.d.ts +13 -4
- package/dist/lightbox/zoom-controller.js +17 -0
- package/dist/list/item/list-item.d.ts +15 -1
- package/dist/list/item/list-item.js +20 -1
- package/dist/list/root/list-root.d.ts +29 -1
- package/dist/list/root/list-root.js +44 -10
- package/dist/listbox/root/listbox-root.d.ts +19 -2
- package/dist/listbox/root/listbox-root.js +16 -8
- package/dist/map/root/map-root.d.ts +4 -0
- package/dist/map/root/map-root.js +1 -1
- package/dist/menu/item/menu-item.js +1 -1
- package/dist/menu/root/menu-root.d.ts +6 -2
- package/dist/menu/root/menu-root.js +9 -2
- package/dist/number-input/root/number-input-root.d.ts +7 -4
- package/dist/number-input/root/number-input-root.js +3 -3
- package/dist/progress/root/progress-root.js +1 -1
- package/dist/select/root/select-root.d.ts +8 -1
- package/dist/select/root/select-root.js +8 -5
- package/dist/skeleton/root/skeleton-root.d.ts +2 -1
- package/dist/skeleton/root/skeleton-root.js +1 -1
- package/dist/steps/root/steps-root.js +8 -6
- package/dist/steps/step/step.d.ts +9 -0
- package/dist/steps/step/step.js +19 -2
- package/dist/tabs/panel/tab-panel.d.ts +22 -1
- package/dist/tabs/panel/tab-panel.js +22 -1
- package/dist/tabs/root/tabs-root.d.ts +21 -5
- package/dist/tabs/root/tabs-root.js +24 -13
- package/dist/tabs/tab/tab.d.ts +4 -2
- package/dist/tabs/tab/tab.js +3 -1
- package/dist/toast/item/toast-item.d.ts +27 -8
- package/dist/toast/item/toast-item.js +47 -11
- package/dist/toast/root/toast-root.d.ts +24 -1
- package/dist/toast/root/toast-root.js +54 -0
- package/dist/tour/root/tour-root.d.ts +34 -0
- package/dist/tour/root/tour-root.js +92 -3
- package/dist/tour/stop/tour-stop.d.ts +33 -1
- package/dist/tour/stop/tour-stop.js +12 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +667 -102
- package/types/vue.d.ts +148 -23
|
@@ -33,10 +33,14 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
33
33
|
this.manual = false;
|
|
34
34
|
this.scrollControls = false;
|
|
35
35
|
this.handleSelection = (event) => {
|
|
36
|
-
if (
|
|
36
|
+
if (!this.tabs.includes(event.target)) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
queueMicrotask(() => {
|
|
40
|
+
if (!event.defaultPrevented) {
|
|
41
|
+
this.requestActive(event.detail.value);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
40
44
|
};
|
|
41
45
|
this.more = { end: false, start: false };
|
|
42
46
|
this.measure = () => {
|
|
@@ -89,12 +93,19 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
89
93
|
const behavior = matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
|
|
90
94
|
strip.scrollBy(down ? { behavior, top: by } : { behavior, left: isRtl(this) ? -by : by });
|
|
91
95
|
}
|
|
96
|
+
beside(gone, tabs) {
|
|
97
|
+
const at = this.held.indexOf(gone);
|
|
98
|
+
const by = new Map(tabs.map((tab) => [tab.value, tab]));
|
|
99
|
+
const order = [...this.held.slice(at + 1), ...this.held.slice(0, at).reverse()];
|
|
100
|
+
return order.map((value) => by.get(value)).find((tab) => tab !== undefined && !tab.disabled);
|
|
101
|
+
}
|
|
92
102
|
sync() {
|
|
93
103
|
const tabs = this.tabs;
|
|
94
104
|
const declared = this.active === '' ? tabs.find((tab) => tab.selected) : undefined;
|
|
95
|
-
const value = (declared ?? this.resolveActive(tabs))?.value ?? '';
|
|
96
105
|
const previous = this.active;
|
|
97
106
|
const gone = this.held.includes(previous) && !tabs.some((tab) => tab.value === previous);
|
|
107
|
+
const value = (declared ?? (gone ? this.beside(previous, tabs) : undefined) ?? this.resolveActive(tabs))
|
|
108
|
+
?.value ?? '';
|
|
98
109
|
this.held = tabs.map((tab) => tab.value);
|
|
99
110
|
if (gone && value !== '') {
|
|
100
111
|
this.active = value;
|
|
@@ -192,19 +203,19 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
192
203
|
if (!this.scrollControls) {
|
|
193
204
|
return nothing;
|
|
194
205
|
}
|
|
195
|
-
const down = this.orientation === 'vertical';
|
|
196
|
-
const icon = down ? (toward > 0 ? caretDown : caretUp) : toward > 0 ? caretRight : caretLeft;
|
|
197
|
-
const glyph = html `<svg
|
|
198
|
-
data-mirror=${down ? nothing : ''}
|
|
199
|
-
aria-hidden="true"
|
|
200
|
-
fill="currentColor"
|
|
201
|
-
viewBox=${icon.viewBox}
|
|
202
|
-
>${unsafeSVG(icon.content)}</svg>`;
|
|
203
206
|
const shown = toward > 0 ? this.more.end : this.more.start;
|
|
204
207
|
const keepFocus = (event) => event.preventDefault();
|
|
205
208
|
return toward > 0
|
|
206
|
-
? html `<button part="scroll-end" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(1)}>${
|
|
207
|
-
: html `<button part="scroll-start" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(-1)}>${
|
|
209
|
+
? html `<button part="scroll-end" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(1)}>${this.renderScrollIcon(1)}</button>`
|
|
210
|
+
: html `<button part="scroll-start" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(-1)}>${this.renderScrollIcon(-1)}</button>`;
|
|
211
|
+
}
|
|
212
|
+
renderScrollIcon(toward) {
|
|
213
|
+
const down = this.orientation === 'vertical';
|
|
214
|
+
const icon = down ? (toward > 0 ? caretDown : caretUp) : toward > 0 ? caretRight : caretLeft;
|
|
215
|
+
const mirror = down ? nothing : '';
|
|
216
|
+
return toward > 0
|
|
217
|
+
? html `<slot name="scroll-end-icon"><svg part="scroll-end-mark" data-mirror=${mirror} aria-hidden="true" fill="currentColor" viewBox=${icon.viewBox}>${unsafeSVG(icon.content)}</svg></slot>`
|
|
218
|
+
: html `<slot name="scroll-start-icon"><svg part="scroll-start-mark" data-mirror=${mirror} aria-hidden="true" fill="currentColor" viewBox=${icon.viewBox}>${unsafeSVG(icon.content)}</svg></slot>`;
|
|
208
219
|
}
|
|
209
220
|
render() {
|
|
210
221
|
return html `<div
|
package/dist/tabs/tab/tab.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
|
|
|
16
16
|
* can: a press on its `dismiss` mark, or Delete or Backspace while the tab
|
|
17
17
|
* has focus, sends `helix-dismiss`. The tab does not remove itself, because
|
|
18
18
|
* your code owns the set, so remove the tab and its panel in your handler.
|
|
19
|
-
* Focus moves to the tab beside it, and the tab
|
|
20
|
-
*
|
|
19
|
+
* Focus moves to the tab beside it, and where the tab was selected, the tab
|
|
20
|
+
* set selects that same tab.
|
|
21
21
|
* The tab says the key through `aria-keyshortcuts`, and the mark is for the
|
|
22
22
|
* pointer, hidden from screen readers.
|
|
23
23
|
*
|
|
@@ -27,11 +27,13 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
|
|
|
27
27
|
* @documentation https://fusion.dev/helix/components/tabs
|
|
28
28
|
*
|
|
29
29
|
* @slot - The tab's label.
|
|
30
|
+
* @slot dismiss-icon - Replaces the glyph on the mark that closes a `dismissible` tab.
|
|
30
31
|
*
|
|
31
32
|
* @event {HelixDismissEvent} helix-dismiss - A `dismissible` tab was asked to close. The tab does not remove itself, so remove it and its panel in response. Cancelable.
|
|
32
33
|
* @event {HelixSelectEvent} helix-select - Fired when the tab is chosen. Cancelable: canceling it stops the change and leaves the decision to you.
|
|
33
34
|
*
|
|
34
35
|
* @csspart dismiss - The mark a pointer presses to close a `dismissible` tab.
|
|
36
|
+
* @csspart dismiss-mark - The glyph on that mark, where none is slotted.
|
|
35
37
|
* @csspart tab - The tab itself.
|
|
36
38
|
*
|
|
37
39
|
* @cssstate selected - While it is the tab selected.
|
package/dist/tabs/tab/tab.js
CHANGED
|
@@ -53,7 +53,9 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinDismissible(MixinSele
|
|
|
53
53
|
return nothing;
|
|
54
54
|
}
|
|
55
55
|
return html `<span part="dismiss" aria-hidden="true" title=${this.dismissLabel} @click=${this.handleDismissPress}
|
|
56
|
-
><
|
|
56
|
+
><slot name="dismiss-icon"
|
|
57
|
+
><svg part="dismiss-mark" fill="currentColor" viewBox=${x.viewBox} aria-hidden="true">${unsafeSVG(x.content)}</svg></slot
|
|
58
|
+
></span
|
|
57
59
|
>`;
|
|
58
60
|
}
|
|
59
61
|
updated(changed) {
|
|
@@ -10,8 +10,9 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
|
|
|
10
10
|
*
|
|
11
11
|
* **Each item runs its own timer.** A timer held by the region would restart
|
|
12
12
|
* with each new message, and the last of three messages would stay for nine
|
|
13
|
-
* seconds. It pauses while the pointer or focus is on the message
|
|
14
|
-
* the page is hidden, so a message is not
|
|
13
|
+
* seconds. It pauses while the pointer or focus is on the message or anywhere
|
|
14
|
+
* in the region's stack, and while the page is hidden, so a message is not
|
|
15
|
+
* spent in a background tab.
|
|
15
16
|
*
|
|
16
17
|
* **Each message is an `alertdialog` named by its words**, which is how a reader
|
|
17
18
|
* who moves into the stack hears which message they are on. Give it a
|
|
@@ -20,10 +21,11 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
|
|
|
20
21
|
* says each message once as it arrives, a `danger` one assertively, so a
|
|
21
22
|
* message is never a live region of its own.
|
|
22
23
|
*
|
|
23
|
-
* A touch or a pen swipes a message toward the end of the line,
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `--toast-swipe-x
|
|
24
|
+
* A touch or a pen swipes a message toward the end of the line, or toward the
|
|
25
|
+
* edge the region's `swipe-direction` names, and past halfway, or quickly,
|
|
26
|
+
* dismisses it through the same `helix-dismiss` as its control. While it
|
|
27
|
+
* moves, the distance is on the `item` part as `--toast-swipe-x` or
|
|
28
|
+
* `--toast-swipe-y`.
|
|
27
29
|
*
|
|
28
30
|
* **Escape dismisses the message holding focus**, through the same
|
|
29
31
|
* `helix-dismiss`, and focus moves to the next message.
|
|
@@ -60,9 +62,10 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
|
|
|
60
62
|
*
|
|
61
63
|
* @cssproperty --toast-duration - How long this message stays, in milliseconds, for a progress hint.
|
|
62
64
|
* @cssproperty --toast-swipe-x - How far a swipe has moved the message across, in pixels, toward the right.
|
|
65
|
+
* @cssproperty --toast-swipe-y - How far a swipe has moved the message down, in pixels, under a `swipe-direction` of `top` or `bottom`.
|
|
63
66
|
*
|
|
64
67
|
* @cssstate closed - The message has been dismissed and is hidden, or leaving.
|
|
65
|
-
* @cssstate paused - The dismiss timer is paused, while a pointer or focus is on the message, or while the page is hidden.
|
|
68
|
+
* @cssstate paused - The dismiss timer is paused, while a pointer or focus is on the message or in the stack, or while the page is hidden.
|
|
66
69
|
* @cssstate waiting - The region holds the message back, past its `max-visible`, until a message before it goes.
|
|
67
70
|
*/
|
|
68
71
|
export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base {
|
|
@@ -96,8 +99,24 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
|
|
|
96
99
|
*/
|
|
97
100
|
get open(): boolean;
|
|
98
101
|
set open(next: boolean);
|
|
102
|
+
/**
|
|
103
|
+
* @internal Set by the region: whether a pointer or focus in the stack holds
|
|
104
|
+
* this message's timer.
|
|
105
|
+
*/
|
|
106
|
+
regionHeld: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* @internal Set by the region: the edge a swipe dismisses toward, from its
|
|
109
|
+
* `swipe-direction`.
|
|
110
|
+
*/
|
|
111
|
+
swipeToward: 'bottom' | 'end' | 'start' | 'top';
|
|
112
|
+
/** Whether the timer is held for `regionHeld`, so the hold is let go once. */
|
|
113
|
+
private regionHolding;
|
|
114
|
+
/** Holds the timer while the region asks, and lets it go once. */
|
|
115
|
+
private holdForRegion;
|
|
116
|
+
/** The physical edge a swipe dismisses toward. */
|
|
117
|
+
private get swipeTo();
|
|
99
118
|
protected readonly swipes: SwipeController;
|
|
100
|
-
/** Writes how far a swipe has moved the message toward
|
|
119
|
+
/** Writes how far a swipe has moved the message toward its edge. */
|
|
101
120
|
private slide;
|
|
102
121
|
/**
|
|
103
122
|
* The dismiss timer, which pauses while a reader is on the message, and
|
|
@@ -4,30 +4,34 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { CountdownController, customElement, drawDismiss, drawDismissGlyph, HelixElement, hostAria, isRtl, MixinDismissible, requestChange, SwipeController, settleSwipe, swipeEdge, toneMark, towardEdge, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { CountdownController, customElement, drawDismiss, drawDismissGlyph, edgeOffset, HelixElement, hostAria, isRtl, MixinDismissible, requestChange, SwipeController, settleSwipe, swipeAxis, swipeEdge, toneMark, towardEdge, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
11
11
|
let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissible(HelixElement) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.regionHeld = false;
|
|
15
|
+
this.swipeToward = 'end';
|
|
16
|
+
this.regionHolding = false;
|
|
14
17
|
this.swipes = new SwipeController(this, {
|
|
15
|
-
axis: () => (this.open ?
|
|
16
|
-
end: (dx,
|
|
17
|
-
const edge =
|
|
18
|
-
const
|
|
18
|
+
axis: () => (this.open ? swipeAxis(this.swipeTo) : undefined),
|
|
19
|
+
end: (dx, dy, vx, vy) => {
|
|
20
|
+
const edge = this.swipeTo;
|
|
21
|
+
const box = this.partNamed('item')?.getBoundingClientRect();
|
|
22
|
+
const across = swipeAxis(edge) === 'x';
|
|
19
23
|
const shown = settleSwipe({
|
|
20
24
|
points: [1],
|
|
21
|
-
size: width,
|
|
22
|
-
travel: Math.max(0, towardEdge(edge, dx,
|
|
23
|
-
velocity: towardEdge(edge, vx,
|
|
25
|
+
size: (across ? box?.width : box?.height) ?? 0,
|
|
26
|
+
travel: Math.max(0, towardEdge(edge, dx, dy)),
|
|
27
|
+
velocity: towardEdge(edge, vx, vy),
|
|
24
28
|
});
|
|
25
29
|
if (shown === 0 && this.dismiss()) {
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
32
|
this.slide(0);
|
|
29
33
|
},
|
|
30
|
-
move: (dx) => this.slide(Math.max(0, towardEdge(
|
|
34
|
+
move: (dx, dy) => this.slide(Math.max(0, towardEdge(this.swipeTo, dx, dy))),
|
|
31
35
|
start: (_target, event) => event.pointerType !== 'mouse',
|
|
32
36
|
surface: () => this.partNamed('item'),
|
|
33
37
|
});
|
|
@@ -81,9 +85,27 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
|
|
|
81
85
|
this.openState = next;
|
|
82
86
|
this.requestUpdate('open', previous);
|
|
83
87
|
}
|
|
88
|
+
holdForRegion() {
|
|
89
|
+
const want = this.regionHeld && this.isConnected;
|
|
90
|
+
if (want === this.regionHolding) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.regionHolding = want;
|
|
94
|
+
if (want) {
|
|
95
|
+
this.countdown.hold();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.countdown.release();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
get swipeTo() {
|
|
102
|
+
return swipeEdge(this.swipeToward, isRtl(this));
|
|
103
|
+
}
|
|
84
104
|
slide(travel) {
|
|
85
|
-
const
|
|
86
|
-
|
|
105
|
+
const item = this.partNamed('item');
|
|
106
|
+
const { x, y } = edgeOffset(this.swipeTo, travel);
|
|
107
|
+
item?.style.setProperty('--toast-swipe-x', x === 0 ? '' : `${Math.round(x)}px`);
|
|
108
|
+
item?.style.setProperty('--toast-swipe-y', y === 0 ? '' : `${Math.round(y)}px`);
|
|
87
109
|
}
|
|
88
110
|
wait(waiting) {
|
|
89
111
|
if (waiting) {
|
|
@@ -133,12 +155,14 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
|
|
|
133
155
|
else {
|
|
134
156
|
this.resume();
|
|
135
157
|
}
|
|
158
|
+
this.holdForRegion();
|
|
136
159
|
}
|
|
137
160
|
this.addEventListener('keydown', this.handleKeydown);
|
|
138
161
|
}
|
|
139
162
|
disconnectedCallback() {
|
|
140
163
|
super.disconnectedCallback();
|
|
141
164
|
this.holding = false;
|
|
165
|
+
this.regionHolding = false;
|
|
142
166
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
143
167
|
}
|
|
144
168
|
willUpdate(changed) {
|
|
@@ -156,6 +180,12 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
|
|
|
156
180
|
if (changed.has('waiting')) {
|
|
157
181
|
this.wait(this.waiting);
|
|
158
182
|
}
|
|
183
|
+
if (changed.has('regionHeld')) {
|
|
184
|
+
this.holdForRegion();
|
|
185
|
+
}
|
|
186
|
+
if (changed.has('swipeToward')) {
|
|
187
|
+
this.partNamed('item')?.style.setProperty('touch-action', swipeAxis(this.swipeTo) === 'y' ? 'pan-x' : '');
|
|
188
|
+
}
|
|
159
189
|
}
|
|
160
190
|
assigned(name) {
|
|
161
191
|
return (this.renderRoot
|
|
@@ -229,6 +259,12 @@ __decorate([
|
|
|
229
259
|
__decorate([
|
|
230
260
|
property({ type: Boolean })
|
|
231
261
|
], HelixHeadlessToastItem.prototype, "open", null);
|
|
262
|
+
__decorate([
|
|
263
|
+
property({ attribute: false, type: Boolean })
|
|
264
|
+
], HelixHeadlessToastItem.prototype, "regionHeld", void 0);
|
|
265
|
+
__decorate([
|
|
266
|
+
property({ attribute: false })
|
|
267
|
+
], HelixHeadlessToastItem.prototype, "swipeToward", void 0);
|
|
232
268
|
__decorate([
|
|
233
269
|
property({ reflect: true })
|
|
234
270
|
], HelixHeadlessToastItem.prototype, "tone", void 0);
|
|
@@ -34,7 +34,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
34
34
|
* to render them from: it appends a message of the region's own tier and
|
|
35
35
|
* removes it once it has been dismissed and has finished leaving. Messages
|
|
36
36
|
* you render yourself stay yours, and the region never removes them. A touch
|
|
37
|
-
* or a pen swipes a message away toward the end of the line
|
|
37
|
+
* or a pen swipes a message away toward the end of the line, or toward the
|
|
38
|
+
* edge `swipe-direction` names.
|
|
39
|
+
*
|
|
40
|
+
* **Every message's timer waits while a pointer or focus is in the stack**, as
|
|
41
|
+
* React Aria's and Radix's regions do, so a reader moving from one message to
|
|
42
|
+
* the next does not lose the one behind them.
|
|
38
43
|
*
|
|
39
44
|
* **The stack is in the top layer.** The `region` part is a `popover="manual"`
|
|
40
45
|
* shown while a message is showing, and shown again above a modal dialog that
|
|
@@ -127,6 +132,11 @@ export declare class HelixHeadlessToast extends HelixElement {
|
|
|
127
132
|
* departures, which is what `orientation` does.
|
|
128
133
|
*/
|
|
129
134
|
placement?: 'bottom-center' | 'bottom-end' | 'bottom-start' | 'top-center' | 'top-end' | 'top-start';
|
|
135
|
+
/**
|
|
136
|
+
* The edge a touch or a pen swipes a message toward to dismiss it: `end`,
|
|
137
|
+
* the end of the line, or `start`, `top` or `bottom`. Unset is `end`.
|
|
138
|
+
*/
|
|
139
|
+
swipeDirection?: 'bottom' | 'end' | 'start' | 'top';
|
|
130
140
|
protected willUpdate(changed: PropertyValues): void;
|
|
131
141
|
/** What the polite region is saying. */
|
|
132
142
|
private said;
|
|
@@ -190,6 +200,19 @@ export declare class HelixHeadlessToast extends HelixElement {
|
|
|
190
200
|
private watcher;
|
|
191
201
|
/** The messages that are showing, in order. */
|
|
192
202
|
private get showing();
|
|
203
|
+
/** Whether a pointer is over the stack. */
|
|
204
|
+
private pointerIn;
|
|
205
|
+
/** Whether focus is in the stack. */
|
|
206
|
+
private focusIn;
|
|
207
|
+
/**
|
|
208
|
+
* Tells every message whether the stack holds its timer and which way it
|
|
209
|
+
* swipes, which only the region knows.
|
|
210
|
+
*/
|
|
211
|
+
private inform;
|
|
212
|
+
private readonly handlePointerEnter;
|
|
213
|
+
private readonly handlePointerLeave;
|
|
214
|
+
/** Focus leaving the stack, and not moving between its messages. */
|
|
215
|
+
private readonly handleFocusOut;
|
|
193
216
|
/** The messages `max-visible` holds back. */
|
|
194
217
|
private readonly held;
|
|
195
218
|
/**
|
|
@@ -40,12 +40,34 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
this.made = new WeakSet();
|
|
43
|
+
this.pointerIn = false;
|
|
44
|
+
this.focusIn = false;
|
|
45
|
+
this.handlePointerEnter = () => {
|
|
46
|
+
this.pointerIn = true;
|
|
47
|
+
this.inform();
|
|
48
|
+
};
|
|
49
|
+
this.handlePointerLeave = () => {
|
|
50
|
+
this.pointerIn = false;
|
|
51
|
+
this.inform();
|
|
52
|
+
};
|
|
53
|
+
this.handleFocusOut = (event) => {
|
|
54
|
+
const next = event.relatedTarget;
|
|
55
|
+
if (next instanceof Node && deepContains(this, next)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.focusIn = false;
|
|
59
|
+
this.inform();
|
|
60
|
+
};
|
|
43
61
|
this.held = new WeakSet();
|
|
44
62
|
this.handleFocusIn = (event) => {
|
|
45
63
|
const from = event.relatedTarget;
|
|
46
64
|
if (from instanceof HTMLElement && !deepContains(this, from)) {
|
|
47
65
|
this.focusOrigin = from;
|
|
48
66
|
}
|
|
67
|
+
if (!this.focusIn) {
|
|
68
|
+
this.focusIn = true;
|
|
69
|
+
this.inform();
|
|
70
|
+
}
|
|
49
71
|
};
|
|
50
72
|
this.handleDismiss = (event) => {
|
|
51
73
|
const item = event.target;
|
|
@@ -177,6 +199,22 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
177
199
|
get showing() {
|
|
178
200
|
return [...this.children].filter((one) => one instanceof HTMLElement && !one.hidden && !this.held.has(one));
|
|
179
201
|
}
|
|
202
|
+
inform() {
|
|
203
|
+
const held = this.pointerIn || this.focusIn;
|
|
204
|
+
for (const one of this.children) {
|
|
205
|
+
const item = one;
|
|
206
|
+
if (!('regionHeld' in item)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (item.regionHeld !== held) {
|
|
210
|
+
item.regionHeld = held;
|
|
211
|
+
}
|
|
212
|
+
const toward = this.swipeDirection ?? 'end';
|
|
213
|
+
if (item.swipeToward !== toward) {
|
|
214
|
+
item.swipeToward = toward;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
180
218
|
queue() {
|
|
181
219
|
const max = this.maxVisible;
|
|
182
220
|
const line = [...this.children].filter((one) => one instanceof HTMLElement && one.open !== false);
|
|
@@ -267,11 +305,15 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
267
305
|
connectedCallback() {
|
|
268
306
|
super.connectedCallback();
|
|
269
307
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
308
|
+
this.addEventListener('focusout', this.handleFocusOut);
|
|
309
|
+
this.addEventListener('pointerenter', this.handlePointerEnter);
|
|
310
|
+
this.addEventListener('pointerleave', this.handlePointerLeave);
|
|
270
311
|
this.addEventListener('helix-dismiss', this.handleDismiss);
|
|
271
312
|
document.addEventListener('keydown', this.handleHotkey);
|
|
272
313
|
this.watcher = new MutationObserver((records) => {
|
|
273
314
|
const added = records.flatMap((record) => [...record.addedNodes]);
|
|
274
315
|
this.restack();
|
|
316
|
+
this.inform();
|
|
275
317
|
this.place(added);
|
|
276
318
|
this.tell(added);
|
|
277
319
|
this.sweep();
|
|
@@ -289,7 +331,12 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
289
331
|
disconnectedCallback() {
|
|
290
332
|
super.disconnectedCallback();
|
|
291
333
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
334
|
+
this.removeEventListener('focusout', this.handleFocusOut);
|
|
335
|
+
this.removeEventListener('pointerenter', this.handlePointerEnter);
|
|
336
|
+
this.removeEventListener('pointerleave', this.handlePointerLeave);
|
|
292
337
|
this.removeEventListener('helix-dismiss', this.handleDismiss);
|
|
338
|
+
this.pointerIn = false;
|
|
339
|
+
this.focusIn = false;
|
|
293
340
|
document.removeEventListener('keydown', this.handleHotkey);
|
|
294
341
|
this.watcher?.disconnect();
|
|
295
342
|
this.watcher = undefined;
|
|
@@ -297,6 +344,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
297
344
|
firstUpdated(changed) {
|
|
298
345
|
super.firstUpdated(changed);
|
|
299
346
|
this.queue();
|
|
347
|
+
this.inform();
|
|
300
348
|
this.place();
|
|
301
349
|
}
|
|
302
350
|
updated(changed) {
|
|
@@ -305,6 +353,9 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
305
353
|
this.restack();
|
|
306
354
|
this.place();
|
|
307
355
|
}
|
|
356
|
+
if (changed.has('swipeDirection')) {
|
|
357
|
+
this.inform();
|
|
358
|
+
}
|
|
308
359
|
}
|
|
309
360
|
render() {
|
|
310
361
|
return html `<div part="region" popover="manual" tabindex="-1"><slot></slot><span part="live" role="status">${this.said}</span><span part="live" role="alert">${this.urgent}</span></div>`;
|
|
@@ -323,6 +374,9 @@ __decorate([
|
|
|
323
374
|
__decorate([
|
|
324
375
|
property({ reflect: true })
|
|
325
376
|
], HelixHeadlessToast.prototype, "placement", void 0);
|
|
377
|
+
__decorate([
|
|
378
|
+
property({ attribute: 'swipe-direction' })
|
|
379
|
+
], HelixHeadlessToast.prototype, "swipeDirection", void 0);
|
|
326
380
|
__decorate([
|
|
327
381
|
state()
|
|
328
382
|
], HelixHeadlessToast.prototype, "said", void 0);
|
|
@@ -44,6 +44,17 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
|
|
|
44
44
|
* least distance that brings it on screen, so the stop that points at it is
|
|
45
45
|
* on screen too.
|
|
46
46
|
*
|
|
47
|
+
* **A stop's `effect` runs as the tour reaches it**, before it is shown, and
|
|
48
|
+
* the stop waits for a promise it returns, with `:state(preparing)` on the
|
|
49
|
+
* tour meanwhile. A function it returns runs when the tour leaves the stop.
|
|
50
|
+
*
|
|
51
|
+
* **A press outside the current stop and its target does nothing by
|
|
52
|
+
* default**, so a reader can use the page the tour is describing. Set
|
|
53
|
+
* `outside-press="close"` to end the tour on one, through `helix-before-close`
|
|
54
|
+
* as Skip does, or `outside-press="block"` to refuse it, so the stop and its
|
|
55
|
+
* target are all a pointer can reach while the tour runs, as Zag's
|
|
56
|
+
* `preventInteraction` does.
|
|
57
|
+
*
|
|
47
58
|
* **Keyboard.** Focus is on the current stop's panel, which is not in the
|
|
48
59
|
* `Tab` order itself.
|
|
49
60
|
*
|
|
@@ -89,6 +100,7 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
|
|
|
89
100
|
* @cssproperty --tour-spotlight-y - The lit box's top edge, from the window's top edge, in pixels. Written on the spotlight while it lights a target.
|
|
90
101
|
*
|
|
91
102
|
* @cssstate open - While the tour is running.
|
|
103
|
+
* @cssstate preparing - While the tour waits for the current stop's `effect` before showing the stop.
|
|
92
104
|
* @cssstate waiting - While the tour waits at a `wait` stop for its target to be on the page.
|
|
93
105
|
*/
|
|
94
106
|
export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
|
|
@@ -134,6 +146,12 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
|
|
|
134
146
|
* Label for the control that goes on.
|
|
135
147
|
*/
|
|
136
148
|
nextLabel: string;
|
|
149
|
+
/**
|
|
150
|
+
* What a press outside the current stop and its target does while the tour
|
|
151
|
+
* runs. Unset, nothing: the page stays usable. `close` ends the tour, as
|
|
152
|
+
* Skip does, and `block` refuses the press.
|
|
153
|
+
*/
|
|
154
|
+
outsidePress?: 'block' | 'close';
|
|
137
155
|
/**
|
|
138
156
|
* Draws each stop's place in the run in its panel, as well as saying it.
|
|
139
157
|
*/
|
|
@@ -201,6 +219,22 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
|
|
|
201
219
|
connectedCallback(): void;
|
|
202
220
|
disconnectedCallback(): void;
|
|
203
221
|
protected willUpdate(changed: PropertyValues): void;
|
|
222
|
+
/**
|
|
223
|
+
* Answers a press outside the current stop and its target, as
|
|
224
|
+
* `outside-press` asks. A press on the tour's own parts, the stop or the
|
|
225
|
+
* target is the reader using the tour.
|
|
226
|
+
*/
|
|
227
|
+
private readonly handleOutside;
|
|
228
|
+
/** The stop whose `effect` has run, and the function to run on leaving it. */
|
|
229
|
+
private entered;
|
|
230
|
+
/** The stop held back while its `effect` settles. */
|
|
231
|
+
private preparing;
|
|
232
|
+
/**
|
|
233
|
+
* Runs the current stop's `effect` as the tour reaches it, and the last
|
|
234
|
+
* one's leaving function as it leaves, and holds the stop back while a
|
|
235
|
+
* promise settles.
|
|
236
|
+
*/
|
|
237
|
+
private enter;
|
|
204
238
|
protected updated(changed: PropertyValues): void;
|
|
205
239
|
/** The target last scrolled into view, so a render that moved nothing scrolls nothing. */
|
|
206
240
|
private revealed;
|
|
@@ -59,6 +59,25 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
59
59
|
this.handleViewport = () => {
|
|
60
60
|
this.light();
|
|
61
61
|
};
|
|
62
|
+
this.handleOutside = (event) => {
|
|
63
|
+
const mode = this.outsidePress;
|
|
64
|
+
if (!this.open || !mode) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const path = event.composedPath();
|
|
68
|
+
const stop = this.current;
|
|
69
|
+
const target = stop?.type === undefined ? stop?.target : undefined;
|
|
70
|
+
if (path.includes(this) || (target && path.includes(target))) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (mode === 'block') {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
}
|
|
77
|
+
else if (event.type === 'pointerdown') {
|
|
78
|
+
this.skip();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
62
81
|
this.followed = false;
|
|
63
82
|
this.veiled = false;
|
|
64
83
|
}
|
|
@@ -122,6 +141,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
122
141
|
this.addEventListener('helix-tour-ask', this.handleAsk);
|
|
123
142
|
this.addEventListener('keydown', this.handleArrows);
|
|
124
143
|
document.addEventListener('keydown', this.handleKeydown);
|
|
144
|
+
document.addEventListener('pointerdown', this.handleOutside, true);
|
|
145
|
+
document.addEventListener('click', this.handleOutside, true);
|
|
125
146
|
window.addEventListener('resize', this.handleViewport, { passive: true });
|
|
126
147
|
window.addEventListener('scroll', this.handleViewport, { capture: true, passive: true });
|
|
127
148
|
if (this.hasUpdated) {
|
|
@@ -133,6 +154,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
133
154
|
this.removeEventListener('helix-tour-ask', this.handleAsk);
|
|
134
155
|
this.removeEventListener('keydown', this.handleArrows);
|
|
135
156
|
document.removeEventListener('keydown', this.handleKeydown);
|
|
157
|
+
document.removeEventListener('pointerdown', this.handleOutside, true);
|
|
158
|
+
document.removeEventListener('click', this.handleOutside, true);
|
|
159
|
+
this.enter(undefined);
|
|
136
160
|
window.removeEventListener('resize', this.handleViewport);
|
|
137
161
|
window.removeEventListener('scroll', this.handleViewport, { capture: true });
|
|
138
162
|
this.sizer?.disconnect();
|
|
@@ -149,8 +173,67 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
149
173
|
role: this.open ? 'region' : '',
|
|
150
174
|
});
|
|
151
175
|
}
|
|
176
|
+
enter(stop) {
|
|
177
|
+
if (stop === this.entered?.stop) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const left = this.entered;
|
|
181
|
+
this.entered = stop ? { stop } : undefined;
|
|
182
|
+
left?.leave?.();
|
|
183
|
+
this.preparing = undefined;
|
|
184
|
+
this.toggleState('preparing', false);
|
|
185
|
+
const effect = stop?.effect;
|
|
186
|
+
if (!stop || !effect) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const ran = effect({
|
|
190
|
+
index: this.index,
|
|
191
|
+
next: () => this.next(),
|
|
192
|
+
skip: () => this.skip(),
|
|
193
|
+
target: stop.target,
|
|
194
|
+
});
|
|
195
|
+
const keep = (leave) => {
|
|
196
|
+
if (typeof leave !== 'function') {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (this.entered?.stop === stop) {
|
|
200
|
+
this.entered.leave = leave;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
;
|
|
204
|
+
leave();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
if (typeof ran?.then !== 'function') {
|
|
208
|
+
keep(ran);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.preparing = stop;
|
|
212
|
+
this.toggleState('preparing', true);
|
|
213
|
+
void ran
|
|
214
|
+
.then(keep, () => undefined)
|
|
215
|
+
.then(() => {
|
|
216
|
+
if (this.preparing !== stop) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this.preparing = undefined;
|
|
220
|
+
this.toggleState('preparing', false);
|
|
221
|
+
this.requestUpdate();
|
|
222
|
+
void this.updateComplete.then(() => {
|
|
223
|
+
const active = deepActiveElement();
|
|
224
|
+
const waiting = active === null ||
|
|
225
|
+
active === document.body ||
|
|
226
|
+
active === this.returnFocus ||
|
|
227
|
+
deepContains(this, active);
|
|
228
|
+
if (waiting) {
|
|
229
|
+
this.focusCurrent();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
152
234
|
updated(changed) {
|
|
153
235
|
super.updated(changed);
|
|
236
|
+
this.enter(this.open ? this.current : undefined);
|
|
154
237
|
this.reveal();
|
|
155
238
|
this.settle();
|
|
156
239
|
this.light();
|
|
@@ -228,7 +311,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
228
311
|
const at = Math.max(0, Math.min(this.index, stops.length - 1));
|
|
229
312
|
stops.forEach((stop, index) => {
|
|
230
313
|
whenHydrated(stop, () => {
|
|
231
|
-
stop.current = this.open && index === at;
|
|
314
|
+
stop.current = this.open && index === at && this.preparing !== stop;
|
|
232
315
|
stop.first = index === 0;
|
|
233
316
|
stop.last = index === stops.length - 1;
|
|
234
317
|
stop.position = formatWords(this, 'progressLabel', {
|
|
@@ -253,7 +336,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
253
336
|
}
|
|
254
337
|
get lit() {
|
|
255
338
|
const stop = this.current;
|
|
256
|
-
return this.open && stop?.type === undefined
|
|
339
|
+
return this.open && stop?.type === undefined && this.preparing !== stop
|
|
340
|
+
? stop?.target
|
|
341
|
+
: undefined;
|
|
257
342
|
}
|
|
258
343
|
get current() {
|
|
259
344
|
const stops = this.stops;
|
|
@@ -285,7 +370,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
285
370
|
});
|
|
286
371
|
}
|
|
287
372
|
light() {
|
|
288
|
-
const
|
|
373
|
+
const shown = this.current;
|
|
374
|
+
const stop = this.open && this.preparing !== shown ? shown : undefined;
|
|
289
375
|
const veiled = stop !== undefined && stop.type !== 'wait' && (stop.type !== undefined || !stop.target);
|
|
290
376
|
if (veiled !== this.veiled) {
|
|
291
377
|
this.veiled = veiled;
|
|
@@ -355,6 +441,9 @@ __decorate([
|
|
|
355
441
|
__decorate([
|
|
356
442
|
property({ attribute: 'next-label' })
|
|
357
443
|
], HelixHeadlessTour.prototype, "nextLabel", void 0);
|
|
444
|
+
__decorate([
|
|
445
|
+
property({ attribute: 'outside-press' })
|
|
446
|
+
], HelixHeadlessTour.prototype, "outsidePress", void 0);
|
|
358
447
|
__decorate([
|
|
359
448
|
property({ type: Boolean })
|
|
360
449
|
], HelixHeadlessTour.prototype, "progress", void 0);
|