@apollovisionlabs/guide-core 0.2.0 → 0.3.1
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/README.md +198 -12
- package/dist/index.cjs +259 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -2
- package/dist/index.d.ts +112 -2
- package/dist/index.mjs +263 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,11 +25,14 @@ __export(index_exports, {
|
|
|
25
25
|
ChecklistProvider: () => ChecklistProvider,
|
|
26
26
|
GuideContext: () => GuideContext,
|
|
27
27
|
GuideProvider: () => GuideProvider,
|
|
28
|
+
HotspotContext: () => HotspotContext,
|
|
29
|
+
HotspotProvider: () => HotspotProvider,
|
|
28
30
|
createBrowserStorage: () => createBrowserStorage,
|
|
29
31
|
createMemoryStorage: () => createMemoryStorage,
|
|
30
32
|
findMissingTargets: () => findMissingTargets,
|
|
31
33
|
initialTourState: () => initialTourState,
|
|
32
34
|
isChecklistProgress: () => isChecklistProgress,
|
|
35
|
+
isHotspotsProgress: () => isHotspotsProgress,
|
|
33
36
|
isLiteralRoute: () => isLiteralRoute,
|
|
34
37
|
isTourProgress: () => isTourProgress,
|
|
35
38
|
matchRoute: () => matchRoute,
|
|
@@ -40,6 +43,7 @@ __export(index_exports, {
|
|
|
40
43
|
useElementRect: () => useElementRect,
|
|
41
44
|
useFocusTrap: () => useFocusTrap,
|
|
42
45
|
useGuideStep: () => useGuideStep,
|
|
46
|
+
useHotspots: () => useHotspots,
|
|
43
47
|
usePrefersReducedMotion: () => usePrefersReducedMotion,
|
|
44
48
|
useTargetElement: () => useTargetElement,
|
|
45
49
|
useTour: () => useTour
|
|
@@ -90,6 +94,11 @@ function isChecklistProgress(value) {
|
|
|
90
94
|
const candidate = value;
|
|
91
95
|
return Array.isArray(candidate.completed) && candidate.completed.every((entry) => typeof entry === "string") && typeof candidate.dismissed === "boolean";
|
|
92
96
|
}
|
|
97
|
+
function isHotspotsProgress(value) {
|
|
98
|
+
if (typeof value !== "object" || value === null) return false;
|
|
99
|
+
const candidate = value;
|
|
100
|
+
return Array.isArray(candidate.seen) && candidate.seen.every((entry) => typeof entry === "string");
|
|
101
|
+
}
|
|
93
102
|
|
|
94
103
|
// src/matchRoute.ts
|
|
95
104
|
function segments(value) {
|
|
@@ -321,6 +330,24 @@ function resolveText(value, key, translate) {
|
|
|
321
330
|
// src/GuideProvider.tsx
|
|
322
331
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
323
332
|
var GuideContext = (0, import_react4.createContext)(null);
|
|
333
|
+
function focusFallback(element) {
|
|
334
|
+
const needsTabIndex = !element.hasAttribute("tabindex") && element.tabIndex < 0;
|
|
335
|
+
if (!needsTabIndex) {
|
|
336
|
+
element.focus();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
element.setAttribute("tabindex", "-1");
|
|
340
|
+
element.focus();
|
|
341
|
+
if (document.activeElement !== element) {
|
|
342
|
+
element.removeAttribute("tabindex");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const onBlur = () => {
|
|
346
|
+
element.removeAttribute("tabindex");
|
|
347
|
+
element.removeEventListener("blur", onBlur);
|
|
348
|
+
};
|
|
349
|
+
element.addEventListener("blur", onBlur);
|
|
350
|
+
}
|
|
324
351
|
function GuideProvider({
|
|
325
352
|
tours,
|
|
326
353
|
children,
|
|
@@ -345,6 +372,7 @@ function GuideProvider({
|
|
|
345
372
|
const [state, dispatch] = (0, import_react4.useReducer)(tourReducer, initialTourState);
|
|
346
373
|
const announce = useAnnouncer();
|
|
347
374
|
const focusOriginRef = (0, import_react4.useRef)(null);
|
|
375
|
+
const lastElementRef = (0, import_react4.useRef)(null);
|
|
348
376
|
const storageWarnedRef = (0, import_react4.useRef)(false);
|
|
349
377
|
const warnStorageFailure = (0, import_react4.useCallback)((error) => {
|
|
350
378
|
if (storageWarnedRef.current) return;
|
|
@@ -380,6 +408,14 @@ function GuideProvider({
|
|
|
380
408
|
dispatch({ type: "NEXT", stepCount: tour.steps.length });
|
|
381
409
|
if (isLast) emit({ type: "tour:complete", tourId: tour.id });
|
|
382
410
|
}, [tour, state.stepIndex, emit]);
|
|
411
|
+
const nextRef = (0, import_react4.useRef)(next);
|
|
412
|
+
nextRef.current = next;
|
|
413
|
+
(0, import_react4.useEffect)(() => {
|
|
414
|
+
if (state.status !== "running" || !element || step?.advanceOn !== "click") return;
|
|
415
|
+
const onClick = () => nextRef.current();
|
|
416
|
+
element.addEventListener("click", onClick);
|
|
417
|
+
return () => element.removeEventListener("click", onClick);
|
|
418
|
+
}, [state.status, element, step?.advanceOn]);
|
|
383
419
|
const previous = (0, import_react4.useCallback)(() => dispatch({ type: "PREVIOUS" }), []);
|
|
384
420
|
const stop = (0, import_react4.useCallback)(() => {
|
|
385
421
|
if (tour) emit({ type: "tour:stop", tourId: tour.id, stepIndex: state.stepIndex });
|
|
@@ -478,12 +514,23 @@ function GuideProvider({
|
|
|
478
514
|
warnStorageFailure(error);
|
|
479
515
|
}
|
|
480
516
|
}, [storage, state.tourId, state.status, state.stepIndex, warnStorageFailure]);
|
|
517
|
+
(0, import_react4.useEffect)(() => {
|
|
518
|
+
if (element) lastElementRef.current = element;
|
|
519
|
+
}, [element]);
|
|
481
520
|
(0, import_react4.useEffect)(() => {
|
|
482
521
|
if (state.status !== "idle" && state.status !== "completed") return;
|
|
483
522
|
const origin = focusOriginRef.current;
|
|
484
|
-
|
|
523
|
+
const fallback = lastElementRef.current;
|
|
485
524
|
focusOriginRef.current = null;
|
|
486
|
-
|
|
525
|
+
lastElementRef.current = null;
|
|
526
|
+
if (typeof document === "undefined") return;
|
|
527
|
+
if (origin && document.contains(origin)) {
|
|
528
|
+
origin.focus();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (document.activeElement !== document.body) return;
|
|
532
|
+
if (!fallback || !document.contains(fallback)) return;
|
|
533
|
+
focusFallback(fallback);
|
|
487
534
|
}, [state.status]);
|
|
488
535
|
const activeStep = (0, import_react4.useMemo)(() => {
|
|
489
536
|
if (!tour || !step || !isActive) return null;
|
|
@@ -494,6 +541,8 @@ function GuideProvider({
|
|
|
494
541
|
stepCount: tour.steps.length,
|
|
495
542
|
element,
|
|
496
543
|
rect,
|
|
544
|
+
interactive: step.interactive === true || step.advanceOn !== void 0,
|
|
545
|
+
awaitsAction: step.advanceOn !== void 0,
|
|
497
546
|
title: resolveText(step.title, step.titleKey, translate),
|
|
498
547
|
body: resolveText(step.body, step.bodyKey, translate),
|
|
499
548
|
isFirst: state.stepIndex === 0,
|
|
@@ -562,6 +611,11 @@ function ChecklistProvider({
|
|
|
562
611
|
return initial;
|
|
563
612
|
});
|
|
564
613
|
const progressRef = (0, import_react7.useRef)(progress);
|
|
614
|
+
const [restoredById, setRestoredById] = (0, import_react7.useState)(() => {
|
|
615
|
+
const initial = {};
|
|
616
|
+
for (const candidate of checklists) initial[candidate.id] = !storage;
|
|
617
|
+
return initial;
|
|
618
|
+
});
|
|
565
619
|
const guide = (0, import_react7.useContext)(GuideContext);
|
|
566
620
|
const storageWarnedRef = (0, import_react7.useRef)(false);
|
|
567
621
|
const warnStorageFailure = (0, import_react7.useCallback)((error) => {
|
|
@@ -593,31 +647,33 @@ function ChecklistProvider({
|
|
|
593
647
|
(0, import_react7.useEffect)(() => {
|
|
594
648
|
if (!storage) return;
|
|
595
649
|
let cancelled = false;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
for (const candidate of checklists) {
|
|
650
|
+
for (const candidate of checklists) {
|
|
651
|
+
void (async () => {
|
|
599
652
|
try {
|
|
600
653
|
const stored = await storage.read(`checklist:${candidate.id}`);
|
|
601
|
-
if (isChecklistProgress(stored))
|
|
654
|
+
if (!cancelled && isChecklistProgress(stored)) {
|
|
655
|
+
const live = progressRef.current[candidate.id] ?? emptyProgress;
|
|
656
|
+
const merged = {
|
|
657
|
+
...progressRef.current,
|
|
658
|
+
[candidate.id]: {
|
|
659
|
+
completed: live.completed.concat(
|
|
660
|
+
stored.completed.filter((id) => !live.completed.includes(id))
|
|
661
|
+
),
|
|
662
|
+
dismissed: live.dismissed || stored.dismissed
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
progressRef.current = merged;
|
|
666
|
+
setProgress(merged);
|
|
667
|
+
}
|
|
602
668
|
} catch (error) {
|
|
603
669
|
warnStorageFailure(error);
|
|
670
|
+
} finally {
|
|
671
|
+
if (!cancelled) {
|
|
672
|
+
setRestoredById((current) => ({ ...current, [candidate.id]: true }));
|
|
673
|
+
}
|
|
604
674
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const merged = { ...progressRef.current };
|
|
608
|
-
for (const [checklistId, stored] of Object.entries(restored)) {
|
|
609
|
-
const live = merged[checklistId] ?? emptyProgress;
|
|
610
|
-
merged[checklistId] = {
|
|
611
|
-
completed: live.completed.concat(
|
|
612
|
-
stored.completed.filter((id) => !live.completed.includes(id))
|
|
613
|
-
),
|
|
614
|
-
dismissed: live.dismissed || stored.dismissed
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
progressRef.current = merged;
|
|
618
|
-
setProgress(merged);
|
|
619
|
-
}
|
|
620
|
-
})();
|
|
675
|
+
})();
|
|
676
|
+
}
|
|
621
677
|
return () => {
|
|
622
678
|
cancelled = true;
|
|
623
679
|
};
|
|
@@ -755,8 +811,18 @@ function ChecklistProvider({
|
|
|
755
811
|
[resolveItem, guide, navigate, toggle, warnNoGuide, warnNoNavigate, warnTourStartFailure]
|
|
756
812
|
);
|
|
757
813
|
const value = (0, import_react7.useMemo)(
|
|
758
|
-
() => ({
|
|
759
|
-
|
|
814
|
+
() => ({
|
|
815
|
+
checklists,
|
|
816
|
+
progress,
|
|
817
|
+
translate,
|
|
818
|
+
restored: restoredById,
|
|
819
|
+
activate,
|
|
820
|
+
toggle,
|
|
821
|
+
complete,
|
|
822
|
+
dismiss,
|
|
823
|
+
reset
|
|
824
|
+
}),
|
|
825
|
+
[checklists, progress, translate, restoredById, activate, toggle, complete, dismiss, reset]
|
|
760
826
|
);
|
|
761
827
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChecklistContext.Provider, { value, children });
|
|
762
828
|
}
|
|
@@ -773,6 +839,7 @@ function useChecklist(checklistId) {
|
|
|
773
839
|
const completed = progress?.completed ?? [];
|
|
774
840
|
const dismissed = progress?.dismissed ?? false;
|
|
775
841
|
const translate = context.translate;
|
|
842
|
+
const restored = context.restored[checklistId] ?? true;
|
|
776
843
|
const items = (0, import_react8.useMemo)(
|
|
777
844
|
() => checklist.items.map((item) => ({
|
|
778
845
|
id: item.id,
|
|
@@ -795,6 +862,7 @@ function useChecklist(checklistId) {
|
|
|
795
862
|
total,
|
|
796
863
|
isComplete,
|
|
797
864
|
dismissed,
|
|
865
|
+
restored,
|
|
798
866
|
activate: (itemId) => activate(checklistId, itemId),
|
|
799
867
|
toggle: (itemId) => toggle(checklistId, itemId),
|
|
800
868
|
complete: (itemId) => complete(checklistId, itemId),
|
|
@@ -807,6 +875,7 @@ function useChecklist(checklistId) {
|
|
|
807
875
|
total,
|
|
808
876
|
isComplete,
|
|
809
877
|
dismissed,
|
|
878
|
+
restored,
|
|
810
879
|
activate,
|
|
811
880
|
toggle,
|
|
812
881
|
complete,
|
|
@@ -816,17 +885,182 @@ function useChecklist(checklistId) {
|
|
|
816
885
|
]
|
|
817
886
|
);
|
|
818
887
|
}
|
|
888
|
+
|
|
889
|
+
// src/HotspotProvider.tsx
|
|
890
|
+
var import_react9 = require("react");
|
|
891
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
892
|
+
var STORAGE_KEY = "hotspots:seen";
|
|
893
|
+
var HotspotContext = (0, import_react9.createContext)(null);
|
|
894
|
+
function HotspotProvider({
|
|
895
|
+
hotspots,
|
|
896
|
+
children,
|
|
897
|
+
storage,
|
|
898
|
+
translate,
|
|
899
|
+
onEvent
|
|
900
|
+
}) {
|
|
901
|
+
const hotspotsById = (0, import_react9.useMemo)(() => {
|
|
902
|
+
const map = /* @__PURE__ */ new Map();
|
|
903
|
+
for (const candidate of hotspots) {
|
|
904
|
+
if (map.has(candidate.id)) {
|
|
905
|
+
throw new Error(`[guide] duplicate hotspot id: ${candidate.id}`);
|
|
906
|
+
}
|
|
907
|
+
map.set(candidate.id, candidate);
|
|
908
|
+
}
|
|
909
|
+
return map;
|
|
910
|
+
}, [hotspots]);
|
|
911
|
+
const [seen, setSeen] = (0, import_react9.useState)([]);
|
|
912
|
+
const [restored, setRestored] = (0, import_react9.useState)(() => !storage);
|
|
913
|
+
const seenRef = (0, import_react9.useRef)(seen);
|
|
914
|
+
const guide = (0, import_react9.useContext)(GuideContext);
|
|
915
|
+
const storageWarnedRef = (0, import_react9.useRef)(false);
|
|
916
|
+
const warnStorageFailure = (0, import_react9.useCallback)((error) => {
|
|
917
|
+
if (storageWarnedRef.current) return;
|
|
918
|
+
storageWarnedRef.current = true;
|
|
919
|
+
console.warn("[guide] storage failed; hotspot state will not be persisted", error);
|
|
920
|
+
}, []);
|
|
921
|
+
const noGuideWarnedRef = (0, import_react9.useRef)(false);
|
|
922
|
+
const warnNoGuide = (0, import_react9.useCallback)(() => {
|
|
923
|
+
if (noGuideWarnedRef.current) return;
|
|
924
|
+
noGuideWarnedRef.current = true;
|
|
925
|
+
console.warn("[guide] a hotspot needs a GuideProvider to launch a tour");
|
|
926
|
+
}, []);
|
|
927
|
+
const tourStartFailedWarnedRef = (0, import_react9.useRef)(false);
|
|
928
|
+
const warnTourStartFailure = (0, import_react9.useCallback)((error) => {
|
|
929
|
+
if (tourStartFailedWarnedRef.current) return;
|
|
930
|
+
tourStartFailedWarnedRef.current = true;
|
|
931
|
+
console.warn("[guide] starting a tour for a hotspot failed", error);
|
|
932
|
+
}, []);
|
|
933
|
+
const onEventRef = (0, import_react9.useRef)(onEvent);
|
|
934
|
+
onEventRef.current = onEvent;
|
|
935
|
+
const emit = (0, import_react9.useCallback)((event) => onEventRef.current?.(event), []);
|
|
936
|
+
(0, import_react9.useEffect)(() => {
|
|
937
|
+
if (!storage) return;
|
|
938
|
+
let cancelled = false;
|
|
939
|
+
void (async () => {
|
|
940
|
+
let stored = null;
|
|
941
|
+
try {
|
|
942
|
+
stored = await storage.read(STORAGE_KEY);
|
|
943
|
+
} catch (error) {
|
|
944
|
+
warnStorageFailure(error);
|
|
945
|
+
if (!cancelled) setRestored(true);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (cancelled) return;
|
|
949
|
+
if (isHotspotsProgress(stored)) {
|
|
950
|
+
const merged = seenRef.current.concat(
|
|
951
|
+
stored.seen.filter((id) => !seenRef.current.includes(id))
|
|
952
|
+
);
|
|
953
|
+
seenRef.current = merged;
|
|
954
|
+
setSeen(merged);
|
|
955
|
+
}
|
|
956
|
+
setRestored(true);
|
|
957
|
+
})();
|
|
958
|
+
return () => {
|
|
959
|
+
cancelled = true;
|
|
960
|
+
};
|
|
961
|
+
}, [storage, warnStorageFailure]);
|
|
962
|
+
const applySeen = (0, import_react9.useCallback)(
|
|
963
|
+
(next) => {
|
|
964
|
+
seenRef.current = next;
|
|
965
|
+
setSeen(next);
|
|
966
|
+
if (!storage) return;
|
|
967
|
+
try {
|
|
968
|
+
void Promise.resolve(storage.write(STORAGE_KEY, { seen: next })).catch(
|
|
969
|
+
warnStorageFailure
|
|
970
|
+
);
|
|
971
|
+
} catch (error) {
|
|
972
|
+
warnStorageFailure(error);
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
[storage, warnStorageFailure]
|
|
976
|
+
);
|
|
977
|
+
const resolve = (0, import_react9.useCallback)(
|
|
978
|
+
(hotspotId) => {
|
|
979
|
+
const hotspot = hotspotsById.get(hotspotId);
|
|
980
|
+
if (!hotspot) {
|
|
981
|
+
console.warn(`[guide] unknown hotspot "${hotspotId}"`);
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
return hotspot;
|
|
985
|
+
},
|
|
986
|
+
[hotspotsById]
|
|
987
|
+
);
|
|
988
|
+
const open = (0, import_react9.useCallback)(
|
|
989
|
+
(hotspotId) => {
|
|
990
|
+
if (!resolve(hotspotId)) return;
|
|
991
|
+
emit({ type: "hotspot:open", hotspotId });
|
|
992
|
+
if (seenRef.current.includes(hotspotId)) return;
|
|
993
|
+
applySeen([...seenRef.current, hotspotId]);
|
|
994
|
+
},
|
|
995
|
+
[resolve, applySeen, emit]
|
|
996
|
+
);
|
|
997
|
+
const startTour = (0, import_react9.useCallback)(
|
|
998
|
+
(hotspotId) => {
|
|
999
|
+
const hotspot = resolve(hotspotId);
|
|
1000
|
+
if (!hotspot?.tourId) return;
|
|
1001
|
+
if (!guide) {
|
|
1002
|
+
warnNoGuide();
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
void guide.start(hotspot.tourId).catch(warnTourStartFailure);
|
|
1006
|
+
},
|
|
1007
|
+
[resolve, guide, warnNoGuide, warnTourStartFailure]
|
|
1008
|
+
);
|
|
1009
|
+
const reset = (0, import_react9.useCallback)(() => applySeen([]), [applySeen]);
|
|
1010
|
+
const shownRef = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
|
|
1011
|
+
const notifyShown = (0, import_react9.useCallback)(
|
|
1012
|
+
(hotspotId) => {
|
|
1013
|
+
if (shownRef.current.has(hotspotId)) return;
|
|
1014
|
+
shownRef.current.add(hotspotId);
|
|
1015
|
+
emit({ type: "hotspot:show", hotspotId });
|
|
1016
|
+
},
|
|
1017
|
+
[emit]
|
|
1018
|
+
);
|
|
1019
|
+
const value = (0, import_react9.useMemo)(
|
|
1020
|
+
() => ({ hotspots, seen, translate, restored, open, startTour, reset, notifyShown }),
|
|
1021
|
+
[hotspots, seen, translate, restored, open, startTour, reset, notifyShown]
|
|
1022
|
+
);
|
|
1023
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HotspotContext.Provider, { value, children });
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/useHotspots.ts
|
|
1027
|
+
var import_react10 = require("react");
|
|
1028
|
+
function useHotspots() {
|
|
1029
|
+
const context = (0, import_react10.useContext)(HotspotContext);
|
|
1030
|
+
if (!context)
|
|
1031
|
+
throw new Error("[guide] useHotspots must be used inside a HotspotProvider");
|
|
1032
|
+
const { seen, translate, restored, open, startTour, reset, notifyShown } = context;
|
|
1033
|
+
const hotspots = (0, import_react10.useMemo)(
|
|
1034
|
+
() => context.hotspots.map((hotspot) => ({
|
|
1035
|
+
id: hotspot.id,
|
|
1036
|
+
target: hotspot.target,
|
|
1037
|
+
title: resolveText(hotspot.title, hotspot.titleKey, translate),
|
|
1038
|
+
body: resolveText(hotspot.body, hotspot.bodyKey, translate),
|
|
1039
|
+
seen: seen.includes(hotspot.id),
|
|
1040
|
+
tourId: hotspot.tourId,
|
|
1041
|
+
placement: hotspot.placement
|
|
1042
|
+
})),
|
|
1043
|
+
[context.hotspots, seen, translate]
|
|
1044
|
+
);
|
|
1045
|
+
return (0, import_react10.useMemo)(
|
|
1046
|
+
() => ({ hotspots, restored, open, startTour, reset, notifyShown }),
|
|
1047
|
+
[hotspots, restored, open, startTour, reset, notifyShown]
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
819
1050
|
// Annotate the CommonJS export names for ESM import in node:
|
|
820
1051
|
0 && (module.exports = {
|
|
821
1052
|
ChecklistContext,
|
|
822
1053
|
ChecklistProvider,
|
|
823
1054
|
GuideContext,
|
|
824
1055
|
GuideProvider,
|
|
1056
|
+
HotspotContext,
|
|
1057
|
+
HotspotProvider,
|
|
825
1058
|
createBrowserStorage,
|
|
826
1059
|
createMemoryStorage,
|
|
827
1060
|
findMissingTargets,
|
|
828
1061
|
initialTourState,
|
|
829
1062
|
isChecklistProgress,
|
|
1063
|
+
isHotspotsProgress,
|
|
830
1064
|
isLiteralRoute,
|
|
831
1065
|
isTourProgress,
|
|
832
1066
|
matchRoute,
|
|
@@ -837,6 +1071,7 @@ function useChecklist(checklistId) {
|
|
|
837
1071
|
useElementRect,
|
|
838
1072
|
useFocusTrap,
|
|
839
1073
|
useGuideStep,
|
|
1074
|
+
useHotspots,
|
|
840
1075
|
usePrefersReducedMotion,
|
|
841
1076
|
useTargetElement,
|
|
842
1077
|
useTour
|