@glowhop/core-tour 1.3.1 → 1.5.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/CHANGELOG.md +193 -0
- package/README.md +3 -3
- package/builder/index.d.ts +15 -16
- package/config/index.d.ts +1 -1
- package/config/index.js +164 -135
- package/config/types.d.ts +27 -19
- package/definition/step-props.d.ts +9 -1
- package/definition/types.d.ts +10 -10
- package/definition/workflow-definition.d.ts +4 -7
- package/dom/tour-view-driver.d.ts +134 -3
- package/elements/base.d.ts +24 -2
- package/elements/popover.d.ts +23 -6
- package/index.d.ts +1 -1
- package/index.js +896 -503
- package/package.json +6 -4
- package/runtime/active-step.d.ts +18 -55
- package/runtime/tour-controller.d.ts +48 -12
- package/state/focus-guard.d.ts +15 -0
- package/types/index.d.ts +187 -86
- package/utils/options.d.ts +13 -1
- package/utils/utils.d.ts +16 -8
package/index.js
CHANGED
|
@@ -12,17 +12,6 @@ function cloneStepProps(props) {
|
|
|
12
12
|
...props.popover,
|
|
13
13
|
animation: props.popover.animation && { ...props.popover.animation },
|
|
14
14
|
arrow: props.popover.arrow && { ...props.popover.arrow },
|
|
15
|
-
keyboardShortcuts: props.popover.keyboardShortcuts && {
|
|
16
|
-
previous: props.popover.keyboardShortcuts.previous && [
|
|
17
|
-
...props.popover.keyboardShortcuts.previous
|
|
18
|
-
],
|
|
19
|
-
advance: props.popover.keyboardShortcuts.advance && [
|
|
20
|
-
...props.popover.keyboardShortcuts.advance
|
|
21
|
-
],
|
|
22
|
-
cancel: props.popover.keyboardShortcuts.cancel && [
|
|
23
|
-
...props.popover.keyboardShortcuts.cancel
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
15
|
placementTryOrder: props.popover.placementTryOrder && [...props.popover.placementTryOrder]
|
|
27
16
|
},
|
|
28
17
|
indicator: props.indicator && {
|
|
@@ -31,9 +20,29 @@ function cloneStepProps(props) {
|
|
|
31
20
|
placementTryOrder: props.indicator.placementTryOrder && [
|
|
32
21
|
...props.indicator.placementTryOrder
|
|
33
22
|
]
|
|
34
|
-
}
|
|
23
|
+
},
|
|
24
|
+
behavior: props.behavior && {
|
|
25
|
+
...props.behavior,
|
|
26
|
+
missingTarget: props.behavior.missingTarget && { ...props.behavior.missingTarget },
|
|
27
|
+
scroll: props.behavior.scroll && { ...props.behavior.scroll }
|
|
28
|
+
},
|
|
29
|
+
controls: cloneControls(props.controls),
|
|
30
|
+
classNames: cloneClassNames(props.classNames)
|
|
35
31
|
};
|
|
36
32
|
}
|
|
33
|
+
function cloneControls(controls) {
|
|
34
|
+
return controls && Object.freeze({
|
|
35
|
+
previous: cloneControl(controls.previous),
|
|
36
|
+
advance: cloneControl(controls.advance),
|
|
37
|
+
cancel: cloneControl(controls.cancel)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function cloneControl(control) {
|
|
41
|
+
return control && Object.freeze({ state: control.state, keys: control.keys && Object.freeze([...control.keys]) });
|
|
42
|
+
}
|
|
43
|
+
function cloneClassNames(classNames) {
|
|
44
|
+
return classNames && Object.freeze({ ...classNames });
|
|
45
|
+
}
|
|
37
46
|
function freezeStepProps(props) {
|
|
38
47
|
const cloned = cloneStepProps(props);
|
|
39
48
|
if (cloned.data)
|
|
@@ -46,14 +55,6 @@ function freezeStepProps(props) {
|
|
|
46
55
|
Object.freeze(cloned.popover.animation);
|
|
47
56
|
if (cloned.popover?.arrow)
|
|
48
57
|
Object.freeze(cloned.popover.arrow);
|
|
49
|
-
if (cloned.popover?.keyboardShortcuts?.previous)
|
|
50
|
-
Object.freeze(cloned.popover.keyboardShortcuts.previous);
|
|
51
|
-
if (cloned.popover?.keyboardShortcuts?.advance)
|
|
52
|
-
Object.freeze(cloned.popover.keyboardShortcuts.advance);
|
|
53
|
-
if (cloned.popover?.keyboardShortcuts?.cancel)
|
|
54
|
-
Object.freeze(cloned.popover.keyboardShortcuts.cancel);
|
|
55
|
-
if (cloned.popover?.keyboardShortcuts)
|
|
56
|
-
Object.freeze(cloned.popover.keyboardShortcuts);
|
|
57
58
|
if (cloned.popover?.placementTryOrder)
|
|
58
59
|
Object.freeze(cloned.popover.placementTryOrder);
|
|
59
60
|
if (cloned.popover)
|
|
@@ -64,6 +65,12 @@ function freezeStepProps(props) {
|
|
|
64
65
|
Object.freeze(cloned.indicator.placementTryOrder);
|
|
65
66
|
if (cloned.indicator)
|
|
66
67
|
Object.freeze(cloned.indicator);
|
|
68
|
+
if (cloned.behavior?.missingTarget)
|
|
69
|
+
Object.freeze(cloned.behavior.missingTarget);
|
|
70
|
+
if (cloned.behavior?.scroll)
|
|
71
|
+
Object.freeze(cloned.behavior.scroll);
|
|
72
|
+
if (cloned.behavior)
|
|
73
|
+
Object.freeze(cloned.behavior);
|
|
67
74
|
return Object.freeze(cloned);
|
|
68
75
|
}
|
|
69
76
|
// packages/core/src/definition/workflow-definition.ts
|
|
@@ -84,11 +91,6 @@ function freezePopover(options) {
|
|
|
84
91
|
...options,
|
|
85
92
|
animation: freezeAnimation(options.animation),
|
|
86
93
|
arrow: options.arrow && freezeRecord({ ...options.arrow }),
|
|
87
|
-
keyboardShortcuts: options.keyboardShortcuts && freezeRecord({
|
|
88
|
-
previous: options.keyboardShortcuts.previous && freezeRecord([...options.keyboardShortcuts.previous]),
|
|
89
|
-
advance: options.keyboardShortcuts.advance && freezeRecord([...options.keyboardShortcuts.advance]),
|
|
90
|
-
cancel: options.keyboardShortcuts.cancel && freezeRecord([...options.keyboardShortcuts.cancel])
|
|
91
|
-
}),
|
|
92
94
|
placementTryOrder: options.placementTryOrder && freezeRecord([...options.placementTryOrder])
|
|
93
95
|
});
|
|
94
96
|
}
|
|
@@ -103,17 +105,11 @@ function freezeStep(draft) {
|
|
|
103
105
|
return freezeRecord({
|
|
104
106
|
id: draft.id,
|
|
105
107
|
target: draft.target,
|
|
106
|
-
resetPropsOnEnter: draft.resetPropsOnEnter,
|
|
107
108
|
props: freezeStepProps(draft.props),
|
|
108
|
-
behavior: draft.behavior && freezeRecord({
|
|
109
|
-
...draft.behavior,
|
|
110
|
-
scroll: draft.behavior.scroll && freezeRecord({ ...draft.behavior.scroll })
|
|
111
|
-
}),
|
|
112
109
|
actions: freezeRecord(draft.actions.map((action) => action)),
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
cancelAction: draft.cancelAction
|
|
110
|
+
targetEvents: freezeRecord(draft.targetEvents.map((handler) => freezeRecord({ ...handler }))),
|
|
111
|
+
beforeEnter: draft.beforeEnter,
|
|
112
|
+
beforeLeave: draft.beforeLeave
|
|
117
113
|
});
|
|
118
114
|
}
|
|
119
115
|
function freezeOptions(options) {
|
|
@@ -122,8 +118,11 @@ function freezeOptions(options) {
|
|
|
122
118
|
overlay: freezeOverlay(options.overlay),
|
|
123
119
|
popover: freezePopover(options.popover),
|
|
124
120
|
indicator: freezeIndicator(options.indicator),
|
|
121
|
+
classNames: cloneClassNames(options.classNames),
|
|
122
|
+
controls: cloneControls(options.controls),
|
|
125
123
|
behavior: options.behavior && freezeRecord({
|
|
126
124
|
...options.behavior,
|
|
125
|
+
missingTarget: options.behavior.missingTarget && freezeRecord({ ...options.behavior.missingTarget }),
|
|
127
126
|
scroll: options.behavior.scroll && freezeRecord({ ...options.behavior.scroll })
|
|
128
127
|
})
|
|
129
128
|
});
|
|
@@ -133,7 +132,7 @@ function cloneWorkflowStepDraft(definition) {
|
|
|
133
132
|
...definition,
|
|
134
133
|
props: cloneStepProps(definition.props),
|
|
135
134
|
actions: definition.actions.map((action) => action),
|
|
136
|
-
|
|
135
|
+
targetEvents: [...definition.targetEvents]
|
|
137
136
|
};
|
|
138
137
|
}
|
|
139
138
|
function assertUniqueStepIds(name, drafts) {
|
|
@@ -246,21 +245,21 @@ class WorkflowBuilder {
|
|
|
246
245
|
this.currentStep = new WorkflowStepBuilder(this, {
|
|
247
246
|
id: options.id,
|
|
248
247
|
target: options.target,
|
|
249
|
-
resetPropsOnEnter: options.resetPropsOnEnter,
|
|
250
248
|
props: {
|
|
251
249
|
title: options.title,
|
|
252
250
|
content: options.content,
|
|
253
251
|
data: cloneStepProps(options).data,
|
|
254
252
|
overlay: options.overlay,
|
|
255
253
|
popover: options.popover,
|
|
256
|
-
indicator: options.indicator
|
|
254
|
+
indicator: options.indicator,
|
|
255
|
+
behavior: options.behavior,
|
|
256
|
+
controls: options.controls,
|
|
257
|
+
classNames: options.classNames
|
|
257
258
|
},
|
|
258
|
-
behavior: options.behavior,
|
|
259
259
|
actions: [],
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
cancelAction: null
|
|
260
|
+
targetEvents: [],
|
|
261
|
+
beforeEnter: null,
|
|
262
|
+
beforeLeave: null
|
|
264
263
|
});
|
|
265
264
|
return this.currentStep;
|
|
266
265
|
}
|
|
@@ -364,19 +363,14 @@ class WorkflowStepBuilder {
|
|
|
364
363
|
this.draft.actions.push(callback);
|
|
365
364
|
return this;
|
|
366
365
|
}
|
|
367
|
-
|
|
366
|
+
beforeEnter(callback) {
|
|
368
367
|
this.assertActive();
|
|
369
|
-
this.draft.
|
|
368
|
+
this.draft.beforeEnter = callback;
|
|
370
369
|
return this;
|
|
371
370
|
}
|
|
372
|
-
|
|
371
|
+
beforeLeave(callback) {
|
|
373
372
|
this.assertActive();
|
|
374
|
-
this.draft.
|
|
375
|
-
return this;
|
|
376
|
-
}
|
|
377
|
-
beforeCancel(callback) {
|
|
378
|
-
this.assertActive();
|
|
379
|
-
this.draft.cancelAction = callback;
|
|
373
|
+
this.draft.beforeLeave = callback;
|
|
380
374
|
return this;
|
|
381
375
|
}
|
|
382
376
|
onTargetEvent(eventOrEvents, callback) {
|
|
@@ -387,7 +381,7 @@ class WorkflowStepBuilder {
|
|
|
387
381
|
for (const event of events) {
|
|
388
382
|
if (event.length === 0)
|
|
389
383
|
throw new TypeError("event name must not be empty");
|
|
390
|
-
this.draft.
|
|
384
|
+
this.draft.targetEvents.push({
|
|
391
385
|
event,
|
|
392
386
|
callback
|
|
393
387
|
});
|
|
@@ -423,12 +417,34 @@ function isNode(value, context) {
|
|
|
423
417
|
const Node = ownerWindow(context)?.Node ?? globalThis.Node;
|
|
424
418
|
return typeof Node === "function" && value instanceof Node;
|
|
425
419
|
}
|
|
420
|
+
var fixedBoxes = new WeakMap;
|
|
421
|
+
function fixedContainingBlock(document2, root) {
|
|
422
|
+
const view = document2.defaultView;
|
|
423
|
+
const key = `${root.clientWidth} ${root.clientHeight} ${view?.innerWidth} ${view?.innerHeight}`;
|
|
424
|
+
const cached = fixedBoxes.get(document2);
|
|
425
|
+
if (cached?.key === key)
|
|
426
|
+
return cached;
|
|
427
|
+
const probe = document2.createElement?.("div");
|
|
428
|
+
if (!probe?.style)
|
|
429
|
+
return null;
|
|
430
|
+
probe.style.cssText = "position:fixed;top:0;right:0;bottom:0;left:0;visibility:hidden;pointer-events:none";
|
|
431
|
+
root.appendChild(probe);
|
|
432
|
+
const { height, width } = probe.getBoundingClientRect();
|
|
433
|
+
probe.remove();
|
|
434
|
+
if (!(width > 0 && height > 0))
|
|
435
|
+
return null;
|
|
436
|
+
const box = { height, key, width };
|
|
437
|
+
fixedBoxes.set(document2, box);
|
|
438
|
+
return box;
|
|
439
|
+
}
|
|
426
440
|
function viewportDimensions(context) {
|
|
427
|
-
const
|
|
441
|
+
const document2 = ownerDocument(context);
|
|
442
|
+
const root = document2?.documentElement;
|
|
428
443
|
const width = root?.clientWidth;
|
|
429
444
|
const height = root?.clientHeight;
|
|
430
445
|
if (typeof width === "number" && width > 0 && typeof height === "number" && height > 0) {
|
|
431
|
-
|
|
446
|
+
const fixed = document2 && root && fixedContainingBlock(document2, root);
|
|
447
|
+
return fixed ? { height: fixed.height, width: fixed.width } : { width, height };
|
|
432
448
|
}
|
|
433
449
|
const currentWindow = ownerWindow(context);
|
|
434
450
|
return {
|
|
@@ -473,14 +489,17 @@ function roundedRectPath(rect, viewport, options, context) {
|
|
|
473
489
|
"Z"
|
|
474
490
|
].join(" ");
|
|
475
491
|
}
|
|
476
|
-
|
|
492
|
+
function isPendingTarget(value) {
|
|
493
|
+
return typeof value?.then === "function";
|
|
494
|
+
}
|
|
495
|
+
function resolveTargetElement(target, options, path = "target") {
|
|
477
496
|
const rootDocument = options.document;
|
|
497
|
+
const validate = (element) => rootDocument ? validateTargetElement(element, rootDocument, path) : element;
|
|
478
498
|
if (typeof target === "string") {
|
|
479
|
-
|
|
480
|
-
return rootDocument ? validateTargetElement(element, rootDocument, path) : element;
|
|
499
|
+
return validate(rootDocument ? rootDocument.querySelector(target) : typeof document === "undefined" ? null : document.querySelector(target));
|
|
481
500
|
} else if (typeof target === "function") {
|
|
482
|
-
const element =
|
|
483
|
-
return
|
|
501
|
+
const element = target({ signal: options.signal });
|
|
502
|
+
return isPendingTarget(element) ? element.then(validate) : validate(element);
|
|
484
503
|
}
|
|
485
504
|
if (rootDocument)
|
|
486
505
|
return validateTargetElement(target, rootDocument, path);
|
|
@@ -504,6 +523,7 @@ class GlowTourElement {
|
|
|
504
523
|
element;
|
|
505
524
|
options;
|
|
506
525
|
animations = new Set;
|
|
526
|
+
filledAnimations = new Set;
|
|
507
527
|
cancelledAnimations = new WeakSet;
|
|
508
528
|
released = false;
|
|
509
529
|
constructor(element, options) {
|
|
@@ -568,13 +588,23 @@ class GlowTourElement {
|
|
|
568
588
|
} finally {
|
|
569
589
|
stopWatchingVisibility();
|
|
570
590
|
this.animations.delete(animation);
|
|
591
|
+
const fill = animation.effect?.getTiming?.().fill;
|
|
592
|
+
if (!this.released && !this.cancelledAnimations.has(animation) && (fill === "forwards" || fill === "both")) {
|
|
593
|
+
this.filledAnimations.add(animation);
|
|
594
|
+
}
|
|
571
595
|
}
|
|
572
596
|
}
|
|
597
|
+
_releaseFilledAnimations() {
|
|
598
|
+
for (const animation of this.filledAnimations) {
|
|
599
|
+
this._cancelAnimation(animation);
|
|
600
|
+
}
|
|
601
|
+
this.filledAnimations.clear();
|
|
602
|
+
}
|
|
573
603
|
getElement() {
|
|
574
604
|
return this.released ? null : this.element;
|
|
575
605
|
}
|
|
576
|
-
disappear() {
|
|
577
|
-
return this._disappear();
|
|
606
|
+
disappear(hideFromAssistiveTechnology) {
|
|
607
|
+
return this._disappear(hideFromAssistiveTechnology);
|
|
578
608
|
}
|
|
579
609
|
release() {
|
|
580
610
|
if (this.released)
|
|
@@ -588,6 +618,7 @@ class GlowTourElement {
|
|
|
588
618
|
this._cancelAnimation(animation);
|
|
589
619
|
}
|
|
590
620
|
this.animations.clear();
|
|
621
|
+
this._releaseFilledAnimations();
|
|
591
622
|
}
|
|
592
623
|
_cancelAnimation(animation) {
|
|
593
624
|
this.cancelledAnimations.add(animation);
|
|
@@ -733,7 +764,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
733
764
|
}
|
|
734
765
|
_cutoutPath(position, step) {
|
|
735
766
|
return `path("${roundedRectPath(position, paintedBoxDimensions(this.element), {
|
|
736
|
-
padding: step.overlay?.padding ?? DEFAULT_OVERLAY_PADDING,
|
|
767
|
+
padding: step.detached ? 0 : step.overlay?.padding ?? DEFAULT_OVERLAY_PADDING,
|
|
737
768
|
radius: step.overlay?.radius ?? DEFAULT_OVERLAY_RADIUS
|
|
738
769
|
}, this.element)}")`;
|
|
739
770
|
}
|
|
@@ -748,7 +779,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
748
779
|
for (const [name, value] of Object.entries(OVERLAY_IDLE_ATTRIBUTES)) {
|
|
749
780
|
el.setAttribute(name, value);
|
|
750
781
|
}
|
|
751
|
-
el.style.setProperty("height", "100lvh");
|
|
782
|
+
el.style.setProperty("height", "max(100%, 100lvh)");
|
|
752
783
|
this.syncViewBox();
|
|
753
784
|
}
|
|
754
785
|
_getPathElement() {
|
|
@@ -1438,7 +1469,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1438
1469
|
};
|
|
1439
1470
|
}
|
|
1440
1471
|
const gap = Math.max(0, step.popover?.gap ?? DEFAULT_POPOVER_GAP);
|
|
1441
|
-
const arrowDisabled = step.popover?.arrow?.
|
|
1472
|
+
const arrowDisabled = step.popover?.arrow?.hidden === true;
|
|
1442
1473
|
const arrowEdgePadding = step.popover?.arrow?.edgePadding ?? DEFAULT_ARROW_EDGE_PADDING;
|
|
1443
1474
|
const popoverPosition = currentElement.getBoundingClientRect();
|
|
1444
1475
|
const viewport = viewportDimensions(currentElement);
|
|
@@ -1448,7 +1479,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1448
1479
|
const maxY = viewport.height - gap - popoverPosition.height;
|
|
1449
1480
|
const targetCenterX = targetPosition.left + targetPosition.width / 2;
|
|
1450
1481
|
const targetCenterY = targetPosition.top + targetPosition.height / 2;
|
|
1451
|
-
if (maxX < minX || maxY < minY) {
|
|
1482
|
+
if (step.detached || maxX < minX || maxY < minY) {
|
|
1452
1483
|
return this._centerPosition(popoverPosition, viewport);
|
|
1453
1484
|
}
|
|
1454
1485
|
const centeredX = targetCenterX - popoverPosition.width / 2;
|
|
@@ -1487,6 +1518,9 @@ class PopoverElement extends GlowTourElement {
|
|
|
1487
1518
|
}
|
|
1488
1519
|
return this._centerPosition(popoverPosition, viewport);
|
|
1489
1520
|
}
|
|
1521
|
+
setAwaitingTarget(awaiting) {
|
|
1522
|
+
this.mutationLease.setAttribute("data-glow-tour-awaiting-target", awaiting ? "" : null);
|
|
1523
|
+
}
|
|
1490
1524
|
_centerPosition(popoverPosition, viewport) {
|
|
1491
1525
|
return {
|
|
1492
1526
|
arrowOffset: null,
|
|
@@ -1517,7 +1551,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1517
1551
|
async present(nextPosition, step) {
|
|
1518
1552
|
await this._appear(nextPosition, step);
|
|
1519
1553
|
}
|
|
1520
|
-
initializeProps() {
|
|
1554
|
+
initializeProps(hideFromAssistiveTechnology = true) {
|
|
1521
1555
|
const el = this.getElement();
|
|
1522
1556
|
if (!el) {
|
|
1523
1557
|
return;
|
|
@@ -1528,8 +1562,12 @@ class PopoverElement extends GlowTourElement {
|
|
|
1528
1562
|
if (!el.hasAttribute("tabindex")) {
|
|
1529
1563
|
this.mutationLease.setAttribute("tabindex", POPOVER_IDLE_ATTRIBUTES.tabindex);
|
|
1530
1564
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1565
|
+
if (hideFromAssistiveTechnology) {
|
|
1566
|
+
this.mutationLease.setAttribute("aria-hidden", POPOVER_IDLE_ATTRIBUTES["aria-hidden"]);
|
|
1567
|
+
this.mutationLease.setAttribute("inert", POPOVER_IDLE_ATTRIBUTES.inert);
|
|
1568
|
+
} else {
|
|
1569
|
+
this.mutationLease.setStyle("pointer-events", "none");
|
|
1570
|
+
}
|
|
1533
1571
|
}
|
|
1534
1572
|
updatePosition(nextPosition, step, onReposition) {
|
|
1535
1573
|
const nextCoordinates = this.resolvePosition(nextPosition, step);
|
|
@@ -1578,7 +1616,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1578
1616
|
let pending = this.pendingReposition;
|
|
1579
1617
|
this.pendingReposition = null;
|
|
1580
1618
|
this.repositionPhase = "fading-out";
|
|
1581
|
-
await this._disappear();
|
|
1619
|
+
await this._disappear(false);
|
|
1582
1620
|
if (generation !== this.repositionGeneration || !this.getElement())
|
|
1583
1621
|
return;
|
|
1584
1622
|
pending = this.pendingReposition ?? pending;
|
|
@@ -1615,7 +1653,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1615
1653
|
_applyArrowStyles(step) {
|
|
1616
1654
|
const arrow = step.popover?.arrow;
|
|
1617
1655
|
ensurePopoverArrowStyles(this.element, {
|
|
1618
|
-
disabled: arrow?.
|
|
1656
|
+
disabled: arrow?.autoStyles === false,
|
|
1619
1657
|
nonce: arrow?.styleNonce
|
|
1620
1658
|
});
|
|
1621
1659
|
this._applyArrowStyle(ARROW_STYLE_PROPERTIES.color, arrow?.color);
|
|
@@ -1633,6 +1671,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1633
1671
|
}
|
|
1634
1672
|
}
|
|
1635
1673
|
async _appear(position, step) {
|
|
1674
|
+
this._releaseFilledAnimations();
|
|
1636
1675
|
const defaultStyles = this._getNextStyles(position, step);
|
|
1637
1676
|
for (const [key, value] of Object.entries(defaultStyles)) {
|
|
1638
1677
|
if (value != null)
|
|
@@ -1647,27 +1686,36 @@ class PopoverElement extends GlowTourElement {
|
|
|
1647
1686
|
return;
|
|
1648
1687
|
this._applyVisibleState();
|
|
1649
1688
|
}
|
|
1650
|
-
async _disappear() {
|
|
1689
|
+
async _disappear(hideFromAssistiveTechnology = true) {
|
|
1690
|
+
this._releaseFilledAnimations();
|
|
1651
1691
|
const animation = this._startAnimation({
|
|
1652
1692
|
opacity: 0
|
|
1653
1693
|
}, this._getAnimationOptions());
|
|
1654
1694
|
if (animation && !await this._waitForAnimation(animation))
|
|
1655
1695
|
return;
|
|
1656
|
-
|
|
1696
|
+
if (hideFromAssistiveTechnology)
|
|
1697
|
+
this._applyHiddenState();
|
|
1698
|
+
else
|
|
1699
|
+
this._applyFadedState();
|
|
1657
1700
|
}
|
|
1658
1701
|
_release() {
|
|
1659
1702
|
this.mutationLease.release();
|
|
1660
1703
|
}
|
|
1661
1704
|
_applyVisibleState() {
|
|
1662
1705
|
this.mutationLease.setStyle("opacity", "1");
|
|
1706
|
+
this.mutationLease.releaseStyle("pointer-events");
|
|
1663
1707
|
this.mutationLease.setAttribute("aria-hidden", null);
|
|
1664
1708
|
this.mutationLease.setAttribute("inert", null);
|
|
1665
1709
|
}
|
|
1666
|
-
|
|
1710
|
+
_applyFadedState() {
|
|
1667
1711
|
this.mutationLease.setStyle("opacity", "0");
|
|
1712
|
+
this.mutationLease.setStyle("pointer-events", "none");
|
|
1713
|
+
this.mutationLease.setStyle("transform", null);
|
|
1714
|
+
}
|
|
1715
|
+
_applyHiddenState() {
|
|
1716
|
+
this._applyFadedState();
|
|
1668
1717
|
this.mutationLease.setAttribute("aria-hidden", "true");
|
|
1669
1718
|
this.mutationLease.setAttribute("inert", "true");
|
|
1670
|
-
this.mutationLease.setStyle("transform", null);
|
|
1671
1719
|
}
|
|
1672
1720
|
}
|
|
1673
1721
|
function toPixels(value) {
|
|
@@ -1750,15 +1798,18 @@ class FocusGuard {
|
|
|
1750
1798
|
this.deactivate();
|
|
1751
1799
|
if (!this.active) {
|
|
1752
1800
|
this.document = document2;
|
|
1753
|
-
this.
|
|
1801
|
+
this.captureInitialFocus(scope.popover);
|
|
1754
1802
|
this.document.addEventListener("focusin", this.handleFocusIn, true);
|
|
1755
1803
|
this.active = true;
|
|
1756
1804
|
}
|
|
1757
1805
|
this.update(scope);
|
|
1806
|
+
if (scope.autoFocus === false)
|
|
1807
|
+
return;
|
|
1758
1808
|
const currentFocus = this.document?.activeElement;
|
|
1759
|
-
if (scope.
|
|
1760
|
-
|
|
1809
|
+
if (scope.deferFocus && isNode(currentFocus, scope.popover) && this.isAllowed(currentFocus)) {
|
|
1810
|
+
return;
|
|
1761
1811
|
}
|
|
1812
|
+
this.focusFallback();
|
|
1762
1813
|
}
|
|
1763
1814
|
update(scope) {
|
|
1764
1815
|
this.popover = scope.popover;
|
|
@@ -1771,10 +1822,23 @@ class FocusGuard {
|
|
|
1771
1822
|
if (this.active)
|
|
1772
1823
|
this.focusFallback();
|
|
1773
1824
|
}
|
|
1774
|
-
|
|
1775
|
-
if (
|
|
1825
|
+
captureInitialFocus(reference, pending) {
|
|
1826
|
+
if (this.active || this.initialFocus)
|
|
1776
1827
|
return;
|
|
1828
|
+
const activeElement = pending ?? ownerDocument(reference)?.activeElement;
|
|
1829
|
+
this.initialFocus = isHTMLElement(activeElement, reference) ? activeElement : null;
|
|
1830
|
+
}
|
|
1831
|
+
deactivate() {
|
|
1832
|
+
const focusToRestore = this.release();
|
|
1833
|
+
if (focusToRestore?.isConnected) {
|
|
1834
|
+
focusToRestore.focus();
|
|
1777
1835
|
}
|
|
1836
|
+
}
|
|
1837
|
+
release() {
|
|
1838
|
+
const focusToRestore = this.initialFocus;
|
|
1839
|
+
this.initialFocus = null;
|
|
1840
|
+
if (!this.active)
|
|
1841
|
+
return null;
|
|
1778
1842
|
this.document?.removeEventListener("focusin", this.handleFocusIn, true);
|
|
1779
1843
|
this.document = null;
|
|
1780
1844
|
this.active = false;
|
|
@@ -1782,11 +1846,7 @@ class FocusGuard {
|
|
|
1782
1846
|
this.allowedTarget = null;
|
|
1783
1847
|
this.allowTargetInteraction = false;
|
|
1784
1848
|
this.restoreFallback();
|
|
1785
|
-
|
|
1786
|
-
this.initialFocus = null;
|
|
1787
|
-
if (focusToRestore?.isConnected) {
|
|
1788
|
-
focusToRestore.focus();
|
|
1789
|
-
}
|
|
1849
|
+
return focusToRestore;
|
|
1790
1850
|
}
|
|
1791
1851
|
isAllowed(target) {
|
|
1792
1852
|
if (target === this.fallback)
|
|
@@ -1825,8 +1885,8 @@ class FocusGuard {
|
|
|
1825
1885
|
}
|
|
1826
1886
|
findFocusable(root, direction) {
|
|
1827
1887
|
const candidates = focusableTourControls(root);
|
|
1828
|
-
const
|
|
1829
|
-
return
|
|
1888
|
+
const find = (trigger) => candidates.find((candidate) => candidate.matches(`[data-glow-tour-${trigger}-trigger]`));
|
|
1889
|
+
return direction === "previous" && find("previous") || find("advance") || null;
|
|
1830
1890
|
}
|
|
1831
1891
|
}
|
|
1832
1892
|
function belongsToScope(target, scope) {
|
|
@@ -1891,11 +1951,157 @@ class ScrollLock {
|
|
|
1891
1951
|
}
|
|
1892
1952
|
}
|
|
1893
1953
|
|
|
1954
|
+
// packages/core/src/utils/options.ts
|
|
1955
|
+
function mergeStepProps(base, patch) {
|
|
1956
|
+
return {
|
|
1957
|
+
...base,
|
|
1958
|
+
...patch,
|
|
1959
|
+
data: patch.data ? { ...base.data, ...patch.data } : base.data,
|
|
1960
|
+
overlay: mergeOverlayOptions(base.overlay, patch.overlay),
|
|
1961
|
+
popover: mergePopoverOptions(base.popover, patch.popover),
|
|
1962
|
+
indicator: mergeIndicatorOptions(base.indicator, patch.indicator),
|
|
1963
|
+
behavior: mergeStepBehavior(base.behavior, patch.behavior),
|
|
1964
|
+
controls: mergeControls(base.controls, patch.controls),
|
|
1965
|
+
classNames: mergeClassNames(base.classNames, patch.classNames)
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
function mergeClassNames(base, patch) {
|
|
1969
|
+
if (!patch)
|
|
1970
|
+
return base;
|
|
1971
|
+
const merged = { ...base };
|
|
1972
|
+
for (const slot in patch) {
|
|
1973
|
+
const key = slot;
|
|
1974
|
+
merged[key] = patch[key] ?? merged[key];
|
|
1975
|
+
}
|
|
1976
|
+
return merged;
|
|
1977
|
+
}
|
|
1978
|
+
function mergeOverlayOptions(defaults, overrides) {
|
|
1979
|
+
if (!defaults && !overrides) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
...mergeBaseOptions(defaults, overrides),
|
|
1984
|
+
color: overrides?.color ?? defaults?.color,
|
|
1985
|
+
opacity: overrides?.opacity ?? defaults?.opacity,
|
|
1986
|
+
padding: overrides?.padding ?? defaults?.padding,
|
|
1987
|
+
radius: overrides?.radius ?? defaults?.radius
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
function mergeIndicatorOptions(defaults, overrides) {
|
|
1991
|
+
if (!defaults && !overrides) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
return {
|
|
1995
|
+
...mergeBaseOptions(defaults, overrides),
|
|
1996
|
+
hidden: overrides?.hidden ?? defaults?.hidden,
|
|
1997
|
+
gap: overrides?.gap ?? defaults?.gap,
|
|
1998
|
+
placementTryOrder: cloneArray(overrides?.placementTryOrder ?? defaults?.placementTryOrder)
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
function mergePopoverOptions(defaults, overrides) {
|
|
2002
|
+
if (!defaults && !overrides) {
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
const placementTryOrder = overrides?.placementTryOrder ?? defaults?.placementTryOrder;
|
|
2006
|
+
const hasArrow = !!defaults?.arrow || !!overrides?.arrow;
|
|
2007
|
+
return {
|
|
2008
|
+
...mergeBaseOptions(defaults, overrides),
|
|
2009
|
+
arrow: hasArrow ? {
|
|
2010
|
+
hidden: overrides?.arrow?.hidden ?? defaults?.arrow?.hidden,
|
|
2011
|
+
color: overrides?.arrow?.color ?? defaults?.arrow?.color,
|
|
2012
|
+
size: overrides?.arrow?.size ?? defaults?.arrow?.size,
|
|
2013
|
+
borderWidth: overrides?.arrow?.borderWidth ?? defaults?.arrow?.borderWidth,
|
|
2014
|
+
borderRadius: overrides?.arrow?.borderRadius ?? defaults?.arrow?.borderRadius,
|
|
2015
|
+
edgePadding: overrides?.arrow?.edgePadding ?? defaults?.arrow?.edgePadding,
|
|
2016
|
+
styleNonce: overrides?.arrow?.styleNonce ?? defaults?.arrow?.styleNonce,
|
|
2017
|
+
autoStyles: overrides?.arrow?.autoStyles ?? defaults?.arrow?.autoStyles
|
|
2018
|
+
} : undefined,
|
|
2019
|
+
gap: overrides?.gap ?? defaults?.gap,
|
|
2020
|
+
placementTryOrder: cloneArray(placementTryOrder)
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
function mergeScrollOptions(defaults, overrides) {
|
|
2024
|
+
if (!defaults && !overrides) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
return {
|
|
2028
|
+
behavior: overrides?.behavior ?? defaults?.behavior,
|
|
2029
|
+
block: overrides?.block ?? defaults?.block,
|
|
2030
|
+
inline: overrides?.inline ?? defaults?.inline,
|
|
2031
|
+
returnDelay: overrides?.returnDelay ?? defaults?.returnDelay
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
function mergeAnimationOptions(defaults, overrides) {
|
|
2035
|
+
if (!defaults) {
|
|
2036
|
+
return overrides ? { ...overrides } : undefined;
|
|
2037
|
+
}
|
|
2038
|
+
if (!overrides) {
|
|
2039
|
+
return { ...defaults };
|
|
2040
|
+
}
|
|
2041
|
+
return {
|
|
2042
|
+
duration: overrides.duration ?? defaults.duration,
|
|
2043
|
+
easing: overrides.easing ?? defaults.easing
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function mergeBaseOptions(defaults, overrides) {
|
|
2047
|
+
return {
|
|
2048
|
+
animated: overrides?.animated ?? defaults?.animated,
|
|
2049
|
+
animation: mergeAnimationOptions(defaults?.animation, overrides?.animation)
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
function mergeStepBehavior(defaults, overrides) {
|
|
2053
|
+
if (!defaults && !overrides) {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
return {
|
|
2057
|
+
allowInteraction: overrides?.allowInteraction ?? defaults?.allowInteraction,
|
|
2058
|
+
allowScroll: overrides?.allowScroll ?? defaults?.allowScroll,
|
|
2059
|
+
autoFocus: overrides?.autoFocus ?? defaults?.autoFocus,
|
|
2060
|
+
autoScroll: overrides?.autoScroll ?? defaults?.autoScroll,
|
|
2061
|
+
missingTarget: mergeMissingTarget(defaults?.missingTarget, overrides?.missingTarget),
|
|
2062
|
+
scroll: mergeScrollOptions(defaults?.scroll, overrides?.scroll),
|
|
2063
|
+
overlayClick: overrides?.overlayClick ?? defaults?.overlayClick
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
function mergeControls(defaults, overrides) {
|
|
2067
|
+
if (!defaults && !overrides)
|
|
2068
|
+
return;
|
|
2069
|
+
return {
|
|
2070
|
+
previous: mergeControl(defaults?.previous, overrides?.previous),
|
|
2071
|
+
advance: mergeControl(defaults?.advance, overrides?.advance),
|
|
2072
|
+
cancel: mergeControl(defaults?.cancel, overrides?.cancel)
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
function mergeControl(defaults, overrides) {
|
|
2076
|
+
if (!defaults && !overrides)
|
|
2077
|
+
return;
|
|
2078
|
+
return {
|
|
2079
|
+
state: overrides?.state ?? defaults?.state,
|
|
2080
|
+
keys: cloneArray(overrides?.keys ?? defaults?.keys)
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function mergeMissingTarget(defaults, overrides) {
|
|
2084
|
+
if (!defaults && !overrides)
|
|
2085
|
+
return;
|
|
2086
|
+
return {
|
|
2087
|
+
strategy: overrides?.strategy ?? defaults?.strategy,
|
|
2088
|
+
timeout: overrides?.timeout ?? defaults?.timeout
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
function cloneArray(value) {
|
|
2092
|
+
return value ? [...value] : undefined;
|
|
2093
|
+
}
|
|
2094
|
+
function isControlAvailable(props, command) {
|
|
2095
|
+
return props !== undefined && props.controls?.[command]?.state !== "disabled";
|
|
2096
|
+
}
|
|
2097
|
+
|
|
1894
2098
|
// packages/core/src/dom/tour-view-driver.ts
|
|
1895
2099
|
var SCROLL_SETTLE_STILL_FRAMES = 2;
|
|
1896
2100
|
var SCROLL_SETTLE_GRACE_FRAMES = 3;
|
|
1897
2101
|
var SCROLL_SETTLE_EPSILON = 0.5;
|
|
1898
2102
|
var SCROLL_SETTLE_TIMEOUT = 2000;
|
|
2103
|
+
var USER_SCROLL_IDLE_DELAY = 150;
|
|
2104
|
+
var DEFAULT_SCROLL_RETURN_DELAY = 500;
|
|
1899
2105
|
var ACTIVE_MODAL_BY_DOCUMENT = new WeakMap;
|
|
1900
2106
|
var DEFAULT_SHORTCUTS = {
|
|
1901
2107
|
previous: ["ArrowLeft", "Backspace"],
|
|
@@ -1917,6 +2123,8 @@ class DomTourViewDriver {
|
|
|
1917
2123
|
active = false;
|
|
1918
2124
|
frozen = false;
|
|
1919
2125
|
awaitingStepUi = false;
|
|
2126
|
+
userScrolling = false;
|
|
2127
|
+
scrollTimer;
|
|
1920
2128
|
activeTarget = null;
|
|
1921
2129
|
targetFocusedAtFreeze = false;
|
|
1922
2130
|
lastTargetRect = null;
|
|
@@ -1925,11 +2133,17 @@ class DomTourViewDriver {
|
|
|
1925
2133
|
modalDocument = null;
|
|
1926
2134
|
modalRoot = null;
|
|
1927
2135
|
overlay = null;
|
|
1928
|
-
|
|
2136
|
+
pendingCommand = null;
|
|
1929
2137
|
pointer = null;
|
|
1930
2138
|
pendingFocusGeneration = null;
|
|
2139
|
+
handledTriggerClick = null;
|
|
1931
2140
|
popover = null;
|
|
1932
2141
|
presentationDirty = false;
|
|
2142
|
+
targetPending = false;
|
|
2143
|
+
popoverHidden = false;
|
|
2144
|
+
focusToRestore = null;
|
|
2145
|
+
appliedAllowInteraction = false;
|
|
2146
|
+
pointerFading = false;
|
|
1933
2147
|
rafId = null;
|
|
1934
2148
|
rafCancel = null;
|
|
1935
2149
|
root = null;
|
|
@@ -1985,12 +2199,41 @@ class DomTourViewDriver {
|
|
|
1985
2199
|
this.pointer?.initializeProps();
|
|
1986
2200
|
this.refreshRegisteredElements();
|
|
1987
2201
|
}
|
|
2202
|
+
setTargetPending(pending) {
|
|
2203
|
+
if (this.disposed || this.targetPending === pending)
|
|
2204
|
+
return;
|
|
2205
|
+
this.targetPending = pending;
|
|
2206
|
+
this.popover?.setAwaitingTarget(pending);
|
|
2207
|
+
const step = this.currentStep;
|
|
2208
|
+
if (!step)
|
|
2209
|
+
return;
|
|
2210
|
+
const focused = this.popover?.getElement()?.ownerDocument.activeElement;
|
|
2211
|
+
const refocus = pending && step.autoFocuses() && this.findTriggers("advance").includes(focused);
|
|
2212
|
+
this.syncControlState(step);
|
|
2213
|
+
if (refocus)
|
|
2214
|
+
this.focusGuard.focus();
|
|
2215
|
+
}
|
|
2216
|
+
setPopoverHidden(hidden) {
|
|
2217
|
+
if (this.disposed || this.popoverHidden === hidden)
|
|
2218
|
+
return;
|
|
2219
|
+
this.popoverHidden = hidden;
|
|
2220
|
+
const step = this.currentStep;
|
|
2221
|
+
const target = this.activeTarget;
|
|
2222
|
+
if (!this.active || !step || !target)
|
|
2223
|
+
return;
|
|
2224
|
+
if (hidden) {
|
|
2225
|
+
this.liftModality();
|
|
2226
|
+
this.concealPopover();
|
|
2227
|
+
} else {
|
|
2228
|
+
this.revealPopover(step, target, this.generation);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
1988
2231
|
async show(step, direction, signal, onBeforePopoverAppear) {
|
|
1989
2232
|
this.throwIfAborted(signal);
|
|
1990
2233
|
const generation = this.beginGeneration();
|
|
1991
2234
|
const removeAbort = this.cancelAnimationsOnAbort(signal);
|
|
1992
|
-
const replaceVisiblePopover = this.active && onBeforePopoverAppear !== undefined;
|
|
1993
|
-
let
|
|
2235
|
+
const replaceVisiblePopover = this.active && !this.popoverHidden && onBeforePopoverAppear !== undefined;
|
|
2236
|
+
let removeTransitionListeners = () => {};
|
|
1994
2237
|
try {
|
|
1995
2238
|
this.cleanupStepResources();
|
|
1996
2239
|
this.throwIfStale(generation, signal);
|
|
@@ -2005,41 +2248,50 @@ class DomTourViewDriver {
|
|
|
2005
2248
|
this.lastViewport = null;
|
|
2006
2249
|
this.presentationDirty = false;
|
|
2007
2250
|
this.awaitingStepUi = true;
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2251
|
+
const modal = !step.allowsInteraction();
|
|
2252
|
+
if (modal)
|
|
2253
|
+
this.claimModal();
|
|
2254
|
+
const onKeydown = (event) => this.queueTransitionKeydown(event, step, generation, replaceVisiblePopover);
|
|
2255
|
+
const onClick = (event) => {
|
|
2256
|
+
if (replaceVisiblePopover)
|
|
2257
|
+
this.queueTransitionClick(event, step, generation);
|
|
2258
|
+
};
|
|
2259
|
+
const currentWindow = this.getWindow();
|
|
2260
|
+
if (typeof currentWindow?.addEventListener === "function") {
|
|
2261
|
+
currentWindow.addEventListener("keydown", onKeydown);
|
|
2262
|
+
currentWindow.addEventListener("click", onClick);
|
|
2263
|
+
removeTransitionListeners = () => {
|
|
2264
|
+
currentWindow.removeEventListener("keydown", onKeydown);
|
|
2265
|
+
currentWindow.removeEventListener("click", onClick);
|
|
2266
|
+
};
|
|
2015
2267
|
}
|
|
2016
2268
|
const target = step.target;
|
|
2017
2269
|
if (!target)
|
|
2018
2270
|
return;
|
|
2019
2271
|
this.activeTarget = target;
|
|
2020
|
-
this.
|
|
2272
|
+
this.focusGuard.captureInitialFocus(target, this.focusToRestore);
|
|
2021
2273
|
const scrolling = this.beginTargetScroll(step, target, signal);
|
|
2022
2274
|
this.throwIfStale(generation, signal);
|
|
2023
|
-
this.initializeElements(step);
|
|
2024
|
-
const resolveRect = () => target
|
|
2275
|
+
this.initializeElements(step, replaceVisiblePopover);
|
|
2276
|
+
const resolveRect = () => presentationRect(step, target);
|
|
2025
2277
|
await this.appear(resolveRect, step, generation, scrolling, replaceVisiblePopover, onBeforePopoverAppear);
|
|
2026
2278
|
this.throwIfStale(generation, signal);
|
|
2027
2279
|
const targetRect = resolveRect();
|
|
2028
2280
|
this.lastTargetRect = snapshotRect(targetRect);
|
|
2029
2281
|
this.lastViewport = snapshotViewport(target);
|
|
2030
2282
|
this.active = true;
|
|
2031
|
-
this.
|
|
2283
|
+
this.engagePopover(step, target, direction, generation);
|
|
2032
2284
|
this.syncScrollLock(step);
|
|
2033
2285
|
this.throwIfStale(generation, signal);
|
|
2034
|
-
|
|
2035
|
-
|
|
2286
|
+
removeTransitionListeners();
|
|
2287
|
+
removeTransitionListeners = () => {};
|
|
2036
2288
|
this.attachStepResources(step, target, generation, signal);
|
|
2037
2289
|
} catch (error) {
|
|
2038
2290
|
if (this.isCurrentGeneration(generation))
|
|
2039
2291
|
this.releaseModality();
|
|
2040
2292
|
throw error;
|
|
2041
2293
|
} finally {
|
|
2042
|
-
|
|
2294
|
+
removeTransitionListeners();
|
|
2043
2295
|
removeAbort();
|
|
2044
2296
|
}
|
|
2045
2297
|
}
|
|
@@ -2049,10 +2301,11 @@ class DomTourViewDriver {
|
|
|
2049
2301
|
this.throwIfAborted(signal);
|
|
2050
2302
|
const generation = this.beginGeneration();
|
|
2051
2303
|
const removeAbort = this.cancelAnimationsOnAbort(signal);
|
|
2304
|
+
this.setTargetPending(false);
|
|
2052
2305
|
try {
|
|
2053
2306
|
this.cleanupStepResources();
|
|
2054
2307
|
this.releaseModality();
|
|
2055
|
-
this.focusGuard.
|
|
2308
|
+
this.focusToRestore = this.focusGuard.release() ?? this.focusToRestore;
|
|
2056
2309
|
this.scrollLock.deactivate();
|
|
2057
2310
|
this.throwIfStale(generation, signal);
|
|
2058
2311
|
this.active = false;
|
|
@@ -2064,20 +2317,24 @@ class DomTourViewDriver {
|
|
|
2064
2317
|
this.currentSignal = null;
|
|
2065
2318
|
this.lastTargetRect = null;
|
|
2066
2319
|
this.lastViewport = null;
|
|
2067
|
-
this.popover?.getElement()?.removeAttribute("aria-modal");
|
|
2068
2320
|
await Promise.allSettled([
|
|
2069
2321
|
this.overlay?.disappear() ?? Promise.resolve(),
|
|
2070
2322
|
this.popover?.disappear() ?? Promise.resolve(),
|
|
2071
2323
|
this.pointer?.disappear() ?? Promise.resolve()
|
|
2072
2324
|
]);
|
|
2073
|
-
this.throwIfStale(generation, signal);
|
|
2074
2325
|
} finally {
|
|
2326
|
+
if (this.isCurrentGeneration(generation)) {
|
|
2327
|
+
this.focusToRestore?.focus();
|
|
2328
|
+
this.focusToRestore = null;
|
|
2329
|
+
}
|
|
2075
2330
|
removeAbort();
|
|
2076
2331
|
}
|
|
2332
|
+
this.throwIfStale(generation, signal);
|
|
2077
2333
|
}
|
|
2078
2334
|
releaseMount() {
|
|
2079
2335
|
if (this.disposed)
|
|
2080
2336
|
return;
|
|
2337
|
+
this.setTargetPending(false);
|
|
2081
2338
|
this.beginGeneration();
|
|
2082
2339
|
this.cleanupStepResources();
|
|
2083
2340
|
this.releaseModality();
|
|
@@ -2124,45 +2381,43 @@ class DomTourViewDriver {
|
|
|
2124
2381
|
if (this.disposed || !step || !target || !targetRect || !signal)
|
|
2125
2382
|
return;
|
|
2126
2383
|
this.activeTarget = target;
|
|
2127
|
-
this.initializeElements(step);
|
|
2384
|
+
this.initializeElements(step, false);
|
|
2128
2385
|
this.awaitingStepUi = true;
|
|
2129
2386
|
await this.appear(() => targetRect, step, generation, null, false);
|
|
2130
2387
|
this.throwIfStale(generation);
|
|
2131
|
-
this.
|
|
2388
|
+
this.engagePopover(step, target, this.direction, generation);
|
|
2132
2389
|
this.syncScrollLock(step);
|
|
2133
2390
|
this.throwIfStale(generation);
|
|
2134
2391
|
this.attachStepResources(step, target, generation, signal);
|
|
2135
2392
|
}
|
|
2136
|
-
initializeElements(step) {
|
|
2137
|
-
const interactionAllowed = step.
|
|
2393
|
+
initializeElements(step, replaceVisiblePopover) {
|
|
2394
|
+
const interactionAllowed = step.allowsInteraction();
|
|
2138
2395
|
this.overlay?.initializeProps();
|
|
2139
|
-
this.overlay?.setAnimationOptions(animationOptions(step, step.overlay, this.overlay.getElement()));
|
|
2396
|
+
this.overlay?.setAnimationOptions(animationOptions(step, step.props.get().overlay, this.overlay.getElement()));
|
|
2140
2397
|
this.overlay?.setInteractionAllowed(interactionAllowed);
|
|
2141
|
-
this.
|
|
2142
|
-
this.popover?.
|
|
2143
|
-
|
|
2144
|
-
if (isHTMLElement(popover, this.root ?? popover) && !interactionAllowed)
|
|
2145
|
-
popover.setAttribute("aria-modal", "true");
|
|
2398
|
+
this.appliedAllowInteraction = interactionAllowed;
|
|
2399
|
+
this.popover?.initializeProps(!replaceVisiblePopover);
|
|
2400
|
+
this.popover?.setAnimationOptions(animationOptions(step, step.props.get().popover, this.popover.getElement()));
|
|
2146
2401
|
this.pointer?.initializeProps();
|
|
2147
|
-
this.pointer?.setAnimationOptions(animationOptions(step, step.indicator, this.pointer.getElement()));
|
|
2402
|
+
this.pointer?.setAnimationOptions(animationOptions(step, step.props.get().indicator, this.pointer.getElement()));
|
|
2148
2403
|
}
|
|
2149
2404
|
syncModality(interactionAllowed) {
|
|
2150
2405
|
if (interactionAllowed) {
|
|
2151
2406
|
this.releaseModality();
|
|
2152
2407
|
return;
|
|
2153
2408
|
}
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2409
|
+
this.claimModal();
|
|
2410
|
+
if (this.popoverHidden) {
|
|
2411
|
+
this.liftModality();
|
|
2156
2412
|
return;
|
|
2157
|
-
const document2 = root.ownerDocument;
|
|
2158
|
-
const owner = ACTIVE_MODAL_BY_DOCUMENT.get(document2);
|
|
2159
|
-
if (owner && owner !== this.modalToken) {
|
|
2160
|
-
throw new Error("GlowTour.js only supports one active modal tour per document");
|
|
2161
2413
|
}
|
|
2162
|
-
|
|
2163
|
-
this.
|
|
2164
|
-
|
|
2414
|
+
const popover = this.popover?.getElement();
|
|
2415
|
+
if (isHTMLElement(popover, this.root ?? popover))
|
|
2416
|
+
popover.setAttribute("aria-modal", "true");
|
|
2417
|
+
const root = this.root;
|
|
2418
|
+
if (!root || this.modalRoot === root)
|
|
2165
2419
|
return;
|
|
2420
|
+
const document2 = root.ownerDocument;
|
|
2166
2421
|
this.restoreInertBranches();
|
|
2167
2422
|
this.modalRoot = root;
|
|
2168
2423
|
let branch = root;
|
|
@@ -2179,14 +2434,28 @@ class DomTourViewDriver {
|
|
|
2179
2434
|
branch = parent;
|
|
2180
2435
|
}
|
|
2181
2436
|
}
|
|
2437
|
+
claimModal() {
|
|
2438
|
+
const document2 = this.root?.ownerDocument;
|
|
2439
|
+
if (!document2)
|
|
2440
|
+
return;
|
|
2441
|
+
const owner = ACTIVE_MODAL_BY_DOCUMENT.get(document2);
|
|
2442
|
+
if (owner && owner !== this.modalToken) {
|
|
2443
|
+
throw new Error("GlowTour.js only supports one active modal tour per document");
|
|
2444
|
+
}
|
|
2445
|
+
ACTIVE_MODAL_BY_DOCUMENT.set(document2, this.modalToken);
|
|
2446
|
+
this.modalDocument = document2;
|
|
2447
|
+
}
|
|
2182
2448
|
releaseModality() {
|
|
2183
|
-
this.
|
|
2184
|
-
this.restoreInertBranches();
|
|
2449
|
+
this.liftModality();
|
|
2185
2450
|
const document2 = this.modalDocument;
|
|
2186
2451
|
if (document2 && ACTIVE_MODAL_BY_DOCUMENT.get(document2) === this.modalToken) {
|
|
2187
2452
|
ACTIVE_MODAL_BY_DOCUMENT.delete(document2);
|
|
2188
2453
|
}
|
|
2189
2454
|
this.modalDocument = null;
|
|
2455
|
+
}
|
|
2456
|
+
liftModality() {
|
|
2457
|
+
this.popover?.getElement()?.removeAttribute("aria-modal");
|
|
2458
|
+
this.restoreInertBranches();
|
|
2190
2459
|
this.modalRoot = null;
|
|
2191
2460
|
}
|
|
2192
2461
|
restoreInertBranches() {
|
|
@@ -2202,14 +2471,14 @@ class DomTourViewDriver {
|
|
|
2202
2471
|
async appear(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear) {
|
|
2203
2472
|
const spotlightRect = resolveRect();
|
|
2204
2473
|
const stepUi = this.presentStepUi(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear);
|
|
2205
|
-
const spotlight = this.overlay?.moveToTarget(spotlightRect, step, scrolling !== null);
|
|
2474
|
+
const spotlight = this.overlay?.moveToTarget(spotlightRect, this.elementProps(step), scrolling !== null);
|
|
2206
2475
|
if (scrolling)
|
|
2207
2476
|
this.schedulePosition(generation);
|
|
2208
2477
|
await Promise.all([spotlight ?? Promise.resolve(), stepUi]);
|
|
2209
2478
|
}
|
|
2210
2479
|
async presentStepUi(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear) {
|
|
2211
2480
|
if (hadVisiblePopover)
|
|
2212
|
-
await this.popover?.disappear();
|
|
2481
|
+
await this.popover?.disappear(false);
|
|
2213
2482
|
if (onBeforePopoverAppear) {
|
|
2214
2483
|
await onBeforePopoverAppear();
|
|
2215
2484
|
this.syncControlState(step);
|
|
@@ -2223,12 +2492,15 @@ class DomTourViewDriver {
|
|
|
2223
2492
|
await this.enterStepUi(resolveRect(), step);
|
|
2224
2493
|
}
|
|
2225
2494
|
enterStepUi(targetRect, step) {
|
|
2226
|
-
const popoverPlacement = this.popover?.resolvePosition(targetRect, step).placement;
|
|
2495
|
+
const popoverPlacement = this.popover?.resolvePosition(targetRect, this.elementProps(step)).placement;
|
|
2227
2496
|
return Promise.all([
|
|
2228
|
-
this.popover?.present(targetRect, step)
|
|
2229
|
-
this.
|
|
2497
|
+
!this.popoverHidden && this.popover?.present(targetRect, this.elementProps(step)) || Promise.resolve(),
|
|
2498
|
+
this.presentPointer(targetRect, step, popoverPlacement) ?? Promise.resolve()
|
|
2230
2499
|
]);
|
|
2231
2500
|
}
|
|
2501
|
+
presentPointer(targetRect, step, popoverPlacement) {
|
|
2502
|
+
return this.isPointerEnabled(step) ? this.pointer?.moveToTarget(targetRect, step.props.get(), true, popoverPlacement) : this.pointer?.disappear();
|
|
2503
|
+
}
|
|
2232
2504
|
attachStepResources(step, target, generation, signal) {
|
|
2233
2505
|
let initialPropsNotification = true;
|
|
2234
2506
|
this.stepCleanups.push(step.props.subscribe(() => {
|
|
@@ -2239,7 +2511,14 @@ class DomTourViewDriver {
|
|
|
2239
2511
|
return;
|
|
2240
2512
|
}
|
|
2241
2513
|
this.presentationDirty = true;
|
|
2514
|
+
this.syncScrollLock(step);
|
|
2515
|
+
if (step.allowsInteraction() === this.appliedAllowInteraction)
|
|
2516
|
+
return;
|
|
2517
|
+
this.syncInteraction(step);
|
|
2518
|
+
this.pointerFading = step.allowsInteraction() === this.appliedAllowInteraction;
|
|
2242
2519
|
}));
|
|
2520
|
+
if (step.allowsInteraction() !== this.appliedAllowInteraction)
|
|
2521
|
+
this.syncInteraction(step);
|
|
2243
2522
|
this.stepCleanups.push(this.commands?.subscribeCapabilities?.((active) => {
|
|
2244
2523
|
if (!this.isCurrentGeneration(generation))
|
|
2245
2524
|
return;
|
|
@@ -2249,7 +2528,7 @@ class DomTourViewDriver {
|
|
|
2249
2528
|
this.focusGuard.focus();
|
|
2250
2529
|
}
|
|
2251
2530
|
if (active)
|
|
2252
|
-
this.
|
|
2531
|
+
this.flushPendingCommand(step, generation);
|
|
2253
2532
|
}) ?? (() => {}));
|
|
2254
2533
|
this.attachTargetResources(step, target, generation, signal);
|
|
2255
2534
|
const currentWindow = this.getWindow(target);
|
|
@@ -2264,6 +2543,20 @@ class DomTourViewDriver {
|
|
|
2264
2543
|
}
|
|
2265
2544
|
});
|
|
2266
2545
|
}
|
|
2546
|
+
const targetDocument = ownerDocument(target);
|
|
2547
|
+
if (typeof targetDocument?.addEventListener === "function") {
|
|
2548
|
+
const passive = { capture: true, passive: true };
|
|
2549
|
+
this.listen(targetDocument, "scroll", (event) => this.onUserScroll(event, step, generation), passive);
|
|
2550
|
+
const interrupt = () => {
|
|
2551
|
+
if (!this.isCurrentGeneration(generation) || !this.userScrolling || !this.awaitingStepUi)
|
|
2552
|
+
return;
|
|
2553
|
+
this.awaitingStepUi = false;
|
|
2554
|
+
this.cancelScroll?.();
|
|
2555
|
+
this.waitForScrollIdle(step, generation);
|
|
2556
|
+
};
|
|
2557
|
+
this.listen(targetDocument, "wheel", interrupt, passive);
|
|
2558
|
+
this.listen(targetDocument, "touchmove", interrupt, passive);
|
|
2559
|
+
}
|
|
2267
2560
|
this.attachButtonHandlers(step);
|
|
2268
2561
|
this.observeControls(step, generation);
|
|
2269
2562
|
this.syncControlState(step);
|
|
@@ -2271,14 +2564,22 @@ class DomTourViewDriver {
|
|
|
2271
2564
|
this.schedulePosition(generation);
|
|
2272
2565
|
}
|
|
2273
2566
|
attachTargetResources(step, target, generation, signal) {
|
|
2274
|
-
|
|
2567
|
+
if (step.detached)
|
|
2568
|
+
return;
|
|
2569
|
+
for (const handler of step.definition.targetEvents) {
|
|
2275
2570
|
const listener = (event) => {
|
|
2276
2571
|
if (!this.isCurrentGeneration(generation))
|
|
2277
2572
|
return;
|
|
2278
2573
|
const context = Object.freeze({
|
|
2279
2574
|
advance: () => this.commandForStep("advance", step, signal),
|
|
2280
2575
|
cancel: () => this.commandForStep("cancel", step, signal),
|
|
2576
|
+
goTo: async (id) => {
|
|
2577
|
+
if (!signal.aborted && this.currentStep === step)
|
|
2578
|
+
await this.commands?.goTo(id);
|
|
2579
|
+
},
|
|
2281
2580
|
previous: () => this.commandForStep("previous", step, signal),
|
|
2581
|
+
direction: step.direction,
|
|
2582
|
+
initialProps: step.initialProps,
|
|
2282
2583
|
props: step.props,
|
|
2283
2584
|
signal,
|
|
2284
2585
|
target
|
|
@@ -2337,21 +2638,21 @@ class DomTourViewDriver {
|
|
|
2337
2638
|
this.freezeForDisconnectedTarget(step, target, generation);
|
|
2338
2639
|
return;
|
|
2339
2640
|
}
|
|
2340
|
-
const targetRect = target
|
|
2641
|
+
const targetRect = presentationRect(step, target);
|
|
2341
2642
|
const targetSnapshot = snapshotRect(targetRect);
|
|
2342
2643
|
const viewportSnapshot = snapshotViewport(target);
|
|
2343
2644
|
const presentationChanged = this.presentationDirty;
|
|
2344
2645
|
if (!presentationChanged && sameRect(targetSnapshot, this.lastTargetRect) && sameViewport(viewportSnapshot, this.lastViewport))
|
|
2345
2646
|
return;
|
|
2346
2647
|
if (presentationChanged) {
|
|
2347
|
-
this.overlay?.setAnimationOptions(animationOptions(step, step.overlay, this.overlay.getElement()));
|
|
2348
|
-
this.popover?.setAnimationOptions(animationOptions(step, step.popover, this.popover.getElement()));
|
|
2349
|
-
this.pointer?.setAnimationOptions(animationOptions(step, step.indicator, this.pointer.getElement()));
|
|
2648
|
+
this.overlay?.setAnimationOptions(animationOptions(step, step.props.get().overlay, this.overlay.getElement()));
|
|
2649
|
+
this.popover?.setAnimationOptions(animationOptions(step, step.props.get().popover, this.popover.getElement()));
|
|
2650
|
+
this.pointer?.setAnimationOptions(animationOptions(step, step.props.get().indicator, this.pointer.getElement()));
|
|
2350
2651
|
this.syncControlState(step);
|
|
2351
2652
|
this.syncShortcutLabels(step);
|
|
2352
2653
|
}
|
|
2353
|
-
this.overlay?.updatePosition(targetRect, step, presentationChanged, (transition) => this.observeDynamicOperation(transition, generation));
|
|
2354
|
-
if (!this.awaitingStepUi)
|
|
2654
|
+
this.overlay?.updatePosition(targetRect, this.elementProps(step), presentationChanged, (transition) => this.observeDynamicOperation(transition, generation));
|
|
2655
|
+
if (!this.awaitingStepUi && !this.userScrolling)
|
|
2355
2656
|
this.trackStepUi(targetRect, step, generation, presentationChanged);
|
|
2356
2657
|
this.lastTargetRect = targetSnapshot;
|
|
2357
2658
|
this.lastViewport = viewportSnapshot;
|
|
@@ -2359,20 +2660,85 @@ class DomTourViewDriver {
|
|
|
2359
2660
|
this.presentationDirty = false;
|
|
2360
2661
|
}
|
|
2361
2662
|
trackStepUi(targetRect, step, generation, presentationChanged) {
|
|
2362
|
-
const popoverPlacement = this.
|
|
2663
|
+
const popoverPlacement = this.placePopover(targetRect, step, generation);
|
|
2363
2664
|
if (presentationChanged) {
|
|
2364
|
-
|
|
2665
|
+
if (this.pointerFading)
|
|
2666
|
+
this.pointerFading = false;
|
|
2667
|
+
else
|
|
2668
|
+
this.pointer?.syncVisibility(this.isPointerEnabled(step), targetRect, step.props.get(), popoverPlacement);
|
|
2365
2669
|
} else if (this.isPointerEnabled(step)) {
|
|
2366
2670
|
const pointer = this.pointer?.getElement();
|
|
2367
2671
|
if (pointer?.getAttribute("aria-hidden") === "true") {
|
|
2368
|
-
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step, true, popoverPlacement), generation);
|
|
2672
|
+
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step.props.get(), true, popoverPlacement), generation);
|
|
2369
2673
|
} else {
|
|
2370
|
-
this.pointer?.updatePosition(targetRect, step, popoverPlacement);
|
|
2674
|
+
this.pointer?.updatePosition(targetRect, step.props.get(), popoverPlacement);
|
|
2371
2675
|
}
|
|
2372
2676
|
} else if (this.pointer?.getElement()?.getAttribute("aria-hidden") !== "true") {
|
|
2373
2677
|
this.observeDynamicOperation(this.pointer?.disappear(), generation);
|
|
2374
2678
|
}
|
|
2375
2679
|
}
|
|
2680
|
+
onUserScroll(event, step, generation) {
|
|
2681
|
+
const target = this.activeTarget;
|
|
2682
|
+
if (!target || !this.isCurrentGeneration(generation) || this.frozen || this.awaitingStepUi || step.detached || !step.allowsScroll() || !event.target?.contains?.(target))
|
|
2683
|
+
return;
|
|
2684
|
+
if (!this.userScrolling) {
|
|
2685
|
+
this.userScrolling = true;
|
|
2686
|
+
this.popover?.cancelAnimations();
|
|
2687
|
+
this.pointer?.cancelAnimations();
|
|
2688
|
+
if (!this.popoverHidden)
|
|
2689
|
+
this.observeDynamicOperation(this.popover?.disappear(false), generation);
|
|
2690
|
+
this.observeDynamicOperation(this.pointer?.disappear(), generation);
|
|
2691
|
+
}
|
|
2692
|
+
this.waitForScrollIdle(step, generation);
|
|
2693
|
+
}
|
|
2694
|
+
waitForScrollIdle(step, generation) {
|
|
2695
|
+
clearTimeout(this.scrollTimer);
|
|
2696
|
+
this.scrollTimer = setTimeout(() => this.settleScroll(step, generation), USER_SCROLL_IDLE_DELAY);
|
|
2697
|
+
}
|
|
2698
|
+
settleScroll(step, generation) {
|
|
2699
|
+
const target = this.activeTarget;
|
|
2700
|
+
if (!target)
|
|
2701
|
+
return;
|
|
2702
|
+
const behavior = step.props.get().behavior;
|
|
2703
|
+
const returnDelay = behavior?.scroll?.returnDelay ?? DEFAULT_SCROLL_RETURN_DELAY;
|
|
2704
|
+
if (returnDelay === false || behavior?.autoScroll === false || isInViewport(target.getBoundingClientRect(), target))
|
|
2705
|
+
this.revealAfterScroll(step, target, generation);
|
|
2706
|
+
else
|
|
2707
|
+
this.scrollTimer = setTimeout(() => void this.scrollBack(step, target, generation), returnDelay);
|
|
2708
|
+
}
|
|
2709
|
+
async scrollBack(step, target, generation) {
|
|
2710
|
+
const signal = this.currentSignal;
|
|
2711
|
+
if (!signal)
|
|
2712
|
+
return;
|
|
2713
|
+
const scrollingBack = () => this.userScrolling && this.awaitingStepUi && !signal.aborted && this.isCurrentGeneration(generation);
|
|
2714
|
+
this.awaitingStepUi = true;
|
|
2715
|
+
try {
|
|
2716
|
+
await (this.scrollTargetIntoView(step, target, signal) ?? this.waitForScrollToSettle(target, signal));
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
if (scrollingBack())
|
|
2719
|
+
this.observeDynamicOperation(Promise.reject(error), generation);
|
|
2720
|
+
}
|
|
2721
|
+
if (scrollingBack())
|
|
2722
|
+
this.revealAfterScroll(step, target, generation);
|
|
2723
|
+
}
|
|
2724
|
+
revealAfterScroll(step, target, generation) {
|
|
2725
|
+
this.stopUserScroll();
|
|
2726
|
+
this.awaitingStepUi = false;
|
|
2727
|
+
this.pointerFading = false;
|
|
2728
|
+
this.popover?.cancelAnimations();
|
|
2729
|
+
this.pointer?.cancelAnimations();
|
|
2730
|
+
this.observeDynamicOperation(this.enterStepUi(presentationRect(step, target), step), generation);
|
|
2731
|
+
}
|
|
2732
|
+
stopUserScroll() {
|
|
2733
|
+
clearTimeout(this.scrollTimer);
|
|
2734
|
+
this.userScrolling = false;
|
|
2735
|
+
}
|
|
2736
|
+
placePopover(targetRect, step, generation) {
|
|
2737
|
+
if (this.popoverHidden) {
|
|
2738
|
+
return this.popover?.resolvePosition(targetRect, this.elementProps(step)).placement;
|
|
2739
|
+
}
|
|
2740
|
+
return this.popover?.updatePosition(targetRect, this.elementProps(step), (reposition) => this.observeDynamicOperation(reposition, generation));
|
|
2741
|
+
}
|
|
2376
2742
|
observeDynamicOperation(operation, generation) {
|
|
2377
2743
|
if (!operation)
|
|
2378
2744
|
return;
|
|
@@ -2387,72 +2753,77 @@ class DomTourViewDriver {
|
|
|
2387
2753
|
}
|
|
2388
2754
|
handleKeydown(event) {
|
|
2389
2755
|
const step = this.currentStep;
|
|
2390
|
-
if (!step || event.defaultPrevented || event.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
2756
|
+
if (!step || this.popoverHidden || event.defaultPrevented || event.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
2391
2757
|
return;
|
|
2392
|
-
if (event.key === "Tab" && step.
|
|
2758
|
+
if (event.key === "Tab" && !step.allowsInteraction()) {
|
|
2393
2759
|
this.loopFocus(event);
|
|
2394
2760
|
return;
|
|
2395
2761
|
}
|
|
2396
|
-
const
|
|
2397
|
-
if (
|
|
2398
|
-
event.preventDefault();
|
|
2399
|
-
this.command("cancel", "keyboard");
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
if (isEditable(event.target, this.root))
|
|
2762
|
+
const command = this.keyboardCommand(event, step, (command2) => this.canCommand(command2, step));
|
|
2763
|
+
if (!command)
|
|
2403
2764
|
return;
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2765
|
+
event.preventDefault();
|
|
2766
|
+
this.command(command, "keyboard");
|
|
2767
|
+
}
|
|
2768
|
+
keyboardCommand(event, step, available) {
|
|
2769
|
+
if (activatesControl(event, this.root))
|
|
2770
|
+
return null;
|
|
2771
|
+
const controls = step.props.get().controls;
|
|
2772
|
+
const shortcut = (command) => (controls?.[command]?.keys ?? DEFAULT_SHORTCUTS[command]).includes(event.key) && available(command);
|
|
2773
|
+
return shortcut("cancel") ? "cancel" : isEditable(event.target, this.root) ? null : shortcut("advance") ? "advance" : shortcut("previous") ? "previous" : null;
|
|
2411
2774
|
}
|
|
2412
2775
|
handleOverlayClick(event, step, target) {
|
|
2413
2776
|
if (this.currentStep !== step || event.defaultPrevented)
|
|
2414
2777
|
return;
|
|
2415
|
-
if (step.
|
|
2778
|
+
if (step.allowsInteraction())
|
|
2416
2779
|
return;
|
|
2417
2780
|
const path = event.composedPath();
|
|
2418
2781
|
const popover = this.popover?.getElement();
|
|
2419
2782
|
const pointer = this.pointer?.getElement();
|
|
2420
|
-
if (path.includes(target) || popover && path.includes(popover) || pointer && path.includes(pointer))
|
|
2783
|
+
if (!step.detached && path.includes(target) || popover && path.includes(popover) || pointer && path.includes(pointer))
|
|
2421
2784
|
return;
|
|
2422
|
-
const overlayClick = step.behavior?.overlayClick ?? "none";
|
|
2785
|
+
const overlayClick = step.props.get().behavior?.overlayClick ?? "none";
|
|
2423
2786
|
if (overlayClick === "advance" && this.canCommand("advance", step)) {
|
|
2424
2787
|
this.command("advance", "overlay");
|
|
2425
2788
|
} else if (overlayClick === "cancel" && this.canCommand("cancel", step)) {
|
|
2426
2789
|
this.command("cancel", "overlay");
|
|
2427
2790
|
}
|
|
2428
2791
|
}
|
|
2429
|
-
queueTransitionKeydown(event, step, generation) {
|
|
2792
|
+
queueTransitionKeydown(event, step, generation, queue) {
|
|
2793
|
+
const target = event.target;
|
|
2794
|
+
const root = this.root;
|
|
2430
2795
|
if (!this.isCurrentGeneration(generation) || event.defaultPrevented || event.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
2431
2796
|
return;
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
command = "cancel";
|
|
2436
|
-
else if (!isEditable(event.target, this.root)) {
|
|
2437
|
-
if ((shortcuts?.advance ?? DEFAULT_SHORTCUTS.advance).includes(event.key) && step.props.get().popover?.disableAdvanceButton !== true)
|
|
2438
|
-
command = "advance";
|
|
2439
|
-
else if ((shortcuts?.previous ?? DEFAULT_SHORTCUTS.previous).includes(event.key) && step.props.get().popover?.disablePreviousButton !== true)
|
|
2440
|
-
command = "previous";
|
|
2797
|
+
if (!step.allowsInteraction() && isHTMLElement(target, root) && target !== target.ownerDocument.body && !root?.contains(target)) {
|
|
2798
|
+
event.preventDefault();
|
|
2799
|
+
return;
|
|
2441
2800
|
}
|
|
2801
|
+
if (!queue)
|
|
2802
|
+
return;
|
|
2803
|
+
const command = this.keyboardCommand(event, step, (command2) => isControlAvailable(step.props.get(), command2));
|
|
2442
2804
|
if (!command)
|
|
2443
2805
|
return;
|
|
2444
2806
|
event.preventDefault();
|
|
2445
|
-
this.
|
|
2807
|
+
this.pendingCommand ??= { command, generation, source: "keyboard" };
|
|
2808
|
+
}
|
|
2809
|
+
queueTransitionClick(event, step, generation) {
|
|
2810
|
+
const scope = this.root ?? this.popover?.getElement();
|
|
2811
|
+
if (!this.isCurrentGeneration(generation) || event.defaultPrevented || event === this.handledTriggerClick || !isHTMLElement(scope, scope) || !isElement(event.target, scope))
|
|
2812
|
+
return;
|
|
2813
|
+
const match = this.findClickedTrigger(event.target, scope);
|
|
2814
|
+
if (!match || this.isLiveDisabled(match.trigger) || !isControlAvailable(step.props.get(), match.command))
|
|
2815
|
+
return;
|
|
2816
|
+
this.pendingCommand ??= { command: match.command, generation, source: "trigger" };
|
|
2446
2817
|
}
|
|
2447
|
-
|
|
2448
|
-
const pending = this.
|
|
2818
|
+
flushPendingCommand(step, generation) {
|
|
2819
|
+
const pending = this.pendingCommand;
|
|
2449
2820
|
if (!pending || pending.generation !== generation)
|
|
2450
2821
|
return;
|
|
2451
|
-
this.
|
|
2822
|
+
this.pendingCommand = null;
|
|
2452
2823
|
queueMicrotask(() => {
|
|
2453
2824
|
if (!this.isCurrentGeneration(generation) || this.currentStep !== step || !this.canCommand(pending.command, step))
|
|
2454
2825
|
return;
|
|
2455
|
-
this.commandForGeneration(pending.command, generation,
|
|
2826
|
+
this.commandForGeneration(pending.command, generation, pending.source);
|
|
2456
2827
|
});
|
|
2457
2828
|
}
|
|
2458
2829
|
loopFocus(event) {
|
|
@@ -2475,36 +2846,74 @@ class DomTourViewDriver {
|
|
|
2475
2846
|
focusable[0]?.focus();
|
|
2476
2847
|
}
|
|
2477
2848
|
}
|
|
2849
|
+
engagePopover(step, target, direction, generation) {
|
|
2850
|
+
this.syncModality(step.allowsInteraction());
|
|
2851
|
+
if (this.popoverHidden)
|
|
2852
|
+
this.concealPopover();
|
|
2853
|
+
else if (this.isPopoverConcealed())
|
|
2854
|
+
this.revealPopover(step, target, generation);
|
|
2855
|
+
else
|
|
2856
|
+
this.activateFocus(step, target, direction, generation);
|
|
2857
|
+
}
|
|
2858
|
+
concealPopover() {
|
|
2859
|
+
this.focusToRestore = this.focusGuard.release() ?? this.focusToRestore;
|
|
2860
|
+
const popover = this.popover;
|
|
2861
|
+
const element = popover?.getElement();
|
|
2862
|
+
if (!popover || !element)
|
|
2863
|
+
return;
|
|
2864
|
+
const focused = element.ownerDocument.activeElement;
|
|
2865
|
+
if (isHTMLElement(focused, element) && element.contains(focused)) {
|
|
2866
|
+
focused.blur();
|
|
2867
|
+
this.activeTarget?.focus();
|
|
2868
|
+
}
|
|
2869
|
+
if (this.isPopoverConcealed())
|
|
2870
|
+
return;
|
|
2871
|
+
popover.cancelAnimations();
|
|
2872
|
+
this.observeDynamicOperation(popover.disappear(), this.generation);
|
|
2873
|
+
}
|
|
2874
|
+
revealPopover(step, target, generation) {
|
|
2875
|
+
this.focusGuard.captureInitialFocus(target, this.focusToRestore);
|
|
2876
|
+
this.popover?.cancelAnimations();
|
|
2877
|
+
this.observeDynamicOperation(this.popover?.present(this.lastTargetRect, this.elementProps(step)).then(() => {
|
|
2878
|
+
if (!this.isCurrentGeneration(generation) || this.popoverHidden)
|
|
2879
|
+
return;
|
|
2880
|
+
this.syncModality(!this.frozen && step.allowsInteraction());
|
|
2881
|
+
this.activateFocus(step, target, this.direction, generation);
|
|
2882
|
+
}), generation);
|
|
2883
|
+
}
|
|
2884
|
+
isPopoverConcealed() {
|
|
2885
|
+
return this.popover?.getElement()?.getAttribute("aria-hidden") === "true";
|
|
2886
|
+
}
|
|
2478
2887
|
activateFocus(step, target, direction, generation) {
|
|
2479
2888
|
const popover = this.popover?.getElement();
|
|
2480
2889
|
if (!isHTMLElement(popover, this.root ?? popover))
|
|
2481
2890
|
return;
|
|
2482
|
-
const autoFocus = step.
|
|
2891
|
+
const autoFocus = step.autoFocuses();
|
|
2483
2892
|
const deferFocus = autoFocus && this.commands?.subscribeCapabilities !== undefined;
|
|
2484
2893
|
if (deferFocus)
|
|
2485
2894
|
this.pendingFocusGeneration = generation;
|
|
2486
2895
|
this.focusGuard.activate({
|
|
2487
2896
|
allowedTarget: target,
|
|
2488
|
-
allowTargetInteraction: step.
|
|
2489
|
-
autoFocus
|
|
2897
|
+
allowTargetInteraction: step.allowsInteraction(),
|
|
2898
|
+
autoFocus,
|
|
2899
|
+
deferFocus,
|
|
2490
2900
|
direction,
|
|
2491
2901
|
fallback: this.root ?? popover.parentElement,
|
|
2492
2902
|
popover
|
|
2493
2903
|
});
|
|
2494
2904
|
}
|
|
2495
2905
|
syncScrollLock(step) {
|
|
2496
|
-
if (step.
|
|
2906
|
+
if (step.allowsScroll()) {
|
|
2497
2907
|
this.scrollLock.deactivate();
|
|
2498
2908
|
return;
|
|
2499
2909
|
}
|
|
2500
2910
|
this.scrollLock.activate(this.root?.ownerDocument ?? this.popover?.getElement()?.ownerDocument);
|
|
2501
2911
|
}
|
|
2502
2912
|
syncShortcutLabels(step) {
|
|
2503
|
-
for (const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
syncKeyShortcuts(previous, step.popover?.keyboardShortcuts?.previous ?? DEFAULT_SHORTCUTS.previous);
|
|
2913
|
+
for (const command of ["advance", "previous"]) {
|
|
2914
|
+
const shortcuts = isControlAvailable(step.props.get(), command) ? step.props.get().controls?.[command]?.keys ?? DEFAULT_SHORTCUTS[command] : [];
|
|
2915
|
+
for (const trigger of this.findTriggers(command))
|
|
2916
|
+
syncKeyShortcuts(trigger, shortcuts);
|
|
2508
2917
|
}
|
|
2509
2918
|
}
|
|
2510
2919
|
attachButtonHandlers(step) {
|
|
@@ -2518,6 +2927,7 @@ class DomTourViewDriver {
|
|
|
2518
2927
|
const match = this.findClickedTrigger(event.target, scope);
|
|
2519
2928
|
if (!match)
|
|
2520
2929
|
return;
|
|
2930
|
+
this.handledTriggerClick = event;
|
|
2521
2931
|
this.deferTriggerCommand(match.command, event, step, generation, match.trigger);
|
|
2522
2932
|
});
|
|
2523
2933
|
}
|
|
@@ -2568,13 +2978,13 @@ class DomTourViewDriver {
|
|
|
2568
2978
|
}
|
|
2569
2979
|
syncControlState(step) {
|
|
2570
2980
|
for (const advance of this.findTriggers("advance")) {
|
|
2571
|
-
this.syncControl(advance, this.commands?.isAdvanceDisabled() === true || step.props.get()
|
|
2981
|
+
this.syncControl(advance, this.commands?.isAdvanceDisabled() === true || !isControlAvailable(step.props.get(), "advance"));
|
|
2572
2982
|
}
|
|
2573
2983
|
for (const previous of this.findTriggers("previous")) {
|
|
2574
|
-
this.syncControl(previous, this.commands?.isPreviousDisabled() === true || step.props.get()
|
|
2984
|
+
this.syncControl(previous, this.commands?.isPreviousDisabled() === true || !isControlAvailable(step.props.get(), "previous"));
|
|
2575
2985
|
}
|
|
2576
2986
|
for (const cancel of this.findTriggers("cancel")) {
|
|
2577
|
-
this.syncControl(cancel, this.commands?.isCancelDisabled() === true);
|
|
2987
|
+
this.syncControl(cancel, this.commands?.isCancelDisabled() === true || !isControlAvailable(step.props.get(), "cancel"));
|
|
2578
2988
|
}
|
|
2579
2989
|
}
|
|
2580
2990
|
findTriggers(direction) {
|
|
@@ -2606,14 +3016,12 @@ class DomTourViewDriver {
|
|
|
2606
3016
|
return !signal.aborted && this.currentStep === step ? this.command(command, "api") : Promise.resolve();
|
|
2607
3017
|
}
|
|
2608
3018
|
canCommand(command, step, trigger) {
|
|
2609
|
-
if (this.isConsumerDisabled(trigger ?? null))
|
|
3019
|
+
if (this.isConsumerDisabled(trigger ?? null) || !isControlAvailable(step.props.get(), command))
|
|
2610
3020
|
return false;
|
|
2611
|
-
if (command === "advance")
|
|
2612
|
-
return
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
return (this.commands?.canPrevious?.() ?? true) && step.props.get().popover?.disablePreviousButton !== true;
|
|
2616
|
-
}
|
|
3021
|
+
if (command === "advance")
|
|
3022
|
+
return this.commands?.canAdvance?.() ?? true;
|
|
3023
|
+
if (command === "previous")
|
|
3024
|
+
return this.commands?.canPrevious?.() ?? true;
|
|
2617
3025
|
return this.commands?.canCancel?.() ?? true;
|
|
2618
3026
|
}
|
|
2619
3027
|
syncControl(element, disabled) {
|
|
@@ -2634,13 +3042,19 @@ class DomTourViewDriver {
|
|
|
2634
3042
|
isLiveDisabled(element) {
|
|
2635
3043
|
return element.disabled || element.getAttribute("aria-disabled") === "true" || this.isConsumerDisabled(element);
|
|
2636
3044
|
}
|
|
3045
|
+
elementProps(step) {
|
|
3046
|
+
const props = step.props.get();
|
|
3047
|
+
return step.detached ? { ...props, detached: true } : props;
|
|
3048
|
+
}
|
|
2637
3049
|
isPointerEnabled(step) {
|
|
2638
|
-
return step.
|
|
3050
|
+
return step.allowsInteraction() && step.props.get().indicator?.hidden !== true;
|
|
2639
3051
|
}
|
|
2640
3052
|
cleanupStepResources() {
|
|
2641
3053
|
this.cancelScroll?.();
|
|
2642
3054
|
this.cancelScroll = null;
|
|
3055
|
+
this.stopUserScroll();
|
|
2643
3056
|
this.presentationDirty = false;
|
|
3057
|
+
this.pointerFading = false;
|
|
2644
3058
|
if (this.rafId !== null)
|
|
2645
3059
|
this.rafCancel?.(this.rafId);
|
|
2646
3060
|
this.rafId = null;
|
|
@@ -2668,22 +3082,51 @@ class DomTourViewDriver {
|
|
|
2668
3082
|
this.targetFocusedAtFreeze = this.isFocusInsideTarget(target);
|
|
2669
3083
|
this.cleanupTargetResources();
|
|
2670
3084
|
this.applyInteractionLock(step, true);
|
|
3085
|
+
if (this.userScrolling) {
|
|
3086
|
+
this.stopUserScroll();
|
|
3087
|
+
const targetRect = this.lastTargetRect;
|
|
3088
|
+
this.popover?.cancelAnimations();
|
|
3089
|
+
if (targetRect && !this.popoverHidden) {
|
|
3090
|
+
this.observeDynamicOperation(this.popover?.present(targetRect, this.elementProps(step)), generation);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
2671
3093
|
Promise.resolve(this.commands?.targetDisconnected(target)).catch((error) => {
|
|
2672
3094
|
this.commands?.reportError(error).catch(() => {});
|
|
2673
3095
|
});
|
|
2674
3096
|
}
|
|
2675
3097
|
applyInteractionLock(step, locked) {
|
|
2676
|
-
const allowed = !locked && step.
|
|
3098
|
+
const allowed = !locked && step.allowsInteraction();
|
|
2677
3099
|
this.overlay?.setInteractionAllowed(allowed);
|
|
2678
3100
|
this.syncModality(allowed);
|
|
3101
|
+
}
|
|
3102
|
+
applyInteraction(step, target) {
|
|
3103
|
+
this.applyInteractionLock(step, false);
|
|
3104
|
+
this.appliedAllowInteraction = step.allowsInteraction();
|
|
2679
3105
|
const popover = this.popover?.getElement();
|
|
2680
|
-
if (isHTMLElement(popover, this.root ?? popover)) {
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
3106
|
+
if (!this.popoverHidden && isHTMLElement(popover, this.root ?? popover)) {
|
|
3107
|
+
this.focusGuard.update({
|
|
3108
|
+
allowedTarget: target,
|
|
3109
|
+
allowTargetInteraction: step.allowsInteraction(),
|
|
3110
|
+
direction: this.direction,
|
|
3111
|
+
fallback: this.root ?? popover.parentElement,
|
|
3112
|
+
popover
|
|
3113
|
+
});
|
|
2685
3114
|
}
|
|
2686
3115
|
}
|
|
3116
|
+
syncInteraction(step) {
|
|
3117
|
+
const target = this.activeTarget;
|
|
3118
|
+
if (this.disposed || !this.active || this.frozen || this.awaitingStepUi && !this.userScrolling || this.currentStep !== step || !target)
|
|
3119
|
+
return;
|
|
3120
|
+
const focusWasInTarget = this.isFocusInsideTarget(target);
|
|
3121
|
+
this.applyInteraction(step, target);
|
|
3122
|
+
if (focusWasInTarget && !step.allowsInteraction() && step.autoFocuses())
|
|
3123
|
+
this.focusGuard.focus();
|
|
3124
|
+
if (this.userScrolling)
|
|
3125
|
+
return;
|
|
3126
|
+
const targetRect = target.getBoundingClientRect();
|
|
3127
|
+
this.pointer?.cancelAnimations();
|
|
3128
|
+
this.observeDynamicOperation(this.presentPointer(targetRect, step, this.popover?.resolvePosition(targetRect, this.elementProps(step)).placement), this.generation);
|
|
3129
|
+
}
|
|
2687
3130
|
isFocusInsideTarget(target) {
|
|
2688
3131
|
const activeElement = target.ownerDocument?.activeElement;
|
|
2689
3132
|
if (!activeElement)
|
|
@@ -2700,23 +3143,15 @@ class DomTourViewDriver {
|
|
|
2700
3143
|
this.frozen = false;
|
|
2701
3144
|
this.currentSignal = signal;
|
|
2702
3145
|
this.activeTarget = target;
|
|
2703
|
-
this.
|
|
3146
|
+
this.applyInteraction(step, target);
|
|
2704
3147
|
this.attachTargetResources(step, target, this.generation, signal);
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
this.focusGuard.update({
|
|
2708
|
-
allowedTarget: target,
|
|
2709
|
-
allowTargetInteraction: step.behavior?.allowInteraction === true,
|
|
2710
|
-
direction: this.direction,
|
|
2711
|
-
fallback: this.root ?? popover.parentElement,
|
|
2712
|
-
popover
|
|
2713
|
-
});
|
|
2714
|
-
}
|
|
2715
|
-
if (this.targetFocusedAtFreeze) {
|
|
2716
|
-
this.targetFocusedAtFreeze = false;
|
|
2717
|
-
if (step.behavior?.allowInteraction === true)
|
|
3148
|
+
if (this.targetFocusedAtFreeze && step.autoFocuses()) {
|
|
3149
|
+
if (step.allowsInteraction())
|
|
2718
3150
|
target.focus();
|
|
3151
|
+
else
|
|
3152
|
+
this.focusGuard.focus();
|
|
2719
3153
|
}
|
|
3154
|
+
this.targetFocusedAtFreeze = false;
|
|
2720
3155
|
this.syncControlState(step);
|
|
2721
3156
|
this.syncShortcutLabels(step);
|
|
2722
3157
|
this.moveToRetargetedRect(step, target);
|
|
@@ -2724,18 +3159,18 @@ class DomTourViewDriver {
|
|
|
2724
3159
|
}
|
|
2725
3160
|
moveToRetargetedRect(step, target) {
|
|
2726
3161
|
const generation = this.generation;
|
|
2727
|
-
const targetRect = target
|
|
2728
|
-
this.observeDynamicOperation(this.overlay?.animateTo(targetRect, step), generation);
|
|
2729
|
-
const placement = this.
|
|
3162
|
+
const targetRect = presentationRect(step, target);
|
|
3163
|
+
this.observeDynamicOperation(this.overlay?.animateTo(targetRect, this.elementProps(step)), generation);
|
|
3164
|
+
const placement = this.placePopover(targetRect, step, generation);
|
|
2730
3165
|
if (this.isPointerEnabled(step)) {
|
|
2731
|
-
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step, true, placement), generation);
|
|
3166
|
+
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step.props.get(), true, placement), generation);
|
|
2732
3167
|
}
|
|
2733
3168
|
this.lastTargetRect = snapshotRect(targetRect);
|
|
2734
3169
|
this.lastViewport = snapshotViewport(target);
|
|
2735
3170
|
}
|
|
2736
3171
|
beginGeneration() {
|
|
2737
3172
|
this.generation += 1;
|
|
2738
|
-
this.
|
|
3173
|
+
this.pendingCommand = null;
|
|
2739
3174
|
this.pendingFocusGeneration = null;
|
|
2740
3175
|
this.cancelElementAnimations();
|
|
2741
3176
|
return this.generation;
|
|
@@ -2755,18 +3190,21 @@ class DomTourViewDriver {
|
|
|
2755
3190
|
}
|
|
2756
3191
|
beginTargetScroll(step, target, signal) {
|
|
2757
3192
|
this.throwIfAborted(signal);
|
|
2758
|
-
if (step.behavior?.
|
|
3193
|
+
if (step.detached || step.props.get().behavior?.autoScroll === false)
|
|
2759
3194
|
return null;
|
|
2760
3195
|
if (isInViewport(target.getBoundingClientRect(), target))
|
|
2761
3196
|
return null;
|
|
2762
|
-
|
|
2763
|
-
if (!currentWindow)
|
|
3197
|
+
if (!this.getWindow(target))
|
|
2764
3198
|
return null;
|
|
2765
|
-
|
|
3199
|
+
return this.scrollTargetIntoView(step, target, signal);
|
|
3200
|
+
}
|
|
3201
|
+
scrollTargetIntoView(step, target, signal) {
|
|
3202
|
+
const scroll = step.props.get().behavior?.scroll;
|
|
3203
|
+
const behavior = prefersReducedMotion(target) ? "instant" : scroll?.behavior ?? "smooth";
|
|
2766
3204
|
target.scrollIntoView({
|
|
2767
3205
|
behavior,
|
|
2768
|
-
block:
|
|
2769
|
-
inline:
|
|
3206
|
+
block: scroll?.block ?? "center",
|
|
3207
|
+
inline: scroll?.inline ?? "nearest"
|
|
2770
3208
|
});
|
|
2771
3209
|
if (behavior === "instant")
|
|
2772
3210
|
return null;
|
|
@@ -2845,6 +3283,12 @@ function animationOptions(step, options, element) {
|
|
|
2845
3283
|
easing: options?.animation?.easing
|
|
2846
3284
|
};
|
|
2847
3285
|
}
|
|
3286
|
+
function presentationRect(step, target) {
|
|
3287
|
+
if (!step.detached)
|
|
3288
|
+
return target.getBoundingClientRect();
|
|
3289
|
+
const viewport = viewportDimensions(target);
|
|
3290
|
+
return { height: 0, left: viewport.width / 2, top: viewport.height / 2, width: 0 };
|
|
3291
|
+
}
|
|
2848
3292
|
function abortError2() {
|
|
2849
3293
|
return new DOMException("The operation was aborted", "AbortError");
|
|
2850
3294
|
}
|
|
@@ -2880,6 +3324,10 @@ function sameViewport(left, right) {
|
|
|
2880
3324
|
function finite(value, fallback = 0) {
|
|
2881
3325
|
return Number.isFinite(value) ? value : fallback;
|
|
2882
3326
|
}
|
|
3327
|
+
function activatesControl(event, context) {
|
|
3328
|
+
const target = event.target;
|
|
3329
|
+
return event.key === "Enter" && isHTMLElement(target, context) && target.matches(FOCUSABLE_SELECTOR);
|
|
3330
|
+
}
|
|
2883
3331
|
function isEditable(target, context) {
|
|
2884
3332
|
if (!isHTMLElement(target, context))
|
|
2885
3333
|
return false;
|
|
@@ -2950,12 +3398,13 @@ function validateIndicator(path, indicator) {
|
|
|
2950
3398
|
validateFiniteNonNegative(`${path}.gap`, indicator.gap);
|
|
2951
3399
|
}
|
|
2952
3400
|
function validateBehavior(path, behavior) {
|
|
2953
|
-
validateFiniteNonNegative(`${path}.
|
|
3401
|
+
validateFiniteNonNegative(`${path}.missingTarget.timeout`, behavior?.missingTarget?.timeout);
|
|
2954
3402
|
}
|
|
2955
3403
|
function validateStepProps(path, props) {
|
|
2956
3404
|
validateOverlay(`${path}.overlay`, props.overlay);
|
|
2957
3405
|
validatePopover(`${path}.popover`, props.popover);
|
|
2958
3406
|
validateIndicator(`${path}.indicator`, props.indicator);
|
|
3407
|
+
validateBehavior(`${path}.behavior`, props.behavior);
|
|
2959
3408
|
}
|
|
2960
3409
|
function validateWorkflowOptions(workflow) {
|
|
2961
3410
|
validateOverlay("options.overlay", workflow.options.overlay);
|
|
@@ -2963,115 +3412,10 @@ function validateWorkflowOptions(workflow) {
|
|
|
2963
3412
|
validateIndicator("options.indicator", workflow.options.indicator);
|
|
2964
3413
|
validateBehavior("options.behavior", workflow.options.behavior);
|
|
2965
3414
|
for (const [index, step] of workflow.steps.entries()) {
|
|
2966
|
-
|
|
2967
|
-
validateBehavior(`${path}.behavior`, step.behavior);
|
|
2968
|
-
validateStepProps(path, step.props);
|
|
3415
|
+
validateStepProps(`steps[${index}]`, step.props);
|
|
2969
3416
|
}
|
|
2970
3417
|
}
|
|
2971
3418
|
|
|
2972
|
-
// packages/core/src/utils/options.ts
|
|
2973
|
-
function mergeOverlayOptions(defaults, overrides) {
|
|
2974
|
-
if (!defaults && !overrides) {
|
|
2975
|
-
return;
|
|
2976
|
-
}
|
|
2977
|
-
return {
|
|
2978
|
-
...mergeBaseOptions(defaults, overrides),
|
|
2979
|
-
color: overrides?.color ?? defaults?.color,
|
|
2980
|
-
opacity: overrides?.opacity ?? defaults?.opacity,
|
|
2981
|
-
padding: overrides?.padding ?? defaults?.padding,
|
|
2982
|
-
radius: overrides?.radius ?? defaults?.radius
|
|
2983
|
-
};
|
|
2984
|
-
}
|
|
2985
|
-
function mergeIndicatorOptions(defaults, overrides) {
|
|
2986
|
-
if (!defaults && !overrides) {
|
|
2987
|
-
return;
|
|
2988
|
-
}
|
|
2989
|
-
return {
|
|
2990
|
-
...mergeBaseOptions(defaults, overrides),
|
|
2991
|
-
disabled: overrides?.disabled ?? defaults?.disabled,
|
|
2992
|
-
gap: overrides?.gap ?? defaults?.gap,
|
|
2993
|
-
placementTryOrder: cloneArray(overrides?.placementTryOrder ?? defaults?.placementTryOrder)
|
|
2994
|
-
};
|
|
2995
|
-
}
|
|
2996
|
-
function mergePopoverOptions(defaults, overrides) {
|
|
2997
|
-
if (!defaults && !overrides) {
|
|
2998
|
-
return;
|
|
2999
|
-
}
|
|
3000
|
-
const placementTryOrder = overrides?.placementTryOrder ?? defaults?.placementTryOrder;
|
|
3001
|
-
const hasArrow = !!defaults?.arrow || !!overrides?.arrow;
|
|
3002
|
-
const hasKeyboardShortcuts = !!defaults?.keyboardShortcuts || !!overrides?.keyboardShortcuts;
|
|
3003
|
-
return {
|
|
3004
|
-
...mergeBaseOptions(defaults, overrides),
|
|
3005
|
-
arrow: hasArrow ? {
|
|
3006
|
-
disabled: overrides?.arrow?.disabled ?? defaults?.arrow?.disabled,
|
|
3007
|
-
color: overrides?.arrow?.color ?? defaults?.arrow?.color,
|
|
3008
|
-
size: overrides?.arrow?.size ?? defaults?.arrow?.size,
|
|
3009
|
-
borderWidth: overrides?.arrow?.borderWidth ?? defaults?.arrow?.borderWidth,
|
|
3010
|
-
borderRadius: overrides?.arrow?.borderRadius ?? defaults?.arrow?.borderRadius,
|
|
3011
|
-
edgePadding: overrides?.arrow?.edgePadding ?? defaults?.arrow?.edgePadding,
|
|
3012
|
-
styleNonce: overrides?.arrow?.styleNonce ?? defaults?.arrow?.styleNonce,
|
|
3013
|
-
disableAutoStyles: overrides?.arrow?.disableAutoStyles ?? defaults?.arrow?.disableAutoStyles
|
|
3014
|
-
} : undefined,
|
|
3015
|
-
disableAdvanceButton: overrides?.disableAdvanceButton ?? defaults?.disableAdvanceButton,
|
|
3016
|
-
disablePreviousButton: overrides?.disablePreviousButton ?? defaults?.disablePreviousButton,
|
|
3017
|
-
gap: overrides?.gap ?? defaults?.gap,
|
|
3018
|
-
hideAdvanceButton: overrides?.hideAdvanceButton ?? defaults?.hideAdvanceButton,
|
|
3019
|
-
hideFooter: overrides?.hideFooter ?? defaults?.hideFooter,
|
|
3020
|
-
hidePreviousButton: overrides?.hidePreviousButton ?? defaults?.hidePreviousButton,
|
|
3021
|
-
placementTryOrder: cloneArray(placementTryOrder),
|
|
3022
|
-
keyboardShortcuts: hasKeyboardShortcuts ? {
|
|
3023
|
-
previous: cloneArray(overrides?.keyboardShortcuts?.previous ?? defaults?.keyboardShortcuts?.previous),
|
|
3024
|
-
advance: cloneArray(overrides?.keyboardShortcuts?.advance ?? defaults?.keyboardShortcuts?.advance),
|
|
3025
|
-
cancel: cloneArray(overrides?.keyboardShortcuts?.cancel ?? defaults?.keyboardShortcuts?.cancel)
|
|
3026
|
-
} : undefined
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
function mergeScrollOptions(defaults, overrides) {
|
|
3030
|
-
if (!defaults && !overrides) {
|
|
3031
|
-
return;
|
|
3032
|
-
}
|
|
3033
|
-
return {
|
|
3034
|
-
behavior: overrides?.behavior ?? defaults?.behavior,
|
|
3035
|
-
block: overrides?.block ?? defaults?.block,
|
|
3036
|
-
inline: overrides?.inline ?? defaults?.inline
|
|
3037
|
-
};
|
|
3038
|
-
}
|
|
3039
|
-
function mergeAnimationOptions(defaults, overrides) {
|
|
3040
|
-
if (!defaults) {
|
|
3041
|
-
return overrides ? { ...overrides } : undefined;
|
|
3042
|
-
}
|
|
3043
|
-
if (!overrides) {
|
|
3044
|
-
return { ...defaults };
|
|
3045
|
-
}
|
|
3046
|
-
return {
|
|
3047
|
-
duration: overrides.duration ?? defaults.duration,
|
|
3048
|
-
easing: overrides.easing ?? defaults.easing
|
|
3049
|
-
};
|
|
3050
|
-
}
|
|
3051
|
-
function mergeBaseOptions(defaults, overrides) {
|
|
3052
|
-
return {
|
|
3053
|
-
animated: overrides?.animated ?? defaults?.animated,
|
|
3054
|
-
animation: mergeAnimationOptions(defaults?.animation, overrides?.animation)
|
|
3055
|
-
};
|
|
3056
|
-
}
|
|
3057
|
-
function mergeStepBehavior(defaults, overrides) {
|
|
3058
|
-
if (!defaults && !overrides) {
|
|
3059
|
-
return;
|
|
3060
|
-
}
|
|
3061
|
-
return {
|
|
3062
|
-
allowInteraction: overrides?.allowInteraction ?? defaults?.allowInteraction,
|
|
3063
|
-
disableAutoFocus: overrides?.disableAutoFocus ?? defaults?.disableAutoFocus,
|
|
3064
|
-
disableAutoScroll: overrides?.disableAutoScroll ?? defaults?.disableAutoScroll,
|
|
3065
|
-
missingTargetStrategy: overrides?.missingTargetStrategy ?? defaults?.missingTargetStrategy,
|
|
3066
|
-
scroll: mergeScrollOptions(defaults?.scroll, overrides?.scroll),
|
|
3067
|
-
targetTimeout: overrides?.targetTimeout ?? defaults?.targetTimeout,
|
|
3068
|
-
overlayClick: overrides?.overlayClick ?? defaults?.overlayClick
|
|
3069
|
-
};
|
|
3070
|
-
}
|
|
3071
|
-
function cloneArray(value) {
|
|
3072
|
-
return value ? [...value] : undefined;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
3419
|
// packages/core/src/runtime/step-props-store.ts
|
|
3076
3420
|
function createStepPropsStore(initialProps, reportListenerError, path = "steps[0]") {
|
|
3077
3421
|
let current = freezeStepProps(initialProps);
|
|
@@ -3085,16 +3429,18 @@ function createStepPropsStore(initialProps, reportListenerError, path = "steps[0
|
|
|
3085
3429
|
} catch {}
|
|
3086
3430
|
}
|
|
3087
3431
|
};
|
|
3432
|
+
const set = (update) => {
|
|
3433
|
+
const next = typeof update === "function" ? update(current) : update;
|
|
3434
|
+
validateStepProps(path, next);
|
|
3435
|
+
current = freezeStepProps(next);
|
|
3436
|
+
const published = current;
|
|
3437
|
+
for (const listener of Array.from(listeners))
|
|
3438
|
+
notify(listener, published);
|
|
3439
|
+
};
|
|
3088
3440
|
return Object.freeze({
|
|
3089
3441
|
get: () => current,
|
|
3090
|
-
set
|
|
3091
|
-
|
|
3092
|
-
validateStepProps(path, next);
|
|
3093
|
-
current = freezeStepProps(next);
|
|
3094
|
-
const published = current;
|
|
3095
|
-
for (const listener of Array.from(listeners))
|
|
3096
|
-
notify(listener, published);
|
|
3097
|
-
},
|
|
3442
|
+
set,
|
|
3443
|
+
update: (patch) => set((props) => mergeStepProps(props, typeof patch === "function" ? patch(props) : patch)),
|
|
3098
3444
|
subscribe: (listener) => {
|
|
3099
3445
|
notify(listener, current);
|
|
3100
3446
|
listeners.add(listener);
|
|
@@ -3116,41 +3462,34 @@ class ActiveStep {
|
|
|
3116
3462
|
rootDocument;
|
|
3117
3463
|
initialProps;
|
|
3118
3464
|
props;
|
|
3119
|
-
behavior;
|
|
3120
3465
|
animated;
|
|
3121
|
-
allowScroll;
|
|
3122
3466
|
target = null;
|
|
3467
|
+
detached = false;
|
|
3468
|
+
direction = "advance";
|
|
3123
3469
|
constructor(definition, defaults, reportSubscriberError = () => {}, path = "steps[0]", rootDocument) {
|
|
3124
3470
|
this.definition = definition;
|
|
3125
3471
|
this.path = path;
|
|
3126
3472
|
this.rootDocument = rootDocument;
|
|
3127
|
-
this.initialProps = freezeStepProps(
|
|
3128
|
-
title: definition.props.title,
|
|
3129
|
-
content: definition.props.content,
|
|
3130
|
-
data: definition.props.data,
|
|
3131
|
-
overlay: mergeOverlayOptions(defaults.overlay, definition.props.overlay),
|
|
3132
|
-
popover: mergePopoverOptions(defaults.popover, definition.props.popover),
|
|
3133
|
-
indicator: mergeIndicatorOptions(defaults.indicator, definition.props.indicator)
|
|
3134
|
-
});
|
|
3473
|
+
this.initialProps = freezeStepProps(mergeStepProps(defaults, definition.props));
|
|
3135
3474
|
this.props = createStepPropsStore(this.initialProps, reportSubscriberError, path);
|
|
3136
|
-
this.behavior = mergeStepBehavior(defaults.behavior, definition.behavior);
|
|
3137
3475
|
this.animated = defaults.animated;
|
|
3138
|
-
this.allowScroll = defaults.allowScroll !== false;
|
|
3139
3476
|
}
|
|
3140
|
-
|
|
3141
|
-
this.props.
|
|
3477
|
+
allowsInteraction() {
|
|
3478
|
+
return !this.detached && this.props.get().behavior?.allowInteraction === true;
|
|
3142
3479
|
}
|
|
3143
|
-
|
|
3144
|
-
return this.props.get().
|
|
3480
|
+
autoFocuses() {
|
|
3481
|
+
return this.props.get().behavior?.autoFocus !== false;
|
|
3145
3482
|
}
|
|
3146
|
-
|
|
3147
|
-
return this.props.get().
|
|
3483
|
+
allowsScroll() {
|
|
3484
|
+
return this.props.get().behavior?.allowScroll !== false;
|
|
3148
3485
|
}
|
|
3149
|
-
|
|
3150
|
-
return this.
|
|
3486
|
+
resolveTarget(signal) {
|
|
3487
|
+
return resolveTargetElement(this.definition.target, { document: this.rootDocument, signal }, this.path);
|
|
3151
3488
|
}
|
|
3152
|
-
|
|
3153
|
-
|
|
3489
|
+
detach() {
|
|
3490
|
+
const body = (this.rootDocument ?? globalThis.document)?.body ?? null;
|
|
3491
|
+
this.detached = body !== null;
|
|
3492
|
+
return body;
|
|
3154
3493
|
}
|
|
3155
3494
|
snapshot() {
|
|
3156
3495
|
return Object.freeze({
|
|
@@ -3231,7 +3570,7 @@ class TourRootBinding {
|
|
|
3231
3570
|
hasPopover() {
|
|
3232
3571
|
return this.popover !== null;
|
|
3233
3572
|
}
|
|
3234
|
-
|
|
3573
|
+
getDocument() {
|
|
3235
3574
|
return this.root.ownerDocument;
|
|
3236
3575
|
}
|
|
3237
3576
|
release() {
|
|
@@ -3365,11 +3704,11 @@ function attachRootBridge(tour, driver, isDisposed, onMountReleaseStart, onMount
|
|
|
3365
3704
|
return {
|
|
3366
3705
|
assertCanRun(workflow) {
|
|
3367
3706
|
if (!binding)
|
|
3368
|
-
throw new Error("Glow tour requires a connected root before
|
|
3707
|
+
throw new Error("Glow tour requires a connected root before start()");
|
|
3369
3708
|
if (workflow.steps.length > 0 && !binding.hasPopover()) {
|
|
3370
|
-
throw new Error("Glow tour requires a connected popover before
|
|
3709
|
+
throw new Error("Glow tour requires a connected popover before start()");
|
|
3371
3710
|
}
|
|
3372
|
-
return binding.
|
|
3711
|
+
return binding.getDocument();
|
|
3373
3712
|
},
|
|
3374
3713
|
release() {
|
|
3375
3714
|
binding?.release();
|
|
@@ -3459,13 +3798,10 @@ function prefixReservations(document2) {
|
|
|
3459
3798
|
var DEFAULT_TARGET_TIMEOUT = 3000;
|
|
3460
3799
|
var TARGET_LOSS_GRACE_MS = 150;
|
|
3461
3800
|
var DISPOSED_ERROR_MESSAGE = "Tour controller is disposed";
|
|
3462
|
-
function
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
if (index === -1) {
|
|
3467
|
-
throw new Error(`Workflow "${workflow.name}" has no step with id "${startAt}" to start at.`);
|
|
3468
|
-
}
|
|
3801
|
+
function resolveStepIndex(workflow, id) {
|
|
3802
|
+
const index = workflow.steps.findIndex((step) => step.id === id);
|
|
3803
|
+
if (index === -1)
|
|
3804
|
+
throw new Error(`Workflow "${workflow.name}" has no step with id "${id}".`);
|
|
3469
3805
|
return index;
|
|
3470
3806
|
}
|
|
3471
3807
|
function normalizedError(error) {
|
|
@@ -3491,13 +3827,16 @@ class TourController {
|
|
|
3491
3827
|
recoveringTarget = null;
|
|
3492
3828
|
operationToken = 0;
|
|
3493
3829
|
publicationRevision = 0;
|
|
3830
|
+
awaitingTargetOperation = null;
|
|
3494
3831
|
operation = null;
|
|
3832
|
+
popoverHidden = false;
|
|
3495
3833
|
disposed = false;
|
|
3496
3834
|
retainedPresentation = null;
|
|
3497
3835
|
stateListeners = new Set;
|
|
3498
3836
|
stepPropsSubscriptions = [];
|
|
3499
3837
|
tourStartedAt = 0;
|
|
3500
3838
|
stepEnteredAt = 0;
|
|
3839
|
+
pendingTourStart;
|
|
3501
3840
|
commandSource = "api";
|
|
3502
3841
|
state = Object.freeze({
|
|
3503
3842
|
get: () => this.snapshot,
|
|
@@ -3523,7 +3862,8 @@ class TourController {
|
|
|
3523
3862
|
canCancel: () => this.status === "active" && this.isCancelAvailable(),
|
|
3524
3863
|
canPrevious: () => this.canNavigate("previous"),
|
|
3525
3864
|
cancel: (source) => this.cancel(source),
|
|
3526
|
-
|
|
3865
|
+
goTo: (id) => this.goTo(id),
|
|
3866
|
+
isAdvanceDisabled: () => this.awaitingTargetOperation !== null || !this.isPresentedAdvanceAvailable(),
|
|
3527
3867
|
isCancelDisabled: () => !this.isPresentedCancelAvailable(),
|
|
3528
3868
|
isPreviousDisabled: () => !this.isPresentedPreviousAvailable(),
|
|
3529
3869
|
previous: (source) => this.previous(source),
|
|
@@ -3544,10 +3884,10 @@ class TourController {
|
|
|
3544
3884
|
create(name, options = {}) {
|
|
3545
3885
|
return new WorkflowBuilder(name, options);
|
|
3546
3886
|
}
|
|
3547
|
-
async
|
|
3887
|
+
async start(workflow, runOptions = {}) {
|
|
3548
3888
|
this.assertNotDisposed();
|
|
3549
3889
|
validateWorkflowOptions(workflow);
|
|
3550
|
-
const startIndex =
|
|
3890
|
+
const startIndex = runOptions.startAt === undefined ? 0 : resolveStepIndex(workflow, runOptions.startAt);
|
|
3551
3891
|
const rootDocument = this.options.assertCanRun?.(workflow) ?? undefined;
|
|
3552
3892
|
const retainedPresentation = this.capturePresentation();
|
|
3553
3893
|
const operation = this.beginOperation();
|
|
@@ -3564,6 +3904,9 @@ class TourController {
|
|
|
3564
3904
|
this.error = null;
|
|
3565
3905
|
this.commandSource = "api";
|
|
3566
3906
|
this.retainedPresentation = retainedPresentation;
|
|
3907
|
+
this.popoverHidden = false;
|
|
3908
|
+
this.driver.setPopoverHidden?.(false);
|
|
3909
|
+
this.pendingTourStart = undefined;
|
|
3567
3910
|
try {
|
|
3568
3911
|
this.setStatus("starting");
|
|
3569
3912
|
this.assertCurrent(operation);
|
|
@@ -3571,16 +3914,16 @@ class TourController {
|
|
|
3571
3914
|
await workflow.options.onStart?.(startContext);
|
|
3572
3915
|
this.assertCurrent(operation);
|
|
3573
3916
|
if (isStartAborted()) {
|
|
3574
|
-
this.resetToIdle();
|
|
3917
|
+
await this.resetToIdle(operation);
|
|
3575
3918
|
return;
|
|
3576
3919
|
}
|
|
3577
3920
|
this.tourStartedAt = Date.now();
|
|
3578
|
-
this.
|
|
3921
|
+
this.pendingTourStart = this.steps[startIndex] ?? null;
|
|
3579
3922
|
if (this.steps.length === 0) {
|
|
3580
3923
|
await this.finish(operation);
|
|
3581
3924
|
return;
|
|
3582
3925
|
}
|
|
3583
|
-
await this.
|
|
3926
|
+
await this.navigate(startIndex, "advance", operation);
|
|
3584
3927
|
} catch (error) {
|
|
3585
3928
|
await this.handleFailure(error, operation);
|
|
3586
3929
|
}
|
|
@@ -3593,15 +3936,16 @@ class TourController {
|
|
|
3593
3936
|
this.assertNotDisposed();
|
|
3594
3937
|
await this.transitionFromPublic("previous", undefined, source);
|
|
3595
3938
|
}
|
|
3596
|
-
async
|
|
3939
|
+
async goTo(id) {
|
|
3597
3940
|
this.assertNotDisposed();
|
|
3598
|
-
|
|
3941
|
+
const workflow = this.workflow;
|
|
3942
|
+
if (!workflow || this.status === "starting" || this.status === "transitioning")
|
|
3599
3943
|
return;
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3944
|
+
const index = resolveStepIndex(workflow, id);
|
|
3945
|
+
await this.transitionFromPublic(this.directionTo(index), index, "api");
|
|
3946
|
+
}
|
|
3947
|
+
directionTo(index) {
|
|
3948
|
+
return index > this.index ? "advance" : "previous";
|
|
3605
3949
|
}
|
|
3606
3950
|
async cancel(source = "api") {
|
|
3607
3951
|
this.assertNotDisposed();
|
|
@@ -3615,6 +3959,17 @@ class TourController {
|
|
|
3615
3959
|
await this.handleFailure(error, operation);
|
|
3616
3960
|
}
|
|
3617
3961
|
}
|
|
3962
|
+
setPopoverHidden(hidden) {
|
|
3963
|
+
this.assertNotDisposed();
|
|
3964
|
+
if (!this.isRunning() || this.popoverHidden === hidden)
|
|
3965
|
+
return;
|
|
3966
|
+
this.popoverHidden = hidden;
|
|
3967
|
+
this.driver.setPopoverHidden?.(hidden);
|
|
3968
|
+
this.publish();
|
|
3969
|
+
}
|
|
3970
|
+
isRunning() {
|
|
3971
|
+
return this.status === "starting" || this.status === "transitioning" || this.status === "active";
|
|
3972
|
+
}
|
|
3618
3973
|
dispose() {
|
|
3619
3974
|
if (this.disposed)
|
|
3620
3975
|
return;
|
|
@@ -3627,6 +3982,7 @@ class TourController {
|
|
|
3627
3982
|
this.direction = "advance";
|
|
3628
3983
|
this.error = null;
|
|
3629
3984
|
this.retainedPresentation = null;
|
|
3985
|
+
this.pendingTourStart = undefined;
|
|
3630
3986
|
this.status = "disposed";
|
|
3631
3987
|
this.publish(true);
|
|
3632
3988
|
this.stateListeners.clear();
|
|
@@ -3647,29 +4003,57 @@ class TourController {
|
|
|
3647
4003
|
this.direction = "advance";
|
|
3648
4004
|
this.error = null;
|
|
3649
4005
|
this.retainedPresentation = null;
|
|
4006
|
+
this.pendingTourStart = undefined;
|
|
3650
4007
|
this.status = "idle";
|
|
3651
4008
|
}
|
|
3652
4009
|
completeMountRelease() {
|
|
3653
4010
|
if (!this.disposed)
|
|
3654
4011
|
this.publish();
|
|
3655
4012
|
}
|
|
3656
|
-
async
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
4013
|
+
async navigate(index, direction, operation, lostStep) {
|
|
4014
|
+
const from = this.currentStep();
|
|
4015
|
+
if (this.status !== "transitioning")
|
|
4016
|
+
this.setStatus("transitioning");
|
|
3660
4017
|
this.assertCurrent(operation);
|
|
3661
|
-
const
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
4018
|
+
const skipped = [];
|
|
4019
|
+
let step = this.steps[index];
|
|
4020
|
+
let target = null;
|
|
4021
|
+
while (step) {
|
|
4022
|
+
target = await this.resolveTarget(step, operation);
|
|
4023
|
+
this.assertCurrent(operation);
|
|
4024
|
+
if (target)
|
|
4025
|
+
break;
|
|
4026
|
+
skipped.push(step);
|
|
4027
|
+
index += direction === "advance" ? 1 : -1;
|
|
4028
|
+
step = this.steps[index];
|
|
4029
|
+
}
|
|
4030
|
+
if (!step || !target) {
|
|
4031
|
+
if (index >= this.steps.length || lostStep)
|
|
4032
|
+
this.direction = direction;
|
|
4033
|
+
if (index >= this.steps.length)
|
|
4034
|
+
await this.finish(operation, skipped);
|
|
4035
|
+
else if (!lostStep)
|
|
4036
|
+
this.setStatus("active");
|
|
4037
|
+
else
|
|
4038
|
+
await this.cancelCurrent(operation);
|
|
3670
4039
|
return;
|
|
3671
4040
|
}
|
|
3672
4041
|
step.target = target;
|
|
4042
|
+
step.direction = direction;
|
|
4043
|
+
if (await this.runStepHook(step.definition.beforeEnter, step, operation, direction)) {
|
|
4044
|
+
if (lostStep)
|
|
4045
|
+
throw this.missingTargetError(lostStep);
|
|
4046
|
+
if (from)
|
|
4047
|
+
this.setStatus("active");
|
|
4048
|
+
else
|
|
4049
|
+
await this.resetToIdle(operation);
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
this.direction = direction;
|
|
4053
|
+
this.flushTourStart();
|
|
4054
|
+
for (const skippedStep of skipped)
|
|
4055
|
+
this.emit("step:skip", skippedStep, 0);
|
|
4056
|
+
this.emitStepLeave(from);
|
|
3673
4057
|
let committed = false;
|
|
3674
4058
|
const commitStep = () => {
|
|
3675
4059
|
this.assertCurrent(operation);
|
|
@@ -3708,29 +4092,24 @@ class TourController {
|
|
|
3708
4092
|
return;
|
|
3709
4093
|
this.setStatus("transitioning");
|
|
3710
4094
|
this.assertCurrent(operation);
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
this.assertCurrent(operation);
|
|
3714
|
-
if (destination !== undefined) {
|
|
3715
|
-
await this.enter(destination, direction, operation);
|
|
3716
|
-
return;
|
|
3717
|
-
}
|
|
3718
|
-
if (direction === "advance") {
|
|
3719
|
-
const nextIndex = this.index + 1;
|
|
3720
|
-
if (nextIndex >= this.steps.length)
|
|
3721
|
-
await this.finish(operation);
|
|
3722
|
-
else
|
|
3723
|
-
await this.enter(nextIndex, direction, operation);
|
|
3724
|
-
return;
|
|
3725
|
-
}
|
|
3726
|
-
if (this.index === 0) {
|
|
3727
|
-
if (this.canCancel())
|
|
3728
|
-
await this.cancelCurrent(operation);
|
|
3729
|
-
else
|
|
3730
|
-
this.setStatus("active");
|
|
4095
|
+
if (await this.runStepHook(step.definition.beforeLeave, step, operation, direction)) {
|
|
4096
|
+
this.setStatus("active");
|
|
3731
4097
|
return;
|
|
3732
4098
|
}
|
|
3733
|
-
await this.
|
|
4099
|
+
await this.navigate(destination ?? this.index + (direction === "advance" ? 1 : -1), direction, operation);
|
|
4100
|
+
}
|
|
4101
|
+
async runStepHook(hook, step, operation, direction) {
|
|
4102
|
+
if (!hook)
|
|
4103
|
+
return false;
|
|
4104
|
+
let aborted = false;
|
|
4105
|
+
await hook(Object.freeze({
|
|
4106
|
+
...this.createStepHookContext(step, operation, direction),
|
|
4107
|
+
abort: () => {
|
|
4108
|
+
aborted = true;
|
|
4109
|
+
}
|
|
4110
|
+
}));
|
|
4111
|
+
this.assertCurrent(operation);
|
|
4112
|
+
return aborted;
|
|
3734
4113
|
}
|
|
3735
4114
|
async runActions(operation) {
|
|
3736
4115
|
const step = this.currentStep();
|
|
@@ -3754,23 +4133,43 @@ class TourController {
|
|
|
3754
4133
|
}
|
|
3755
4134
|
async resolveTarget(step, operation) {
|
|
3756
4135
|
const signal = this.signalFor(operation);
|
|
3757
|
-
const
|
|
3758
|
-
const
|
|
4136
|
+
const missingTarget = step.props.get().behavior?.missingTarget;
|
|
4137
|
+
const strategy = missingTarget?.strategy ?? "error";
|
|
4138
|
+
const timeout = missingTarget?.timeout ?? DEFAULT_TARGET_TIMEOUT;
|
|
3759
4139
|
const startedAt = Date.now();
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
4140
|
+
step.detached = false;
|
|
4141
|
+
try {
|
|
4142
|
+
while (true) {
|
|
4143
|
+
const pending = step.resolveTarget(signal);
|
|
4144
|
+
if (isPendingTarget(pending))
|
|
4145
|
+
this.setAwaitingTarget(operation, true);
|
|
4146
|
+
const target = await pending;
|
|
4147
|
+
this.assertCurrent(operation);
|
|
4148
|
+
if (target)
|
|
4149
|
+
return target;
|
|
4150
|
+
if (strategy === "skip")
|
|
4151
|
+
return null;
|
|
4152
|
+
const body = strategy === "detached" && step.detach();
|
|
4153
|
+
if (body)
|
|
4154
|
+
return body;
|
|
4155
|
+
if (strategy !== "wait" || Date.now() - startedAt >= timeout) {
|
|
4156
|
+
throw this.missingTargetError(step);
|
|
4157
|
+
}
|
|
4158
|
+
this.setAwaitingTarget(operation, true);
|
|
4159
|
+
await abortableDelay(16, signal);
|
|
4160
|
+
this.assertCurrent(operation);
|
|
3769
4161
|
}
|
|
3770
|
-
|
|
3771
|
-
this.
|
|
4162
|
+
} finally {
|
|
4163
|
+
this.setAwaitingTarget(operation, false);
|
|
3772
4164
|
}
|
|
3773
4165
|
}
|
|
4166
|
+
setAwaitingTarget(operation, awaiting) {
|
|
4167
|
+
if (this.awaitingTargetOperation === operation === awaiting)
|
|
4168
|
+
return;
|
|
4169
|
+
this.awaitingTargetOperation = awaiting ? operation : null;
|
|
4170
|
+
this.driver.setTargetPending?.(awaiting);
|
|
4171
|
+
this.publish();
|
|
4172
|
+
}
|
|
3774
4173
|
async pollForTarget(step, operation, budgetMs) {
|
|
3775
4174
|
const startedAt = Date.now();
|
|
3776
4175
|
while (true) {
|
|
@@ -3796,28 +4195,20 @@ class TourController {
|
|
|
3796
4195
|
const operation = this.beginOperation();
|
|
3797
4196
|
try {
|
|
3798
4197
|
this.assertCurrent(operation);
|
|
3799
|
-
|
|
4198
|
+
let recovered = await this.pollForTarget(step, operation, TARGET_LOSS_GRACE_MS);
|
|
3800
4199
|
this.assertCurrent(operation);
|
|
3801
|
-
if (
|
|
3802
|
-
step.
|
|
3803
|
-
|
|
4200
|
+
if (!recovered) {
|
|
4201
|
+
const strategy = step.props.get().behavior?.missingTarget?.strategy ?? "error";
|
|
4202
|
+
if (strategy === "skip") {
|
|
4203
|
+
await this.navigate(index + (direction === "advance" ? 1 : -1), direction, operation, step);
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
recovered = strategy === "detached" ? step.detach() : strategy === "wait" ? await this.pollForTarget(step, operation, (step.props.get().behavior?.missingTarget?.timeout ?? DEFAULT_TARGET_TIMEOUT) - TARGET_LOSS_GRACE_MS) : null;
|
|
3804
4207
|
this.assertCurrent(operation);
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
}
|
|
3808
|
-
const strategy = step.behavior?.missingTargetStrategy ?? "error";
|
|
3809
|
-
if (strategy === "skip") {
|
|
3810
|
-
await this.advancePastRecoveryMissingTarget(step, index, direction, operation);
|
|
3811
|
-
return;
|
|
4208
|
+
if (!recovered)
|
|
4209
|
+
throw this.missingTargetError(step);
|
|
3812
4210
|
}
|
|
3813
|
-
|
|
3814
|
-
throw this.missingTargetError(step);
|
|
3815
|
-
const timeout = step.behavior?.targetTimeout ?? DEFAULT_TARGET_TIMEOUT;
|
|
3816
|
-
const recoveredAfterWait = await this.pollForTarget(step, operation, Math.max(0, timeout - TARGET_LOSS_GRACE_MS));
|
|
3817
|
-
this.assertCurrent(operation);
|
|
3818
|
-
if (!recoveredAfterWait)
|
|
3819
|
-
throw this.missingTargetError(step);
|
|
3820
|
-
step.target = recoveredAfterWait;
|
|
4211
|
+
step.target = recovered;
|
|
3821
4212
|
await this.driver.retarget(step, this.signalFor(operation));
|
|
3822
4213
|
this.assertCurrent(operation);
|
|
3823
4214
|
this.publish();
|
|
@@ -3830,34 +4221,10 @@ class TourController {
|
|
|
3830
4221
|
this.recoveringTarget = null;
|
|
3831
4222
|
}
|
|
3832
4223
|
}
|
|
3833
|
-
async advancePastMissingTarget(index, direction, operation) {
|
|
3834
|
-
const nextIndex = direction === "advance" ? index + 1 : index - 1;
|
|
3835
|
-
if (nextIndex >= this.steps.length)
|
|
3836
|
-
await this.finish(operation);
|
|
3837
|
-
else if (nextIndex < 0) {
|
|
3838
|
-
if (this.canCancel())
|
|
3839
|
-
await this.cancelCurrent(operation);
|
|
3840
|
-
else
|
|
3841
|
-
this.setStatus("active");
|
|
3842
|
-
} else
|
|
3843
|
-
await this.enter(nextIndex, direction, operation);
|
|
3844
|
-
}
|
|
3845
|
-
async advancePastRecoveryMissingTarget(step, index, direction, operation) {
|
|
3846
|
-
const nextIndex = direction === "advance" ? index + 1 : index - 1;
|
|
3847
|
-
if (nextIndex >= this.steps.length)
|
|
3848
|
-
await this.finish(operation);
|
|
3849
|
-
else if (nextIndex < 0) {
|
|
3850
|
-
if (this.canCancel())
|
|
3851
|
-
await this.cancelCurrent(operation);
|
|
3852
|
-
else
|
|
3853
|
-
throw this.missingTargetError(step);
|
|
3854
|
-
} else
|
|
3855
|
-
await this.enter(nextIndex, direction, operation);
|
|
3856
|
-
}
|
|
3857
4224
|
missingTargetError(step) {
|
|
3858
4225
|
return new Error(`Missing target at ${step.path}: ${String(step.definition.target)}`);
|
|
3859
4226
|
}
|
|
3860
|
-
async finish(operation) {
|
|
4227
|
+
async finish(operation, skipped = []) {
|
|
3861
4228
|
this.assertCurrent(operation);
|
|
3862
4229
|
const step = this.currentStep();
|
|
3863
4230
|
const { context, isAborted } = this.createLifecycleHookContext(step);
|
|
@@ -3867,9 +4234,12 @@ class TourController {
|
|
|
3867
4234
|
if (step)
|
|
3868
4235
|
this.setStatus("active");
|
|
3869
4236
|
else
|
|
3870
|
-
this.resetToIdle();
|
|
4237
|
+
await this.resetToIdle(operation);
|
|
3871
4238
|
return;
|
|
3872
4239
|
}
|
|
4240
|
+
this.flushTourStart();
|
|
4241
|
+
for (const skippedStep of skipped)
|
|
4242
|
+
this.emit("step:skip", skippedStep, 0);
|
|
3873
4243
|
this.emitStepLeave(step);
|
|
3874
4244
|
await this.driver.clear(this.signalFor(operation));
|
|
3875
4245
|
this.assertCurrent(operation);
|
|
@@ -3880,10 +4250,6 @@ class TourController {
|
|
|
3880
4250
|
}
|
|
3881
4251
|
async cancelCurrent(operation) {
|
|
3882
4252
|
const step = this.currentStep();
|
|
3883
|
-
if (step?.definition.cancelAction) {
|
|
3884
|
-
await step.definition.cancelAction(this.createBeforeActionStepContext(step));
|
|
3885
|
-
}
|
|
3886
|
-
this.assertCurrent(operation);
|
|
3887
4253
|
const { context, isAborted } = this.createLifecycleHookContext(step);
|
|
3888
4254
|
await this.workflow?.options.onCancel?.(context);
|
|
3889
4255
|
this.assertCurrent(operation);
|
|
@@ -3891,6 +4257,7 @@ class TourController {
|
|
|
3891
4257
|
this.setStatus("active");
|
|
3892
4258
|
return;
|
|
3893
4259
|
}
|
|
4260
|
+
this.flushTourStart();
|
|
3894
4261
|
this.emitStepLeave(step);
|
|
3895
4262
|
await this.driver.clear(this.signalFor(operation));
|
|
3896
4263
|
this.assertCurrent(operation);
|
|
@@ -3909,12 +4276,17 @@ class TourController {
|
|
|
3909
4276
|
});
|
|
3910
4277
|
return { context, isAborted: () => aborted };
|
|
3911
4278
|
}
|
|
3912
|
-
resetToIdle() {
|
|
4279
|
+
async resetToIdle(operation) {
|
|
4280
|
+
if (this.retainedPresentation) {
|
|
4281
|
+
await this.driver.clear(this.signalFor(operation));
|
|
4282
|
+
this.assertCurrent(operation);
|
|
4283
|
+
}
|
|
3913
4284
|
this.workflow = null;
|
|
3914
4285
|
this.releaseStepPropsSubscriptions();
|
|
3915
4286
|
this.steps = [];
|
|
3916
4287
|
this.index = -1;
|
|
3917
4288
|
this.retainedPresentation = null;
|
|
4289
|
+
this.pendingTourStart = undefined;
|
|
3918
4290
|
this.setStatus("idle");
|
|
3919
4291
|
}
|
|
3920
4292
|
async handleFailure(reason, operation) {
|
|
@@ -3924,6 +4296,7 @@ class TourController {
|
|
|
3924
4296
|
this.error = error;
|
|
3925
4297
|
this.retainedPresentation = null;
|
|
3926
4298
|
this.setStatus("error");
|
|
4299
|
+
this.flushTourStart();
|
|
3927
4300
|
this.emit("tour:error", this.currentStep(), Date.now() - this.tourStartedAt, error);
|
|
3928
4301
|
if (!this.isCurrent(operation))
|
|
3929
4302
|
throw error;
|
|
@@ -3938,9 +4311,8 @@ class TourController {
|
|
|
3938
4311
|
return this.operationToken;
|
|
3939
4312
|
}
|
|
3940
4313
|
createStepContext(step, operation, onControl) {
|
|
3941
|
-
if (!step.target)
|
|
3942
|
-
throw new Error("Cannot create a step context without a target");
|
|
3943
4314
|
return Object.freeze({
|
|
4315
|
+
...this.createStepHookContext(step, operation, step.direction),
|
|
3944
4316
|
advance: async () => {
|
|
3945
4317
|
onControl?.();
|
|
3946
4318
|
this.assertCurrent(operation);
|
|
@@ -3952,21 +4324,29 @@ class TourController {
|
|
|
3952
4324
|
if (this.canCancel())
|
|
3953
4325
|
await this.cancelCurrent(operation);
|
|
3954
4326
|
},
|
|
4327
|
+
goTo: async (id) => {
|
|
4328
|
+
onControl?.();
|
|
4329
|
+
this.assertCurrent(operation);
|
|
4330
|
+
if (!this.workflow)
|
|
4331
|
+
return;
|
|
4332
|
+
const index = resolveStepIndex(this.workflow, id);
|
|
4333
|
+
await this.transition(this.directionTo(index), operation, index);
|
|
4334
|
+
},
|
|
3955
4335
|
previous: async () => {
|
|
3956
4336
|
onControl?.();
|
|
3957
4337
|
this.assertCurrent(operation);
|
|
3958
4338
|
await this.transition("previous", operation);
|
|
3959
|
-
}
|
|
3960
|
-
props: step.props,
|
|
3961
|
-
signal: this.signalFor(operation),
|
|
3962
|
-
target: step.target
|
|
4339
|
+
}
|
|
3963
4340
|
});
|
|
3964
4341
|
}
|
|
3965
|
-
|
|
4342
|
+
createStepHookContext(step, operation, direction) {
|
|
3966
4343
|
if (!step.target)
|
|
3967
4344
|
throw new Error("Cannot create a step context without a target");
|
|
3968
4345
|
return Object.freeze({
|
|
3969
|
-
|
|
4346
|
+
direction,
|
|
4347
|
+
initialProps: step.initialProps,
|
|
4348
|
+
props: step.props,
|
|
4349
|
+
signal: this.signalFor(operation),
|
|
3970
4350
|
target: step.target
|
|
3971
4351
|
});
|
|
3972
4352
|
}
|
|
@@ -3974,6 +4354,10 @@ class TourController {
|
|
|
3974
4354
|
this.operationToken += 1;
|
|
3975
4355
|
this.operation?.abort();
|
|
3976
4356
|
this.operation = null;
|
|
4357
|
+
if (this.awaitingTargetOperation !== null) {
|
|
4358
|
+
this.awaitingTargetOperation = null;
|
|
4359
|
+
this.driver.setTargetPending?.(false);
|
|
4360
|
+
}
|
|
3977
4361
|
}
|
|
3978
4362
|
signalFor(operation) {
|
|
3979
4363
|
this.assertCurrent(operation);
|
|
@@ -4009,12 +4393,10 @@ class TourController {
|
|
|
4009
4393
|
return direction === "advance" || this.index > 0;
|
|
4010
4394
|
}
|
|
4011
4395
|
isAdvanceButtonAvailable() {
|
|
4012
|
-
|
|
4013
|
-
return props !== undefined && props.popover?.disableAdvanceButton !== true;
|
|
4396
|
+
return isControlAvailable(this.currentStep()?.props.get(), "advance");
|
|
4014
4397
|
}
|
|
4015
4398
|
isPreviousButtonAvailable() {
|
|
4016
|
-
|
|
4017
|
-
return props !== undefined && props.popover?.disablePreviousButton !== true && this.index > 0;
|
|
4399
|
+
return isControlAvailable(this.currentStep()?.props.get(), "previous") && this.index > 0;
|
|
4018
4400
|
}
|
|
4019
4401
|
isCancelAvailable() {
|
|
4020
4402
|
return this.currentStep() !== null && this.canCancel();
|
|
@@ -4029,7 +4411,7 @@ class TourController {
|
|
|
4029
4411
|
return this.currentStep() ? this.isCancelAvailable() : this.retainedPresentation?.canCancel ?? false;
|
|
4030
4412
|
}
|
|
4031
4413
|
canCancel() {
|
|
4032
|
-
return
|
|
4414
|
+
return this.status !== "finished" && this.status !== "cancelled" && this.status !== "error";
|
|
4033
4415
|
}
|
|
4034
4416
|
setStatus(status) {
|
|
4035
4417
|
this.status = status;
|
|
@@ -4087,6 +4469,13 @@ class TourController {
|
|
|
4087
4469
|
this.reportSubscriberError(error);
|
|
4088
4470
|
}
|
|
4089
4471
|
}
|
|
4472
|
+
flushTourStart() {
|
|
4473
|
+
const step = this.pendingTourStart;
|
|
4474
|
+
if (step === undefined)
|
|
4475
|
+
return;
|
|
4476
|
+
this.pendingTourStart = undefined;
|
|
4477
|
+
this.emit("tour:start", step, 0);
|
|
4478
|
+
}
|
|
4090
4479
|
emitStepLeave(step) {
|
|
4091
4480
|
if (!step || !this.hasEventListeners())
|
|
4092
4481
|
return;
|
|
@@ -4132,6 +4521,8 @@ class TourController {
|
|
|
4132
4521
|
isFirstStep,
|
|
4133
4522
|
isLastStep,
|
|
4134
4523
|
status: this.status,
|
|
4524
|
+
awaitingTarget: this.awaitingTargetOperation !== null,
|
|
4525
|
+
popoverHidden: this.popoverHidden && this.isRunning(),
|
|
4135
4526
|
error: this.error
|
|
4136
4527
|
});
|
|
4137
4528
|
}
|
|
@@ -4169,9 +4560,11 @@ function createGlowTour(options = {}) {
|
|
|
4169
4560
|
cancel: () => controller.cancel(),
|
|
4170
4561
|
create: (name, options2) => controller.create(name, options2),
|
|
4171
4562
|
dispose: () => controller.dispose(),
|
|
4172
|
-
|
|
4563
|
+
goTo: (id) => controller.goTo(id),
|
|
4564
|
+
hidePopover: () => controller.setPopoverHidden(true),
|
|
4173
4565
|
previous: () => controller.previous(),
|
|
4174
|
-
|
|
4566
|
+
showPopover: () => controller.setPopoverHidden(false),
|
|
4567
|
+
start: (workflow, runOptions) => controller.start(workflow, runOptions),
|
|
4175
4568
|
state: controller.state
|
|
4176
4569
|
};
|
|
4177
4570
|
bridge = attachRootBridge(tour, driver, () => controller.isDisposed(), () => controller.beginMountRelease(), () => controller.completeMountRelease());
|