@glowhop/core-tour 1.3.0 → 1.4.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 +182 -0
- package/README.md +3 -3
- package/builder/index.d.ts +15 -16
- package/config/index.d.ts +1 -1
- package/config/index.js +161 -134
- 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 +46 -3
- package/elements/base.d.ts +24 -2
- package/elements/popover.d.ts +18 -6
- package/index.d.ts +1 -1
- package/index.js +628 -472
- package/package.json +1 -1
- package/runtime/active-step.d.ts +16 -54
- package/runtime/tour-controller.d.ts +30 -12
- package/state/focus-guard.d.ts +15 -0
- package/types/index.d.ts +153 -85
- package/utils/options.d.ts +13 -1
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
|
-
|
|
368
|
-
this.assertActive();
|
|
369
|
-
this.draft.advanceAction = callback;
|
|
370
|
-
return this;
|
|
371
|
-
}
|
|
372
|
-
beforePrevious(callback) {
|
|
366
|
+
beforeEnter(callback) {
|
|
373
367
|
this.assertActive();
|
|
374
|
-
this.draft.
|
|
368
|
+
this.draft.beforeEnter = callback;
|
|
375
369
|
return this;
|
|
376
370
|
}
|
|
377
|
-
|
|
371
|
+
beforeLeave(callback) {
|
|
378
372
|
this.assertActive();
|
|
379
|
-
this.draft.
|
|
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
|
});
|
|
@@ -504,6 +498,7 @@ class GlowTourElement {
|
|
|
504
498
|
element;
|
|
505
499
|
options;
|
|
506
500
|
animations = new Set;
|
|
501
|
+
filledAnimations = new Set;
|
|
507
502
|
cancelledAnimations = new WeakSet;
|
|
508
503
|
released = false;
|
|
509
504
|
constructor(element, options) {
|
|
@@ -568,13 +563,23 @@ class GlowTourElement {
|
|
|
568
563
|
} finally {
|
|
569
564
|
stopWatchingVisibility();
|
|
570
565
|
this.animations.delete(animation);
|
|
566
|
+
const fill = animation.effect?.getTiming?.().fill;
|
|
567
|
+
if (!this.released && !this.cancelledAnimations.has(animation) && (fill === "forwards" || fill === "both")) {
|
|
568
|
+
this.filledAnimations.add(animation);
|
|
569
|
+
}
|
|
571
570
|
}
|
|
572
571
|
}
|
|
572
|
+
_releaseFilledAnimations() {
|
|
573
|
+
for (const animation of this.filledAnimations) {
|
|
574
|
+
this._cancelAnimation(animation);
|
|
575
|
+
}
|
|
576
|
+
this.filledAnimations.clear();
|
|
577
|
+
}
|
|
573
578
|
getElement() {
|
|
574
579
|
return this.released ? null : this.element;
|
|
575
580
|
}
|
|
576
|
-
disappear() {
|
|
577
|
-
return this._disappear();
|
|
581
|
+
disappear(hideFromAssistiveTechnology) {
|
|
582
|
+
return this._disappear(hideFromAssistiveTechnology);
|
|
578
583
|
}
|
|
579
584
|
release() {
|
|
580
585
|
if (this.released)
|
|
@@ -588,6 +593,7 @@ class GlowTourElement {
|
|
|
588
593
|
this._cancelAnimation(animation);
|
|
589
594
|
}
|
|
590
595
|
this.animations.clear();
|
|
596
|
+
this._releaseFilledAnimations();
|
|
591
597
|
}
|
|
592
598
|
_cancelAnimation(animation) {
|
|
593
599
|
this.cancelledAnimations.add(animation);
|
|
@@ -733,7 +739,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
733
739
|
}
|
|
734
740
|
_cutoutPath(position, step) {
|
|
735
741
|
return `path("${roundedRectPath(position, paintedBoxDimensions(this.element), {
|
|
736
|
-
padding: step.overlay?.padding ?? DEFAULT_OVERLAY_PADDING,
|
|
742
|
+
padding: step.detached ? 0 : step.overlay?.padding ?? DEFAULT_OVERLAY_PADDING,
|
|
737
743
|
radius: step.overlay?.radius ?? DEFAULT_OVERLAY_RADIUS
|
|
738
744
|
}, this.element)}")`;
|
|
739
745
|
}
|
|
@@ -1438,7 +1444,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1438
1444
|
};
|
|
1439
1445
|
}
|
|
1440
1446
|
const gap = Math.max(0, step.popover?.gap ?? DEFAULT_POPOVER_GAP);
|
|
1441
|
-
const arrowDisabled = step.popover?.arrow?.
|
|
1447
|
+
const arrowDisabled = step.popover?.arrow?.hidden === true;
|
|
1442
1448
|
const arrowEdgePadding = step.popover?.arrow?.edgePadding ?? DEFAULT_ARROW_EDGE_PADDING;
|
|
1443
1449
|
const popoverPosition = currentElement.getBoundingClientRect();
|
|
1444
1450
|
const viewport = viewportDimensions(currentElement);
|
|
@@ -1448,7 +1454,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1448
1454
|
const maxY = viewport.height - gap - popoverPosition.height;
|
|
1449
1455
|
const targetCenterX = targetPosition.left + targetPosition.width / 2;
|
|
1450
1456
|
const targetCenterY = targetPosition.top + targetPosition.height / 2;
|
|
1451
|
-
if (maxX < minX || maxY < minY) {
|
|
1457
|
+
if (step.detached || maxX < minX || maxY < minY) {
|
|
1452
1458
|
return this._centerPosition(popoverPosition, viewport);
|
|
1453
1459
|
}
|
|
1454
1460
|
const centeredX = targetCenterX - popoverPosition.width / 2;
|
|
@@ -1517,7 +1523,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1517
1523
|
async present(nextPosition, step) {
|
|
1518
1524
|
await this._appear(nextPosition, step);
|
|
1519
1525
|
}
|
|
1520
|
-
initializeProps() {
|
|
1526
|
+
initializeProps(hideFromAssistiveTechnology = true) {
|
|
1521
1527
|
const el = this.getElement();
|
|
1522
1528
|
if (!el) {
|
|
1523
1529
|
return;
|
|
@@ -1528,8 +1534,12 @@ class PopoverElement extends GlowTourElement {
|
|
|
1528
1534
|
if (!el.hasAttribute("tabindex")) {
|
|
1529
1535
|
this.mutationLease.setAttribute("tabindex", POPOVER_IDLE_ATTRIBUTES.tabindex);
|
|
1530
1536
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1537
|
+
if (hideFromAssistiveTechnology) {
|
|
1538
|
+
this.mutationLease.setAttribute("aria-hidden", POPOVER_IDLE_ATTRIBUTES["aria-hidden"]);
|
|
1539
|
+
this.mutationLease.setAttribute("inert", POPOVER_IDLE_ATTRIBUTES.inert);
|
|
1540
|
+
} else {
|
|
1541
|
+
this.mutationLease.setStyle("pointer-events", "none");
|
|
1542
|
+
}
|
|
1533
1543
|
}
|
|
1534
1544
|
updatePosition(nextPosition, step, onReposition) {
|
|
1535
1545
|
const nextCoordinates = this.resolvePosition(nextPosition, step);
|
|
@@ -1578,7 +1588,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1578
1588
|
let pending = this.pendingReposition;
|
|
1579
1589
|
this.pendingReposition = null;
|
|
1580
1590
|
this.repositionPhase = "fading-out";
|
|
1581
|
-
await this._disappear();
|
|
1591
|
+
await this._disappear(false);
|
|
1582
1592
|
if (generation !== this.repositionGeneration || !this.getElement())
|
|
1583
1593
|
return;
|
|
1584
1594
|
pending = this.pendingReposition ?? pending;
|
|
@@ -1615,7 +1625,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1615
1625
|
_applyArrowStyles(step) {
|
|
1616
1626
|
const arrow = step.popover?.arrow;
|
|
1617
1627
|
ensurePopoverArrowStyles(this.element, {
|
|
1618
|
-
disabled: arrow?.
|
|
1628
|
+
disabled: arrow?.autoStyles === false,
|
|
1619
1629
|
nonce: arrow?.styleNonce
|
|
1620
1630
|
});
|
|
1621
1631
|
this._applyArrowStyle(ARROW_STYLE_PROPERTIES.color, arrow?.color);
|
|
@@ -1633,6 +1643,7 @@ class PopoverElement extends GlowTourElement {
|
|
|
1633
1643
|
}
|
|
1634
1644
|
}
|
|
1635
1645
|
async _appear(position, step) {
|
|
1646
|
+
this._releaseFilledAnimations();
|
|
1636
1647
|
const defaultStyles = this._getNextStyles(position, step);
|
|
1637
1648
|
for (const [key, value] of Object.entries(defaultStyles)) {
|
|
1638
1649
|
if (value != null)
|
|
@@ -1647,27 +1658,36 @@ class PopoverElement extends GlowTourElement {
|
|
|
1647
1658
|
return;
|
|
1648
1659
|
this._applyVisibleState();
|
|
1649
1660
|
}
|
|
1650
|
-
async _disappear() {
|
|
1661
|
+
async _disappear(hideFromAssistiveTechnology = true) {
|
|
1662
|
+
this._releaseFilledAnimations();
|
|
1651
1663
|
const animation = this._startAnimation({
|
|
1652
1664
|
opacity: 0
|
|
1653
1665
|
}, this._getAnimationOptions());
|
|
1654
1666
|
if (animation && !await this._waitForAnimation(animation))
|
|
1655
1667
|
return;
|
|
1656
|
-
|
|
1668
|
+
if (hideFromAssistiveTechnology)
|
|
1669
|
+
this._applyHiddenState();
|
|
1670
|
+
else
|
|
1671
|
+
this._applyFadedState();
|
|
1657
1672
|
}
|
|
1658
1673
|
_release() {
|
|
1659
1674
|
this.mutationLease.release();
|
|
1660
1675
|
}
|
|
1661
1676
|
_applyVisibleState() {
|
|
1662
1677
|
this.mutationLease.setStyle("opacity", "1");
|
|
1678
|
+
this.mutationLease.releaseStyle("pointer-events");
|
|
1663
1679
|
this.mutationLease.setAttribute("aria-hidden", null);
|
|
1664
1680
|
this.mutationLease.setAttribute("inert", null);
|
|
1665
1681
|
}
|
|
1666
|
-
|
|
1682
|
+
_applyFadedState() {
|
|
1667
1683
|
this.mutationLease.setStyle("opacity", "0");
|
|
1684
|
+
this.mutationLease.setStyle("pointer-events", "none");
|
|
1685
|
+
this.mutationLease.setStyle("transform", null);
|
|
1686
|
+
}
|
|
1687
|
+
_applyHiddenState() {
|
|
1688
|
+
this._applyFadedState();
|
|
1668
1689
|
this.mutationLease.setAttribute("aria-hidden", "true");
|
|
1669
1690
|
this.mutationLease.setAttribute("inert", "true");
|
|
1670
|
-
this.mutationLease.setStyle("transform", null);
|
|
1671
1691
|
}
|
|
1672
1692
|
}
|
|
1673
1693
|
function toPixels(value) {
|
|
@@ -1750,15 +1770,18 @@ class FocusGuard {
|
|
|
1750
1770
|
this.deactivate();
|
|
1751
1771
|
if (!this.active) {
|
|
1752
1772
|
this.document = document2;
|
|
1753
|
-
this.
|
|
1773
|
+
this.captureInitialFocus(scope.popover);
|
|
1754
1774
|
this.document.addEventListener("focusin", this.handleFocusIn, true);
|
|
1755
1775
|
this.active = true;
|
|
1756
1776
|
}
|
|
1757
1777
|
this.update(scope);
|
|
1778
|
+
if (scope.autoFocus === false)
|
|
1779
|
+
return;
|
|
1758
1780
|
const currentFocus = this.document?.activeElement;
|
|
1759
|
-
if (scope.
|
|
1760
|
-
|
|
1781
|
+
if (scope.deferFocus && isNode(currentFocus, scope.popover) && this.isAllowed(currentFocus)) {
|
|
1782
|
+
return;
|
|
1761
1783
|
}
|
|
1784
|
+
this.focusFallback();
|
|
1762
1785
|
}
|
|
1763
1786
|
update(scope) {
|
|
1764
1787
|
this.popover = scope.popover;
|
|
@@ -1771,10 +1794,23 @@ class FocusGuard {
|
|
|
1771
1794
|
if (this.active)
|
|
1772
1795
|
this.focusFallback();
|
|
1773
1796
|
}
|
|
1774
|
-
|
|
1775
|
-
if (
|
|
1797
|
+
captureInitialFocus(reference, pending) {
|
|
1798
|
+
if (this.active || this.initialFocus)
|
|
1776
1799
|
return;
|
|
1800
|
+
const activeElement = pending ?? ownerDocument(reference)?.activeElement;
|
|
1801
|
+
this.initialFocus = isHTMLElement(activeElement, reference) ? activeElement : null;
|
|
1802
|
+
}
|
|
1803
|
+
deactivate() {
|
|
1804
|
+
const focusToRestore = this.release();
|
|
1805
|
+
if (focusToRestore?.isConnected) {
|
|
1806
|
+
focusToRestore.focus();
|
|
1777
1807
|
}
|
|
1808
|
+
}
|
|
1809
|
+
release() {
|
|
1810
|
+
const focusToRestore = this.initialFocus;
|
|
1811
|
+
this.initialFocus = null;
|
|
1812
|
+
if (!this.active)
|
|
1813
|
+
return null;
|
|
1778
1814
|
this.document?.removeEventListener("focusin", this.handleFocusIn, true);
|
|
1779
1815
|
this.document = null;
|
|
1780
1816
|
this.active = false;
|
|
@@ -1782,11 +1818,7 @@ class FocusGuard {
|
|
|
1782
1818
|
this.allowedTarget = null;
|
|
1783
1819
|
this.allowTargetInteraction = false;
|
|
1784
1820
|
this.restoreFallback();
|
|
1785
|
-
|
|
1786
|
-
this.initialFocus = null;
|
|
1787
|
-
if (focusToRestore?.isConnected) {
|
|
1788
|
-
focusToRestore.focus();
|
|
1789
|
-
}
|
|
1821
|
+
return focusToRestore;
|
|
1790
1822
|
}
|
|
1791
1823
|
isAllowed(target) {
|
|
1792
1824
|
if (target === this.fallback)
|
|
@@ -1825,8 +1857,8 @@ class FocusGuard {
|
|
|
1825
1857
|
}
|
|
1826
1858
|
findFocusable(root, direction) {
|
|
1827
1859
|
const candidates = focusableTourControls(root);
|
|
1828
|
-
const
|
|
1829
|
-
return
|
|
1860
|
+
const find = (trigger) => candidates.find((candidate) => candidate.matches(`[data-glow-tour-${trigger}-trigger]`));
|
|
1861
|
+
return direction === "previous" && find("previous") || find("advance") || null;
|
|
1830
1862
|
}
|
|
1831
1863
|
}
|
|
1832
1864
|
function belongsToScope(target, scope) {
|
|
@@ -1891,6 +1923,149 @@ class ScrollLock {
|
|
|
1891
1923
|
}
|
|
1892
1924
|
}
|
|
1893
1925
|
|
|
1926
|
+
// packages/core/src/utils/options.ts
|
|
1927
|
+
function mergeStepProps(base, patch) {
|
|
1928
|
+
return {
|
|
1929
|
+
...base,
|
|
1930
|
+
...patch,
|
|
1931
|
+
data: patch.data ? { ...base.data, ...patch.data } : base.data,
|
|
1932
|
+
overlay: mergeOverlayOptions(base.overlay, patch.overlay),
|
|
1933
|
+
popover: mergePopoverOptions(base.popover, patch.popover),
|
|
1934
|
+
indicator: mergeIndicatorOptions(base.indicator, patch.indicator),
|
|
1935
|
+
behavior: mergeStepBehavior(base.behavior, patch.behavior),
|
|
1936
|
+
controls: mergeControls(base.controls, patch.controls),
|
|
1937
|
+
classNames: mergeClassNames(base.classNames, patch.classNames)
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
function mergeClassNames(base, patch) {
|
|
1941
|
+
if (!patch)
|
|
1942
|
+
return base;
|
|
1943
|
+
const merged = { ...base };
|
|
1944
|
+
for (const slot in patch) {
|
|
1945
|
+
const key = slot;
|
|
1946
|
+
merged[key] = patch[key] ?? merged[key];
|
|
1947
|
+
}
|
|
1948
|
+
return merged;
|
|
1949
|
+
}
|
|
1950
|
+
function mergeOverlayOptions(defaults, overrides) {
|
|
1951
|
+
if (!defaults && !overrides) {
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
return {
|
|
1955
|
+
...mergeBaseOptions(defaults, overrides),
|
|
1956
|
+
color: overrides?.color ?? defaults?.color,
|
|
1957
|
+
opacity: overrides?.opacity ?? defaults?.opacity,
|
|
1958
|
+
padding: overrides?.padding ?? defaults?.padding,
|
|
1959
|
+
radius: overrides?.radius ?? defaults?.radius
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
function mergeIndicatorOptions(defaults, overrides) {
|
|
1963
|
+
if (!defaults && !overrides) {
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
return {
|
|
1967
|
+
...mergeBaseOptions(defaults, overrides),
|
|
1968
|
+
hidden: overrides?.hidden ?? defaults?.hidden,
|
|
1969
|
+
gap: overrides?.gap ?? defaults?.gap,
|
|
1970
|
+
placementTryOrder: cloneArray(overrides?.placementTryOrder ?? defaults?.placementTryOrder)
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
function mergePopoverOptions(defaults, overrides) {
|
|
1974
|
+
if (!defaults && !overrides) {
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
const placementTryOrder = overrides?.placementTryOrder ?? defaults?.placementTryOrder;
|
|
1978
|
+
const hasArrow = !!defaults?.arrow || !!overrides?.arrow;
|
|
1979
|
+
return {
|
|
1980
|
+
...mergeBaseOptions(defaults, overrides),
|
|
1981
|
+
arrow: hasArrow ? {
|
|
1982
|
+
hidden: overrides?.arrow?.hidden ?? defaults?.arrow?.hidden,
|
|
1983
|
+
color: overrides?.arrow?.color ?? defaults?.arrow?.color,
|
|
1984
|
+
size: overrides?.arrow?.size ?? defaults?.arrow?.size,
|
|
1985
|
+
borderWidth: overrides?.arrow?.borderWidth ?? defaults?.arrow?.borderWidth,
|
|
1986
|
+
borderRadius: overrides?.arrow?.borderRadius ?? defaults?.arrow?.borderRadius,
|
|
1987
|
+
edgePadding: overrides?.arrow?.edgePadding ?? defaults?.arrow?.edgePadding,
|
|
1988
|
+
styleNonce: overrides?.arrow?.styleNonce ?? defaults?.arrow?.styleNonce,
|
|
1989
|
+
autoStyles: overrides?.arrow?.autoStyles ?? defaults?.arrow?.autoStyles
|
|
1990
|
+
} : undefined,
|
|
1991
|
+
gap: overrides?.gap ?? defaults?.gap,
|
|
1992
|
+
placementTryOrder: cloneArray(placementTryOrder)
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function mergeScrollOptions(defaults, overrides) {
|
|
1996
|
+
if (!defaults && !overrides) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
return {
|
|
2000
|
+
behavior: overrides?.behavior ?? defaults?.behavior,
|
|
2001
|
+
block: overrides?.block ?? defaults?.block,
|
|
2002
|
+
inline: overrides?.inline ?? defaults?.inline
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
function mergeAnimationOptions(defaults, overrides) {
|
|
2006
|
+
if (!defaults) {
|
|
2007
|
+
return overrides ? { ...overrides } : undefined;
|
|
2008
|
+
}
|
|
2009
|
+
if (!overrides) {
|
|
2010
|
+
return { ...defaults };
|
|
2011
|
+
}
|
|
2012
|
+
return {
|
|
2013
|
+
duration: overrides.duration ?? defaults.duration,
|
|
2014
|
+
easing: overrides.easing ?? defaults.easing
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
function mergeBaseOptions(defaults, overrides) {
|
|
2018
|
+
return {
|
|
2019
|
+
animated: overrides?.animated ?? defaults?.animated,
|
|
2020
|
+
animation: mergeAnimationOptions(defaults?.animation, overrides?.animation)
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
function mergeStepBehavior(defaults, overrides) {
|
|
2024
|
+
if (!defaults && !overrides) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
return {
|
|
2028
|
+
allowInteraction: overrides?.allowInteraction ?? defaults?.allowInteraction,
|
|
2029
|
+
allowScroll: overrides?.allowScroll ?? defaults?.allowScroll,
|
|
2030
|
+
autoFocus: overrides?.autoFocus ?? defaults?.autoFocus,
|
|
2031
|
+
autoScroll: overrides?.autoScroll ?? defaults?.autoScroll,
|
|
2032
|
+
missingTarget: mergeMissingTarget(defaults?.missingTarget, overrides?.missingTarget),
|
|
2033
|
+
scroll: mergeScrollOptions(defaults?.scroll, overrides?.scroll),
|
|
2034
|
+
overlayClick: overrides?.overlayClick ?? defaults?.overlayClick
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
function mergeControls(defaults, overrides) {
|
|
2038
|
+
if (!defaults && !overrides)
|
|
2039
|
+
return;
|
|
2040
|
+
return {
|
|
2041
|
+
previous: mergeControl(defaults?.previous, overrides?.previous),
|
|
2042
|
+
advance: mergeControl(defaults?.advance, overrides?.advance),
|
|
2043
|
+
cancel: mergeControl(defaults?.cancel, overrides?.cancel)
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function mergeControl(defaults, overrides) {
|
|
2047
|
+
if (!defaults && !overrides)
|
|
2048
|
+
return;
|
|
2049
|
+
return {
|
|
2050
|
+
state: overrides?.state ?? defaults?.state,
|
|
2051
|
+
keys: cloneArray(overrides?.keys ?? defaults?.keys)
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function mergeMissingTarget(defaults, overrides) {
|
|
2055
|
+
if (!defaults && !overrides)
|
|
2056
|
+
return;
|
|
2057
|
+
return {
|
|
2058
|
+
strategy: overrides?.strategy ?? defaults?.strategy,
|
|
2059
|
+
timeout: overrides?.timeout ?? defaults?.timeout
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function cloneArray(value) {
|
|
2063
|
+
return value ? [...value] : undefined;
|
|
2064
|
+
}
|
|
2065
|
+
function isControlAvailable(props, command) {
|
|
2066
|
+
return props !== undefined && props.controls?.[command]?.state !== "disabled";
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1894
2069
|
// packages/core/src/dom/tour-view-driver.ts
|
|
1895
2070
|
var SCROLL_SETTLE_STILL_FRAMES = 2;
|
|
1896
2071
|
var SCROLL_SETTLE_GRACE_FRAMES = 3;
|
|
@@ -1925,11 +2100,15 @@ class DomTourViewDriver {
|
|
|
1925
2100
|
modalDocument = null;
|
|
1926
2101
|
modalRoot = null;
|
|
1927
2102
|
overlay = null;
|
|
1928
|
-
|
|
2103
|
+
pendingCommand = null;
|
|
1929
2104
|
pointer = null;
|
|
1930
2105
|
pendingFocusGeneration = null;
|
|
2106
|
+
handledTriggerClick = null;
|
|
1931
2107
|
popover = null;
|
|
1932
2108
|
presentationDirty = false;
|
|
2109
|
+
focusToRestore = null;
|
|
2110
|
+
appliedAllowInteraction = false;
|
|
2111
|
+
pointerFading = false;
|
|
1933
2112
|
rafId = null;
|
|
1934
2113
|
rafCancel = null;
|
|
1935
2114
|
root = null;
|
|
@@ -1990,7 +2169,7 @@ class DomTourViewDriver {
|
|
|
1990
2169
|
const generation = this.beginGeneration();
|
|
1991
2170
|
const removeAbort = this.cancelAnimationsOnAbort(signal);
|
|
1992
2171
|
const replaceVisiblePopover = this.active && onBeforePopoverAppear !== undefined;
|
|
1993
|
-
let
|
|
2172
|
+
let removeTransitionListeners = () => {};
|
|
1994
2173
|
try {
|
|
1995
2174
|
this.cleanupStepResources();
|
|
1996
2175
|
this.throwIfStale(generation, signal);
|
|
@@ -2005,41 +2184,51 @@ class DomTourViewDriver {
|
|
|
2005
2184
|
this.lastViewport = null;
|
|
2006
2185
|
this.presentationDirty = false;
|
|
2007
2186
|
this.awaitingStepUi = true;
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2187
|
+
const modal = !step.allowsInteraction();
|
|
2188
|
+
if (modal)
|
|
2189
|
+
this.claimModal();
|
|
2190
|
+
const onKeydown = (event) => this.queueTransitionKeydown(event, step, generation, replaceVisiblePopover);
|
|
2191
|
+
const onClick = (event) => {
|
|
2192
|
+
if (replaceVisiblePopover)
|
|
2193
|
+
this.queueTransitionClick(event, step, generation);
|
|
2194
|
+
};
|
|
2195
|
+
const currentWindow = this.getWindow();
|
|
2196
|
+
if (typeof currentWindow?.addEventListener === "function") {
|
|
2197
|
+
currentWindow.addEventListener("keydown", onKeydown);
|
|
2198
|
+
currentWindow.addEventListener("click", onClick);
|
|
2199
|
+
removeTransitionListeners = () => {
|
|
2200
|
+
currentWindow.removeEventListener("keydown", onKeydown);
|
|
2201
|
+
currentWindow.removeEventListener("click", onClick);
|
|
2202
|
+
};
|
|
2015
2203
|
}
|
|
2016
2204
|
const target = step.target;
|
|
2017
2205
|
if (!target)
|
|
2018
2206
|
return;
|
|
2019
2207
|
this.activeTarget = target;
|
|
2020
|
-
this.
|
|
2208
|
+
this.focusGuard.captureInitialFocus(target, this.focusToRestore);
|
|
2021
2209
|
const scrolling = this.beginTargetScroll(step, target, signal);
|
|
2022
2210
|
this.throwIfStale(generation, signal);
|
|
2023
|
-
this.initializeElements(step);
|
|
2024
|
-
const resolveRect = () => target
|
|
2211
|
+
this.initializeElements(step, replaceVisiblePopover);
|
|
2212
|
+
const resolveRect = () => presentationRect(step, target);
|
|
2025
2213
|
await this.appear(resolveRect, step, generation, scrolling, replaceVisiblePopover, onBeforePopoverAppear);
|
|
2026
2214
|
this.throwIfStale(generation, signal);
|
|
2027
2215
|
const targetRect = resolveRect();
|
|
2028
2216
|
this.lastTargetRect = snapshotRect(targetRect);
|
|
2029
2217
|
this.lastViewport = snapshotViewport(target);
|
|
2030
2218
|
this.active = true;
|
|
2219
|
+
this.syncModality(step.allowsInteraction());
|
|
2031
2220
|
this.activateFocus(step, target, direction, generation);
|
|
2032
2221
|
this.syncScrollLock(step);
|
|
2033
2222
|
this.throwIfStale(generation, signal);
|
|
2034
|
-
|
|
2035
|
-
|
|
2223
|
+
removeTransitionListeners();
|
|
2224
|
+
removeTransitionListeners = () => {};
|
|
2036
2225
|
this.attachStepResources(step, target, generation, signal);
|
|
2037
2226
|
} catch (error) {
|
|
2038
2227
|
if (this.isCurrentGeneration(generation))
|
|
2039
2228
|
this.releaseModality();
|
|
2040
2229
|
throw error;
|
|
2041
2230
|
} finally {
|
|
2042
|
-
|
|
2231
|
+
removeTransitionListeners();
|
|
2043
2232
|
removeAbort();
|
|
2044
2233
|
}
|
|
2045
2234
|
}
|
|
@@ -2052,7 +2241,7 @@ class DomTourViewDriver {
|
|
|
2052
2241
|
try {
|
|
2053
2242
|
this.cleanupStepResources();
|
|
2054
2243
|
this.releaseModality();
|
|
2055
|
-
this.focusGuard.
|
|
2244
|
+
this.focusToRestore = this.focusGuard.release() ?? this.focusToRestore;
|
|
2056
2245
|
this.scrollLock.deactivate();
|
|
2057
2246
|
this.throwIfStale(generation, signal);
|
|
2058
2247
|
this.active = false;
|
|
@@ -2064,16 +2253,19 @@ class DomTourViewDriver {
|
|
|
2064
2253
|
this.currentSignal = null;
|
|
2065
2254
|
this.lastTargetRect = null;
|
|
2066
2255
|
this.lastViewport = null;
|
|
2067
|
-
this.popover?.getElement()?.removeAttribute("aria-modal");
|
|
2068
2256
|
await Promise.allSettled([
|
|
2069
2257
|
this.overlay?.disappear() ?? Promise.resolve(),
|
|
2070
2258
|
this.popover?.disappear() ?? Promise.resolve(),
|
|
2071
2259
|
this.pointer?.disappear() ?? Promise.resolve()
|
|
2072
2260
|
]);
|
|
2073
|
-
this.throwIfStale(generation, signal);
|
|
2074
2261
|
} finally {
|
|
2262
|
+
if (this.isCurrentGeneration(generation)) {
|
|
2263
|
+
this.focusToRestore?.focus();
|
|
2264
|
+
this.focusToRestore = null;
|
|
2265
|
+
}
|
|
2075
2266
|
removeAbort();
|
|
2076
2267
|
}
|
|
2268
|
+
this.throwIfStale(generation, signal);
|
|
2077
2269
|
}
|
|
2078
2270
|
releaseMount() {
|
|
2079
2271
|
if (this.disposed)
|
|
@@ -2124,45 +2316,40 @@ class DomTourViewDriver {
|
|
|
2124
2316
|
if (this.disposed || !step || !target || !targetRect || !signal)
|
|
2125
2317
|
return;
|
|
2126
2318
|
this.activeTarget = target;
|
|
2127
|
-
this.initializeElements(step);
|
|
2319
|
+
this.initializeElements(step, false);
|
|
2128
2320
|
this.awaitingStepUi = true;
|
|
2129
2321
|
await this.appear(() => targetRect, step, generation, null, false);
|
|
2130
2322
|
this.throwIfStale(generation);
|
|
2323
|
+
this.syncModality(step.allowsInteraction());
|
|
2131
2324
|
this.activateFocus(step, target, this.direction, generation);
|
|
2132
2325
|
this.syncScrollLock(step);
|
|
2133
2326
|
this.throwIfStale(generation);
|
|
2134
2327
|
this.attachStepResources(step, target, generation, signal);
|
|
2135
2328
|
}
|
|
2136
|
-
initializeElements(step) {
|
|
2137
|
-
const interactionAllowed = step.
|
|
2329
|
+
initializeElements(step, replaceVisiblePopover) {
|
|
2330
|
+
const interactionAllowed = step.allowsInteraction();
|
|
2138
2331
|
this.overlay?.initializeProps();
|
|
2139
|
-
this.overlay?.setAnimationOptions(animationOptions(step, step.overlay, this.overlay.getElement()));
|
|
2332
|
+
this.overlay?.setAnimationOptions(animationOptions(step, step.props.get().overlay, this.overlay.getElement()));
|
|
2140
2333
|
this.overlay?.setInteractionAllowed(interactionAllowed);
|
|
2141
|
-
this.
|
|
2142
|
-
this.popover?.
|
|
2143
|
-
|
|
2144
|
-
if (isHTMLElement(popover, this.root ?? popover) && !interactionAllowed)
|
|
2145
|
-
popover.setAttribute("aria-modal", "true");
|
|
2334
|
+
this.appliedAllowInteraction = interactionAllowed;
|
|
2335
|
+
this.popover?.initializeProps(!replaceVisiblePopover);
|
|
2336
|
+
this.popover?.setAnimationOptions(animationOptions(step, step.props.get().popover, this.popover.getElement()));
|
|
2146
2337
|
this.pointer?.initializeProps();
|
|
2147
|
-
this.pointer?.setAnimationOptions(animationOptions(step, step.indicator, this.pointer.getElement()));
|
|
2338
|
+
this.pointer?.setAnimationOptions(animationOptions(step, step.props.get().indicator, this.pointer.getElement()));
|
|
2148
2339
|
}
|
|
2149
2340
|
syncModality(interactionAllowed) {
|
|
2150
2341
|
if (interactionAllowed) {
|
|
2151
2342
|
this.releaseModality();
|
|
2152
2343
|
return;
|
|
2153
2344
|
}
|
|
2345
|
+
this.claimModal();
|
|
2346
|
+
const popover = this.popover?.getElement();
|
|
2347
|
+
if (isHTMLElement(popover, this.root ?? popover))
|
|
2348
|
+
popover.setAttribute("aria-modal", "true");
|
|
2154
2349
|
const root = this.root;
|
|
2155
|
-
if (!root)
|
|
2350
|
+
if (!root || this.modalRoot === root)
|
|
2156
2351
|
return;
|
|
2157
2352
|
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
|
-
}
|
|
2162
|
-
ACTIVE_MODAL_BY_DOCUMENT.set(document2, this.modalToken);
|
|
2163
|
-
this.modalDocument = document2;
|
|
2164
|
-
if (this.modalRoot === root)
|
|
2165
|
-
return;
|
|
2166
2353
|
this.restoreInertBranches();
|
|
2167
2354
|
this.modalRoot = root;
|
|
2168
2355
|
let branch = root;
|
|
@@ -2179,6 +2366,17 @@ class DomTourViewDriver {
|
|
|
2179
2366
|
branch = parent;
|
|
2180
2367
|
}
|
|
2181
2368
|
}
|
|
2369
|
+
claimModal() {
|
|
2370
|
+
const document2 = this.root?.ownerDocument;
|
|
2371
|
+
if (!document2)
|
|
2372
|
+
return;
|
|
2373
|
+
const owner = ACTIVE_MODAL_BY_DOCUMENT.get(document2);
|
|
2374
|
+
if (owner && owner !== this.modalToken) {
|
|
2375
|
+
throw new Error("GlowTour.js only supports one active modal tour per document");
|
|
2376
|
+
}
|
|
2377
|
+
ACTIVE_MODAL_BY_DOCUMENT.set(document2, this.modalToken);
|
|
2378
|
+
this.modalDocument = document2;
|
|
2379
|
+
}
|
|
2182
2380
|
releaseModality() {
|
|
2183
2381
|
this.popover?.getElement()?.removeAttribute("aria-modal");
|
|
2184
2382
|
this.restoreInertBranches();
|
|
@@ -2202,14 +2400,14 @@ class DomTourViewDriver {
|
|
|
2202
2400
|
async appear(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear) {
|
|
2203
2401
|
const spotlightRect = resolveRect();
|
|
2204
2402
|
const stepUi = this.presentStepUi(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear);
|
|
2205
|
-
const spotlight = this.overlay?.moveToTarget(spotlightRect, step, scrolling !== null);
|
|
2403
|
+
const spotlight = this.overlay?.moveToTarget(spotlightRect, this.elementProps(step), scrolling !== null);
|
|
2206
2404
|
if (scrolling)
|
|
2207
2405
|
this.schedulePosition(generation);
|
|
2208
2406
|
await Promise.all([spotlight ?? Promise.resolve(), stepUi]);
|
|
2209
2407
|
}
|
|
2210
2408
|
async presentStepUi(resolveRect, step, generation, scrolling, hadVisiblePopover, onBeforePopoverAppear) {
|
|
2211
2409
|
if (hadVisiblePopover)
|
|
2212
|
-
await this.popover?.disappear();
|
|
2410
|
+
await this.popover?.disappear(false);
|
|
2213
2411
|
if (onBeforePopoverAppear) {
|
|
2214
2412
|
await onBeforePopoverAppear();
|
|
2215
2413
|
this.syncControlState(step);
|
|
@@ -2223,12 +2421,15 @@ class DomTourViewDriver {
|
|
|
2223
2421
|
await this.enterStepUi(resolveRect(), step);
|
|
2224
2422
|
}
|
|
2225
2423
|
enterStepUi(targetRect, step) {
|
|
2226
|
-
const popoverPlacement = this.popover?.resolvePosition(targetRect, step).placement;
|
|
2424
|
+
const popoverPlacement = this.popover?.resolvePosition(targetRect, this.elementProps(step)).placement;
|
|
2227
2425
|
return Promise.all([
|
|
2228
|
-
this.popover?.present(targetRect, step) ?? Promise.resolve(),
|
|
2229
|
-
this.
|
|
2426
|
+
this.popover?.present(targetRect, this.elementProps(step)) ?? Promise.resolve(),
|
|
2427
|
+
this.presentPointer(targetRect, step, popoverPlacement) ?? Promise.resolve()
|
|
2230
2428
|
]);
|
|
2231
2429
|
}
|
|
2430
|
+
presentPointer(targetRect, step, popoverPlacement) {
|
|
2431
|
+
return this.isPointerEnabled(step) ? this.pointer?.moveToTarget(targetRect, step.props.get(), true, popoverPlacement) : this.pointer?.disappear();
|
|
2432
|
+
}
|
|
2232
2433
|
attachStepResources(step, target, generation, signal) {
|
|
2233
2434
|
let initialPropsNotification = true;
|
|
2234
2435
|
this.stepCleanups.push(step.props.subscribe(() => {
|
|
@@ -2239,7 +2440,14 @@ class DomTourViewDriver {
|
|
|
2239
2440
|
return;
|
|
2240
2441
|
}
|
|
2241
2442
|
this.presentationDirty = true;
|
|
2443
|
+
this.syncScrollLock(step);
|
|
2444
|
+
if (step.allowsInteraction() === this.appliedAllowInteraction)
|
|
2445
|
+
return;
|
|
2446
|
+
this.syncInteraction(step);
|
|
2447
|
+
this.pointerFading = step.allowsInteraction() === this.appliedAllowInteraction;
|
|
2242
2448
|
}));
|
|
2449
|
+
if (step.allowsInteraction() !== this.appliedAllowInteraction)
|
|
2450
|
+
this.syncInteraction(step);
|
|
2243
2451
|
this.stepCleanups.push(this.commands?.subscribeCapabilities?.((active) => {
|
|
2244
2452
|
if (!this.isCurrentGeneration(generation))
|
|
2245
2453
|
return;
|
|
@@ -2249,7 +2457,7 @@ class DomTourViewDriver {
|
|
|
2249
2457
|
this.focusGuard.focus();
|
|
2250
2458
|
}
|
|
2251
2459
|
if (active)
|
|
2252
|
-
this.
|
|
2460
|
+
this.flushPendingCommand(step, generation);
|
|
2253
2461
|
}) ?? (() => {}));
|
|
2254
2462
|
this.attachTargetResources(step, target, generation, signal);
|
|
2255
2463
|
const currentWindow = this.getWindow(target);
|
|
@@ -2271,14 +2479,22 @@ class DomTourViewDriver {
|
|
|
2271
2479
|
this.schedulePosition(generation);
|
|
2272
2480
|
}
|
|
2273
2481
|
attachTargetResources(step, target, generation, signal) {
|
|
2274
|
-
|
|
2482
|
+
if (step.detached)
|
|
2483
|
+
return;
|
|
2484
|
+
for (const handler of step.definition.targetEvents) {
|
|
2275
2485
|
const listener = (event) => {
|
|
2276
2486
|
if (!this.isCurrentGeneration(generation))
|
|
2277
2487
|
return;
|
|
2278
2488
|
const context = Object.freeze({
|
|
2279
2489
|
advance: () => this.commandForStep("advance", step, signal),
|
|
2280
2490
|
cancel: () => this.commandForStep("cancel", step, signal),
|
|
2491
|
+
goTo: async (id) => {
|
|
2492
|
+
if (!signal.aborted && this.currentStep === step)
|
|
2493
|
+
await this.commands?.goTo(id);
|
|
2494
|
+
},
|
|
2281
2495
|
previous: () => this.commandForStep("previous", step, signal),
|
|
2496
|
+
direction: step.direction,
|
|
2497
|
+
initialProps: step.initialProps,
|
|
2282
2498
|
props: step.props,
|
|
2283
2499
|
signal,
|
|
2284
2500
|
target
|
|
@@ -2337,20 +2553,20 @@ class DomTourViewDriver {
|
|
|
2337
2553
|
this.freezeForDisconnectedTarget(step, target, generation);
|
|
2338
2554
|
return;
|
|
2339
2555
|
}
|
|
2340
|
-
const targetRect = target
|
|
2556
|
+
const targetRect = presentationRect(step, target);
|
|
2341
2557
|
const targetSnapshot = snapshotRect(targetRect);
|
|
2342
2558
|
const viewportSnapshot = snapshotViewport(target);
|
|
2343
2559
|
const presentationChanged = this.presentationDirty;
|
|
2344
2560
|
if (!presentationChanged && sameRect(targetSnapshot, this.lastTargetRect) && sameViewport(viewportSnapshot, this.lastViewport))
|
|
2345
2561
|
return;
|
|
2346
2562
|
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()));
|
|
2563
|
+
this.overlay?.setAnimationOptions(animationOptions(step, step.props.get().overlay, this.overlay.getElement()));
|
|
2564
|
+
this.popover?.setAnimationOptions(animationOptions(step, step.props.get().popover, this.popover.getElement()));
|
|
2565
|
+
this.pointer?.setAnimationOptions(animationOptions(step, step.props.get().indicator, this.pointer.getElement()));
|
|
2350
2566
|
this.syncControlState(step);
|
|
2351
2567
|
this.syncShortcutLabels(step);
|
|
2352
2568
|
}
|
|
2353
|
-
this.overlay?.updatePosition(targetRect, step, presentationChanged, (transition) => this.observeDynamicOperation(transition, generation));
|
|
2569
|
+
this.overlay?.updatePosition(targetRect, this.elementProps(step), presentationChanged, (transition) => this.observeDynamicOperation(transition, generation));
|
|
2354
2570
|
if (!this.awaitingStepUi)
|
|
2355
2571
|
this.trackStepUi(targetRect, step, generation, presentationChanged);
|
|
2356
2572
|
this.lastTargetRect = targetSnapshot;
|
|
@@ -2359,15 +2575,18 @@ class DomTourViewDriver {
|
|
|
2359
2575
|
this.presentationDirty = false;
|
|
2360
2576
|
}
|
|
2361
2577
|
trackStepUi(targetRect, step, generation, presentationChanged) {
|
|
2362
|
-
const popoverPlacement = this.popover?.updatePosition(targetRect, step, (reposition) => this.observeDynamicOperation(reposition, generation));
|
|
2578
|
+
const popoverPlacement = this.popover?.updatePosition(targetRect, this.elementProps(step), (reposition) => this.observeDynamicOperation(reposition, generation));
|
|
2363
2579
|
if (presentationChanged) {
|
|
2364
|
-
|
|
2580
|
+
if (this.pointerFading)
|
|
2581
|
+
this.pointerFading = false;
|
|
2582
|
+
else
|
|
2583
|
+
this.pointer?.syncVisibility(this.isPointerEnabled(step), targetRect, step.props.get(), popoverPlacement);
|
|
2365
2584
|
} else if (this.isPointerEnabled(step)) {
|
|
2366
2585
|
const pointer = this.pointer?.getElement();
|
|
2367
2586
|
if (pointer?.getAttribute("aria-hidden") === "true") {
|
|
2368
|
-
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step, true, popoverPlacement), generation);
|
|
2587
|
+
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step.props.get(), true, popoverPlacement), generation);
|
|
2369
2588
|
} else {
|
|
2370
|
-
this.pointer?.updatePosition(targetRect, step, popoverPlacement);
|
|
2589
|
+
this.pointer?.updatePosition(targetRect, step.props.get(), popoverPlacement);
|
|
2371
2590
|
}
|
|
2372
2591
|
} else if (this.pointer?.getElement()?.getAttribute("aria-hidden") !== "true") {
|
|
2373
2592
|
this.observeDynamicOperation(this.pointer?.disappear(), generation);
|
|
@@ -2389,70 +2608,75 @@ class DomTourViewDriver {
|
|
|
2389
2608
|
const step = this.currentStep;
|
|
2390
2609
|
if (!step || event.defaultPrevented || event.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
2391
2610
|
return;
|
|
2392
|
-
if (event.key === "Tab" && step.
|
|
2611
|
+
if (event.key === "Tab" && !step.allowsInteraction()) {
|
|
2393
2612
|
this.loopFocus(event);
|
|
2394
2613
|
return;
|
|
2395
2614
|
}
|
|
2396
|
-
const
|
|
2397
|
-
if (
|
|
2398
|
-
event.preventDefault();
|
|
2399
|
-
this.command("cancel", "keyboard");
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
if (isEditable(event.target, this.root))
|
|
2615
|
+
const command = this.keyboardCommand(event, step, (command2) => this.canCommand(command2, step));
|
|
2616
|
+
if (!command)
|
|
2403
2617
|
return;
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2618
|
+
event.preventDefault();
|
|
2619
|
+
this.command(command, "keyboard");
|
|
2620
|
+
}
|
|
2621
|
+
keyboardCommand(event, step, available) {
|
|
2622
|
+
if (activatesControl(event, this.root))
|
|
2623
|
+
return null;
|
|
2624
|
+
const controls = step.props.get().controls;
|
|
2625
|
+
const shortcut = (command) => (controls?.[command]?.keys ?? DEFAULT_SHORTCUTS[command]).includes(event.key) && available(command);
|
|
2626
|
+
return shortcut("cancel") ? "cancel" : isEditable(event.target, this.root) ? null : shortcut("advance") ? "advance" : shortcut("previous") ? "previous" : null;
|
|
2411
2627
|
}
|
|
2412
2628
|
handleOverlayClick(event, step, target) {
|
|
2413
2629
|
if (this.currentStep !== step || event.defaultPrevented)
|
|
2414
2630
|
return;
|
|
2415
|
-
if (step.
|
|
2631
|
+
if (step.allowsInteraction())
|
|
2416
2632
|
return;
|
|
2417
2633
|
const path = event.composedPath();
|
|
2418
2634
|
const popover = this.popover?.getElement();
|
|
2419
2635
|
const pointer = this.pointer?.getElement();
|
|
2420
|
-
if (path.includes(target) || popover && path.includes(popover) || pointer && path.includes(pointer))
|
|
2636
|
+
if (!step.detached && path.includes(target) || popover && path.includes(popover) || pointer && path.includes(pointer))
|
|
2421
2637
|
return;
|
|
2422
|
-
const overlayClick = step.behavior?.overlayClick ?? "none";
|
|
2638
|
+
const overlayClick = step.props.get().behavior?.overlayClick ?? "none";
|
|
2423
2639
|
if (overlayClick === "advance" && this.canCommand("advance", step)) {
|
|
2424
2640
|
this.command("advance", "overlay");
|
|
2425
2641
|
} else if (overlayClick === "cancel" && this.canCommand("cancel", step)) {
|
|
2426
2642
|
this.command("cancel", "overlay");
|
|
2427
2643
|
}
|
|
2428
2644
|
}
|
|
2429
|
-
queueTransitionKeydown(event, step, generation) {
|
|
2645
|
+
queueTransitionKeydown(event, step, generation, queue) {
|
|
2646
|
+
const target = event.target;
|
|
2647
|
+
const root = this.root;
|
|
2430
2648
|
if (!this.isCurrentGeneration(generation) || event.defaultPrevented || event.isComposing || event.ctrlKey || event.metaKey || event.altKey)
|
|
2431
2649
|
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";
|
|
2650
|
+
if (!step.allowsInteraction() && isHTMLElement(target, root) && target !== target.ownerDocument.body && !root?.contains(target)) {
|
|
2651
|
+
event.preventDefault();
|
|
2652
|
+
return;
|
|
2441
2653
|
}
|
|
2654
|
+
if (!queue)
|
|
2655
|
+
return;
|
|
2656
|
+
const command = this.keyboardCommand(event, step, (command2) => isControlAvailable(step.props.get(), command2));
|
|
2442
2657
|
if (!command)
|
|
2443
2658
|
return;
|
|
2444
2659
|
event.preventDefault();
|
|
2445
|
-
this.
|
|
2660
|
+
this.pendingCommand ??= { command, generation, source: "keyboard" };
|
|
2661
|
+
}
|
|
2662
|
+
queueTransitionClick(event, step, generation) {
|
|
2663
|
+
const scope = this.root ?? this.popover?.getElement();
|
|
2664
|
+
if (!this.isCurrentGeneration(generation) || event.defaultPrevented || event === this.handledTriggerClick || !isHTMLElement(scope, scope) || !isElement(event.target, scope))
|
|
2665
|
+
return;
|
|
2666
|
+
const match = this.findClickedTrigger(event.target, scope);
|
|
2667
|
+
if (!match || this.isLiveDisabled(match.trigger) || !isControlAvailable(step.props.get(), match.command))
|
|
2668
|
+
return;
|
|
2669
|
+
this.pendingCommand ??= { command: match.command, generation, source: "trigger" };
|
|
2446
2670
|
}
|
|
2447
|
-
|
|
2448
|
-
const pending = this.
|
|
2671
|
+
flushPendingCommand(step, generation) {
|
|
2672
|
+
const pending = this.pendingCommand;
|
|
2449
2673
|
if (!pending || pending.generation !== generation)
|
|
2450
2674
|
return;
|
|
2451
|
-
this.
|
|
2675
|
+
this.pendingCommand = null;
|
|
2452
2676
|
queueMicrotask(() => {
|
|
2453
2677
|
if (!this.isCurrentGeneration(generation) || this.currentStep !== step || !this.canCommand(pending.command, step))
|
|
2454
2678
|
return;
|
|
2455
|
-
this.commandForGeneration(pending.command, generation,
|
|
2679
|
+
this.commandForGeneration(pending.command, generation, pending.source);
|
|
2456
2680
|
});
|
|
2457
2681
|
}
|
|
2458
2682
|
loopFocus(event) {
|
|
@@ -2479,32 +2703,32 @@ class DomTourViewDriver {
|
|
|
2479
2703
|
const popover = this.popover?.getElement();
|
|
2480
2704
|
if (!isHTMLElement(popover, this.root ?? popover))
|
|
2481
2705
|
return;
|
|
2482
|
-
const autoFocus = step.
|
|
2706
|
+
const autoFocus = step.autoFocuses();
|
|
2483
2707
|
const deferFocus = autoFocus && this.commands?.subscribeCapabilities !== undefined;
|
|
2484
2708
|
if (deferFocus)
|
|
2485
2709
|
this.pendingFocusGeneration = generation;
|
|
2486
2710
|
this.focusGuard.activate({
|
|
2487
2711
|
allowedTarget: target,
|
|
2488
|
-
allowTargetInteraction: step.
|
|
2489
|
-
autoFocus
|
|
2712
|
+
allowTargetInteraction: step.allowsInteraction(),
|
|
2713
|
+
autoFocus,
|
|
2714
|
+
deferFocus,
|
|
2490
2715
|
direction,
|
|
2491
2716
|
fallback: this.root ?? popover.parentElement,
|
|
2492
2717
|
popover
|
|
2493
2718
|
});
|
|
2494
2719
|
}
|
|
2495
2720
|
syncScrollLock(step) {
|
|
2496
|
-
if (step.
|
|
2721
|
+
if (step.allowsScroll()) {
|
|
2497
2722
|
this.scrollLock.deactivate();
|
|
2498
2723
|
return;
|
|
2499
2724
|
}
|
|
2500
2725
|
this.scrollLock.activate(this.root?.ownerDocument ?? this.popover?.getElement()?.ownerDocument);
|
|
2501
2726
|
}
|
|
2502
2727
|
syncShortcutLabels(step) {
|
|
2503
|
-
for (const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
syncKeyShortcuts(previous, step.popover?.keyboardShortcuts?.previous ?? DEFAULT_SHORTCUTS.previous);
|
|
2728
|
+
for (const command of ["advance", "previous"]) {
|
|
2729
|
+
const shortcuts = isControlAvailable(step.props.get(), command) ? step.props.get().controls?.[command]?.keys ?? DEFAULT_SHORTCUTS[command] : [];
|
|
2730
|
+
for (const trigger of this.findTriggers(command))
|
|
2731
|
+
syncKeyShortcuts(trigger, shortcuts);
|
|
2508
2732
|
}
|
|
2509
2733
|
}
|
|
2510
2734
|
attachButtonHandlers(step) {
|
|
@@ -2518,6 +2742,7 @@ class DomTourViewDriver {
|
|
|
2518
2742
|
const match = this.findClickedTrigger(event.target, scope);
|
|
2519
2743
|
if (!match)
|
|
2520
2744
|
return;
|
|
2745
|
+
this.handledTriggerClick = event;
|
|
2521
2746
|
this.deferTriggerCommand(match.command, event, step, generation, match.trigger);
|
|
2522
2747
|
});
|
|
2523
2748
|
}
|
|
@@ -2568,13 +2793,13 @@ class DomTourViewDriver {
|
|
|
2568
2793
|
}
|
|
2569
2794
|
syncControlState(step) {
|
|
2570
2795
|
for (const advance of this.findTriggers("advance")) {
|
|
2571
|
-
this.syncControl(advance, this.commands?.isAdvanceDisabled() === true || step.props.get()
|
|
2796
|
+
this.syncControl(advance, this.commands?.isAdvanceDisabled() === true || !isControlAvailable(step.props.get(), "advance"));
|
|
2572
2797
|
}
|
|
2573
2798
|
for (const previous of this.findTriggers("previous")) {
|
|
2574
|
-
this.syncControl(previous, this.commands?.isPreviousDisabled() === true || step.props.get()
|
|
2799
|
+
this.syncControl(previous, this.commands?.isPreviousDisabled() === true || !isControlAvailable(step.props.get(), "previous"));
|
|
2575
2800
|
}
|
|
2576
2801
|
for (const cancel of this.findTriggers("cancel")) {
|
|
2577
|
-
this.syncControl(cancel, this.commands?.isCancelDisabled() === true);
|
|
2802
|
+
this.syncControl(cancel, this.commands?.isCancelDisabled() === true || !isControlAvailable(step.props.get(), "cancel"));
|
|
2578
2803
|
}
|
|
2579
2804
|
}
|
|
2580
2805
|
findTriggers(direction) {
|
|
@@ -2606,14 +2831,12 @@ class DomTourViewDriver {
|
|
|
2606
2831
|
return !signal.aborted && this.currentStep === step ? this.command(command, "api") : Promise.resolve();
|
|
2607
2832
|
}
|
|
2608
2833
|
canCommand(command, step, trigger) {
|
|
2609
|
-
if (this.isConsumerDisabled(trigger ?? null))
|
|
2834
|
+
if (this.isConsumerDisabled(trigger ?? null) || !isControlAvailable(step.props.get(), command))
|
|
2610
2835
|
return false;
|
|
2611
|
-
if (command === "advance")
|
|
2612
|
-
return
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
return (this.commands?.canPrevious?.() ?? true) && step.props.get().popover?.disablePreviousButton !== true;
|
|
2616
|
-
}
|
|
2836
|
+
if (command === "advance")
|
|
2837
|
+
return this.commands?.canAdvance?.() ?? true;
|
|
2838
|
+
if (command === "previous")
|
|
2839
|
+
return this.commands?.canPrevious?.() ?? true;
|
|
2617
2840
|
return this.commands?.canCancel?.() ?? true;
|
|
2618
2841
|
}
|
|
2619
2842
|
syncControl(element, disabled) {
|
|
@@ -2634,13 +2857,18 @@ class DomTourViewDriver {
|
|
|
2634
2857
|
isLiveDisabled(element) {
|
|
2635
2858
|
return element.disabled || element.getAttribute("aria-disabled") === "true" || this.isConsumerDisabled(element);
|
|
2636
2859
|
}
|
|
2860
|
+
elementProps(step) {
|
|
2861
|
+
const props = step.props.get();
|
|
2862
|
+
return step.detached ? { ...props, detached: true } : props;
|
|
2863
|
+
}
|
|
2637
2864
|
isPointerEnabled(step) {
|
|
2638
|
-
return step.
|
|
2865
|
+
return step.allowsInteraction() && step.props.get().indicator?.hidden !== true;
|
|
2639
2866
|
}
|
|
2640
2867
|
cleanupStepResources() {
|
|
2641
2868
|
this.cancelScroll?.();
|
|
2642
2869
|
this.cancelScroll = null;
|
|
2643
2870
|
this.presentationDirty = false;
|
|
2871
|
+
this.pointerFading = false;
|
|
2644
2872
|
if (this.rafId !== null)
|
|
2645
2873
|
this.rafCancel?.(this.rafId);
|
|
2646
2874
|
this.rafId = null;
|
|
@@ -2673,17 +2901,36 @@ class DomTourViewDriver {
|
|
|
2673
2901
|
});
|
|
2674
2902
|
}
|
|
2675
2903
|
applyInteractionLock(step, locked) {
|
|
2676
|
-
const allowed = !locked && step.
|
|
2904
|
+
const allowed = !locked && step.allowsInteraction();
|
|
2677
2905
|
this.overlay?.setInteractionAllowed(allowed);
|
|
2678
2906
|
this.syncModality(allowed);
|
|
2907
|
+
}
|
|
2908
|
+
applyInteraction(step, target) {
|
|
2909
|
+
this.applyInteractionLock(step, false);
|
|
2910
|
+
this.appliedAllowInteraction = step.allowsInteraction();
|
|
2679
2911
|
const popover = this.popover?.getElement();
|
|
2680
2912
|
if (isHTMLElement(popover, this.root ?? popover)) {
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2913
|
+
this.focusGuard.update({
|
|
2914
|
+
allowedTarget: target,
|
|
2915
|
+
allowTargetInteraction: step.allowsInteraction(),
|
|
2916
|
+
direction: this.direction,
|
|
2917
|
+
fallback: this.root ?? popover.parentElement,
|
|
2918
|
+
popover
|
|
2919
|
+
});
|
|
2685
2920
|
}
|
|
2686
2921
|
}
|
|
2922
|
+
syncInteraction(step) {
|
|
2923
|
+
const target = this.activeTarget;
|
|
2924
|
+
if (this.disposed || !this.active || this.frozen || this.awaitingStepUi || this.currentStep !== step || !target)
|
|
2925
|
+
return;
|
|
2926
|
+
const focusWasInTarget = this.isFocusInsideTarget(target);
|
|
2927
|
+
this.applyInteraction(step, target);
|
|
2928
|
+
if (focusWasInTarget && !step.allowsInteraction() && step.autoFocuses())
|
|
2929
|
+
this.focusGuard.focus();
|
|
2930
|
+
const targetRect = target.getBoundingClientRect();
|
|
2931
|
+
this.pointer?.cancelAnimations();
|
|
2932
|
+
this.observeDynamicOperation(this.presentPointer(targetRect, step, this.popover?.resolvePosition(targetRect, this.elementProps(step)).placement), this.generation);
|
|
2933
|
+
}
|
|
2687
2934
|
isFocusInsideTarget(target) {
|
|
2688
2935
|
const activeElement = target.ownerDocument?.activeElement;
|
|
2689
2936
|
if (!activeElement)
|
|
@@ -2700,23 +2947,15 @@ class DomTourViewDriver {
|
|
|
2700
2947
|
this.frozen = false;
|
|
2701
2948
|
this.currentSignal = signal;
|
|
2702
2949
|
this.activeTarget = target;
|
|
2703
|
-
this.
|
|
2950
|
+
this.applyInteraction(step, target);
|
|
2704
2951
|
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)
|
|
2952
|
+
if (this.targetFocusedAtFreeze && step.autoFocuses()) {
|
|
2953
|
+
if (step.allowsInteraction())
|
|
2718
2954
|
target.focus();
|
|
2955
|
+
else
|
|
2956
|
+
this.focusGuard.focus();
|
|
2719
2957
|
}
|
|
2958
|
+
this.targetFocusedAtFreeze = false;
|
|
2720
2959
|
this.syncControlState(step);
|
|
2721
2960
|
this.syncShortcutLabels(step);
|
|
2722
2961
|
this.moveToRetargetedRect(step, target);
|
|
@@ -2724,18 +2963,18 @@ class DomTourViewDriver {
|
|
|
2724
2963
|
}
|
|
2725
2964
|
moveToRetargetedRect(step, target) {
|
|
2726
2965
|
const generation = this.generation;
|
|
2727
|
-
const targetRect = target
|
|
2728
|
-
this.observeDynamicOperation(this.overlay?.animateTo(targetRect, step), generation);
|
|
2729
|
-
const placement = this.popover?.updatePosition(targetRect, step, (reposition) => this.observeDynamicOperation(reposition, generation));
|
|
2966
|
+
const targetRect = presentationRect(step, target);
|
|
2967
|
+
this.observeDynamicOperation(this.overlay?.animateTo(targetRect, this.elementProps(step)), generation);
|
|
2968
|
+
const placement = this.popover?.updatePosition(targetRect, this.elementProps(step), (reposition) => this.observeDynamicOperation(reposition, generation));
|
|
2730
2969
|
if (this.isPointerEnabled(step)) {
|
|
2731
|
-
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step, true, placement), generation);
|
|
2970
|
+
this.observeDynamicOperation(this.pointer?.moveToTarget(targetRect, step.props.get(), true, placement), generation);
|
|
2732
2971
|
}
|
|
2733
2972
|
this.lastTargetRect = snapshotRect(targetRect);
|
|
2734
2973
|
this.lastViewport = snapshotViewport(target);
|
|
2735
2974
|
}
|
|
2736
2975
|
beginGeneration() {
|
|
2737
2976
|
this.generation += 1;
|
|
2738
|
-
this.
|
|
2977
|
+
this.pendingCommand = null;
|
|
2739
2978
|
this.pendingFocusGeneration = null;
|
|
2740
2979
|
this.cancelElementAnimations();
|
|
2741
2980
|
return this.generation;
|
|
@@ -2755,18 +2994,18 @@ class DomTourViewDriver {
|
|
|
2755
2994
|
}
|
|
2756
2995
|
beginTargetScroll(step, target, signal) {
|
|
2757
2996
|
this.throwIfAborted(signal);
|
|
2758
|
-
if (step.behavior?.
|
|
2997
|
+
if (step.detached || step.props.get().behavior?.autoScroll === false)
|
|
2759
2998
|
return null;
|
|
2760
2999
|
if (isInViewport(target.getBoundingClientRect(), target))
|
|
2761
3000
|
return null;
|
|
2762
3001
|
const currentWindow = this.getWindow(target);
|
|
2763
3002
|
if (!currentWindow)
|
|
2764
3003
|
return null;
|
|
2765
|
-
const behavior = prefersReducedMotion(target) ? "instant" : step.behavior?.scroll?.behavior ?? "smooth";
|
|
3004
|
+
const behavior = prefersReducedMotion(target) ? "instant" : step.props.get().behavior?.scroll?.behavior ?? "smooth";
|
|
2766
3005
|
target.scrollIntoView({
|
|
2767
3006
|
behavior,
|
|
2768
|
-
block: step.behavior?.scroll?.block ?? "center",
|
|
2769
|
-
inline: step.behavior?.scroll?.inline ?? "nearest"
|
|
3007
|
+
block: step.props.get().behavior?.scroll?.block ?? "center",
|
|
3008
|
+
inline: step.props.get().behavior?.scroll?.inline ?? "nearest"
|
|
2770
3009
|
});
|
|
2771
3010
|
if (behavior === "instant")
|
|
2772
3011
|
return null;
|
|
@@ -2845,6 +3084,12 @@ function animationOptions(step, options, element) {
|
|
|
2845
3084
|
easing: options?.animation?.easing
|
|
2846
3085
|
};
|
|
2847
3086
|
}
|
|
3087
|
+
function presentationRect(step, target) {
|
|
3088
|
+
if (!step.detached)
|
|
3089
|
+
return target.getBoundingClientRect();
|
|
3090
|
+
const viewport = viewportDimensions(target);
|
|
3091
|
+
return { height: 0, left: viewport.width / 2, top: viewport.height / 2, width: 0 };
|
|
3092
|
+
}
|
|
2848
3093
|
function abortError2() {
|
|
2849
3094
|
return new DOMException("The operation was aborted", "AbortError");
|
|
2850
3095
|
}
|
|
@@ -2880,6 +3125,10 @@ function sameViewport(left, right) {
|
|
|
2880
3125
|
function finite(value, fallback = 0) {
|
|
2881
3126
|
return Number.isFinite(value) ? value : fallback;
|
|
2882
3127
|
}
|
|
3128
|
+
function activatesControl(event, context) {
|
|
3129
|
+
const target = event.target;
|
|
3130
|
+
return event.key === "Enter" && isHTMLElement(target, context) && target.matches(FOCUSABLE_SELECTOR);
|
|
3131
|
+
}
|
|
2883
3132
|
function isEditable(target, context) {
|
|
2884
3133
|
if (!isHTMLElement(target, context))
|
|
2885
3134
|
return false;
|
|
@@ -2950,12 +3199,13 @@ function validateIndicator(path, indicator) {
|
|
|
2950
3199
|
validateFiniteNonNegative(`${path}.gap`, indicator.gap);
|
|
2951
3200
|
}
|
|
2952
3201
|
function validateBehavior(path, behavior) {
|
|
2953
|
-
validateFiniteNonNegative(`${path}.
|
|
3202
|
+
validateFiniteNonNegative(`${path}.missingTarget.timeout`, behavior?.missingTarget?.timeout);
|
|
2954
3203
|
}
|
|
2955
3204
|
function validateStepProps(path, props) {
|
|
2956
3205
|
validateOverlay(`${path}.overlay`, props.overlay);
|
|
2957
3206
|
validatePopover(`${path}.popover`, props.popover);
|
|
2958
3207
|
validateIndicator(`${path}.indicator`, props.indicator);
|
|
3208
|
+
validateBehavior(`${path}.behavior`, props.behavior);
|
|
2959
3209
|
}
|
|
2960
3210
|
function validateWorkflowOptions(workflow) {
|
|
2961
3211
|
validateOverlay("options.overlay", workflow.options.overlay);
|
|
@@ -2963,113 +3213,8 @@ function validateWorkflowOptions(workflow) {
|
|
|
2963
3213
|
validateIndicator("options.indicator", workflow.options.indicator);
|
|
2964
3214
|
validateBehavior("options.behavior", workflow.options.behavior);
|
|
2965
3215
|
for (const [index, step] of workflow.steps.entries()) {
|
|
2966
|
-
|
|
2967
|
-
validateBehavior(`${path}.behavior`, step.behavior);
|
|
2968
|
-
validateStepProps(path, step.props);
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
|
|
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;
|
|
3216
|
+
validateStepProps(`steps[${index}]`, step.props);
|
|
3060
3217
|
}
|
|
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
3218
|
}
|
|
3074
3219
|
|
|
3075
3220
|
// packages/core/src/runtime/step-props-store.ts
|
|
@@ -3085,16 +3230,18 @@ function createStepPropsStore(initialProps, reportListenerError, path = "steps[0
|
|
|
3085
3230
|
} catch {}
|
|
3086
3231
|
}
|
|
3087
3232
|
};
|
|
3233
|
+
const set = (update) => {
|
|
3234
|
+
const next = typeof update === "function" ? update(current) : update;
|
|
3235
|
+
validateStepProps(path, next);
|
|
3236
|
+
current = freezeStepProps(next);
|
|
3237
|
+
const published = current;
|
|
3238
|
+
for (const listener of Array.from(listeners))
|
|
3239
|
+
notify(listener, published);
|
|
3240
|
+
};
|
|
3088
3241
|
return Object.freeze({
|
|
3089
3242
|
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
|
-
},
|
|
3243
|
+
set,
|
|
3244
|
+
update: (patch) => set((props) => mergeStepProps(props, typeof patch === "function" ? patch(props) : patch)),
|
|
3098
3245
|
subscribe: (listener) => {
|
|
3099
3246
|
notify(listener, current);
|
|
3100
3247
|
listeners.add(listener);
|
|
@@ -3116,42 +3263,35 @@ class ActiveStep {
|
|
|
3116
3263
|
rootDocument;
|
|
3117
3264
|
initialProps;
|
|
3118
3265
|
props;
|
|
3119
|
-
behavior;
|
|
3120
3266
|
animated;
|
|
3121
|
-
allowScroll;
|
|
3122
3267
|
target = null;
|
|
3268
|
+
detached = false;
|
|
3269
|
+
direction = "advance";
|
|
3123
3270
|
constructor(definition, defaults, reportSubscriberError = () => {}, path = "steps[0]", rootDocument) {
|
|
3124
3271
|
this.definition = definition;
|
|
3125
3272
|
this.path = path;
|
|
3126
3273
|
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
|
-
});
|
|
3274
|
+
this.initialProps = freezeStepProps(mergeStepProps(defaults, definition.props));
|
|
3135
3275
|
this.props = createStepPropsStore(this.initialProps, reportSubscriberError, path);
|
|
3136
|
-
this.behavior = mergeStepBehavior(defaults.behavior, definition.behavior);
|
|
3137
3276
|
this.animated = defaults.animated;
|
|
3138
|
-
this.allowScroll = defaults.allowScroll !== false;
|
|
3139
|
-
}
|
|
3140
|
-
reset() {
|
|
3141
|
-
this.props.set(this.initialProps);
|
|
3142
3277
|
}
|
|
3143
|
-
|
|
3144
|
-
return this.props.get().
|
|
3278
|
+
allowsInteraction() {
|
|
3279
|
+
return !this.detached && this.props.get().behavior?.allowInteraction === true;
|
|
3145
3280
|
}
|
|
3146
|
-
|
|
3147
|
-
return this.props.get().
|
|
3281
|
+
autoFocuses() {
|
|
3282
|
+
return this.props.get().behavior?.autoFocus !== false;
|
|
3148
3283
|
}
|
|
3149
|
-
|
|
3150
|
-
return this.props.get().
|
|
3284
|
+
allowsScroll() {
|
|
3285
|
+
return this.props.get().behavior?.allowScroll !== false;
|
|
3151
3286
|
}
|
|
3152
3287
|
async resolveTarget(signal) {
|
|
3153
3288
|
return await resolveTargetElement(this.definition.target, { document: this.rootDocument, signal }, this.path);
|
|
3154
3289
|
}
|
|
3290
|
+
detach() {
|
|
3291
|
+
const body = (this.rootDocument ?? globalThis.document)?.body ?? null;
|
|
3292
|
+
this.detached = body !== null;
|
|
3293
|
+
return body;
|
|
3294
|
+
}
|
|
3155
3295
|
snapshot() {
|
|
3156
3296
|
return Object.freeze({
|
|
3157
3297
|
id: this.definition.id,
|
|
@@ -3231,7 +3371,7 @@ class TourRootBinding {
|
|
|
3231
3371
|
hasPopover() {
|
|
3232
3372
|
return this.popover !== null;
|
|
3233
3373
|
}
|
|
3234
|
-
|
|
3374
|
+
getDocument() {
|
|
3235
3375
|
return this.root.ownerDocument;
|
|
3236
3376
|
}
|
|
3237
3377
|
release() {
|
|
@@ -3365,11 +3505,11 @@ function attachRootBridge(tour, driver, isDisposed, onMountReleaseStart, onMount
|
|
|
3365
3505
|
return {
|
|
3366
3506
|
assertCanRun(workflow) {
|
|
3367
3507
|
if (!binding)
|
|
3368
|
-
throw new Error("Glow tour requires a connected root before
|
|
3508
|
+
throw new Error("Glow tour requires a connected root before start()");
|
|
3369
3509
|
if (workflow.steps.length > 0 && !binding.hasPopover()) {
|
|
3370
|
-
throw new Error("Glow tour requires a connected popover before
|
|
3510
|
+
throw new Error("Glow tour requires a connected popover before start()");
|
|
3371
3511
|
}
|
|
3372
|
-
return binding.
|
|
3512
|
+
return binding.getDocument();
|
|
3373
3513
|
},
|
|
3374
3514
|
release() {
|
|
3375
3515
|
binding?.release();
|
|
@@ -3459,13 +3599,10 @@ function prefixReservations(document2) {
|
|
|
3459
3599
|
var DEFAULT_TARGET_TIMEOUT = 3000;
|
|
3460
3600
|
var TARGET_LOSS_GRACE_MS = 150;
|
|
3461
3601
|
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
|
-
}
|
|
3602
|
+
function resolveStepIndex(workflow, id) {
|
|
3603
|
+
const index = workflow.steps.findIndex((step) => step.id === id);
|
|
3604
|
+
if (index === -1)
|
|
3605
|
+
throw new Error(`Workflow "${workflow.name}" has no step with id "${id}".`);
|
|
3469
3606
|
return index;
|
|
3470
3607
|
}
|
|
3471
3608
|
function normalizedError(error) {
|
|
@@ -3498,6 +3635,7 @@ class TourController {
|
|
|
3498
3635
|
stepPropsSubscriptions = [];
|
|
3499
3636
|
tourStartedAt = 0;
|
|
3500
3637
|
stepEnteredAt = 0;
|
|
3638
|
+
pendingTourStart;
|
|
3501
3639
|
commandSource = "api";
|
|
3502
3640
|
state = Object.freeze({
|
|
3503
3641
|
get: () => this.snapshot,
|
|
@@ -3523,6 +3661,7 @@ class TourController {
|
|
|
3523
3661
|
canCancel: () => this.status === "active" && this.isCancelAvailable(),
|
|
3524
3662
|
canPrevious: () => this.canNavigate("previous"),
|
|
3525
3663
|
cancel: (source) => this.cancel(source),
|
|
3664
|
+
goTo: (id) => this.goTo(id),
|
|
3526
3665
|
isAdvanceDisabled: () => !this.isPresentedAdvanceAvailable(),
|
|
3527
3666
|
isCancelDisabled: () => !this.isPresentedCancelAvailable(),
|
|
3528
3667
|
isPreviousDisabled: () => !this.isPresentedPreviousAvailable(),
|
|
@@ -3544,10 +3683,10 @@ class TourController {
|
|
|
3544
3683
|
create(name, options = {}) {
|
|
3545
3684
|
return new WorkflowBuilder(name, options);
|
|
3546
3685
|
}
|
|
3547
|
-
async
|
|
3686
|
+
async start(workflow, runOptions = {}) {
|
|
3548
3687
|
this.assertNotDisposed();
|
|
3549
3688
|
validateWorkflowOptions(workflow);
|
|
3550
|
-
const startIndex =
|
|
3689
|
+
const startIndex = runOptions.startAt === undefined ? 0 : resolveStepIndex(workflow, runOptions.startAt);
|
|
3551
3690
|
const rootDocument = this.options.assertCanRun?.(workflow) ?? undefined;
|
|
3552
3691
|
const retainedPresentation = this.capturePresentation();
|
|
3553
3692
|
const operation = this.beginOperation();
|
|
@@ -3564,6 +3703,7 @@ class TourController {
|
|
|
3564
3703
|
this.error = null;
|
|
3565
3704
|
this.commandSource = "api";
|
|
3566
3705
|
this.retainedPresentation = retainedPresentation;
|
|
3706
|
+
this.pendingTourStart = undefined;
|
|
3567
3707
|
try {
|
|
3568
3708
|
this.setStatus("starting");
|
|
3569
3709
|
this.assertCurrent(operation);
|
|
@@ -3571,16 +3711,16 @@ class TourController {
|
|
|
3571
3711
|
await workflow.options.onStart?.(startContext);
|
|
3572
3712
|
this.assertCurrent(operation);
|
|
3573
3713
|
if (isStartAborted()) {
|
|
3574
|
-
this.resetToIdle();
|
|
3714
|
+
await this.resetToIdle(operation);
|
|
3575
3715
|
return;
|
|
3576
3716
|
}
|
|
3577
3717
|
this.tourStartedAt = Date.now();
|
|
3578
|
-
this.
|
|
3718
|
+
this.pendingTourStart = this.steps[startIndex] ?? null;
|
|
3579
3719
|
if (this.steps.length === 0) {
|
|
3580
3720
|
await this.finish(operation);
|
|
3581
3721
|
return;
|
|
3582
3722
|
}
|
|
3583
|
-
await this.
|
|
3723
|
+
await this.navigate(startIndex, "advance", operation);
|
|
3584
3724
|
} catch (error) {
|
|
3585
3725
|
await this.handleFailure(error, operation);
|
|
3586
3726
|
}
|
|
@@ -3593,15 +3733,16 @@ class TourController {
|
|
|
3593
3733
|
this.assertNotDisposed();
|
|
3594
3734
|
await this.transitionFromPublic("previous", undefined, source);
|
|
3595
3735
|
}
|
|
3596
|
-
async
|
|
3736
|
+
async goTo(id) {
|
|
3597
3737
|
this.assertNotDisposed();
|
|
3598
|
-
|
|
3738
|
+
const workflow = this.workflow;
|
|
3739
|
+
if (!workflow || this.status === "starting" || this.status === "transitioning")
|
|
3599
3740
|
return;
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3741
|
+
const index = resolveStepIndex(workflow, id);
|
|
3742
|
+
await this.transitionFromPublic(this.directionTo(index), index, "api");
|
|
3743
|
+
}
|
|
3744
|
+
directionTo(index) {
|
|
3745
|
+
return index > this.index ? "advance" : "previous";
|
|
3605
3746
|
}
|
|
3606
3747
|
async cancel(source = "api") {
|
|
3607
3748
|
this.assertNotDisposed();
|
|
@@ -3627,6 +3768,7 @@ class TourController {
|
|
|
3627
3768
|
this.direction = "advance";
|
|
3628
3769
|
this.error = null;
|
|
3629
3770
|
this.retainedPresentation = null;
|
|
3771
|
+
this.pendingTourStart = undefined;
|
|
3630
3772
|
this.status = "disposed";
|
|
3631
3773
|
this.publish(true);
|
|
3632
3774
|
this.stateListeners.clear();
|
|
@@ -3647,29 +3789,57 @@ class TourController {
|
|
|
3647
3789
|
this.direction = "advance";
|
|
3648
3790
|
this.error = null;
|
|
3649
3791
|
this.retainedPresentation = null;
|
|
3792
|
+
this.pendingTourStart = undefined;
|
|
3650
3793
|
this.status = "idle";
|
|
3651
3794
|
}
|
|
3652
3795
|
completeMountRelease() {
|
|
3653
3796
|
if (!this.disposed)
|
|
3654
3797
|
this.publish();
|
|
3655
3798
|
}
|
|
3656
|
-
async
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3799
|
+
async navigate(index, direction, operation, lostStep) {
|
|
3800
|
+
const from = this.currentStep();
|
|
3801
|
+
if (this.status !== "transitioning")
|
|
3802
|
+
this.setStatus("transitioning");
|
|
3660
3803
|
this.assertCurrent(operation);
|
|
3661
|
-
const
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3804
|
+
const skipped = [];
|
|
3805
|
+
let step = this.steps[index];
|
|
3806
|
+
let target = null;
|
|
3807
|
+
while (step) {
|
|
3808
|
+
target = await this.resolveTarget(step, operation);
|
|
3809
|
+
this.assertCurrent(operation);
|
|
3810
|
+
if (target)
|
|
3811
|
+
break;
|
|
3812
|
+
skipped.push(step);
|
|
3813
|
+
index += direction === "advance" ? 1 : -1;
|
|
3814
|
+
step = this.steps[index];
|
|
3815
|
+
}
|
|
3816
|
+
if (!step || !target) {
|
|
3817
|
+
if (index >= this.steps.length || lostStep)
|
|
3818
|
+
this.direction = direction;
|
|
3819
|
+
if (index >= this.steps.length)
|
|
3820
|
+
await this.finish(operation, skipped);
|
|
3821
|
+
else if (!lostStep)
|
|
3822
|
+
this.setStatus("active");
|
|
3823
|
+
else
|
|
3824
|
+
await this.cancelCurrent(operation);
|
|
3670
3825
|
return;
|
|
3671
3826
|
}
|
|
3672
3827
|
step.target = target;
|
|
3828
|
+
step.direction = direction;
|
|
3829
|
+
if (await this.runStepHook(step.definition.beforeEnter, step, operation, direction)) {
|
|
3830
|
+
if (lostStep)
|
|
3831
|
+
throw this.missingTargetError(lostStep);
|
|
3832
|
+
if (from)
|
|
3833
|
+
this.setStatus("active");
|
|
3834
|
+
else
|
|
3835
|
+
await this.resetToIdle(operation);
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
this.direction = direction;
|
|
3839
|
+
this.flushTourStart();
|
|
3840
|
+
for (const skippedStep of skipped)
|
|
3841
|
+
this.emit("step:skip", skippedStep, 0);
|
|
3842
|
+
this.emitStepLeave(from);
|
|
3673
3843
|
let committed = false;
|
|
3674
3844
|
const commitStep = () => {
|
|
3675
3845
|
this.assertCurrent(operation);
|
|
@@ -3708,29 +3878,24 @@ class TourController {
|
|
|
3708
3878
|
return;
|
|
3709
3879
|
this.setStatus("transitioning");
|
|
3710
3880
|
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");
|
|
3881
|
+
if (await this.runStepHook(step.definition.beforeLeave, step, operation, direction)) {
|
|
3882
|
+
this.setStatus("active");
|
|
3731
3883
|
return;
|
|
3732
3884
|
}
|
|
3733
|
-
await this.
|
|
3885
|
+
await this.navigate(destination ?? this.index + (direction === "advance" ? 1 : -1), direction, operation);
|
|
3886
|
+
}
|
|
3887
|
+
async runStepHook(hook, step, operation, direction) {
|
|
3888
|
+
if (!hook)
|
|
3889
|
+
return false;
|
|
3890
|
+
let aborted = false;
|
|
3891
|
+
await hook(Object.freeze({
|
|
3892
|
+
...this.createStepHookContext(step, operation, direction),
|
|
3893
|
+
abort: () => {
|
|
3894
|
+
aborted = true;
|
|
3895
|
+
}
|
|
3896
|
+
}));
|
|
3897
|
+
this.assertCurrent(operation);
|
|
3898
|
+
return aborted;
|
|
3734
3899
|
}
|
|
3735
3900
|
async runActions(operation) {
|
|
3736
3901
|
const step = this.currentStep();
|
|
@@ -3754,9 +3919,11 @@ class TourController {
|
|
|
3754
3919
|
}
|
|
3755
3920
|
async resolveTarget(step, operation) {
|
|
3756
3921
|
const signal = this.signalFor(operation);
|
|
3757
|
-
const
|
|
3758
|
-
const
|
|
3922
|
+
const missingTarget = step.props.get().behavior?.missingTarget;
|
|
3923
|
+
const strategy = missingTarget?.strategy ?? "error";
|
|
3924
|
+
const timeout = missingTarget?.timeout ?? DEFAULT_TARGET_TIMEOUT;
|
|
3759
3925
|
const startedAt = Date.now();
|
|
3926
|
+
step.detached = false;
|
|
3760
3927
|
while (true) {
|
|
3761
3928
|
const target = await step.resolveTarget(signal);
|
|
3762
3929
|
this.assertCurrent(operation);
|
|
@@ -3764,6 +3931,9 @@ class TourController {
|
|
|
3764
3931
|
return target;
|
|
3765
3932
|
if (strategy === "skip")
|
|
3766
3933
|
return null;
|
|
3934
|
+
const body = strategy === "detached" && step.detach();
|
|
3935
|
+
if (body)
|
|
3936
|
+
return body;
|
|
3767
3937
|
if (strategy !== "wait" || Date.now() - startedAt >= timeout) {
|
|
3768
3938
|
throw this.missingTargetError(step);
|
|
3769
3939
|
}
|
|
@@ -3796,28 +3966,20 @@ class TourController {
|
|
|
3796
3966
|
const operation = this.beginOperation();
|
|
3797
3967
|
try {
|
|
3798
3968
|
this.assertCurrent(operation);
|
|
3799
|
-
|
|
3969
|
+
let recovered = await this.pollForTarget(step, operation, TARGET_LOSS_GRACE_MS);
|
|
3800
3970
|
this.assertCurrent(operation);
|
|
3801
|
-
if (
|
|
3802
|
-
step.
|
|
3803
|
-
|
|
3971
|
+
if (!recovered) {
|
|
3972
|
+
const strategy = step.props.get().behavior?.missingTarget?.strategy ?? "error";
|
|
3973
|
+
if (strategy === "skip") {
|
|
3974
|
+
await this.navigate(index + (direction === "advance" ? 1 : -1), direction, operation, step);
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3977
|
+
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
3978
|
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;
|
|
3979
|
+
if (!recovered)
|
|
3980
|
+
throw this.missingTargetError(step);
|
|
3812
3981
|
}
|
|
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;
|
|
3982
|
+
step.target = recovered;
|
|
3821
3983
|
await this.driver.retarget(step, this.signalFor(operation));
|
|
3822
3984
|
this.assertCurrent(operation);
|
|
3823
3985
|
this.publish();
|
|
@@ -3830,34 +3992,10 @@ class TourController {
|
|
|
3830
3992
|
this.recoveringTarget = null;
|
|
3831
3993
|
}
|
|
3832
3994
|
}
|
|
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
3995
|
missingTargetError(step) {
|
|
3858
3996
|
return new Error(`Missing target at ${step.path}: ${String(step.definition.target)}`);
|
|
3859
3997
|
}
|
|
3860
|
-
async finish(operation) {
|
|
3998
|
+
async finish(operation, skipped = []) {
|
|
3861
3999
|
this.assertCurrent(operation);
|
|
3862
4000
|
const step = this.currentStep();
|
|
3863
4001
|
const { context, isAborted } = this.createLifecycleHookContext(step);
|
|
@@ -3867,9 +4005,12 @@ class TourController {
|
|
|
3867
4005
|
if (step)
|
|
3868
4006
|
this.setStatus("active");
|
|
3869
4007
|
else
|
|
3870
|
-
this.resetToIdle();
|
|
4008
|
+
await this.resetToIdle(operation);
|
|
3871
4009
|
return;
|
|
3872
4010
|
}
|
|
4011
|
+
this.flushTourStart();
|
|
4012
|
+
for (const skippedStep of skipped)
|
|
4013
|
+
this.emit("step:skip", skippedStep, 0);
|
|
3873
4014
|
this.emitStepLeave(step);
|
|
3874
4015
|
await this.driver.clear(this.signalFor(operation));
|
|
3875
4016
|
this.assertCurrent(operation);
|
|
@@ -3880,10 +4021,6 @@ class TourController {
|
|
|
3880
4021
|
}
|
|
3881
4022
|
async cancelCurrent(operation) {
|
|
3882
4023
|
const step = this.currentStep();
|
|
3883
|
-
if (step?.definition.cancelAction) {
|
|
3884
|
-
await step.definition.cancelAction(this.createBeforeActionStepContext(step));
|
|
3885
|
-
}
|
|
3886
|
-
this.assertCurrent(operation);
|
|
3887
4024
|
const { context, isAborted } = this.createLifecycleHookContext(step);
|
|
3888
4025
|
await this.workflow?.options.onCancel?.(context);
|
|
3889
4026
|
this.assertCurrent(operation);
|
|
@@ -3891,6 +4028,7 @@ class TourController {
|
|
|
3891
4028
|
this.setStatus("active");
|
|
3892
4029
|
return;
|
|
3893
4030
|
}
|
|
4031
|
+
this.flushTourStart();
|
|
3894
4032
|
this.emitStepLeave(step);
|
|
3895
4033
|
await this.driver.clear(this.signalFor(operation));
|
|
3896
4034
|
this.assertCurrent(operation);
|
|
@@ -3909,12 +4047,17 @@ class TourController {
|
|
|
3909
4047
|
});
|
|
3910
4048
|
return { context, isAborted: () => aborted };
|
|
3911
4049
|
}
|
|
3912
|
-
resetToIdle() {
|
|
4050
|
+
async resetToIdle(operation) {
|
|
4051
|
+
if (this.retainedPresentation) {
|
|
4052
|
+
await this.driver.clear(this.signalFor(operation));
|
|
4053
|
+
this.assertCurrent(operation);
|
|
4054
|
+
}
|
|
3913
4055
|
this.workflow = null;
|
|
3914
4056
|
this.releaseStepPropsSubscriptions();
|
|
3915
4057
|
this.steps = [];
|
|
3916
4058
|
this.index = -1;
|
|
3917
4059
|
this.retainedPresentation = null;
|
|
4060
|
+
this.pendingTourStart = undefined;
|
|
3918
4061
|
this.setStatus("idle");
|
|
3919
4062
|
}
|
|
3920
4063
|
async handleFailure(reason, operation) {
|
|
@@ -3924,6 +4067,7 @@ class TourController {
|
|
|
3924
4067
|
this.error = error;
|
|
3925
4068
|
this.retainedPresentation = null;
|
|
3926
4069
|
this.setStatus("error");
|
|
4070
|
+
this.flushTourStart();
|
|
3927
4071
|
this.emit("tour:error", this.currentStep(), Date.now() - this.tourStartedAt, error);
|
|
3928
4072
|
if (!this.isCurrent(operation))
|
|
3929
4073
|
throw error;
|
|
@@ -3938,9 +4082,8 @@ class TourController {
|
|
|
3938
4082
|
return this.operationToken;
|
|
3939
4083
|
}
|
|
3940
4084
|
createStepContext(step, operation, onControl) {
|
|
3941
|
-
if (!step.target)
|
|
3942
|
-
throw new Error("Cannot create a step context without a target");
|
|
3943
4085
|
return Object.freeze({
|
|
4086
|
+
...this.createStepHookContext(step, operation, step.direction),
|
|
3944
4087
|
advance: async () => {
|
|
3945
4088
|
onControl?.();
|
|
3946
4089
|
this.assertCurrent(operation);
|
|
@@ -3952,21 +4095,29 @@ class TourController {
|
|
|
3952
4095
|
if (this.canCancel())
|
|
3953
4096
|
await this.cancelCurrent(operation);
|
|
3954
4097
|
},
|
|
4098
|
+
goTo: async (id) => {
|
|
4099
|
+
onControl?.();
|
|
4100
|
+
this.assertCurrent(operation);
|
|
4101
|
+
if (!this.workflow)
|
|
4102
|
+
return;
|
|
4103
|
+
const index = resolveStepIndex(this.workflow, id);
|
|
4104
|
+
await this.transition(this.directionTo(index), operation, index);
|
|
4105
|
+
},
|
|
3955
4106
|
previous: async () => {
|
|
3956
4107
|
onControl?.();
|
|
3957
4108
|
this.assertCurrent(operation);
|
|
3958
4109
|
await this.transition("previous", operation);
|
|
3959
|
-
}
|
|
3960
|
-
props: step.props,
|
|
3961
|
-
signal: this.signalFor(operation),
|
|
3962
|
-
target: step.target
|
|
4110
|
+
}
|
|
3963
4111
|
});
|
|
3964
4112
|
}
|
|
3965
|
-
|
|
4113
|
+
createStepHookContext(step, operation, direction) {
|
|
3966
4114
|
if (!step.target)
|
|
3967
4115
|
throw new Error("Cannot create a step context without a target");
|
|
3968
4116
|
return Object.freeze({
|
|
3969
|
-
|
|
4117
|
+
direction,
|
|
4118
|
+
initialProps: step.initialProps,
|
|
4119
|
+
props: step.props,
|
|
4120
|
+
signal: this.signalFor(operation),
|
|
3970
4121
|
target: step.target
|
|
3971
4122
|
});
|
|
3972
4123
|
}
|
|
@@ -4009,12 +4160,10 @@ class TourController {
|
|
|
4009
4160
|
return direction === "advance" || this.index > 0;
|
|
4010
4161
|
}
|
|
4011
4162
|
isAdvanceButtonAvailable() {
|
|
4012
|
-
|
|
4013
|
-
return props !== undefined && props.popover?.disableAdvanceButton !== true;
|
|
4163
|
+
return isControlAvailable(this.currentStep()?.props.get(), "advance");
|
|
4014
4164
|
}
|
|
4015
4165
|
isPreviousButtonAvailable() {
|
|
4016
|
-
|
|
4017
|
-
return props !== undefined && props.popover?.disablePreviousButton !== true && this.index > 0;
|
|
4166
|
+
return isControlAvailable(this.currentStep()?.props.get(), "previous") && this.index > 0;
|
|
4018
4167
|
}
|
|
4019
4168
|
isCancelAvailable() {
|
|
4020
4169
|
return this.currentStep() !== null && this.canCancel();
|
|
@@ -4029,7 +4178,7 @@ class TourController {
|
|
|
4029
4178
|
return this.currentStep() ? this.isCancelAvailable() : this.retainedPresentation?.canCancel ?? false;
|
|
4030
4179
|
}
|
|
4031
4180
|
canCancel() {
|
|
4032
|
-
return
|
|
4181
|
+
return this.status !== "finished" && this.status !== "cancelled" && this.status !== "error";
|
|
4033
4182
|
}
|
|
4034
4183
|
setStatus(status) {
|
|
4035
4184
|
this.status = status;
|
|
@@ -4087,6 +4236,13 @@ class TourController {
|
|
|
4087
4236
|
this.reportSubscriberError(error);
|
|
4088
4237
|
}
|
|
4089
4238
|
}
|
|
4239
|
+
flushTourStart() {
|
|
4240
|
+
const step = this.pendingTourStart;
|
|
4241
|
+
if (step === undefined)
|
|
4242
|
+
return;
|
|
4243
|
+
this.pendingTourStart = undefined;
|
|
4244
|
+
this.emit("tour:start", step, 0);
|
|
4245
|
+
}
|
|
4090
4246
|
emitStepLeave(step) {
|
|
4091
4247
|
if (!step || !this.hasEventListeners())
|
|
4092
4248
|
return;
|
|
@@ -4169,9 +4325,9 @@ function createGlowTour(options = {}) {
|
|
|
4169
4325
|
cancel: () => controller.cancel(),
|
|
4170
4326
|
create: (name, options2) => controller.create(name, options2),
|
|
4171
4327
|
dispose: () => controller.dispose(),
|
|
4172
|
-
|
|
4328
|
+
goTo: (id) => controller.goTo(id),
|
|
4173
4329
|
previous: () => controller.previous(),
|
|
4174
|
-
|
|
4330
|
+
start: (workflow, runOptions) => controller.start(workflow, runOptions),
|
|
4175
4331
|
state: controller.state
|
|
4176
4332
|
};
|
|
4177
4333
|
bridge = attachRootBridge(tour, driver, () => controller.isDisposed(), () => controller.beginMountRelease(), () => controller.completeMountRelease());
|