@eifi1/ui-kit 0.49.0 → 0.50.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 +12 -8
- package/dist/admin/user-roster.d.ts +16 -10
- package/dist/admin/user-roster.js +7 -3
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +3 -1
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js +3 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +14 -0
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/combobox-core.js +1 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox-suggestions.js +1 -0
- package/dist/components/combobox-suggestions.js.map +1 -1
- package/dist/components/combobox.js +3 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +28 -2
- package/dist/components/currency-select.js +56 -10
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +11 -2
- package/dist/components/data-table-mobile.js +62 -8
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row.d.ts +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table.js +3 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dropdown.js +1 -0
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +1 -0
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/inline-entity-combobox.js +12 -0
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/modal.js +1 -0
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -1
- package/dist/components/number-input.js +6 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.js +2 -0
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -0
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.js +1 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +14 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +3 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +16 -1
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +14 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +14 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +14 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +14 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +14 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +1 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/lib/data-attributes.d.ts +11 -0
- package/dist/lib/data-attributes.js +10 -0
- package/dist/lib/data-attributes.js.map +1 -0
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +2 -0
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +10 -0
- package/dist/lib/minus.js.map +1 -1
- package/dist/lib/quiet-focus.d.ts +4 -0
- package/dist/lib/quiet-focus.js +19 -0
- package/dist/lib/quiet-focus.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/react.d.ts +25 -0
- package/dist/testing/react.js +19 -0
- package/dist/testing/react.js.map +1 -0
- package/dist/testing/tour.d.ts +31 -0
- package/dist/testing/tour.js +12 -0
- package/dist/testing/tour.js.map +1 -0
- package/dist/tour/tour-dock.d.ts +42 -0
- package/dist/tour/tour-dock.js +80 -0
- package/dist/tour/tour-dock.js.map +1 -0
- package/dist/tour/tour-engine.d.ts +12 -0
- package/dist/tour/tour-engine.js +31 -0
- package/dist/tour/tour-engine.js.map +1 -0
- package/dist/tour/tour-gates.d.ts +71 -0
- package/dist/tour/tour-gates.js +114 -0
- package/dist/tour/tour-gates.js.map +1 -0
- package/dist/tour/tour-guide-button.d.ts +27 -0
- package/dist/tour/tour-guide-button.js +45 -0
- package/dist/tour/tour-guide-button.js.map +1 -0
- package/dist/tour/tour-guide-labels.d.ts +8 -0
- package/dist/tour/tour-guide-labels.js +9 -0
- package/dist/tour/tour-guide-labels.js.map +1 -0
- package/dist/tour/tour-labels.js +9 -1
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +144 -9
- package/dist/tour/tour.js +598 -59
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-url-sync.d.ts +7 -2
- package/dist/tour/use-tour-url-sync.js +27 -1
- package/dist/tour/use-tour-url-sync.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.js +15 -2
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +11 -0
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +11 -5
- package/dist/wizard/use-wizard.js.map +1 -1
- package/package.json +9 -1
- package/src/admin/user-roster.tsx +22 -12
- package/src/auth/verify-email.tsx +10 -0
- package/src/components/amount-input.tsx +5 -4
- package/src/components/autocomplete.tsx +20 -0
- package/src/components/combobox-core.tsx +2 -0
- package/src/components/combobox-suggestions.tsx +2 -0
- package/src/components/combobox.tsx +5 -1
- package/src/components/currency-select.tsx +95 -6
- package/src/components/data-table-mobile.tsx +99 -8
- package/src/components/data-table-row.tsx +1 -0
- package/src/components/data-table.tsx +9 -1
- package/src/components/dropdown.tsx +2 -0
- package/src/components/full-bleed-dialog.tsx +3 -0
- package/src/components/inline-entity-combobox.tsx +17 -0
- package/src/components/modal.tsx +3 -0
- package/src/components/number-input.tsx +8 -3
- package/src/components/numpad-sheet.tsx +4 -0
- package/src/components/picker-sheet.tsx +4 -0
- package/src/components/popover.tsx +2 -0
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +13 -0
- package/src/i18n/kit-labels.tsx +3 -0
- package/src/i18n/locales/es.ts +13 -0
- package/src/i18n/locales/fr.ts +13 -0
- package/src/i18n/locales/hu.ts +13 -0
- package/src/i18n/locales/it.ts +13 -0
- package/src/i18n/locales/zh.ts +13 -0
- package/src/i18n/review.ts +1 -0
- package/src/lib/data-attributes.ts +14 -0
- package/src/lib/format.ts +2 -0
- package/src/lib/minus.ts +10 -0
- package/src/lib/quiet-focus.ts +32 -0
- package/src/testing/index.ts +8 -0
- package/src/testing/react.ts +59 -0
- package/src/testing/tour.ts +41 -0
- package/src/tour/tour-dock.ts +132 -0
- package/src/tour/tour-engine.ts +41 -0
- package/src/tour/tour-gates.ts +195 -0
- package/src/tour/tour-guide-button.tsx +64 -0
- package/src/tour/tour-guide-labels.ts +15 -0
- package/src/tour/tour-labels.ts +8 -0
- package/src/tour/tour.tsx +989 -86
- package/src/tour/use-tour-url-sync.ts +43 -4
- package/src/tour.ts +3 -0
- package/src/wizard/stepper-nav.tsx +23 -3
- package/src/wizard/types.ts +12 -0
- package/src/wizard/use-wizard.ts +16 -9
package/dist/tour/tour.js
CHANGED
|
@@ -5,19 +5,42 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
|
+
useId,
|
|
8
9
|
useLayoutEffect,
|
|
10
|
+
useMemo,
|
|
9
11
|
useRef,
|
|
10
|
-
useState
|
|
12
|
+
useState,
|
|
13
|
+
useSyncExternalStore
|
|
11
14
|
} from "react";
|
|
12
15
|
import { createPortal } from "react-dom";
|
|
13
16
|
import { cn } from "../lib/cn.js";
|
|
14
17
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
15
18
|
import { Button } from "../components/button.js";
|
|
16
19
|
import { useFocusTrap } from "../hooks/use-focus-trap.js";
|
|
20
|
+
import { useDismissLayer } from "../hooks/use-dismiss.js";
|
|
17
21
|
import { sameRect } from "../hooks/use-anchored-rect.js";
|
|
18
22
|
import { dirOf, horizontalStep } from "../lib/direction.js";
|
|
23
|
+
import { isComposing } from "../lib/ime.js";
|
|
24
|
+
import { focusQuietly } from "../lib/quiet-focus.js";
|
|
19
25
|
import { DEFAULT_TOUR_LABELS } from "./tour-labels.js";
|
|
20
26
|
import { completeStored, rememberAccount, rememberMigrate } from "./use-tour-progress.js";
|
|
27
|
+
import { createGateRegistry, devWarnings, TourInternalContext } from "./tour-gates.js";
|
|
28
|
+
import { liveEngine } from "./tour-engine.js";
|
|
29
|
+
import { DOCK_MAX_WIDTH, dockEdge, dockStyle, readBand, scrollIntoFreeHalf } from "./tour-dock.js";
|
|
30
|
+
import { useTourGate, useTourGateRelease } from "./tour-gates.js";
|
|
31
|
+
const ENGINE_KEYS = [
|
|
32
|
+
"active",
|
|
33
|
+
"activeId",
|
|
34
|
+
"start",
|
|
35
|
+
"stop",
|
|
36
|
+
"next",
|
|
37
|
+
"prev",
|
|
38
|
+
"goToStep",
|
|
39
|
+
"index",
|
|
40
|
+
"total",
|
|
41
|
+
"position",
|
|
42
|
+
"count"
|
|
43
|
+
];
|
|
21
44
|
const TourContext = createContext(null);
|
|
22
45
|
function useTour() {
|
|
23
46
|
const ctx = useContext(TourContext);
|
|
@@ -27,25 +50,28 @@ function useTour() {
|
|
|
27
50
|
function useTourOptional() {
|
|
28
51
|
return useContext(TourContext);
|
|
29
52
|
}
|
|
30
|
-
function devWarnings() {
|
|
31
|
-
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
32
|
-
}
|
|
33
53
|
function TourProvider({
|
|
34
54
|
children,
|
|
35
55
|
labels,
|
|
36
56
|
progress,
|
|
37
|
-
onComplete
|
|
57
|
+
onComplete,
|
|
58
|
+
launch,
|
|
59
|
+
dockOnPhones = true
|
|
38
60
|
}) {
|
|
39
61
|
const merged = useKitLabels("tour", DEFAULT_TOUR_LABELS, labels);
|
|
40
62
|
const [steps, setSteps] = useState(null);
|
|
41
|
-
const [
|
|
63
|
+
const [nav, setNav] = useState(NO_NAV);
|
|
64
|
+
const index = nav.index;
|
|
65
|
+
const [refusal, setRefusal] = useState(null);
|
|
42
66
|
const [activeId, setActiveId] = useState(null);
|
|
43
67
|
const optsRef = useRef({});
|
|
44
68
|
const progressRef = useRef(progress);
|
|
45
69
|
const onCompleteRef = useRef(onComplete);
|
|
70
|
+
const launchRef = useRef(launch);
|
|
46
71
|
useLayoutEffect(() => {
|
|
47
72
|
progressRef.current = progress;
|
|
48
73
|
onCompleteRef.current = onComplete;
|
|
74
|
+
launchRef.current = launch;
|
|
49
75
|
});
|
|
50
76
|
const storageKey = progress?.storageKey;
|
|
51
77
|
const migrate = progress?.migrate;
|
|
@@ -61,15 +87,89 @@ function TourProvider({
|
|
|
61
87
|
stepsRef.current = steps;
|
|
62
88
|
indexRef.current = index;
|
|
63
89
|
}, [steps, index]);
|
|
90
|
+
const floorRef = useRef(0);
|
|
64
91
|
const active = !!steps && steps.length > 0;
|
|
65
92
|
const total = steps?.length ?? 0;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
const [gates] = useState(createGateRegistry);
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
gates.setActive(active);
|
|
96
|
+
}, [gates, active]);
|
|
97
|
+
const canLaunch = !!launch;
|
|
98
|
+
const internals = useMemo(() => ({ gates, canLaunch }), [gates, canLaunch]);
|
|
99
|
+
const shown = useCallback(
|
|
100
|
+
(s, i, via) => isShown(s[i], stepContext(gates, via)),
|
|
101
|
+
[gates]
|
|
102
|
+
);
|
|
103
|
+
const arrive = useCallback(
|
|
104
|
+
(s, i, via) => {
|
|
105
|
+
const ctx = stepContext(gates, via);
|
|
106
|
+
let position = 0;
|
|
107
|
+
let count = 0;
|
|
108
|
+
for (let j = floorRef.current; j < s.length; j++) {
|
|
109
|
+
if (j !== i && !isShown(s[j], ctx)) continue;
|
|
110
|
+
count++;
|
|
111
|
+
if (j <= i) position++;
|
|
112
|
+
}
|
|
113
|
+
indexRef.current = i;
|
|
114
|
+
setRefusal(null);
|
|
115
|
+
setNav({ index: i, floor: floorRef.current, via, position, count });
|
|
116
|
+
},
|
|
117
|
+
[gates]
|
|
118
|
+
);
|
|
119
|
+
const forward = useCallback(
|
|
120
|
+
(s, from, via) => {
|
|
121
|
+
for (let j = Math.max(from, floorRef.current); j < s.length; j++) if (shown(s, j, via)) return j;
|
|
122
|
+
return -1;
|
|
123
|
+
},
|
|
124
|
+
[shown]
|
|
125
|
+
);
|
|
126
|
+
const backward = useCallback(
|
|
127
|
+
(s, from, via) => {
|
|
128
|
+
for (let j = Math.min(from, s.length - 1); j >= floorRef.current; j--) if (shown(s, j, via)) return j;
|
|
129
|
+
return -1;
|
|
130
|
+
},
|
|
131
|
+
[shown]
|
|
132
|
+
);
|
|
133
|
+
const startSteps = useCallback(
|
|
134
|
+
(s, opts) => {
|
|
135
|
+
optsRef.current = opts ?? {};
|
|
136
|
+
optsRef.current.onStart?.();
|
|
137
|
+
const last = Math.max(0, s.length - 1);
|
|
138
|
+
floorRef.current = opts?.at === void 0 ? 0 : resolveAt(s, opts.at);
|
|
139
|
+
const via = opts?.at === void 0 ? "start" : "at";
|
|
140
|
+
const wanted = Math.min(Math.max(floorRef.current, opts?.startIndex ?? floorRef.current), last);
|
|
141
|
+
let to = forward(s, wanted, via);
|
|
142
|
+
if (to < 0) to = backward(s, wanted, via);
|
|
143
|
+
if (to < 0) return;
|
|
144
|
+
arrive(s, to, via);
|
|
145
|
+
setActiveId(opts?.id ?? opts?.tour?.id ?? null);
|
|
146
|
+
setSteps(s);
|
|
147
|
+
},
|
|
148
|
+
[arrive, forward, backward]
|
|
149
|
+
);
|
|
150
|
+
const ownStartRef = useRef(null);
|
|
151
|
+
const [live] = useState(() => liveEngine(ENGINE_KEYS));
|
|
152
|
+
const engine = live.engine;
|
|
153
|
+
const start = useCallback(
|
|
154
|
+
(what, opts) => {
|
|
155
|
+
if (typeof what !== "string") {
|
|
156
|
+
startSteps(what, opts);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const run = launchRef.current;
|
|
160
|
+
if (!run) {
|
|
161
|
+
if (devWarnings()) console.warn(`[ui-kit] start("${what}") needs TourProvider's \`launch\` prop.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
void Promise.resolve(run(what, { at: opts?.at }, ownStartRef.current, engine)).catch(
|
|
165
|
+
(error) => console.error(error)
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
[startSteps, engine]
|
|
169
|
+
);
|
|
170
|
+
useLayoutEffect(() => {
|
|
171
|
+
ownStartRef.current = start;
|
|
172
|
+
}, [start]);
|
|
73
173
|
const stop = useCallback(() => {
|
|
74
174
|
setSteps(null);
|
|
75
175
|
setActiveId(null);
|
|
@@ -103,34 +203,117 @@ function TourProvider({
|
|
|
103
203
|
setSteps(null);
|
|
104
204
|
setActiveId(null);
|
|
105
205
|
}, []);
|
|
106
|
-
const goToStep = useCallback(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
206
|
+
const goToStep = useCallback(
|
|
207
|
+
(i) => {
|
|
208
|
+
const s = stepsRef.current;
|
|
209
|
+
if (!s || s.length === 0) return;
|
|
210
|
+
const wanted = Math.min(Math.max(floorRef.current, i), s.length - 1);
|
|
211
|
+
let to = forward(s, wanted, "goto");
|
|
212
|
+
if (to < 0) to = backward(s, wanted, "goto");
|
|
213
|
+
if (to >= 0) arrive(s, to, "goto");
|
|
214
|
+
},
|
|
215
|
+
[arrive, forward, backward]
|
|
216
|
+
);
|
|
111
217
|
const next = useCallback(() => {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
218
|
+
const s = stepsRef.current;
|
|
219
|
+
if (!s || s.length === 0) return;
|
|
220
|
+
const to = forward(s, indexRef.current + 1, "next");
|
|
221
|
+
if (to < 0) finish();
|
|
222
|
+
else arrive(s, to, "next");
|
|
223
|
+
}, [arrive, forward, finish]);
|
|
117
224
|
const prev = useCallback(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
225
|
+
const s = stepsRef.current;
|
|
226
|
+
if (!s) return;
|
|
227
|
+
const to = backward(s, indexRef.current - 1, "back");
|
|
228
|
+
if (to >= 0) arrive(s, to, "back");
|
|
229
|
+
}, [arrive, backward]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!active) return;
|
|
232
|
+
let seq = 0;
|
|
233
|
+
return gates.listen((signal) => {
|
|
234
|
+
const s = stepsRef.current;
|
|
235
|
+
if (!s) return;
|
|
236
|
+
const i = indexRef.current;
|
|
237
|
+
if (signal.kind === "refuse") {
|
|
238
|
+
if (s[i]?.awaitGate === signal.key) setRefusal({ step: s[i], reason: signal.reason, seq: ++seq });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const awaiting = s[i]?.awaitGate === signal.key ? i : s.findIndex((st, k) => k > i && st.awaitGate === signal.key);
|
|
242
|
+
if (awaiting < 0) return;
|
|
243
|
+
const to = forward(s, awaiting + 1, "next");
|
|
244
|
+
if (to < 0) finish();
|
|
245
|
+
else arrive(s, to, "next");
|
|
246
|
+
});
|
|
247
|
+
}, [active, gates, arrive, forward, finish]);
|
|
248
|
+
const value = {
|
|
249
|
+
active,
|
|
250
|
+
activeId,
|
|
251
|
+
start,
|
|
252
|
+
stop,
|
|
253
|
+
next,
|
|
254
|
+
prev,
|
|
255
|
+
goToStep,
|
|
256
|
+
index,
|
|
257
|
+
total,
|
|
258
|
+
position: active ? nav.position : 0,
|
|
259
|
+
count: active ? nav.count : 0
|
|
260
|
+
};
|
|
261
|
+
useLayoutEffect(() => {
|
|
262
|
+
live.set(value);
|
|
263
|
+
});
|
|
264
|
+
return /* @__PURE__ */ jsx(TourInternalContext.Provider, { value: internals, children: /* @__PURE__ */ jsxs(TourContext.Provider, { value, children: [
|
|
121
265
|
children,
|
|
122
266
|
active && /* @__PURE__ */ jsx(
|
|
123
267
|
TourOverlay,
|
|
124
268
|
{
|
|
125
269
|
steps,
|
|
126
270
|
index,
|
|
271
|
+
via: nav.via,
|
|
272
|
+
floor: nav.floor,
|
|
273
|
+
position: nav.position,
|
|
274
|
+
count: nav.count,
|
|
275
|
+
refusal: refusal?.step === steps[index] ? refusal : null,
|
|
276
|
+
onGoTo: goToStep,
|
|
127
277
|
labels: merged,
|
|
278
|
+
gates,
|
|
279
|
+
dockOnPhones,
|
|
128
280
|
onNext: next,
|
|
129
281
|
onPrev: prev,
|
|
130
282
|
onSkip: skip
|
|
131
283
|
}
|
|
132
284
|
)
|
|
133
|
-
] });
|
|
285
|
+
] }) });
|
|
286
|
+
}
|
|
287
|
+
const NO_NAV = { index: 0, floor: 0, via: "start", position: 0, count: 0 };
|
|
288
|
+
const NOOP = () => {
|
|
289
|
+
};
|
|
290
|
+
function stepContext(gates, via) {
|
|
291
|
+
return { gate: (key) => gates.get(key), via };
|
|
292
|
+
}
|
|
293
|
+
function resolveAt(steps, at) {
|
|
294
|
+
if (typeof at === "number") return Math.min(Math.max(0, Math.floor(at)), Math.max(0, steps.length - 1));
|
|
295
|
+
const anchor = `data-tour="${at}"`;
|
|
296
|
+
const found = steps.findIndex((step) => step.target?.includes(anchor));
|
|
297
|
+
if (found >= 0) return found;
|
|
298
|
+
if (devWarnings()) console.warn(`[ui-kit] A tour was started at "${at}", but no step targets [${anchor}]: it starts at 0.`);
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
function firstEmptied(steps, floor, index, gates) {
|
|
302
|
+
const ctx = stepContext(gates, "goto");
|
|
303
|
+
for (let j = floor; j < index; j++) {
|
|
304
|
+
const key = steps[j].gate;
|
|
305
|
+
if (key && gates.get(key) === false && isShown(steps[j], ctx)) return j;
|
|
306
|
+
}
|
|
307
|
+
return -1;
|
|
308
|
+
}
|
|
309
|
+
function isShown(step, ctx) {
|
|
310
|
+
if (!step.when) return true;
|
|
311
|
+
try {
|
|
312
|
+
return step.when(ctx) === true;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error(error);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
134
317
|
}
|
|
135
318
|
function isVisible(el) {
|
|
136
319
|
const r = el.getBoundingClientRect();
|
|
@@ -149,29 +332,142 @@ function rectOf(el) {
|
|
|
149
332
|
const r = el.getBoundingClientRect();
|
|
150
333
|
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
151
334
|
}
|
|
335
|
+
const RING_BAND = 6;
|
|
336
|
+
function spotRectOf(el, step) {
|
|
337
|
+
const own = rectOf(el);
|
|
338
|
+
if (!isFieldStep(step)) return own;
|
|
339
|
+
const ids = /* @__PURE__ */ new Set();
|
|
340
|
+
const holders = [el, ...Array.from(el.querySelectorAll("[aria-describedby]"))];
|
|
341
|
+
for (const holder of holders) {
|
|
342
|
+
for (const id of (holder.getAttribute("aria-describedby") ?? "").split(/\s+/)) if (id) ids.add(id);
|
|
343
|
+
}
|
|
344
|
+
if (ids.size === 0) return own;
|
|
345
|
+
const lines = [];
|
|
346
|
+
for (const id of ids) {
|
|
347
|
+
const line = document.getElementById(id);
|
|
348
|
+
if (!line || el.contains(line) || line.closest('[role="tooltip"]') || !isVisible(line)) continue;
|
|
349
|
+
const r = line.getBoundingClientRect();
|
|
350
|
+
if (r.width > 1 && r.height > 1) lines.push(r);
|
|
351
|
+
}
|
|
352
|
+
let top = own.top;
|
|
353
|
+
let left = own.left;
|
|
354
|
+
let right = own.left + own.width;
|
|
355
|
+
let bottom = own.top + own.height;
|
|
356
|
+
const reach = (step.padding ?? 8) + RING_BAND;
|
|
357
|
+
for (let grew = true; grew; ) {
|
|
358
|
+
grew = false;
|
|
359
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
360
|
+
const r = lines[i];
|
|
361
|
+
const across = r.left < right && r.right > left;
|
|
362
|
+
const gap = Math.max(r.top - bottom, top - r.bottom);
|
|
363
|
+
if (!across || gap > reach) continue;
|
|
364
|
+
top = Math.min(top, r.top);
|
|
365
|
+
left = Math.min(left, r.left);
|
|
366
|
+
right = Math.max(right, r.right);
|
|
367
|
+
bottom = Math.max(bottom, r.bottom);
|
|
368
|
+
lines.splice(i, 1);
|
|
369
|
+
grew = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return { top, left, width: right - left, height: bottom - top };
|
|
373
|
+
}
|
|
374
|
+
function layerHost(el) {
|
|
375
|
+
return el.closest("[data-kit-layer]");
|
|
376
|
+
}
|
|
377
|
+
function coversViewport(host, el) {
|
|
378
|
+
let surface = host;
|
|
379
|
+
if (host.getAttribute("data-kit-layer") === "backdrop") {
|
|
380
|
+
for (const child of Array.from(host.children)) {
|
|
381
|
+
if (child.contains(el)) {
|
|
382
|
+
surface = child;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const r = surface.getBoundingClientRect();
|
|
388
|
+
const vv = window.visualViewport;
|
|
389
|
+
const top = vv?.offsetTop ?? 0;
|
|
390
|
+
const bottom = top + (vv?.height ?? window.innerHeight);
|
|
391
|
+
const width = vv?.width ?? window.innerWidth;
|
|
392
|
+
return r.width > 0 && r.left <= 1 && r.top <= top + 1 && r.right >= width - 1 && r.bottom >= bottom - 1;
|
|
393
|
+
}
|
|
394
|
+
function docks(step, dockOnPhones) {
|
|
395
|
+
if (typeof window === "undefined" || window.innerWidth >= DOCK_MAX_WIDTH || !step.target) return false;
|
|
396
|
+
return step.placement === "dock" || step.placement === void 0 && dockOnPhones && isFieldStep(step);
|
|
397
|
+
}
|
|
398
|
+
function isFieldStep(step) {
|
|
399
|
+
return !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
|
|
400
|
+
}
|
|
401
|
+
function firstFocusable(el) {
|
|
402
|
+
if (el instanceof HTMLElement && el.matches(FOCUSABLE_CONTROL)) return el;
|
|
403
|
+
return el.querySelector(FOCUSABLE_CONTROL);
|
|
404
|
+
}
|
|
405
|
+
function tabStops(el) {
|
|
406
|
+
const all = el.matches(FOCUSABLE_CONTROL) ? [el] : [];
|
|
407
|
+
return all.concat(
|
|
408
|
+
Array.from(el.querySelectorAll(FOCUSABLE_CONTROL)).filter(
|
|
409
|
+
(x) => x.tabIndex >= 0 && !x.closest("[hidden],[inert]")
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const FOCUSABLE_CONTROL = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
414
|
+
function coarsePointer() {
|
|
415
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : false;
|
|
416
|
+
}
|
|
152
417
|
function TourOverlay({
|
|
153
418
|
steps,
|
|
154
419
|
index,
|
|
420
|
+
via,
|
|
421
|
+
floor,
|
|
422
|
+
position,
|
|
423
|
+
count,
|
|
424
|
+
refusal,
|
|
425
|
+
onGoTo,
|
|
155
426
|
labels,
|
|
427
|
+
gates,
|
|
428
|
+
dockOnPhones,
|
|
156
429
|
onNext,
|
|
157
430
|
onPrev,
|
|
158
431
|
onSkip
|
|
159
432
|
}) {
|
|
160
433
|
const step = steps[index];
|
|
161
|
-
const isFirst =
|
|
162
|
-
const isLast =
|
|
434
|
+
const isFirst = position <= 1;
|
|
435
|
+
const isLast = position >= count;
|
|
163
436
|
const [located, setLocated] = useState(null);
|
|
164
437
|
const current = located !== null && located.index === index && located.step === step ? located : null;
|
|
165
438
|
const ready = current !== null;
|
|
166
439
|
const rect = current?.rect ?? null;
|
|
167
440
|
const dir = current?.dir ?? "ltr";
|
|
168
441
|
const rootRef = useRef(null);
|
|
442
|
+
const cardRef = useRef(null);
|
|
443
|
+
const bodyId = useId();
|
|
444
|
+
const hintId = useId();
|
|
445
|
+
const gateVersion = useSyncExternalStore(gates.subscribe, gates.version, gates.version);
|
|
446
|
+
const gateKey = step.gate;
|
|
447
|
+
const enforced = !!gateKey && gates.has(gateKey);
|
|
448
|
+
const locked = enforced && gates.get(gateKey) !== true;
|
|
449
|
+
const [polite, setPolite] = useState("");
|
|
450
|
+
const announce = useCallback((text) => {
|
|
451
|
+
setPolite((prev) => prev === text ? `${text}\u200B` : text);
|
|
452
|
+
}, []);
|
|
169
453
|
useEffect(() => {
|
|
170
454
|
let cancelled = false;
|
|
171
455
|
let raf = 0;
|
|
172
|
-
const settle = (el) =>
|
|
456
|
+
const settle = (el) => {
|
|
457
|
+
let layer2 = null;
|
|
458
|
+
let fullScreen = false;
|
|
459
|
+
if (el && isFieldStep(step)) {
|
|
460
|
+
const host = layerHost(el);
|
|
461
|
+
layer2 = document.createElement("div");
|
|
462
|
+
layer2.setAttribute("data-tour-layer", "");
|
|
463
|
+
if (host) host.after(layer2);
|
|
464
|
+
else document.body.appendChild(layer2);
|
|
465
|
+
fullScreen = !!host && coversViewport(host, el);
|
|
466
|
+
}
|
|
467
|
+
setLocated({ index, step, rect: el ? spotRectOf(el, step) : null, dir: dirOf(el), layer: layer2, fullScreen });
|
|
468
|
+
};
|
|
173
469
|
void (async () => {
|
|
174
|
-
await step.beforeStep?.();
|
|
470
|
+
await step.beforeStep?.(stepContext(gates, via));
|
|
175
471
|
if (cancelled) return;
|
|
176
472
|
if (!step.target) {
|
|
177
473
|
settle(null);
|
|
@@ -182,7 +478,8 @@ function TourOverlay({
|
|
|
182
478
|
if (cancelled) return;
|
|
183
479
|
const el = queryVisibleTarget(step.target);
|
|
184
480
|
if (el) {
|
|
185
|
-
|
|
481
|
+
if (docks(step, dockOnPhones)) scrollIntoFreeHalf(el, null, null);
|
|
482
|
+
else el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
186
483
|
settle(el);
|
|
187
484
|
return;
|
|
188
485
|
}
|
|
@@ -201,10 +498,15 @@ function TourOverlay({
|
|
|
201
498
|
const update = () => {
|
|
202
499
|
const el = queryVisibleTarget(step.target);
|
|
203
500
|
if (el) {
|
|
204
|
-
const next =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
501
|
+
const next = spotRectOf(el, step);
|
|
502
|
+
const host = layerHost(el);
|
|
503
|
+
const full = !!host && coversViewport(host, el);
|
|
504
|
+
setLocated((prev) => {
|
|
505
|
+
if (!prev || prev.index !== index || prev.step !== step) return prev;
|
|
506
|
+
const fullScreen = prev.layer ? full : prev.fullScreen;
|
|
507
|
+
if (sameRect(prev.rect, next) && fullScreen === prev.fullScreen) return prev;
|
|
508
|
+
return { ...prev, rect: next, fullScreen };
|
|
509
|
+
});
|
|
208
510
|
}
|
|
209
511
|
};
|
|
210
512
|
window.addEventListener("scroll", update, true);
|
|
@@ -216,6 +518,23 @@ function TourOverlay({
|
|
|
216
518
|
clearInterval(id);
|
|
217
519
|
};
|
|
218
520
|
}, [index, step]);
|
|
521
|
+
const layer = located?.layer ?? null;
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
if (!layer) return;
|
|
524
|
+
return () => layer.remove();
|
|
525
|
+
}, [layer]);
|
|
526
|
+
useDismissLayer(
|
|
527
|
+
ready,
|
|
528
|
+
(e) => {
|
|
529
|
+
if (fieldStepNow && step.target && e.target instanceof Node) {
|
|
530
|
+
const el = queryVisibleTarget(step.target);
|
|
531
|
+
if (el?.contains(e.target)) return;
|
|
532
|
+
}
|
|
533
|
+
onSkip();
|
|
534
|
+
},
|
|
535
|
+
// Not an overlay to `useOverlayOpen`: a floating launcher a tour spotlights stays.
|
|
536
|
+
{ overlay: false }
|
|
537
|
+
);
|
|
219
538
|
const spotted = rect !== null;
|
|
220
539
|
useEffect(() => {
|
|
221
540
|
if (!ready || !spotted || !step.awaitClick || !step.target) return;
|
|
@@ -225,7 +544,112 @@ function TourOverlay({
|
|
|
225
544
|
el.addEventListener("click", onClick, { once: true });
|
|
226
545
|
return () => el.removeEventListener("click", onClick);
|
|
227
546
|
}, [ready, spotted, index, step, onNext]);
|
|
228
|
-
const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput);
|
|
547
|
+
const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
|
|
548
|
+
const guided = interactive && !!(step.gate || step.awaitInput || step.awaitGate);
|
|
549
|
+
const fieldStepNow = interactive;
|
|
550
|
+
const awaitsRelease = spotted && !!step.target && !!step.awaitGate;
|
|
551
|
+
const warnedGateRef = useRef(null);
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
if (!ready || !gateKey || gates.has(gateKey) || !devWarnings() || warnedGateRef.current === step) return;
|
|
554
|
+
warnedGateRef.current = step;
|
|
555
|
+
console.warn(
|
|
556
|
+
`[ui-kit] Tour step "${step.title}" waits for the gate "${gateKey}", which nothing publishes: its Next is open. Publish it with useTourGate beside the field, or the form has closed under the tour.`
|
|
557
|
+
);
|
|
558
|
+
}, [ready, gateKey, gates, step, gateVersion]);
|
|
559
|
+
useEffect(() => {
|
|
560
|
+
if (!ready || !step.optional || step.gate || !devWarnings()) return;
|
|
561
|
+
console.warn(
|
|
562
|
+
`[ui-kit] Tour step "${step.title}" is \`optional\` without a \`gate\`: it shows the Optional chip and a plain Next. "Leave empty" needs a gate to compare.`
|
|
563
|
+
);
|
|
564
|
+
}, [ready, step]);
|
|
565
|
+
const wasLockedRef = useRef(null);
|
|
566
|
+
useEffect(() => {
|
|
567
|
+
if (!ready || !gateKey) return;
|
|
568
|
+
const before = wasLockedRef.current;
|
|
569
|
+
if (before && before.step === step && before.locked && !locked && enforced) announce(labels.gateReady);
|
|
570
|
+
wasLockedRef.current = { step, locked };
|
|
571
|
+
}, [ready, gateKey, step, locked, enforced, announce, labels.gateReady]);
|
|
572
|
+
useEffect(() => {
|
|
573
|
+
if (!ready || !guided || !step.target) return;
|
|
574
|
+
const el = queryVisibleTarget(step.target);
|
|
575
|
+
const control = el ? firstFocusable(el) : null;
|
|
576
|
+
const previous = control ? control.getAttribute("aria-describedby") : null;
|
|
577
|
+
if (control) control.setAttribute("aria-describedby", previous ? `${previous} ${bodyId}` : bodyId);
|
|
578
|
+
if (coarsePointer() || !control) cardRef.current?.focus();
|
|
579
|
+
else if (!el.contains(document.activeElement)) focusQuietly(control);
|
|
580
|
+
if (!control) return;
|
|
581
|
+
return () => {
|
|
582
|
+
if (previous === null) control.removeAttribute("aria-describedby");
|
|
583
|
+
else control.setAttribute("aria-describedby", previous);
|
|
584
|
+
};
|
|
585
|
+
}, [ready, guided, index, step, bodyId]);
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
if (!ready || !guided || !step.target) return;
|
|
588
|
+
const onTab = (e) => {
|
|
589
|
+
if (e.key !== "Tab") return;
|
|
590
|
+
const el = queryVisibleTarget(step.target);
|
|
591
|
+
const card = cardRef.current;
|
|
592
|
+
if (!el || !card) return;
|
|
593
|
+
const active = document.activeElement;
|
|
594
|
+
const fieldStops = tabStops(el);
|
|
595
|
+
const cardStops = tabStops(card);
|
|
596
|
+
if (fieldStops.length === 0 || cardStops.length === 0) return;
|
|
597
|
+
let to = null;
|
|
598
|
+
if (active instanceof Node && el.contains(active)) {
|
|
599
|
+
if (!e.shiftKey && active === fieldStops[fieldStops.length - 1]) to = cardStops[0];
|
|
600
|
+
else if (e.shiftKey && active === fieldStops[0]) to = cardStops[cardStops.length - 1];
|
|
601
|
+
} else if (active instanceof Node && card.contains(active)) {
|
|
602
|
+
if (!e.shiftKey && active === cardStops[cardStops.length - 1]) to = fieldStops[0];
|
|
603
|
+
else if (e.shiftKey && (active === cardStops[0] || active === card)) to = fieldStops[fieldStops.length - 1];
|
|
604
|
+
}
|
|
605
|
+
if (!to) return;
|
|
606
|
+
e.preventDefault();
|
|
607
|
+
e.stopPropagation();
|
|
608
|
+
to.focus();
|
|
609
|
+
};
|
|
610
|
+
document.addEventListener("keydown", onTab, true);
|
|
611
|
+
return () => document.removeEventListener("keydown", onTab, true);
|
|
612
|
+
}, [ready, guided, index, step]);
|
|
613
|
+
useEffect(() => {
|
|
614
|
+
if (!ready || !spotted || !gateKey || !step.target) return;
|
|
615
|
+
const el = queryVisibleTarget(step.target);
|
|
616
|
+
if (!el) return;
|
|
617
|
+
const satisfied = () => !gates.has(gateKey) || gates.get(gateKey) === true;
|
|
618
|
+
let timer = 0;
|
|
619
|
+
let armed = el.contains(document.activeElement);
|
|
620
|
+
const check = () => {
|
|
621
|
+
window.clearTimeout(timer);
|
|
622
|
+
timer = window.setTimeout(() => {
|
|
623
|
+
if (satisfied()) onNext();
|
|
624
|
+
}, 0);
|
|
625
|
+
};
|
|
626
|
+
const onFocusIn = () => {
|
|
627
|
+
armed = true;
|
|
628
|
+
};
|
|
629
|
+
const onFocusOut = (e) => {
|
|
630
|
+
const to = e.relatedTarget;
|
|
631
|
+
if (to instanceof Node && el.contains(to)) return;
|
|
632
|
+
const wasArmed = armed;
|
|
633
|
+
armed = false;
|
|
634
|
+
if (!wasArmed) return;
|
|
635
|
+
if (to === null || to instanceof Node && rootRef.current?.contains(to)) check();
|
|
636
|
+
};
|
|
637
|
+
const onKeyDown = (e) => {
|
|
638
|
+
if (e.key !== "Enter" || !(e.target instanceof Node) || !el.contains(e.target)) return;
|
|
639
|
+
if (e.defaultPrevented || isComposing(e)) return;
|
|
640
|
+
if (e.target instanceof HTMLTextAreaElement && !(e.ctrlKey || e.metaKey)) return;
|
|
641
|
+
check();
|
|
642
|
+
};
|
|
643
|
+
el.addEventListener("focusin", onFocusIn);
|
|
644
|
+
el.addEventListener("focusout", onFocusOut);
|
|
645
|
+
document.addEventListener("keydown", onKeyDown);
|
|
646
|
+
return () => {
|
|
647
|
+
window.clearTimeout(timer);
|
|
648
|
+
el.removeEventListener("focusin", onFocusIn);
|
|
649
|
+
el.removeEventListener("focusout", onFocusOut);
|
|
650
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
651
|
+
};
|
|
652
|
+
}, [ready, spotted, gateKey, gates, index, step, onNext]);
|
|
229
653
|
useEffect(() => {
|
|
230
654
|
if (!ready || !spotted || !step.awaitInput || !step.target) return;
|
|
231
655
|
const el = queryVisibleTarget(step.target);
|
|
@@ -259,34 +683,44 @@ function TourOverlay({
|
|
|
259
683
|
el.removeEventListener("change", check);
|
|
260
684
|
};
|
|
261
685
|
}, [ready, spotted, index, step, onNext]);
|
|
686
|
+
const gateHint = step.gateHint ?? labels.gateHint;
|
|
687
|
+
const lockedNext = useCallback(() => {
|
|
688
|
+
const el = step.target ? queryVisibleTarget(step.target) : null;
|
|
689
|
+
const control = el ? firstFocusable(el) : null;
|
|
690
|
+
if (control) focusQuietly(control);
|
|
691
|
+
announce(gateHint);
|
|
692
|
+
}, [step.target, announce, gateHint]);
|
|
693
|
+
const tryNext = awaitsRelease ? NOOP : locked ? lockedNext : onNext;
|
|
694
|
+
const backTo = refusal ? firstEmptied(steps, floor, index, gates) : -1;
|
|
262
695
|
useEffect(() => {
|
|
263
696
|
const onKey = (e) => {
|
|
264
697
|
if (interactive && e.target instanceof Node && e.target !== document.body && !rootRef.current?.contains(e.target)) {
|
|
265
698
|
return;
|
|
266
699
|
}
|
|
267
700
|
if (e.key === "Escape") {
|
|
268
|
-
onSkip();
|
|
701
|
+
if (!ready) onSkip();
|
|
269
702
|
return;
|
|
270
703
|
}
|
|
271
704
|
if (e.key === "Enter") {
|
|
272
705
|
if (step.awaitClick || step.awaitInput || ownsKey(e.target, "Enter")) return;
|
|
273
706
|
e.preventDefault();
|
|
274
|
-
|
|
707
|
+
tryNext();
|
|
275
708
|
return;
|
|
276
709
|
}
|
|
277
710
|
if (ownsKey(e.target, e.key)) return;
|
|
278
711
|
const delta = horizontalStep(e.key, rootRef.current);
|
|
279
712
|
if (delta === 1) {
|
|
280
713
|
e.preventDefault();
|
|
281
|
-
|
|
714
|
+
tryNext();
|
|
282
715
|
} else if (delta === -1) {
|
|
283
716
|
if (!isFirst) onPrev();
|
|
284
717
|
}
|
|
285
718
|
};
|
|
286
719
|
document.addEventListener("keydown", onKey);
|
|
287
720
|
return () => document.removeEventListener("keydown", onKey);
|
|
288
|
-
}, [isFirst, step, interactive,
|
|
721
|
+
}, [isFirst, step, interactive, tryNext, onPrev, onSkip, ready]);
|
|
289
722
|
if (!ready) return null;
|
|
723
|
+
const bare = interactive && !!current?.fullScreen;
|
|
290
724
|
const pad = step.padding ?? 8;
|
|
291
725
|
const spot = rect ? { top: rect.top - pad, left: rect.left - pad, width: rect.width + pad * 2, height: rect.height + pad * 2 } : null;
|
|
292
726
|
return createPortal(
|
|
@@ -294,15 +728,16 @@ function TourOverlay({
|
|
|
294
728
|
"div",
|
|
295
729
|
{
|
|
296
730
|
ref: rootRef,
|
|
297
|
-
className: cn("pointer-events-none fixed inset-0", interactive ? "z-
|
|
731
|
+
className: cn("pointer-events-none fixed inset-0", interactive ? "z-50" : "z-[60]"),
|
|
298
732
|
role: "dialog",
|
|
299
733
|
"aria-modal": interactive ? void 0 : "true",
|
|
300
734
|
"data-tour-overlay": "",
|
|
301
735
|
"data-tour-interactive": interactive ? "" : void 0,
|
|
736
|
+
"data-tour-bare": bare ? "" : void 0,
|
|
302
737
|
"aria-label": step.title,
|
|
303
738
|
dir,
|
|
304
739
|
children: [
|
|
305
|
-
spot && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
|
|
740
|
+
spot && !bare && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
|
|
306
741
|
spot ? /* @__PURE__ */ jsx(
|
|
307
742
|
"div",
|
|
308
743
|
{
|
|
@@ -330,7 +765,7 @@ function TourOverlay({
|
|
|
330
765
|
"0 0 0 3px rgba(255,255,255,0.98)",
|
|
331
766
|
"0 0 0 5px rgba(15,23,42,0.92)",
|
|
332
767
|
"0 0 18px 6px rgba(15,23,42,0.45)",
|
|
333
|
-
"0 0 0 9999px rgba(15,23,42,0.70)"
|
|
768
|
+
...bare ? [] : ["0 0 0 9999px rgba(15,23,42,0.70)"]
|
|
334
769
|
].join(", ")
|
|
335
770
|
}
|
|
336
771
|
}
|
|
@@ -338,23 +773,36 @@ function TourOverlay({
|
|
|
338
773
|
/* @__PURE__ */ jsx(
|
|
339
774
|
TourCard,
|
|
340
775
|
{
|
|
776
|
+
cardRef,
|
|
341
777
|
step,
|
|
342
778
|
index,
|
|
343
|
-
|
|
779
|
+
position,
|
|
780
|
+
total: count,
|
|
344
781
|
labels,
|
|
345
782
|
isFirst,
|
|
346
783
|
isLast,
|
|
347
784
|
rect: spot,
|
|
348
785
|
dir,
|
|
786
|
+
trap: !guided,
|
|
787
|
+
bodyId,
|
|
788
|
+
hintId,
|
|
789
|
+
locked,
|
|
790
|
+
gateHint,
|
|
349
791
|
onBack: onPrev,
|
|
350
|
-
onNext,
|
|
351
|
-
|
|
792
|
+
onNext: tryNext,
|
|
793
|
+
onLeaveEmpty: onNext,
|
|
794
|
+
onSkip,
|
|
795
|
+
awaitsRelease,
|
|
796
|
+
docked: spot !== null && docks(step, dockOnPhones),
|
|
797
|
+
refusal,
|
|
798
|
+
backTo: backTo >= 0 ? { title: steps[backTo].title, go: () => onGoTo(backTo) } : null
|
|
352
799
|
}
|
|
353
|
-
)
|
|
800
|
+
),
|
|
801
|
+
/* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: polite })
|
|
354
802
|
]
|
|
355
803
|
}
|
|
356
804
|
),
|
|
357
|
-
document.body
|
|
805
|
+
interactive && current?.layer || document.body
|
|
358
806
|
);
|
|
359
807
|
}
|
|
360
808
|
function fieldValues(el) {
|
|
@@ -433,38 +881,108 @@ function placeCard(rect, cw, ch, vw, vh, m, placement, dir = "ltr") {
|
|
|
433
881
|
}
|
|
434
882
|
}
|
|
435
883
|
function TourCard({
|
|
884
|
+
cardRef: ref,
|
|
436
885
|
step,
|
|
437
886
|
index,
|
|
887
|
+
position,
|
|
438
888
|
total,
|
|
439
889
|
labels,
|
|
440
890
|
isFirst,
|
|
441
891
|
isLast,
|
|
442
892
|
rect,
|
|
443
893
|
dir,
|
|
894
|
+
trap,
|
|
895
|
+
bodyId,
|
|
896
|
+
hintId,
|
|
897
|
+
locked,
|
|
898
|
+
gateHint,
|
|
444
899
|
onBack,
|
|
445
900
|
onNext,
|
|
446
|
-
|
|
901
|
+
onLeaveEmpty,
|
|
902
|
+
onSkip,
|
|
903
|
+
awaitsRelease,
|
|
904
|
+
docked,
|
|
905
|
+
refusal,
|
|
906
|
+
backTo
|
|
447
907
|
}) {
|
|
448
|
-
const ref = useRef(null);
|
|
449
908
|
const [style, setStyle] = useState({ opacity: 0 });
|
|
450
|
-
useFocusTrap(ref, { active:
|
|
909
|
+
useFocusTrap(ref, { active: trap });
|
|
910
|
+
const edgeRef = useRef(null);
|
|
911
|
+
const [bandTick, setBandTick] = useState(0);
|
|
912
|
+
const [edge, setEdge] = useState(null);
|
|
913
|
+
useEffect(() => {
|
|
914
|
+
if (!docked) return;
|
|
915
|
+
let last = "";
|
|
916
|
+
const look = () => {
|
|
917
|
+
const target = step.target ? queryVisibleTarget(step.target) : null;
|
|
918
|
+
const band = readBand(target, ref.current);
|
|
919
|
+
const key = `${band.top}:${band.bottom}`;
|
|
920
|
+
if (key === last) return;
|
|
921
|
+
last = key;
|
|
922
|
+
if (target) {
|
|
923
|
+
const r = target.getBoundingClientRect();
|
|
924
|
+
if (r.top < band.top || r.bottom > band.bottom) {
|
|
925
|
+
edgeRef.current = scrollIntoFreeHalf(target, ref.current, edgeRef.current);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
setBandTick((t) => t + 1);
|
|
929
|
+
};
|
|
930
|
+
const vv = window.visualViewport;
|
|
931
|
+
vv?.addEventListener("resize", look);
|
|
932
|
+
vv?.addEventListener("scroll", look);
|
|
933
|
+
window.addEventListener("resize", look);
|
|
934
|
+
const id = window.setInterval(look, 250);
|
|
935
|
+
return () => {
|
|
936
|
+
vv?.removeEventListener("resize", look);
|
|
937
|
+
vv?.removeEventListener("scroll", look);
|
|
938
|
+
window.removeEventListener("resize", look);
|
|
939
|
+
clearInterval(id);
|
|
940
|
+
};
|
|
941
|
+
}, [docked, step.target, ref]);
|
|
451
942
|
useLayoutEffect(() => {
|
|
452
943
|
const card = ref.current;
|
|
453
944
|
if (!card) return;
|
|
945
|
+
if (docked) {
|
|
946
|
+
const target = step.target ? queryVisibleTarget(step.target) : null;
|
|
947
|
+
const band = readBand(target, card);
|
|
948
|
+
const next = rect ? dockEdge(rect, band, edgeRef.current, card.offsetHeight) : "bottom";
|
|
949
|
+
edgeRef.current = next;
|
|
950
|
+
setEdge(next);
|
|
951
|
+
setStyle(dockStyle(next, rect, band));
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
setEdge(null);
|
|
454
955
|
setStyle(
|
|
455
956
|
placeCard(rect, card.offsetWidth, card.offsetHeight, window.innerWidth, window.innerHeight, 12, step.placement, dir)
|
|
456
957
|
);
|
|
457
|
-
}, [rect, index, step.placement, dir]);
|
|
958
|
+
}, [ref, rect, index, step.placement, step.target, dir, docked, bandTick]);
|
|
959
|
+
const awaitsTarget = (step.awaitClick || step.awaitInput) && step.target && rect || awaitsRelease;
|
|
458
960
|
return /* @__PURE__ */ jsxs(
|
|
459
961
|
"div",
|
|
460
962
|
{
|
|
461
963
|
ref,
|
|
462
964
|
tabIndex: -1,
|
|
463
965
|
style,
|
|
464
|
-
|
|
966
|
+
"data-tour-card": "",
|
|
967
|
+
"data-tour-docked": edge ?? void 0,
|
|
968
|
+
className: cn(
|
|
969
|
+
"pointer-events-auto fixed z-[61] w-80 max-w-[calc(100vw-1.5rem)] rounded-lg border border-subtle bg-surface p-4 shadow-xl outline-none transition-all duration-200 ease-out",
|
|
970
|
+
// Docked, the height is capped: the body scrolls, the title and buttons stay.
|
|
971
|
+
docked && "flex flex-col"
|
|
972
|
+
),
|
|
465
973
|
children: [
|
|
466
|
-
/* @__PURE__ */
|
|
467
|
-
|
|
974
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-sm font-semibold text-primary", children: [
|
|
975
|
+
/* @__PURE__ */ jsx("span", { children: step.title }),
|
|
976
|
+
step.optional && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-subtle px-1.5 py-px text-xs font-normal text-muted", children: labels.optional })
|
|
977
|
+
] }),
|
|
978
|
+
/* @__PURE__ */ jsx(
|
|
979
|
+
"div",
|
|
980
|
+
{
|
|
981
|
+
id: bodyId,
|
|
982
|
+
className: cn("mt-1 text-sm leading-relaxed text-secondary", docked && "min-h-0 shrink overflow-y-auto"),
|
|
983
|
+
children: step.body
|
|
984
|
+
}
|
|
985
|
+
),
|
|
468
986
|
step.action && /* @__PURE__ */ jsx("div", { className: "mt-3", children: step.action }),
|
|
469
987
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2", children: [
|
|
470
988
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
@@ -478,12 +996,31 @@ function TourCard({
|
|
|
478
996
|
}
|
|
479
997
|
),
|
|
480
998
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
481
|
-
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(
|
|
999
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(position, total) }),
|
|
482
1000
|
!isFirst && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onBack, className: "px-2.5 py-1 text-xs", children: labels.back }),
|
|
483
|
-
|
|
1001
|
+
step.optional && locked && !awaitsTarget && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onLeaveEmpty, className: "px-2.5 py-1 text-xs", children: labels.leaveEmpty }),
|
|
1002
|
+
!awaitsTarget && /* @__PURE__ */ jsx(
|
|
1003
|
+
Button,
|
|
1004
|
+
{
|
|
1005
|
+
variant: "accent",
|
|
1006
|
+
onClick: onNext,
|
|
1007
|
+
"aria-disabled": locked || void 0,
|
|
1008
|
+
"aria-describedby": locked ? hintId : void 0,
|
|
1009
|
+
className: cn("px-2.5 py-1 text-xs", locked && "opacity-60"),
|
|
1010
|
+
children: isLast ? labels.done : labels.next
|
|
1011
|
+
}
|
|
1012
|
+
)
|
|
484
1013
|
] })
|
|
485
1014
|
] }),
|
|
486
|
-
|
|
1015
|
+
awaitsTarget && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: awaitsRelease ? step.awaitGateHint ?? labels.awaitGateHint : step.awaitInput && !step.awaitClick ? labels.awaitInputHint : labels.awaitClickHint }),
|
|
1016
|
+
awaitsRelease && /* @__PURE__ */ jsxs("div", { "aria-live": "assertive", "aria-atomic": "true", className: "space-y-1", children: [
|
|
1017
|
+
refusal && /* @__PURE__ */ jsxs("p", { "data-tour-refused": "", className: "text-xs text-danger", children: [
|
|
1018
|
+
refusal.reason ?? labels.awaitGateRefused,
|
|
1019
|
+
refusal.seq % 2 === 0 ? "\u200B" : ""
|
|
1020
|
+
] }),
|
|
1021
|
+
refusal && backTo && /* @__PURE__ */ jsx(Button, { variant: "link", onClick: backTo.go, className: "text-xs", children: labels.backToStep(backTo.title) })
|
|
1022
|
+
] }),
|
|
1023
|
+
locked && !awaitsTarget && /* @__PURE__ */ jsx("div", { id: hintId, className: "text-center text-xs italic text-placeholder", children: gateHint })
|
|
487
1024
|
] })
|
|
488
1025
|
]
|
|
489
1026
|
}
|
|
@@ -493,6 +1030,8 @@ export {
|
|
|
493
1030
|
DEFAULT_TOUR_LABELS,
|
|
494
1031
|
TourProvider,
|
|
495
1032
|
useTour,
|
|
1033
|
+
useTourGate,
|
|
1034
|
+
useTourGateRelease,
|
|
496
1035
|
useTourOptional
|
|
497
1036
|
};
|
|
498
1037
|
//# sourceMappingURL=tour.js.map
|