@glowhop/core-tour 1.3.1 → 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/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
- eventHandlers: freezeRecord(draft.eventHandlers.map((handler) => freezeRecord({ ...handler }))),
114
- advanceAction: draft.advanceAction,
115
- previousAction: draft.previousAction,
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
- eventHandlers: [...definition.eventHandlers]
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
- eventHandlers: [],
261
- advanceAction: null,
262
- previousAction: null,
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
- beforeAdvance(callback) {
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.previousAction = callback;
368
+ this.draft.beforeEnter = callback;
375
369
  return this;
376
370
  }
377
- beforeCancel(callback) {
371
+ beforeLeave(callback) {
378
372
  this.assertActive();
379
- this.draft.cancelAction = callback;
373
+ this.draft.beforeLeave = callback;
380
374
  return this;
381
375
  }
382
376
  onTargetEvent(eventOrEvents, callback) {
@@ -387,7 +381,7 @@ class WorkflowStepBuilder {
387
381
  for (const event of events) {
388
382
  if (event.length === 0)
389
383
  throw new TypeError("event name must not be empty");
390
- this.draft.eventHandlers.push({
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?.disabled === true;
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
- this.mutationLease.setAttribute("aria-hidden", POPOVER_IDLE_ATTRIBUTES["aria-hidden"]);
1532
- this.mutationLease.setAttribute("inert", POPOVER_IDLE_ATTRIBUTES.inert);
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?.disableAutoStyles,
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
- this._applyHiddenState();
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
- _applyHiddenState() {
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.initialFocus = isHTMLElement(this.document.activeElement, scope.popover) ? this.document.activeElement : null;
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.autoFocus !== false || !isNode(currentFocus, scope.popover) || !this.isAllowed(currentFocus)) {
1760
- this.focusFallback();
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
- deactivate() {
1775
- if (!this.active) {
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
- const focusToRestore = this.initialFocus;
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 selector = direction === "advance" ? "[data-glow-tour-advance-trigger]" : "[data-glow-tour-previous-trigger]";
1829
- return candidates.find((candidate) => candidate.matches(selector)) ?? null;
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
- pendingKeyboardCommand = null;
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 removeTransitionKeydown = () => {};
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
- if (replaceVisiblePopover) {
2009
- const listener = (event) => this.queueTransitionKeydown(event, step, generation);
2010
- const currentWindow = this.getWindow();
2011
- if (typeof currentWindow?.addEventListener === "function") {
2012
- currentWindow.addEventListener("keydown", listener);
2013
- removeTransitionKeydown = () => currentWindow.removeEventListener("keydown", listener);
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.syncModality(step.behavior?.allowInteraction === true);
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.getBoundingClientRect();
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
- removeTransitionKeydown();
2035
- removeTransitionKeydown = () => {};
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
- removeTransitionKeydown();
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.deactivate();
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.behavior?.allowInteraction === true;
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.popover?.initializeProps();
2142
- this.popover?.setAnimationOptions(animationOptions(step, step.popover, this.popover.getElement()));
2143
- const popover = this.popover?.getElement();
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.isPointerEnabled(step) ? this.pointer?.moveToTarget(targetRect, step, true, popoverPlacement) ?? Promise.resolve() : this.pointer?.disappear() ?? Promise.resolve()
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.flushPendingKeyboardCommand(step, generation);
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
- for (const handler of step.definition.eventHandlers) {
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.getBoundingClientRect();
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
- this.pointer?.syncVisibility(this.isPointerEnabled(step), targetRect, step, popoverPlacement);
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.behavior?.allowInteraction !== true) {
2611
+ if (event.key === "Tab" && !step.allowsInteraction()) {
2393
2612
  this.loopFocus(event);
2394
2613
  return;
2395
2614
  }
2396
- const shortcuts = step.popover?.keyboardShortcuts;
2397
- if ((shortcuts?.cancel ?? DEFAULT_SHORTCUTS.cancel).includes(event.key) && this.canCommand("cancel", step)) {
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
- if ((shortcuts?.advance ?? DEFAULT_SHORTCUTS.advance).includes(event.key) && this.canCommand("advance", step)) {
2405
- event.preventDefault();
2406
- this.command("advance", "keyboard");
2407
- } else if ((shortcuts?.previous ?? DEFAULT_SHORTCUTS.previous).includes(event.key) && this.canCommand("previous", step)) {
2408
- event.preventDefault();
2409
- this.command("previous", "keyboard");
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.behavior?.allowInteraction === true)
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
- const shortcuts = step.popover?.keyboardShortcuts;
2433
- let command = null;
2434
- if ((shortcuts?.cancel ?? DEFAULT_SHORTCUTS.cancel).includes(event.key))
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.pendingKeyboardCommand ??= { command, generation };
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
- flushPendingKeyboardCommand(step, generation) {
2448
- const pending = this.pendingKeyboardCommand;
2671
+ flushPendingCommand(step, generation) {
2672
+ const pending = this.pendingCommand;
2449
2673
  if (!pending || pending.generation !== generation)
2450
2674
  return;
2451
- this.pendingKeyboardCommand = null;
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, "keyboard");
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.behavior?.disableAutoFocus !== true;
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.behavior?.allowInteraction === true,
2489
- autoFocus: autoFocus && !deferFocus,
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.allowScroll) {
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 advance of this.findTriggers("advance")) {
2504
- syncKeyShortcuts(advance, step.popover?.keyboardShortcuts?.advance ?? DEFAULT_SHORTCUTS.advance);
2505
- }
2506
- for (const previous of this.findTriggers("previous")) {
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().popover?.disableAdvanceButton === true);
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().popover?.disablePreviousButton === true);
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 (this.commands?.canAdvance?.() ?? true) && step.props.get().popover?.disableAdvanceButton !== true;
2613
- }
2614
- if (command === "previous") {
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.behavior?.allowInteraction === true && step.indicator?.disabled !== true;
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.behavior?.allowInteraction === true;
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
- if (allowed)
2682
- popover.removeAttribute("aria-modal");
2683
- else
2684
- popover.setAttribute("aria-modal", "true");
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.applyInteractionLock(step, false);
2950
+ this.applyInteraction(step, target);
2704
2951
  this.attachTargetResources(step, target, this.generation, signal);
2705
- const popover = this.popover?.getElement();
2706
- if (isHTMLElement(popover, this.root ?? popover)) {
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.getBoundingClientRect();
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.pendingKeyboardCommand = null;
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?.disableAutoScroll)
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}.targetTimeout`, behavior?.targetTimeout);
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
- const path = `steps[${index}]`;
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: (update) => {
3091
- const next = typeof update === "function" ? update(current) : update;
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
- get overlay() {
3144
- return this.props.get().overlay;
3278
+ allowsInteraction() {
3279
+ return !this.detached && this.props.get().behavior?.allowInteraction === true;
3145
3280
  }
3146
- get popover() {
3147
- return this.props.get().popover;
3281
+ autoFocuses() {
3282
+ return this.props.get().behavior?.autoFocus !== false;
3148
3283
  }
3149
- get indicator() {
3150
- return this.props.get().indicator;
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
- get document() {
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 run()");
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 run()");
3510
+ throw new Error("Glow tour requires a connected popover before start()");
3371
3511
  }
3372
- return binding.document;
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 resolveStartIndex(workflow, startAt) {
3463
- if (startAt === undefined)
3464
- return 0;
3465
- const index = workflow.steps.findIndex((step) => step.id === startAt);
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 run(workflow, runOptions = {}) {
3686
+ async start(workflow, runOptions = {}) {
3548
3687
  this.assertNotDisposed();
3549
3688
  validateWorkflowOptions(workflow);
3550
- const startIndex = resolveStartIndex(workflow, runOptions.startAt);
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.emit("tour:start", this.steps[startIndex] ?? null, 0);
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.enter(startIndex, "advance", operation);
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 goToStep(index) {
3736
+ async goTo(id) {
3597
3737
  this.assertNotDisposed();
3598
- if (this.status === "starting" || this.status === "transitioning")
3738
+ const workflow = this.workflow;
3739
+ if (!workflow || this.status === "starting" || this.status === "transitioning")
3599
3740
  return;
3600
- if (index < 0 || index >= this.steps.length) {
3601
- throw new Error(`Step index ${index} is out of bounds`);
3602
- }
3603
- const direction = index > this.index ? "advance" : "previous";
3604
- await this.transitionFromPublic(direction, index, "api");
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 enter(index, direction, operation) {
3657
- this.direction = direction;
3658
- this.emitStepLeave(this.currentStep());
3659
- this.setStatus("transitioning");
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 step = this.steps[index];
3662
- if (!step)
3663
- throw new Error(`Step index ${index} is out of bounds`);
3664
- if (step.definition.resetPropsOnEnter !== false)
3665
- step.reset();
3666
- const target = await this.resolveTarget(step, operation);
3667
- this.assertCurrent(operation);
3668
- if (!target) {
3669
- await this.advancePastMissingTarget(index, direction, operation);
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
- const hook = direction === "advance" ? step.definition.advanceAction : step.definition.previousAction;
3712
- await hook?.(this.createBeforeActionStepContext(step));
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.enter(this.index - 1, direction, operation);
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 strategy = step.behavior?.missingTargetStrategy ?? "error";
3758
- const timeout = step.behavior?.targetTimeout ?? DEFAULT_TARGET_TIMEOUT;
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
- const recoveredDuringGrace = await this.pollForTarget(step, operation, TARGET_LOSS_GRACE_MS);
3969
+ let recovered = await this.pollForTarget(step, operation, TARGET_LOSS_GRACE_MS);
3800
3970
  this.assertCurrent(operation);
3801
- if (recoveredDuringGrace) {
3802
- step.target = recoveredDuringGrace;
3803
- await this.driver.retarget(step, this.signalFor(operation));
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
- this.publish();
3806
- return;
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
- if (strategy !== "wait")
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
- createBeforeActionStepContext(step) {
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
- ...step.props.get(),
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
- const props = this.currentStep()?.props.get();
4013
- return props !== undefined && props.popover?.disableAdvanceButton !== true;
4163
+ return isControlAvailable(this.currentStep()?.props.get(), "advance");
4014
4164
  }
4015
4165
  isPreviousButtonAvailable() {
4016
- const props = this.currentStep()?.props.get();
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 (this.workflow?.options.cancellable ?? true) && this.status !== "finished" && this.status !== "cancelled" && this.status !== "error";
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
- goToStep: (index) => controller.goToStep(index),
4328
+ goTo: (id) => controller.goTo(id),
4173
4329
  previous: () => controller.previous(),
4174
- run: (workflow, runOptions) => controller.run(workflow, runOptions),
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());