@fusion.dev/helix-headless 0.158.0 → 0.160.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 +1925 -174
- 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/date-input/root/date-input-root.d.ts +108 -19
- package/dist/date-input/root/date-input-root.js +112 -13
- package/dist/date-picker/root/date-picker-root.d.ts +81 -16
- package/dist/date-picker/root/date-picker-root.js +140 -36
- package/dist/element-events.generated.js +9 -1
- package/dist/format-date/fields.js +1 -0
- package/dist/format-date/options.d.ts +3 -1
- package/dist/format-date/options.js +4 -1
- package/dist/format-date/root/format-date-root.d.ts +27 -11
- package/dist/format-date/root/format-date-root.js +6 -0
- 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/listbox/root/listbox-root.js +5 -0
- package/dist/notifications/notification/notification.d.ts +1 -0
- package/dist/notifications/notification/notification.js +2 -1
- package/dist/slider/root/slider-root.d.ts +54 -3
- package/dist/slider/root/slider-root.js +124 -2
- 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/time-input/root/time-input-root.d.ts +55 -7
- package/dist/time-input/root/time-input-root.js +70 -10
- package/dist/time-picker/root/time-picker-root.d.ts +27 -5
- package/dist/time-picker/root/time-picker-root.js +52 -13
- 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/toggle/group/toggle-group.d.ts +18 -1
- package/dist/toggle/group/toggle-group.js +8 -2
- package/dist/toggle/root/toggle-root.d.ts +2 -2
- package/dist/toggle/root/toggle-root.js +2 -1
- 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 +1119 -218
- package/types/vue.d.ts +278 -51
- package/types/words.d.ts +72 -0
|
@@ -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);
|
|
@@ -37,7 +37,7 @@ declare const HelixHeadlessToggleGroup_base: (new (...args: any[]) => import("@f
|
|
|
37
37
|
* | Right, Down | Moves to the next item, from the last back to the first. In a single group, chooses it. |
|
|
38
38
|
* | Left, Up | Moves to the item before, from the first round to the last. In a single group, chooses it. |
|
|
39
39
|
* | Home, End | Moves to the first or the last item. In a single group, chooses it. |
|
|
40
|
-
* | Space, Enter | Chooses the focused item, or presses or releases it in a multiple group. |
|
|
40
|
+
* | Space, Enter | Chooses the focused item, or presses or releases it in a multiple group. With `allow-empty-selection`, takes back the chosen item. |
|
|
41
41
|
*
|
|
42
42
|
* Left and Right swap on a right-to-left page, and a disabled item is passed
|
|
43
43
|
* over. A single group is a `radiogroup` of items with `role="radio"` and
|
|
@@ -117,6 +117,15 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
117
117
|
* `MixinCollection` records for the same reason.
|
|
118
118
|
*/
|
|
119
119
|
private adopt;
|
|
120
|
+
/**
|
|
121
|
+
* The items whose `selected` this group has written, which `adopt` passes
|
|
122
|
+
* over: the mark is this group's answer, and the markup declared nothing.
|
|
123
|
+
*
|
|
124
|
+
* Measured before this: in a group with no default, choosing List and then
|
|
125
|
+
* resetting the form left List chosen and submitted, because `adopt` read
|
|
126
|
+
* the mark `sync` had put on it as a declaration and made it the default.
|
|
127
|
+
*/
|
|
128
|
+
private readonly marked;
|
|
120
129
|
/** Marks the chosen item, separately from placing the indicator. */
|
|
121
130
|
private sync;
|
|
122
131
|
/**
|
|
@@ -139,6 +148,14 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
139
148
|
private get chosenItem();
|
|
140
149
|
/** Whether several items can be pressed at once. */
|
|
141
150
|
private get multiple();
|
|
151
|
+
/**
|
|
152
|
+
* Whether a press on the chosen item, or Space or Enter on it, takes it
|
|
153
|
+
* back so nothing is chosen, as React Aria's `ToggleButtonGroup` and
|
|
154
|
+
* Radix's `ToggleGroup` do in a single group. Absent, a single group always
|
|
155
|
+
* keeps its choice once it has one, as a radio group does. A multiple group
|
|
156
|
+
* releases a pressed item either way.
|
|
157
|
+
*/
|
|
158
|
+
allowEmptySelection: boolean;
|
|
142
159
|
/**
|
|
143
160
|
* Whether the group holds a tab stop of its own.
|
|
144
161
|
*
|
|
@@ -11,6 +11,7 @@ import { HelixHeadlessToggle } from '../root/toggle-root.js';
|
|
|
11
11
|
let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.marked = new WeakSet();
|
|
14
15
|
this.lastFocused = undefined;
|
|
15
16
|
this.noteFocus = (event) => {
|
|
16
17
|
const item = event.target;
|
|
@@ -19,6 +20,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
19
20
|
this.sync();
|
|
20
21
|
}
|
|
21
22
|
};
|
|
23
|
+
this.allowEmptySelection = false;
|
|
22
24
|
this.focusable = true;
|
|
23
25
|
this.indicator = new IndicatorController(this, {
|
|
24
26
|
active: () => this.chosenItem,
|
|
@@ -40,7 +42,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
40
42
|
}
|
|
41
43
|
const value = event.detail.value;
|
|
42
44
|
if (!this.multiple) {
|
|
43
|
-
this.requestValue(value);
|
|
45
|
+
this.requestValue(this.allowEmptySelection && value === this.value ? '' : value);
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
const next = toggleValue(this.values, value, this.items.map((item) => item.value));
|
|
@@ -63,7 +65,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
63
65
|
if (this.value !== '' || this.children === undefined) {
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
|
-
const declared = this.items.find((item) => item.selected);
|
|
68
|
+
const declared = this.items.find((item) => item.defaultSelected || (item.selected && !this.marked.has(item)));
|
|
67
69
|
if (declared) {
|
|
68
70
|
this.defaultValue = declared.value;
|
|
69
71
|
}
|
|
@@ -73,6 +75,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
73
75
|
const pressed = new Set(this.values);
|
|
74
76
|
const entry = this.focusable ? this.entry() : undefined;
|
|
75
77
|
for (const item of this.items) {
|
|
78
|
+
this.marked.add(item);
|
|
76
79
|
item.selected = item === active;
|
|
77
80
|
item.pressed = this.multiple ? pressed.has(item.value) : undefined;
|
|
78
81
|
item.focusable = item === entry;
|
|
@@ -192,6 +195,9 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
192
195
|
return html `${inside ? nothing : label}<div part="row"><slot name="start"></slot><div part="track">${inside ? label : nothing}<div part="indicator" aria-hidden="true"></div><slot @slotchange=${this.handleItems}></slot></div><slot name="end"></slot></div>${this.renderHint()}${this.renderFeedback()}`;
|
|
193
196
|
}
|
|
194
197
|
};
|
|
198
|
+
__decorate([
|
|
199
|
+
property({ attribute: 'allow-empty-selection', type: Boolean })
|
|
200
|
+
], HelixHeadlessToggleGroup.prototype, "allowEmptySelection", void 0);
|
|
195
201
|
__decorate([
|
|
196
202
|
property({ attribute: false })
|
|
197
203
|
], HelixHeadlessToggleGroup.prototype, "focusable", void 0);
|
|
@@ -31,9 +31,9 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
|
|
|
31
31
|
* @event {Event} change - Fired after a toggle button on its own is pressed or released.
|
|
32
32
|
* @event {HelixSelectEvent} helix-select - Fired when the item is chosen. Cancelable: call `preventDefault()` to keep the current choice.
|
|
33
33
|
*
|
|
34
|
-
* @csspart item - The radio button.
|
|
34
|
+
* @csspart item - The button: a radio in a group where one is chosen, and a button with `aria-pressed` on its own or in a group where several can be.
|
|
35
35
|
*
|
|
36
|
-
* @cssstate pressed - The item is pressed, in a group where several can be.
|
|
36
|
+
* @cssstate pressed - The item is pressed, on its own or in a group where several can be.
|
|
37
37
|
* @cssstate selected - The item is chosen.
|
|
38
38
|
*/
|
|
39
39
|
export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
|
|
@@ -4,7 +4,7 @@ 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 { customElement, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, customElement, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(HelixElement) {
|
|
@@ -39,6 +39,7 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
39
39
|
firstUpdated(changed) {
|
|
40
40
|
super.firstUpdated(changed);
|
|
41
41
|
this.finishHydration();
|
|
42
|
+
auditName(this, () => this.partNamed('item'), 'Give it words in its default slot, an `aria-label`, or a label on the icon it holds.');
|
|
42
43
|
}
|
|
43
44
|
render() {
|
|
44
45
|
const alone = this.alone;
|
|
@@ -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);
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
import { HelixElement, type Placement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { HelixElement, OpenPhaseController, type Placement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../button/index.js';
|
|
4
|
+
/** What a stop's `effect` is given: the stop's place, its target, and the tour's moves. */
|
|
5
|
+
export interface TourStopContext {
|
|
6
|
+
/** The stop's index in the tour. */
|
|
7
|
+
index: number;
|
|
8
|
+
/** Goes on to the next stop, as Next does. */
|
|
9
|
+
next(): void;
|
|
10
|
+
/** Ends the tour early, as Skip does. */
|
|
11
|
+
skip(): void;
|
|
12
|
+
/** The element `for` names, now, or `null` where it is not on the page yet. */
|
|
13
|
+
target: HTMLElement | null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A stop's `effect`: run as the tour reaches the stop, before it is shown. A
|
|
17
|
+
* promise holds the stop back until it settles, and a function returned, or
|
|
18
|
+
* resolved, runs when the tour leaves the stop.
|
|
19
|
+
*/
|
|
20
|
+
export type TourStopEffect = (context: TourStopContext) => void | (() => void) | Promise<void | (() => void)>;
|
|
4
21
|
/**
|
|
5
22
|
* @summary One stop on a tour: the element it points at, and a panel of content about it.
|
|
6
23
|
*
|
|
@@ -30,6 +47,13 @@ import '../../button/index.js';
|
|
|
30
47
|
* get Back, Next, and Skip, already wired. Slot your own `actions` and drive
|
|
31
48
|
* them with `next()`, `back()`, and `skip()` on the root.
|
|
32
49
|
*
|
|
50
|
+
* **Set `effect` to prepare the page for a stop**, such as opening the menu
|
|
51
|
+
* it points into or loading what it describes. It is a function, set from
|
|
52
|
+
* script, that the tour runs as it reaches the stop. Return a promise and the
|
|
53
|
+
* stop waits for it, and the tour has `:state(preparing)` meanwhile; return a
|
|
54
|
+
* function, or resolve to one, and the tour runs it when it leaves the stop.
|
|
55
|
+
* Zag's tour step has the same `effect`.
|
|
56
|
+
*
|
|
33
57
|
* **`for` is an id, as with `Tooltip`**, so you can point at a target elsewhere
|
|
34
58
|
* on the page from markup. It resolves against the stop's root node, so a stop
|
|
35
59
|
* inside a shadow tree points at a target in that tree.
|
|
@@ -81,6 +105,8 @@ import '../../button/index.js';
|
|
|
81
105
|
* @csspart skip-spinner - The ring `skip` turns while it is pending.
|
|
82
106
|
* @csspart skip-text - The words on that button, trimmed to their capitals and baseline.
|
|
83
107
|
*
|
|
108
|
+
* @cssstate entering - From the moment the stop is shown until its entrance has finished.
|
|
109
|
+
* @cssstate exiting - From the moment the stop is left until its exit has finished.
|
|
84
110
|
* @cssstate placed-bottom - The panel sits below its target.
|
|
85
111
|
* @cssstate placed-left - The panel sits to the left of its target.
|
|
86
112
|
* @cssstate placed-right - The panel sits to the right of its target.
|
|
@@ -110,6 +136,11 @@ export declare class HelixHeadlessTourStop extends HelixElement {
|
|
|
110
136
|
* Label for the control that goes on from the last stop.
|
|
111
137
|
*/
|
|
112
138
|
doneLabel: string;
|
|
139
|
+
/**
|
|
140
|
+
* Run as the tour reaches the stop, before it is shown: see the description
|
|
141
|
+
* above. A function, so it is set from script and has no attribute.
|
|
142
|
+
*/
|
|
143
|
+
effect?: TourStopEffect;
|
|
113
144
|
/**
|
|
114
145
|
* Whether the stop is the first one, with no stop before it.
|
|
115
146
|
*/
|
|
@@ -165,6 +196,7 @@ export declare class HelixHeadlessTourStop extends HelixElement {
|
|
|
165
196
|
* makes.
|
|
166
197
|
*/
|
|
167
198
|
private side?;
|
|
199
|
+
protected readonly phases: OpenPhaseController;
|
|
168
200
|
/** The element the stop points at, or `null` when no element has the `for` id. */
|
|
169
201
|
get target(): HTMLElement | null;
|
|
170
202
|
private readonly floating;
|
|
@@ -4,7 +4,7 @@ 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 { arrowReach, customElement, elementById, HelixElement, HelixTourAskEvent, PopoverController, reflectElements, sideOf, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { arrowReach, customElement, elementById, HelixElement, HelixTourAskEvent, OpenPhaseController, PopoverController, reflectElements, sideOf, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
@@ -25,6 +25,14 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
25
25
|
this.position = '';
|
|
26
26
|
this.progress = false;
|
|
27
27
|
this.skipLabel = 'Skip';
|
|
28
|
+
this.phases = new OpenPhaseController(this, {
|
|
29
|
+
mark: {
|
|
30
|
+
entering: (on) => this.toggleState('entering', on),
|
|
31
|
+
exiting: (on) => this.toggleState('exiting', on),
|
|
32
|
+
},
|
|
33
|
+
open: () => this.current && this.type !== 'wait',
|
|
34
|
+
surface: () => this.partNamed('bubble'),
|
|
35
|
+
});
|
|
28
36
|
this.floating = new PopoverController(this, {
|
|
29
37
|
anchor: () => this.target,
|
|
30
38
|
arrow: () => this.partNamed('arrow'),
|
|
@@ -154,6 +162,9 @@ __decorate([
|
|
|
154
162
|
words,
|
|
155
163
|
property({ attribute: 'done-label' })
|
|
156
164
|
], HelixHeadlessTourStop.prototype, "doneLabel", void 0);
|
|
165
|
+
__decorate([
|
|
166
|
+
property({ attribute: false })
|
|
167
|
+
], HelixHeadlessTourStop.prototype, "effect", void 0);
|
|
157
168
|
__decorate([
|
|
158
169
|
property({ reflect: true, type: Boolean })
|
|
159
170
|
], HelixHeadlessTourStop.prototype, "first", void 0);
|