@flowsterix/react 0.11.0 → 0.12.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.
Files changed (33) hide show
  1. package/dist/chunk-D5LQLRSU.cjs +289 -0
  2. package/dist/chunk-FCOKCGV3.cjs +21 -0
  3. package/dist/chunk-HPVLOLCD.cjs +1 -0
  4. package/dist/components/TourPopoverPortal.d.ts +1 -1
  5. package/dist/components/TourPopoverPortal.d.ts.map +1 -1
  6. package/dist/context.d.ts +6 -0
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/devtools/DevToolsProvider.d.ts.map +1 -1
  9. package/dist/devtools/components/FlowEditModal.d.ts.map +1 -1
  10. package/dist/devtools/components/FlowItem.d.ts.map +1 -1
  11. package/dist/devtools/components/GrabberOverlay.d.ts +1 -0
  12. package/dist/devtools/components/GrabberOverlay.d.ts.map +1 -1
  13. package/dist/devtools/components/StepItem.d.ts.map +1 -1
  14. package/dist/devtools/components/StepList.d.ts.map +1 -1
  15. package/dist/devtools/components/TabNav.d.ts.map +1 -1
  16. package/dist/devtools/components/Toolbar.d.ts.map +1 -1
  17. package/dist/devtools/globalBridge.d.ts +24 -0
  18. package/dist/devtools/globalBridge.d.ts.map +1 -0
  19. package/dist/devtools/hooks/useFlowsData.d.ts.map +1 -1
  20. package/dist/devtools/index.cjs +849 -577
  21. package/dist/devtools/index.mjs +605 -281
  22. package/dist/devtools/motion.d.ts +64 -0
  23. package/dist/devtools/motion.d.ts.map +1 -0
  24. package/dist/hooks/useHiddenTargetFallback.d.ts.map +1 -1
  25. package/dist/hooks/useTourOverlay.d.ts.map +1 -1
  26. package/dist/index.cjs +701 -927
  27. package/dist/index.mjs +116 -42
  28. package/dist/router/index.cjs +13 -202
  29. package/dist/router/nextAppRouterAdapter.cjs +11 -200
  30. package/dist/router/nextPagesRouterAdapter.cjs +10 -199
  31. package/dist/router/reactRouterAdapter.cjs +10 -199
  32. package/dist/router/tanstackRouterAdapter.cjs +22 -214
  33. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -335,6 +335,7 @@ var usePreferredAnimationAdapter = (options) => {
335
335
 
336
336
  // src/context.tsx
337
337
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
338
+ var DEVTOOLS_BRIDGE_KEY = "__FLOWSTERIX_DEVTOOLS_BRIDGE__";
338
339
  var TourContext = createContext4(void 0);
339
340
  var DEFAULT_STORAGE_PREFIX = "tour";
340
341
  var useFlowMap = (flows) => {
@@ -745,6 +746,48 @@ var TourProvider = ({
745
746
  pendingResumeRef.current.delete(activeFlowId);
746
747
  void runResumeHooks(definition, state, resumeStrategy);
747
748
  }, [activeFlowId, flowMap, resolveResumeStrategy, runResumeHooks, state]);
749
+ const resolvedStorageAdapter = useMemo3(() => {
750
+ if (storageAdapter) return storageAdapter;
751
+ return fallbackStorageRef.current ?? null;
752
+ }, [storageAdapter]);
753
+ const getStorageKey = useCallback2(
754
+ (flowId) => storageNamespace ? `${storageNamespace}:${flowId}` : `${DEFAULT_STORAGE_PREFIX}:${flowId}`,
755
+ [storageNamespace]
756
+ );
757
+ const getFlowState = useCallback2(
758
+ async (flowId) => {
759
+ if (!resolvedStorageAdapter) return null;
760
+ const key = getStorageKey(flowId);
761
+ const snapshot = await resolveMaybePromise(resolvedStorageAdapter.get(key));
762
+ if (!snapshot) return null;
763
+ return snapshot.value;
764
+ },
765
+ [resolvedStorageAdapter, getStorageKey]
766
+ );
767
+ const deleteFlowStorage = useCallback2(
768
+ async (flowId) => {
769
+ if (!resolvedStorageAdapter) return;
770
+ const key = getStorageKey(flowId);
771
+ await resolveMaybePromise(resolvedStorageAdapter.remove(key));
772
+ },
773
+ [resolvedStorageAdapter, getStorageKey]
774
+ );
775
+ const updateFlowStorage = useCallback2(
776
+ async (flowId, newState) => {
777
+ if (!resolvedStorageAdapter) return;
778
+ const key = getStorageKey(flowId);
779
+ const definition = flowMap.get(flowId);
780
+ if (!definition) return;
781
+ await resolveMaybePromise(
782
+ resolvedStorageAdapter.set(key, {
783
+ version: newState.version,
784
+ value: newState,
785
+ updatedAt: Date.now()
786
+ })
787
+ );
788
+ },
789
+ [resolvedStorageAdapter, getStorageKey, flowMap]
790
+ );
748
791
  const contextValue = useMemo3(
749
792
  () => ({
750
793
  flows: flowMap,
@@ -768,7 +811,10 @@ var TourProvider = ({
768
811
  delayInfo,
769
812
  setDelayInfo,
770
813
  backdropInteraction: backdropInteractionProp,
771
- lockBodyScroll: lockBodyScrollProp
814
+ lockBodyScroll: lockBodyScrollProp,
815
+ getFlowState,
816
+ deleteFlowStorage,
817
+ updateFlowStorage
772
818
  }),
773
819
  [
774
820
  activeFlowId,
@@ -792,7 +838,10 @@ var TourProvider = ({
792
838
  state,
793
839
  toggleDebug,
794
840
  backdropInteractionProp,
795
- lockBodyScrollProp
841
+ lockBodyScrollProp,
842
+ getFlowState,
843
+ deleteFlowStorage,
844
+ updateFlowStorage
796
845
  ]
797
846
  );
798
847
  const resolvedAnimationAdapter = usePreferredAnimationAdapter({
@@ -800,12 +849,7 @@ var TourProvider = ({
800
849
  reducedMotionAdapter,
801
850
  enabled: autoDetectReducedMotion
802
851
  });
803
- const resolvedStorageAdapter = useMemo3(() => {
804
- if (storageAdapter) return storageAdapter;
805
- return fallbackStorageRef.current ?? null;
806
- }, [storageAdapter]);
807
852
  const devToolsContextValue = useMemo3(() => {
808
- const getStorageKey = (flowId) => storageNamespace ? `${storageNamespace}:${flowId}` : `${DEFAULT_STORAGE_PREFIX}:${flowId}`;
809
853
  return {
810
854
  flows: flowMap,
811
855
  activeFlowId,
@@ -817,41 +861,57 @@ var TourProvider = ({
817
861
  storeRef.current.cancel();
818
862
  }
819
863
  },
820
- deleteFlowStorage: async (flowId) => {
821
- if (!resolvedStorageAdapter) return;
822
- const key = getStorageKey(flowId);
823
- await resolveMaybePromise(resolvedStorageAdapter.remove(key));
824
- },
825
- updateFlowStorage: async (flowId, newState) => {
826
- if (!resolvedStorageAdapter) return;
827
- const key = getStorageKey(flowId);
828
- const definition = flowMap.get(flowId);
829
- if (!definition) return;
830
- await resolveMaybePromise(
831
- resolvedStorageAdapter.set(key, {
832
- version: newState.version,
833
- value: newState,
834
- updatedAt: Date.now()
835
- })
836
- );
837
- },
838
- getFlowState: async (flowId) => {
839
- if (!resolvedStorageAdapter) return null;
840
- const key = getStorageKey(flowId);
841
- const snapshot = await resolveMaybePromise(
842
- resolvedStorageAdapter.get(key)
843
- );
844
- if (!snapshot) return null;
845
- return snapshot.value;
846
- }
864
+ deleteFlowStorage,
865
+ updateFlowStorage,
866
+ getFlowState
847
867
  };
848
868
  }, [
849
869
  flowMap,
850
870
  activeFlowId,
851
871
  state,
852
872
  resolvedStorageAdapter,
853
- storageNamespace
873
+ storageNamespace,
874
+ deleteFlowStorage,
875
+ updateFlowStorage,
876
+ getFlowState
854
877
  ]);
878
+ useEffect3(() => {
879
+ if (typeof window === "undefined") return;
880
+ const bridgeValue = {
881
+ flows: flowMap,
882
+ activeFlowId,
883
+ state,
884
+ cancel: () => {
885
+ if (storeRef.current) {
886
+ storeRef.current.cancel();
887
+ }
888
+ },
889
+ getFlowState,
890
+ deleteFlowStorage,
891
+ updateFlowStorage
892
+ };
893
+ const w = window;
894
+ if (!w[DEVTOOLS_BRIDGE_KEY]) {
895
+ w[DEVTOOLS_BRIDGE_KEY] = { value: null, listeners: /* @__PURE__ */ new Set() };
896
+ }
897
+ const bridge = w[DEVTOOLS_BRIDGE_KEY];
898
+ bridge.value = bridgeValue;
899
+ for (const listener of bridge.listeners) {
900
+ listener(bridgeValue);
901
+ }
902
+ }, [flowMap, activeFlowId, state, getFlowState, deleteFlowStorage, updateFlowStorage]);
903
+ useEffect3(() => {
904
+ return () => {
905
+ if (typeof window === "undefined") return;
906
+ const w = window;
907
+ if (w[DEVTOOLS_BRIDGE_KEY]) {
908
+ w[DEVTOOLS_BRIDGE_KEY].value = null;
909
+ for (const listener of w[DEVTOOLS_BRIDGE_KEY].listeners) {
910
+ listener(null);
911
+ }
912
+ }
913
+ };
914
+ }, []);
855
915
  return /* @__PURE__ */ jsx3(AnimationAdapterProvider, { adapter: resolvedAnimationAdapter, children: /* @__PURE__ */ jsx3(LabelsProvider, { value: mergedLabels, children: /* @__PURE__ */ jsx3(DialogRegistryProvider, { children: /* @__PURE__ */ jsx3(TourContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(DevToolsContext.Provider, { value: devToolsContextValue, children: [
856
916
  /* @__PURE__ */ jsx3(
857
917
  DialogAutomationBridge,
@@ -1909,10 +1969,12 @@ var useHiddenTargetFallback = ({
1909
1969
  graceTimeoutRef.current = null;
1910
1970
  }
1911
1971
  };
1972
+ const stepChangeTimeRef = useRef5(0);
1912
1973
  useEffect6(() => {
1974
+ stepChangeTimeRef.current = Date.now();
1913
1975
  skipTriggeredRef.current = false;
1914
1976
  setUsingScreenFallback(false);
1915
- setIsInGracePeriod(false);
1977
+ setIsInGracePeriod(true);
1916
1978
  clearPendingTimeout();
1917
1979
  clearGraceTimeout();
1918
1980
  return () => {
@@ -1929,11 +1991,22 @@ var useHiddenTargetFallback = ({
1929
1991
  const isMissingWithNoRect = target.visibility === "missing" && target.status === "resolving" && target.rect === null && target.lastResolvedRect === null;
1930
1992
  const isMissingAfterNavigation = target.visibility === "missing" && target.status === "resolving" && target.rect === null;
1931
1993
  const shouldHandleHiddenTarget = !target.isScreen && (isHiddenOrDetached || isMissingWithNoRect || isMissingAfterNavigation);
1932
- if (!shouldHandleHiddenTarget) {
1994
+ const timeSinceStepChange = Date.now() - stepChangeTimeRef.current;
1995
+ const MIN_GRACE_AFTER_STEP_CHANGE = 300;
1996
+ if (!shouldHandleHiddenTarget && target.visibility !== "unknown") {
1997
+ if (timeSinceStepChange < MIN_GRACE_AFTER_STEP_CHANGE) {
1998
+ graceTimeoutRef.current = globalThis.setTimeout(() => {
1999
+ setIsInGracePeriod(false);
2000
+ }, MIN_GRACE_AFTER_STEP_CHANGE - timeSinceStepChange);
2001
+ return void 0;
2002
+ }
1933
2003
  setUsingScreenFallback(false);
1934
2004
  setIsInGracePeriod(false);
1935
2005
  return void 0;
1936
2006
  }
2007
+ if (target.visibility === "unknown") {
2008
+ return void 0;
2009
+ }
1937
2010
  setIsInGracePeriod(true);
1938
2011
  if (hiddenMode !== "screen") {
1939
2012
  setUsingScreenFallback(false);
@@ -2588,11 +2661,11 @@ var useTourOverlay = (options) => {
2588
2661
  };
2589
2662
  return;
2590
2663
  }
2591
- if (target.status === "idle") {
2664
+ if (target.status === "idle" && !isInGracePeriod) {
2592
2665
  hasShownRef.current = false;
2593
2666
  lastReadyTargetRef.current = null;
2594
2667
  }
2595
- }, [target]);
2668
+ }, [target, isInGracePeriod]);
2596
2669
  const viewport = getViewportRect();
2597
2670
  const cachedTarget = lastReadyTargetRef.current;
2598
2671
  const highlightTarget = target.status === "ready" ? target : cachedTarget;
@@ -3469,7 +3542,8 @@ var TourPopoverPortal = ({
3469
3542
  layoutId,
3470
3543
  containerComponent,
3471
3544
  contentComponent,
3472
- transitionsOverride
3545
+ transitionsOverride,
3546
+ isInGracePeriod = false
3473
3547
  }) => {
3474
3548
  if (!isBrowser) return null;
3475
3549
  const host = portalHost();
@@ -3493,10 +3567,10 @@ var TourPopoverPortal = ({
3493
3567
  rect: { ...target.rect },
3494
3568
  isScreen: target.isScreen
3495
3569
  };
3496
- } else if (target.status === "idle") {
3570
+ } else if (target.status === "idle" && !isInGracePeriod) {
3497
3571
  lastReadyTargetRef.current = null;
3498
3572
  }
3499
- }, [target.isScreen, target.rect, target.status]);
3573
+ }, [target.isScreen, target.rect, target.status, isInGracePeriod]);
3500
3574
  const cachedTarget = lastReadyTargetRef.current;
3501
3575
  const resolvedRect = target.rect ?? target.lastResolvedRect ?? cachedTarget?.rect ?? null;
3502
3576
  const resolvedIsScreen = target.status === "ready" ? target.isScreen : cachedTarget?.isScreen ?? target.isScreen;
@@ -1,206 +1,17 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('../chunk-HPVLOLCD.cjs');
19
2
 
20
- // src/router/index.ts
21
- var router_exports = {};
22
- __export(router_exports, {
23
- createPathString: () => createPathString,
24
- getCurrentRoutePath: () => getCurrentRoutePath,
25
- matchRoute: () => matchRoute,
26
- notifyRouteChange: () => notifyRouteChange,
27
- routeGatingChannel: () => routeGatingChannel,
28
- subscribeToRouteChanges: () => subscribeToRouteChanges
29
- });
30
- module.exports = __toCommonJS(router_exports);
31
3
 
32
- // src/utils/dom.ts
33
- var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
34
4
 
35
- // src/router/routeGating.ts
36
- var DEFAULT_POLL_MS = 150;
37
- var normalizePathname = (pathname) => {
38
- if (typeof pathname !== "string" || pathname.length === 0) {
39
- return "/";
40
- }
41
- return pathname.startsWith("/") ? pathname : `/${pathname}`;
42
- };
43
- var normalizePrefixedSegment = (value, prefix) => {
44
- if (typeof value !== "string" || value.length === 0) {
45
- return "";
46
- }
47
- return value.startsWith(prefix) ? value : `${prefix}${value}`;
48
- };
49
- var getWindowPath = () => {
50
- if (!isBrowser) return "/";
51
- const { pathname, search, hash } = window.location;
52
- return normalizePathname(pathname) + normalizePrefixedSegment(search, "?") + normalizePrefixedSegment(hash, "#");
53
- };
54
- var normalizeExternalPath = (path) => {
55
- if (path.length === 0) {
56
- return "/";
57
- }
58
- try {
59
- const parsed = new URL(path, "http://flowsterix.local");
60
- return normalizePathname(parsed.pathname) + normalizePrefixedSegment(parsed.search, "?") + normalizePrefixedSegment(parsed.hash, "#");
61
- } catch {
62
- const [withoutHash, hash = ""] = path.split("#");
63
- const [base, search = ""] = withoutHash.split("?");
64
- return normalizePathname(base) + normalizePrefixedSegment(search ? `?${search}` : "", "?") + normalizePrefixedSegment(hash ? `#${hash}` : "", "#");
65
- }
66
- };
67
- var RouteGatingChannel = class {
68
- #listeners = /* @__PURE__ */ new Set();
69
- #currentPath = getWindowPath();
70
- #teardown = null;
71
- #attachDefaultListeners() {
72
- if (!isBrowser) return;
73
- if (this.#teardown) return;
74
- let lastPath = getWindowPath();
75
- const emitIfChanged = () => {
76
- const nextPath = getWindowPath();
77
- if (nextPath === lastPath) return;
78
- lastPath = nextPath;
79
- this.notify(nextPath);
80
- };
81
- const handler = () => emitIfChanged();
82
- window.addEventListener("popstate", handler);
83
- window.addEventListener("hashchange", handler);
84
- const pollId = window.setInterval(emitIfChanged, DEFAULT_POLL_MS);
85
- this.#teardown = () => {
86
- window.removeEventListener("popstate", handler);
87
- window.removeEventListener("hashchange", handler);
88
- window.clearInterval(pollId);
89
- this.#teardown = null;
90
- };
91
- }
92
- #detachDefaultListeners() {
93
- if (this.#listeners.size > 0) return;
94
- this.#teardown?.();
95
- this.#teardown = null;
96
- }
97
- getCurrentPath() {
98
- if (isBrowser) {
99
- this.#currentPath = getWindowPath();
100
- }
101
- return this.#currentPath;
102
- }
103
- notify(path) {
104
- const resolved = typeof path === "string" && path.length > 0 ? normalizeExternalPath(path) : this.getCurrentPath();
105
- if (resolved === this.#currentPath) {
106
- this.#currentPath = resolved;
107
- return;
108
- }
109
- this.#currentPath = resolved;
110
- for (const listener of Array.from(this.#listeners)) {
111
- try {
112
- listener(resolved);
113
- } catch (error) {
114
- console.warn("[tour][route-gating] listener error", error);
115
- }
116
- }
117
- }
118
- subscribe(listener) {
119
- if (this.#listeners.has(listener)) {
120
- return () => {
121
- this.#listeners.delete(listener);
122
- this.#detachDefaultListeners();
123
- };
124
- }
125
- this.#listeners.add(listener);
126
- if (this.#listeners.size === 1) {
127
- this.#attachDefaultListeners();
128
- }
129
- const current = this.getCurrentPath();
130
- try {
131
- listener(current);
132
- } catch (error) {
133
- console.warn("[tour][route-gating] listener error", error);
134
- }
135
- return () => {
136
- this.#listeners.delete(listener);
137
- this.#detachDefaultListeners();
138
- };
139
- }
140
- };
141
- var routeGatingChannel = new RouteGatingChannel();
142
- var getCurrentRoutePath = () => routeGatingChannel.getCurrentPath();
143
- var notifyRouteChange = (path) => {
144
- routeGatingChannel.notify(path);
145
- };
146
- var subscribeToRouteChanges = (listener) => {
147
- return routeGatingChannel.subscribe(listener);
148
- };
149
- var matchRoute = (params) => {
150
- const { pattern, path } = params;
151
- if (!pattern) return true;
152
- if (typeof pattern === "string") return path === pattern;
153
- return pattern.test(path);
154
- };
155
5
 
156
- // src/router/utils.ts
157
- var ensurePrefix = (value, prefix) => value.startsWith(prefix) ? value : `${prefix}${value}`;
158
- var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
159
- var toSearchString = (value) => {
160
- if (!isNonEmptyString(value)) {
161
- if (value instanceof URLSearchParams) {
162
- const serialized = value.toString();
163
- return serialized.length > 0 ? `?${serialized}` : "";
164
- }
165
- if (typeof value === "object" && value !== null) {
166
- try {
167
- const params = new URLSearchParams();
168
- for (const [key, raw] of Object.entries(
169
- value
170
- )) {
171
- if (raw === void 0 || raw === null) continue;
172
- params.set(key, String(raw));
173
- }
174
- const serialized = params.toString();
175
- return serialized.length > 0 ? `?${serialized}` : "";
176
- } catch {
177
- return "";
178
- }
179
- }
180
- return "";
181
- }
182
- if (value === "?") return "";
183
- return value.startsWith("?") ? value : ensurePrefix(value, "?");
184
- };
185
- var toHashString = (value) => {
186
- if (!isNonEmptyString(value)) {
187
- return "";
188
- }
189
- if (value === "#") return "";
190
- return value.startsWith("#") ? value : ensurePrefix(value, "#");
191
- };
192
- var createPathString = (pathname, search, hash) => {
193
- const normalizedPath = isNonEmptyString(pathname) ? pathname.startsWith("/") ? pathname : `/${pathname}` : "/";
194
- const searchPart = toSearchString(search);
195
- const hashPart = toHashString(hash);
196
- return `${normalizedPath}${searchPart}${hashPart}`;
197
- };
198
- // Annotate the CommonJS export names for ESM import in node:
199
- 0 && (module.exports = {
200
- createPathString,
201
- getCurrentRoutePath,
202
- matchRoute,
203
- notifyRouteChange,
204
- routeGatingChannel,
205
- subscribeToRouteChanges
206
- });
6
+
7
+
8
+
9
+ var _chunkD5LQLRSUcjs = require('../chunk-D5LQLRSU.cjs');
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+ exports.createPathString = _chunkD5LQLRSUcjs.createPathString; exports.getCurrentRoutePath = _chunkD5LQLRSUcjs.getCurrentRoutePath; exports.matchRoute = _chunkD5LQLRSUcjs.matchRoute; exports.notifyRouteChange = _chunkD5LQLRSUcjs.notifyRouteChange; exports.routeGatingChannel = _chunkD5LQLRSUcjs.routeGatingChannel; exports.subscribeToRouteChanges = _chunkD5LQLRSUcjs.subscribeToRouteChanges;