@sphynxoid/stepwise 0.1.0 → 0.2.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/README.md +150 -127
- package/dist/adapters/driver-js.d.ts +1 -1
- package/dist/adapters/gsap.d.ts +1 -1
- package/dist/adapters/scroll.d.ts +1 -1
- package/dist/index.d.ts +204 -5
- package/dist/index.js +535 -148
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -0
- package/dist/{types-CPf9ZD_Z.d.ts → types-IT_9L8On.d.ts} +8 -3
- package/package.json +101 -86
package/dist/index.js
CHANGED
|
@@ -59,8 +59,17 @@ function useSteps(items, opts) {
|
|
|
59
59
|
|
|
60
60
|
// src/use-step-player.ts
|
|
61
61
|
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
62
|
-
function useStepPlayer({
|
|
62
|
+
function useStepPlayer({
|
|
63
|
+
total,
|
|
64
|
+
baseDelayMs = 900,
|
|
65
|
+
autoPlay = false,
|
|
66
|
+
engine,
|
|
67
|
+
syncHash,
|
|
68
|
+
onEnd,
|
|
69
|
+
respectReducedMotion = true
|
|
70
|
+
}) {
|
|
63
71
|
const core = useSteps(total);
|
|
72
|
+
const { go, next: coreNext, prev: corePrev, first } = core;
|
|
64
73
|
const [started, setStarted] = useState2(autoPlay && total > 0);
|
|
65
74
|
const [playing, setPlaying] = useState2(autoPlay && total > 0);
|
|
66
75
|
const [speed, setSpeed] = useState2(1);
|
|
@@ -81,10 +90,16 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
81
90
|
}, []);
|
|
82
91
|
const index = started && core.index >= 0 ? core.index : -1;
|
|
83
92
|
const atEnd = total > 0 && index >= 0 && index >= total - 1;
|
|
84
|
-
const engineRef = useRef2(engine);
|
|
85
|
-
engineRef.current = engine;
|
|
86
93
|
const totalRef = useRef2(total);
|
|
87
94
|
totalRef.current = total;
|
|
95
|
+
const cursorRef = useRef2(core.index);
|
|
96
|
+
cursorRef.current = core.index;
|
|
97
|
+
const playingRef = useRef2(playing);
|
|
98
|
+
playingRef.current = playing;
|
|
99
|
+
const atEndRef = useRef2(atEnd);
|
|
100
|
+
atEndRef.current = atEnd;
|
|
101
|
+
const engineRef = useRef2(engine);
|
|
102
|
+
engineRef.current = engine;
|
|
88
103
|
const engineApiRef = useRef2({});
|
|
89
104
|
useEffect2(() => {
|
|
90
105
|
if (!engine) return;
|
|
@@ -92,7 +107,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
92
107
|
get total() {
|
|
93
108
|
return totalRef.current;
|
|
94
109
|
},
|
|
95
|
-
onFrame: (f) =>
|
|
110
|
+
onFrame: (f) => go(f),
|
|
96
111
|
onEnd: () => {
|
|
97
112
|
markEnded();
|
|
98
113
|
setPlaying(false);
|
|
@@ -108,17 +123,24 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
108
123
|
engineApiRef.current = {};
|
|
109
124
|
handle.disconnect();
|
|
110
125
|
};
|
|
111
|
-
}, [engine]);
|
|
126
|
+
}, [engine, go, markEnded]);
|
|
127
|
+
useEffect2(() => {
|
|
128
|
+
if (!respectReducedMotion || !autoPlay) return;
|
|
129
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
130
|
+
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
131
|
+
setPlaying(false);
|
|
132
|
+
engineApiRef.current.pause?.();
|
|
133
|
+
}, []);
|
|
112
134
|
const start = useCallback2(() => {
|
|
113
135
|
setStarted(true);
|
|
114
136
|
setPlaying(true);
|
|
115
137
|
clearEnded();
|
|
116
|
-
|
|
138
|
+
first();
|
|
117
139
|
if (engineRef.current) {
|
|
118
140
|
engineApiRef.current.seek?.(0);
|
|
119
141
|
engineApiRef.current.play?.();
|
|
120
142
|
}
|
|
121
|
-
}, [
|
|
143
|
+
}, [first, clearEnded]);
|
|
122
144
|
const reset = useCallback2(() => {
|
|
123
145
|
setStarted(false);
|
|
124
146
|
setPlaying(false);
|
|
@@ -127,62 +149,59 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
127
149
|
}, [clearEnded]);
|
|
128
150
|
const seek = useCallback2(
|
|
129
151
|
(f) => {
|
|
130
|
-
|
|
152
|
+
const t = totalRef.current;
|
|
153
|
+
if (t === 0) return;
|
|
131
154
|
setStarted(true);
|
|
132
155
|
setPlaying(false);
|
|
133
156
|
clearEnded();
|
|
134
|
-
|
|
157
|
+
go(Math.max(0, Math.min(f, t - 1)));
|
|
135
158
|
if (engineRef.current) {
|
|
136
159
|
engineApiRef.current.pause?.();
|
|
137
160
|
engineApiRef.current.seek?.(f);
|
|
138
161
|
}
|
|
139
162
|
},
|
|
140
|
-
[
|
|
163
|
+
[go, clearEnded]
|
|
141
164
|
);
|
|
142
165
|
const next = useCallback2(
|
|
143
166
|
(onEmpty) => {
|
|
144
167
|
setPlaying(false);
|
|
145
|
-
|
|
168
|
+
const t = totalRef.current;
|
|
169
|
+
if (t === 0) {
|
|
146
170
|
onEmpty?.();
|
|
147
171
|
return;
|
|
148
172
|
}
|
|
149
173
|
setStarted(true);
|
|
150
174
|
clearEnded();
|
|
151
|
-
|
|
175
|
+
const target = Math.min(cursorRef.current + 1, t - 1);
|
|
176
|
+
coreNext();
|
|
152
177
|
if (engineRef.current) {
|
|
153
178
|
engineApiRef.current.pause?.();
|
|
154
|
-
engineApiRef.current.seek?.(
|
|
179
|
+
engineApiRef.current.seek?.(target);
|
|
155
180
|
}
|
|
156
181
|
},
|
|
157
|
-
[
|
|
182
|
+
[coreNext, clearEnded]
|
|
158
183
|
);
|
|
159
184
|
const prev = useCallback2(() => {
|
|
160
185
|
setPlaying(false);
|
|
161
186
|
clearEnded();
|
|
162
|
-
|
|
187
|
+
const target = Math.max(0, cursorRef.current - 1);
|
|
188
|
+
corePrev();
|
|
163
189
|
if (engineRef.current) {
|
|
164
190
|
engineApiRef.current.pause?.();
|
|
165
|
-
engineApiRef.current.seek?.(
|
|
191
|
+
engineApiRef.current.seek?.(target);
|
|
166
192
|
}
|
|
167
|
-
}, [
|
|
193
|
+
}, [corePrev, clearEnded]);
|
|
168
194
|
const toggle = useCallback2(
|
|
169
195
|
(onEmpty) => {
|
|
170
|
-
if (
|
|
196
|
+
if (totalRef.current === 0) {
|
|
171
197
|
onEmpty?.();
|
|
172
198
|
return;
|
|
173
199
|
}
|
|
174
|
-
if (!
|
|
175
|
-
|
|
176
|
-
setPlaying(true);
|
|
177
|
-
clearEnded();
|
|
178
|
-
core.first();
|
|
179
|
-
if (engineRef.current) {
|
|
180
|
-
engineApiRef.current.seek?.(0);
|
|
181
|
-
engineApiRef.current.play?.();
|
|
182
|
-
}
|
|
200
|
+
if (!playingRef.current && atEndRef.current) {
|
|
201
|
+
start();
|
|
183
202
|
return;
|
|
184
203
|
}
|
|
185
|
-
const nextPlaying = !
|
|
204
|
+
const nextPlaying = !playingRef.current;
|
|
186
205
|
setPlaying(nextPlaying);
|
|
187
206
|
setStarted(true);
|
|
188
207
|
if (engineRef.current) {
|
|
@@ -190,7 +209,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
190
209
|
else engineApiRef.current.pause?.();
|
|
191
210
|
}
|
|
192
211
|
},
|
|
193
|
-
[
|
|
212
|
+
[start]
|
|
194
213
|
);
|
|
195
214
|
const changeSpeed = useCallback2((s) => {
|
|
196
215
|
setSpeed(s);
|
|
@@ -207,25 +226,22 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
207
226
|
setPlaying(false);
|
|
208
227
|
return;
|
|
209
228
|
}
|
|
210
|
-
const id = setTimeout(() =>
|
|
229
|
+
const id = setTimeout(() => coreNext(), baseDelayMs / speed);
|
|
211
230
|
return () => clearTimeout(id);
|
|
212
|
-
}, [playing, started, core, total, speed, baseDelayMs, markEnded]);
|
|
213
|
-
const hashKey = syncHash;
|
|
214
|
-
const coreGoRef = useRef2(core.go);
|
|
215
|
-
coreGoRef.current = core.go;
|
|
231
|
+
}, [playing, started, core.index, coreNext, total, speed, baseDelayMs, markEnded]);
|
|
216
232
|
const lastWrittenHash = useRef2("");
|
|
217
233
|
const readHash = useCallback2(() => {
|
|
218
|
-
if (!
|
|
234
|
+
if (!syncHash || typeof window === "undefined" || total === 0) return;
|
|
219
235
|
const hash = window.location.hash;
|
|
220
236
|
if (hash === lastWrittenHash.current) return;
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
const f = Number(
|
|
224
|
-
if (
|
|
237
|
+
const value = readHashParam(hash, syncHash);
|
|
238
|
+
if (value === null || !/^\d+$/.test(value)) return;
|
|
239
|
+
const f = Number(value);
|
|
240
|
+
if (f < total) {
|
|
225
241
|
setStarted(true);
|
|
226
|
-
|
|
242
|
+
go(f);
|
|
227
243
|
}
|
|
228
|
-
}, [
|
|
244
|
+
}, [syncHash, total, go]);
|
|
229
245
|
useEffect2(() => {
|
|
230
246
|
readHash();
|
|
231
247
|
const onHashChange = () => readHash();
|
|
@@ -234,7 +250,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
234
250
|
}, [readHash]);
|
|
235
251
|
useEffect2(() => {
|
|
236
252
|
if (!syncHash || index < 0 || typeof window === "undefined") return;
|
|
237
|
-
const next2 =
|
|
253
|
+
const next2 = writeHashParam(window.location.hash, syncHash, String(index));
|
|
238
254
|
if (window.location.hash !== next2) {
|
|
239
255
|
lastWrittenHash.current = next2;
|
|
240
256
|
window.history.replaceState(null, "", next2);
|
|
@@ -261,25 +277,395 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
|
|
|
261
277
|
[total, index, playing, speed, atEnd, ended, start, reset, seek, next, prev, toggle, changeSpeed]
|
|
262
278
|
);
|
|
263
279
|
}
|
|
280
|
+
function hashParts(hash) {
|
|
281
|
+
const body = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
282
|
+
return body ? body.split("&") : [];
|
|
283
|
+
}
|
|
284
|
+
function readHashParam(hash, key) {
|
|
285
|
+
for (const part of hashParts(hash)) {
|
|
286
|
+
const eq = part.indexOf("=");
|
|
287
|
+
if (eq > 0 && part.slice(0, eq) === key) return part.slice(eq + 1);
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
function writeHashParam(hash, key, value) {
|
|
292
|
+
const parts = hashParts(hash);
|
|
293
|
+
const pair = `${key}=${value}`;
|
|
294
|
+
const i = parts.findIndex((p) => p.startsWith(`${key}=`));
|
|
295
|
+
if (i >= 0) parts[i] = pair;
|
|
296
|
+
else parts.push(pair);
|
|
297
|
+
return `#${parts.join("&")}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/use-trace.ts
|
|
301
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
302
|
+
function useTrace(steps, opts) {
|
|
303
|
+
const player = useStepPlayer({ ...opts, total: steps.length });
|
|
304
|
+
const stepsRef = useRef3(steps);
|
|
305
|
+
stepsRef.current = steps;
|
|
306
|
+
const frame = steps.length === 0 ? -1 : Math.max(0, Math.min(player.index, steps.length - 1));
|
|
307
|
+
const step = frame >= 0 ? steps[frame] : void 0;
|
|
308
|
+
const toneAt = useCallback3((i) => stepsRef.current[i]?.tone ?? "neutral", []);
|
|
309
|
+
const titleAt = useCallback3((i) => stepsRef.current[i]?.label ?? `step ${i + 1}`, []);
|
|
310
|
+
return useMemo3(() => ({ player, step, frame, toneAt, titleAt }), [player, step, frame, toneAt, titleAt]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/create-trace.ts
|
|
314
|
+
function createTrace() {
|
|
315
|
+
const frames = [];
|
|
316
|
+
const builder = {
|
|
317
|
+
step(step) {
|
|
318
|
+
frames.push(step);
|
|
319
|
+
return builder;
|
|
320
|
+
},
|
|
321
|
+
get length() {
|
|
322
|
+
return frames.length;
|
|
323
|
+
},
|
|
324
|
+
last() {
|
|
325
|
+
return frames[frames.length - 1];
|
|
326
|
+
},
|
|
327
|
+
build() {
|
|
328
|
+
return frames.slice();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
return builder;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/use-synced-tracks.ts
|
|
335
|
+
import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef4 } from "react";
|
|
336
|
+
function alignTracks(tracks, key) {
|
|
337
|
+
const names = Object.keys(tracks);
|
|
338
|
+
const lens = names.map((n) => tracks[n].length);
|
|
339
|
+
const rows = [];
|
|
340
|
+
const holdCell = (len, at) => ({ index: len === 0 ? -1 : Math.min(at, len - 1), held: true });
|
|
341
|
+
if (!key) {
|
|
342
|
+
const length = Math.max(0, ...lens);
|
|
343
|
+
for (let i = 0; i < length; i++) {
|
|
344
|
+
const row = {};
|
|
345
|
+
names.forEach((n, t) => {
|
|
346
|
+
row[n] = i < lens[t] ? { index: i, held: false } : holdCell(lens[t], i);
|
|
347
|
+
});
|
|
348
|
+
rows.push(row);
|
|
349
|
+
}
|
|
350
|
+
return rows;
|
|
351
|
+
}
|
|
352
|
+
const keyed = names.map(
|
|
353
|
+
(n) => tracks[n].map((step, i) => {
|
|
354
|
+
const k = key(step, i, n);
|
|
355
|
+
return k == null ? null : k;
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
const qualified = keyed.map((ks) => {
|
|
359
|
+
const seen = /* @__PURE__ */ new Map();
|
|
360
|
+
return ks.map((k) => {
|
|
361
|
+
if (k === null) return null;
|
|
362
|
+
const n = seen.get(k) ?? 0;
|
|
363
|
+
seen.set(k, n + 1);
|
|
364
|
+
return `${k}\0${n}`;
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
const where = /* @__PURE__ */ new Map();
|
|
368
|
+
qualified.forEach(
|
|
369
|
+
(ks, t) => ks.forEach((k, i) => {
|
|
370
|
+
if (k === null) return;
|
|
371
|
+
let m = where.get(k);
|
|
372
|
+
if (!m) where.set(k, m = /* @__PURE__ */ new Map());
|
|
373
|
+
m.set(t, i);
|
|
374
|
+
})
|
|
375
|
+
);
|
|
376
|
+
for (const [k, m] of where) if (m.size < 2) where.delete(k);
|
|
377
|
+
const pos = names.map(() => 0);
|
|
378
|
+
while (pos.some((p, t) => p < lens[t])) {
|
|
379
|
+
const live = names.map((_, t) => pos[t] < lens[t]);
|
|
380
|
+
const blocked = names.map((_, t) => {
|
|
381
|
+
if (!live[t]) return false;
|
|
382
|
+
const k = qualified[t][pos[t]];
|
|
383
|
+
const m = k === null ? void 0 : where.get(k);
|
|
384
|
+
if (!m) return false;
|
|
385
|
+
for (const [u, at] of m) if (u !== t && at > pos[u]) return true;
|
|
386
|
+
return false;
|
|
387
|
+
});
|
|
388
|
+
const anyFree = live.some((l, t) => l && !blocked[t]);
|
|
389
|
+
const row = {};
|
|
390
|
+
names.forEach((n, t) => {
|
|
391
|
+
if (live[t] && (!blocked[t] || !anyFree)) {
|
|
392
|
+
row[n] = { index: pos[t], held: false };
|
|
393
|
+
pos[t]++;
|
|
394
|
+
} else {
|
|
395
|
+
row[n] = holdCell(lens[t], Math.max(0, pos[t] - 1));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
rows.push(row);
|
|
399
|
+
}
|
|
400
|
+
return rows;
|
|
401
|
+
}
|
|
402
|
+
var TONE_RANK = ["bad", "good", "write", "accent", "neutral", "idle"];
|
|
403
|
+
function useSyncedTracks(tracks, opts) {
|
|
404
|
+
const { key, ...playerOpts } = opts ?? {};
|
|
405
|
+
const keyRef = useRef4(key);
|
|
406
|
+
keyRef.current = key;
|
|
407
|
+
const cache = useRef4(null);
|
|
408
|
+
const names = Object.keys(tracks);
|
|
409
|
+
const arrays = names.map((n) => tracks[n]);
|
|
410
|
+
const c = cache.current;
|
|
411
|
+
if (!c || c.keyed !== !!key || c.names.length !== names.length || names.some((n, i) => c.names[i] !== n || c.arrays[i] !== arrays[i])) {
|
|
412
|
+
cache.current = { names, arrays, keyed: !!key, rows: alignTracks(tracks, keyRef.current) };
|
|
413
|
+
}
|
|
414
|
+
const rows = cache.current.rows;
|
|
415
|
+
const player = useStepPlayer({ ...playerOpts, total: rows.length });
|
|
416
|
+
const tracksRef = useRef4(tracks);
|
|
417
|
+
tracksRef.current = tracks;
|
|
418
|
+
const rowsRef = useRef4(rows);
|
|
419
|
+
rowsRef.current = rows;
|
|
420
|
+
const frame = rows.length === 0 ? -1 : Math.max(0, Math.min(player.index, rows.length - 1));
|
|
421
|
+
const row = frame >= 0 ? rows[frame] : void 0;
|
|
422
|
+
const aligned = cache.current;
|
|
423
|
+
const view = useMemo4(() => {
|
|
424
|
+
const out = {};
|
|
425
|
+
aligned.names.forEach((n, t) => {
|
|
426
|
+
const list = aligned.arrays[t];
|
|
427
|
+
const cell = row?.[n];
|
|
428
|
+
const index = cell ? cell.index : list.length === 0 ? -1 : 0;
|
|
429
|
+
out[n] = {
|
|
430
|
+
step: index >= 0 ? list[index] : void 0,
|
|
431
|
+
index,
|
|
432
|
+
held: cell?.held ?? false,
|
|
433
|
+
done: list.length > 0 && index === list.length - 1,
|
|
434
|
+
length: list.length
|
|
435
|
+
};
|
|
436
|
+
});
|
|
437
|
+
return out;
|
|
438
|
+
}, [aligned, row]);
|
|
439
|
+
const moved = useCallback4((i) => {
|
|
440
|
+
const r = rowsRef.current[i];
|
|
441
|
+
if (!r) return [];
|
|
442
|
+
return Object.keys(r).filter((n) => !r[n].held).map((n) => ({ name: n, step: tracksRef.current[n]?.[r[n].index] })).filter((m) => !!m.step);
|
|
443
|
+
}, []);
|
|
444
|
+
const toneAt = useCallback4(
|
|
445
|
+
(i) => {
|
|
446
|
+
const tones = moved(i).map((m) => m.step.tone);
|
|
447
|
+
return TONE_RANK.find((t) => tones.includes(t)) ?? "neutral";
|
|
448
|
+
},
|
|
449
|
+
[moved]
|
|
450
|
+
);
|
|
451
|
+
const titleAt = useCallback4(
|
|
452
|
+
(i) => {
|
|
453
|
+
const parts = moved(i).map((m) => `${m.name}: ${m.step.label}`);
|
|
454
|
+
return parts.length ? parts.join(" \xB7 ") : `frame ${i + 1}`;
|
|
455
|
+
},
|
|
456
|
+
[moved]
|
|
457
|
+
);
|
|
458
|
+
return useMemo4(
|
|
459
|
+
() => ({ player, frame, total: rows.length, tracks: view, rows, toneAt, titleAt }),
|
|
460
|
+
[player, frame, rows, view, toneAt, titleAt]
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/use-lesson-stream.ts
|
|
465
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo5, useRef as useRef5, useState as useState4 } from "react";
|
|
466
|
+
|
|
467
|
+
// src/use-prefers-reduced-motion.ts
|
|
468
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
469
|
+
function usePrefersReducedMotion() {
|
|
470
|
+
const [reduced, setReduced] = useState3(false);
|
|
471
|
+
useEffect3(() => {
|
|
472
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
473
|
+
setReduced(mq.matches);
|
|
474
|
+
const onChange = (e) => setReduced(e.matches);
|
|
475
|
+
mq.addEventListener("change", onChange);
|
|
476
|
+
return () => mq.removeEventListener("change", onChange);
|
|
477
|
+
}, []);
|
|
478
|
+
return reduced;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/use-lesson-stream.ts
|
|
482
|
+
var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
|
|
483
|
+
function useLessonStream(count, opts = {}) {
|
|
484
|
+
const { radius = 1, approachBelow = 600, approachAbove = 300, scrollToSeed = true } = opts;
|
|
485
|
+
const last = count - 1;
|
|
486
|
+
const seed = count === 0 ? -1 : Math.max(0, Math.min(opts.seed ?? 0, last));
|
|
487
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
488
|
+
const core = useSteps(count, { initialIndex: Math.max(0, seed) });
|
|
489
|
+
const coreGoRef = useRef5(core.go);
|
|
490
|
+
coreGoRef.current = core.go;
|
|
491
|
+
const activeRef = useRef5(core.index);
|
|
492
|
+
activeRef.current = core.index;
|
|
493
|
+
const [range, setRange] = useState4(() => ({
|
|
494
|
+
first: Math.max(0, seed - radius),
|
|
495
|
+
last: Math.min(last, seed + radius)
|
|
496
|
+
}));
|
|
497
|
+
const first = Math.max(0, Math.min(range.first, last));
|
|
498
|
+
const lastMounted = Math.min(range.last, last);
|
|
499
|
+
const rangeRef = useRef5({ first, last: lastMounted });
|
|
500
|
+
rangeRef.current = { first, last: lastMounted };
|
|
501
|
+
const countRef = useRef5(count);
|
|
502
|
+
countRef.current = count;
|
|
503
|
+
const reducedRef = useRef5(reducedMotion);
|
|
504
|
+
reducedRef.current = reducedMotion;
|
|
505
|
+
const onActiveRef = useRef5(opts.onActiveChange);
|
|
506
|
+
onActiveRef.current = opts.onActiveChange;
|
|
507
|
+
const optsRef = useRef5({ approachBelow, approachAbove });
|
|
508
|
+
optsRef.current = { approachBelow, approachAbove };
|
|
509
|
+
const blocks = useRef5(/* @__PURE__ */ new Map());
|
|
510
|
+
const refCache = useRef5(/* @__PURE__ */ new Map());
|
|
511
|
+
const refFor = useCallback5((i) => {
|
|
512
|
+
let fn = refCache.current.get(i);
|
|
513
|
+
if (!fn) {
|
|
514
|
+
fn = (el) => {
|
|
515
|
+
if (el) blocks.current.set(i, el);
|
|
516
|
+
else blocks.current.delete(i);
|
|
517
|
+
};
|
|
518
|
+
refCache.current.set(i, fn);
|
|
519
|
+
}
|
|
520
|
+
return fn;
|
|
521
|
+
}, []);
|
|
522
|
+
const causeRef = useRef5("scroll");
|
|
523
|
+
const expandTo = useCallback5((i, cause) => {
|
|
524
|
+
const r = rangeRef.current;
|
|
525
|
+
if (i >= r.first && i <= r.last) return;
|
|
526
|
+
causeRef.current = cause;
|
|
527
|
+
const grown = { first: Math.min(r.first, i), last: Math.max(r.last, i) };
|
|
528
|
+
rangeRef.current = grown;
|
|
529
|
+
setRange(grown);
|
|
530
|
+
}, []);
|
|
531
|
+
const scheduleRef = useRef5(null);
|
|
532
|
+
useEffect4(() => {
|
|
533
|
+
if (count === 0) return;
|
|
534
|
+
let raf = 0;
|
|
535
|
+
const update = () => {
|
|
536
|
+
raf = 0;
|
|
537
|
+
if (blocks.current.size === 0) return;
|
|
538
|
+
const mid = window.innerHeight / 2;
|
|
539
|
+
let best = -1;
|
|
540
|
+
for (const [i, el] of blocks.current) {
|
|
541
|
+
const rect = el.getBoundingClientRect();
|
|
542
|
+
if (rect.top <= mid && rect.bottom >= mid) best = Math.max(best, i);
|
|
543
|
+
}
|
|
544
|
+
if (best === -1) best = Math.min(...blocks.current.keys());
|
|
545
|
+
if (best !== activeRef.current) {
|
|
546
|
+
activeRef.current = best;
|
|
547
|
+
coreGoRef.current(best);
|
|
548
|
+
onActiveRef.current?.(best);
|
|
549
|
+
}
|
|
550
|
+
const { first: f, last: l } = rangeRef.current;
|
|
551
|
+
const { approachBelow: below, approachAbove: above } = optsRef.current;
|
|
552
|
+
const lastEl = blocks.current.get(l);
|
|
553
|
+
if (lastEl && l < countRef.current - 1 && lastEl.getBoundingClientRect().bottom - window.innerHeight < below) {
|
|
554
|
+
expandTo(l + 1, "scroll");
|
|
555
|
+
}
|
|
556
|
+
const firstEl = blocks.current.get(f);
|
|
557
|
+
if (firstEl && f > 0 && firstEl.getBoundingClientRect().top > -above) {
|
|
558
|
+
expandTo(f - 1, "scroll");
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const onScroll = () => {
|
|
562
|
+
if (!raf) raf = requestAnimationFrame(update);
|
|
563
|
+
};
|
|
564
|
+
scheduleRef.current = onScroll;
|
|
565
|
+
update();
|
|
566
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
567
|
+
window.addEventListener("resize", onScroll);
|
|
568
|
+
return () => {
|
|
569
|
+
scheduleRef.current = null;
|
|
570
|
+
window.removeEventListener("scroll", onScroll);
|
|
571
|
+
window.removeEventListener("resize", onScroll);
|
|
572
|
+
if (raf) cancelAnimationFrame(raf);
|
|
573
|
+
};
|
|
574
|
+
}, [count, expandTo]);
|
|
575
|
+
useEffect4(() => {
|
|
576
|
+
scheduleRef.current?.();
|
|
577
|
+
}, [first, lastMounted]);
|
|
578
|
+
const firstRef = useRef5(first);
|
|
579
|
+
useIsoLayoutEffect(() => {
|
|
580
|
+
if (first < firstRef.current && causeRef.current === "scroll") {
|
|
581
|
+
let height = 0;
|
|
582
|
+
for (let i = first; i < firstRef.current; i++) {
|
|
583
|
+
height += blocks.current.get(i)?.getBoundingClientRect().height ?? 0;
|
|
584
|
+
}
|
|
585
|
+
if (height) window.scrollBy({ top: height, behavior: "auto" });
|
|
586
|
+
}
|
|
587
|
+
causeRef.current = "scroll";
|
|
588
|
+
firstRef.current = first;
|
|
589
|
+
}, [first]);
|
|
590
|
+
useIsoLayoutEffect(() => {
|
|
591
|
+
if (!scrollToSeed || seed <= 0) return;
|
|
592
|
+
blocks.current.get(seed)?.scrollIntoView({ block: "start", behavior: "auto" });
|
|
593
|
+
}, []);
|
|
594
|
+
const [nav, setNav] = useState4(null);
|
|
595
|
+
useEffect4(() => {
|
|
596
|
+
if (!nav) return;
|
|
597
|
+
const raf = requestAnimationFrame(
|
|
598
|
+
() => blocks.current.get(nav.target)?.scrollIntoView({ behavior: reducedRef.current ? "auto" : "smooth", block: "start" })
|
|
599
|
+
);
|
|
600
|
+
return () => cancelAnimationFrame(raf);
|
|
601
|
+
}, [nav]);
|
|
602
|
+
const goTo = useCallback5(
|
|
603
|
+
(i) => {
|
|
604
|
+
const c = countRef.current;
|
|
605
|
+
if (c === 0) return;
|
|
606
|
+
const target = Math.max(0, Math.min(i, c - 1));
|
|
607
|
+
expandTo(target, "nav");
|
|
608
|
+
setNav({ target });
|
|
609
|
+
},
|
|
610
|
+
[expandTo]
|
|
611
|
+
);
|
|
612
|
+
const next = useCallback5(() => goTo(activeRef.current + 1), [goTo]);
|
|
613
|
+
const prev = useCallback5(() => goTo(activeRef.current - 1), [goTo]);
|
|
614
|
+
const mounted = useMemo5(() => {
|
|
615
|
+
const out = [];
|
|
616
|
+
for (let i = first; i <= lastMounted; i++) out.push(i);
|
|
617
|
+
return out;
|
|
618
|
+
}, [first, lastMounted]);
|
|
619
|
+
return useMemo5(
|
|
620
|
+
() => ({
|
|
621
|
+
active: core.index,
|
|
622
|
+
seed,
|
|
623
|
+
mounted,
|
|
624
|
+
complete: count > 0 && lastMounted === last,
|
|
625
|
+
refFor,
|
|
626
|
+
goTo,
|
|
627
|
+
next,
|
|
628
|
+
prev,
|
|
629
|
+
reducedMotion
|
|
630
|
+
}),
|
|
631
|
+
[core.index, seed, mounted, count, lastMounted, last, refFor, goTo, next, prev, reducedMotion]
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
function useFadeIn(initiallyVisible = false) {
|
|
635
|
+
const reduced = usePrefersReducedMotion();
|
|
636
|
+
const [shown, setShown] = useState4(initiallyVisible);
|
|
637
|
+
useEffect4(() => {
|
|
638
|
+
if (shown) return;
|
|
639
|
+
if (reduced) {
|
|
640
|
+
setShown(true);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const raf = requestAnimationFrame(() => setShown(true));
|
|
644
|
+
return () => cancelAnimationFrame(raf);
|
|
645
|
+
}, [shown, reduced]);
|
|
646
|
+
return shown;
|
|
647
|
+
}
|
|
264
648
|
|
|
265
649
|
// src/use-guided-tour.ts
|
|
266
|
-
import { useCallback as
|
|
650
|
+
import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef6, useState as useState5 } from "react";
|
|
267
651
|
function useGuidedTour(steps, opts) {
|
|
268
|
-
const [running, setRunning] =
|
|
269
|
-
const runningRef =
|
|
652
|
+
const [running, setRunning] = useState5(false);
|
|
653
|
+
const runningRef = useRef6(running);
|
|
270
654
|
runningRef.current = running;
|
|
271
655
|
const core = useSteps(steps.length);
|
|
272
|
-
const stepsRef =
|
|
656
|
+
const stepsRef = useRef6(steps);
|
|
273
657
|
stepsRef.current = steps;
|
|
274
|
-
const driverRef =
|
|
658
|
+
const driverRef = useRef6(opts?.driver);
|
|
275
659
|
driverRef.current = opts?.driver;
|
|
276
|
-
const coreGoRef =
|
|
660
|
+
const coreGoRef = useRef6(core.go);
|
|
277
661
|
coreGoRef.current = core.go;
|
|
278
|
-
const
|
|
662
|
+
const cursorRef = useRef6(core.index);
|
|
663
|
+
cursorRef.current = core.index;
|
|
664
|
+
const isSkipped = useCallback6(
|
|
279
665
|
(i) => stepsRef.current[i]?.when?.() === false,
|
|
280
666
|
[]
|
|
281
667
|
);
|
|
282
|
-
const settle =
|
|
668
|
+
const settle = useCallback6(
|
|
283
669
|
(start2, dir) => {
|
|
284
670
|
const len = stepsRef.current.length;
|
|
285
671
|
let i = start2;
|
|
@@ -291,7 +677,7 @@ function useGuidedTour(steps, opts) {
|
|
|
291
677
|
},
|
|
292
678
|
[isSkipped]
|
|
293
679
|
);
|
|
294
|
-
const arrive =
|
|
680
|
+
const arrive = useCallback6(
|
|
295
681
|
(i, dir) => {
|
|
296
682
|
const target = settle(i, dir);
|
|
297
683
|
if (target === null) {
|
|
@@ -303,14 +689,14 @@ function useGuidedTour(steps, opts) {
|
|
|
303
689
|
},
|
|
304
690
|
[settle]
|
|
305
691
|
);
|
|
306
|
-
const goTo =
|
|
692
|
+
const goTo = useCallback6(
|
|
307
693
|
(i) => {
|
|
308
694
|
if (stepsRef.current.length === 0) return;
|
|
309
695
|
arrive(Math.max(0, Math.min(i, stepsRef.current.length - 1)), 1);
|
|
310
696
|
},
|
|
311
697
|
[arrive]
|
|
312
698
|
);
|
|
313
|
-
const goToId =
|
|
699
|
+
const goToId = useCallback6(
|
|
314
700
|
(id) => {
|
|
315
701
|
const i = stepsRef.current.findIndex((s) => s.id === id);
|
|
316
702
|
if (i >= 0) goTo(i);
|
|
@@ -318,11 +704,11 @@ function useGuidedTour(steps, opts) {
|
|
|
318
704
|
[goTo]
|
|
319
705
|
);
|
|
320
706
|
const index = running ? core.index : null;
|
|
321
|
-
|
|
707
|
+
useEffect5(() => {
|
|
322
708
|
if (!running) return;
|
|
323
709
|
stepsRef.current[core.index]?.onEnter?.();
|
|
324
710
|
}, [running, core.index]);
|
|
325
|
-
|
|
711
|
+
useEffect5(() => {
|
|
326
712
|
const driver = driverRef.current;
|
|
327
713
|
if (!driver) return;
|
|
328
714
|
if (!running) {
|
|
@@ -341,14 +727,14 @@ function useGuidedTour(steps, opts) {
|
|
|
341
727
|
back: () => goTo(core.index - 1),
|
|
342
728
|
end: () => setRunning(false)
|
|
343
729
|
});
|
|
344
|
-
}, [running, core.index, goTo
|
|
345
|
-
|
|
346
|
-
const start =
|
|
730
|
+
}, [running, core.index, goTo]);
|
|
731
|
+
useEffect5(() => () => driverRef.current?.hide(), []);
|
|
732
|
+
const start = useCallback6(() => {
|
|
347
733
|
if (stepsRef.current.length === 0) return;
|
|
348
734
|
arrive(0, 1);
|
|
349
735
|
}, [arrive]);
|
|
350
|
-
const end =
|
|
351
|
-
const resolveNext =
|
|
736
|
+
const end = useCallback6(() => setRunning(false), []);
|
|
737
|
+
const resolveNext = useCallback6((i) => {
|
|
352
738
|
const list = stepsRef.current;
|
|
353
739
|
const edge = list[i]?.next?.();
|
|
354
740
|
if (edge != null) {
|
|
@@ -357,9 +743,9 @@ function useGuidedTour(steps, opts) {
|
|
|
357
743
|
}
|
|
358
744
|
return i + 1;
|
|
359
745
|
}, []);
|
|
360
|
-
const next =
|
|
746
|
+
const next = useCallback6(() => {
|
|
361
747
|
if (!runningRef.current) return;
|
|
362
|
-
const i =
|
|
748
|
+
const i = cursorRef.current;
|
|
363
749
|
if (stepsRef.current[i]?.gate?.() === false) return;
|
|
364
750
|
const target = resolveNext(i);
|
|
365
751
|
if (target > stepsRef.current.length - 1) {
|
|
@@ -367,20 +753,23 @@ function useGuidedTour(steps, opts) {
|
|
|
367
753
|
return;
|
|
368
754
|
}
|
|
369
755
|
arrive(target, 1);
|
|
370
|
-
}, [
|
|
371
|
-
const back =
|
|
756
|
+
}, [arrive, resolveNext]);
|
|
757
|
+
const back = useCallback6(() => {
|
|
372
758
|
if (!runningRef.current) return;
|
|
373
|
-
const i =
|
|
759
|
+
const i = cursorRef.current;
|
|
374
760
|
if (i <= 0) return;
|
|
375
761
|
arrive(i - 1, -1);
|
|
376
|
-
}, [
|
|
762
|
+
}, [arrive]);
|
|
377
763
|
const current = running ? stepsRef.current[core.index] ?? null : null;
|
|
378
764
|
const blocked = current?.gate?.() === false;
|
|
379
|
-
return
|
|
765
|
+
return useMemo6(
|
|
766
|
+
() => ({ index, current, active: running, blocked, start, end, next, back, goto: goTo, gotoId: goToId }),
|
|
767
|
+
[index, current, running, blocked, start, end, next, back, goTo, goToId]
|
|
768
|
+
);
|
|
380
769
|
}
|
|
381
770
|
|
|
382
771
|
// src/use-chapters.ts
|
|
383
|
-
import { useCallback as
|
|
772
|
+
import { useCallback as useCallback7, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef7, useState as useState6 } from "react";
|
|
384
773
|
|
|
385
774
|
// src/flatten.ts
|
|
386
775
|
function flattenChapters(chapters, depth = 0) {
|
|
@@ -399,17 +788,17 @@ function flattenChapters(chapters, depth = 0) {
|
|
|
399
788
|
|
|
400
789
|
// src/use-chapters.ts
|
|
401
790
|
function useChapters(chapters) {
|
|
402
|
-
const [mounted, setMounted] =
|
|
403
|
-
const flat =
|
|
404
|
-
const flatRef =
|
|
791
|
+
const [mounted, setMounted] = useState6(false);
|
|
792
|
+
const flat = useMemo7(() => flattenChapters(chapters), [chapters]);
|
|
793
|
+
const flatRef = useRef7(flat);
|
|
405
794
|
flatRef.current = flat;
|
|
406
|
-
const [active, setActive] =
|
|
407
|
-
const activeRef =
|
|
795
|
+
const [active, setActive] = useState6(0);
|
|
796
|
+
const activeRef = useRef7(0);
|
|
408
797
|
activeRef.current = active;
|
|
409
798
|
const core = useSteps(flat.length, { initialIndex: 0 });
|
|
410
|
-
const coreGoRef =
|
|
799
|
+
const coreGoRef = useRef7(core.go);
|
|
411
800
|
coreGoRef.current = core.go;
|
|
412
|
-
|
|
801
|
+
useEffect6(() => {
|
|
413
802
|
if (!mounted) return;
|
|
414
803
|
let raf = 0;
|
|
415
804
|
const update = () => {
|
|
@@ -447,8 +836,8 @@ function useChapters(chapters) {
|
|
|
447
836
|
if (raf) cancelAnimationFrame(raf);
|
|
448
837
|
};
|
|
449
838
|
}, [mounted, flat.length]);
|
|
450
|
-
|
|
451
|
-
const goTo =
|
|
839
|
+
useEffect6(() => setMounted(true), []);
|
|
840
|
+
const goTo = useCallback7((i) => {
|
|
452
841
|
const entry = flatRef.current[i];
|
|
453
842
|
if (!entry) return;
|
|
454
843
|
setActive(i);
|
|
@@ -457,25 +846,25 @@ function useChapters(chapters) {
|
|
|
457
846
|
if (!el) return;
|
|
458
847
|
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
459
848
|
}, []);
|
|
460
|
-
const next =
|
|
849
|
+
const next = useCallback7(() => {
|
|
461
850
|
if (activeRef.current < flatRef.current.length - 1) goTo(activeRef.current + 1);
|
|
462
851
|
}, [goTo]);
|
|
463
|
-
const prev =
|
|
852
|
+
const prev = useCallback7(() => {
|
|
464
853
|
if (activeRef.current > 0) goTo(activeRef.current - 1);
|
|
465
854
|
}, [goTo]);
|
|
466
|
-
return { active: core.index, goTo, next, prev };
|
|
855
|
+
return useMemo7(() => ({ active: core.index, goTo, next, prev }), [core.index, goTo, next, prev]);
|
|
467
856
|
}
|
|
468
857
|
|
|
469
858
|
// src/use-global-keys.ts
|
|
470
|
-
import { useEffect as
|
|
859
|
+
import { useEffect as useEffect7, useRef as useRef8 } from "react";
|
|
471
860
|
var DEFAULT_IGNORED_TAGS = ["INPUT", "TEXTAREA", "SELECT", "BUTTON"];
|
|
472
861
|
function useGlobalKeys(map, opts) {
|
|
473
|
-
const mapRef =
|
|
862
|
+
const mapRef = useRef8(map);
|
|
474
863
|
mapRef.current = map;
|
|
475
|
-
const ignoreRef =
|
|
476
|
-
const enabledRef =
|
|
864
|
+
const ignoreRef = useRef8(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
|
|
865
|
+
const enabledRef = useRef8(opts?.enabled ?? true);
|
|
477
866
|
enabledRef.current = opts?.enabled ?? true;
|
|
478
|
-
|
|
867
|
+
useEffect7(() => {
|
|
479
868
|
const onKey = (e) => {
|
|
480
869
|
const gate = enabledRef.current;
|
|
481
870
|
const enabled = typeof gate === "function" ? gate() : gate;
|
|
@@ -493,7 +882,7 @@ function useGlobalKeys(map, opts) {
|
|
|
493
882
|
}
|
|
494
883
|
|
|
495
884
|
// src/use-discoveries.ts
|
|
496
|
-
import { useCallback as
|
|
885
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo8, useRef as useRef9, useState as useState7 } from "react";
|
|
497
886
|
function localStorageStore(key = "stepwise.discoveries") {
|
|
498
887
|
return {
|
|
499
888
|
read() {
|
|
@@ -544,23 +933,23 @@ function memoryStore(seed = []) {
|
|
|
544
933
|
};
|
|
545
934
|
}
|
|
546
935
|
function useDiscoveries(opts) {
|
|
547
|
-
const defaultStoreRef =
|
|
548
|
-
const storeRef =
|
|
936
|
+
const defaultStoreRef = useRef9(null);
|
|
937
|
+
const storeRef = useRef9(null);
|
|
549
938
|
if (opts?.store) {
|
|
550
939
|
storeRef.current = opts.store;
|
|
551
940
|
} else {
|
|
552
941
|
if (!defaultStoreRef.current) defaultStoreRef.current = localStorageStore(opts?.key);
|
|
553
942
|
storeRef.current = defaultStoreRef.current;
|
|
554
943
|
}
|
|
555
|
-
const [ids, setIds] =
|
|
556
|
-
const [ready, setReady] =
|
|
557
|
-
const idsRef =
|
|
944
|
+
const [ids, setIds] = useState7(() => /* @__PURE__ */ new Set());
|
|
945
|
+
const [ready, setReady] = useState7(false);
|
|
946
|
+
const idsRef = useRef9(ids);
|
|
558
947
|
idsRef.current = ids;
|
|
559
|
-
|
|
948
|
+
useEffect8(() => {
|
|
560
949
|
setIds(new Set(storeRef.current.read()));
|
|
561
950
|
setReady(true);
|
|
562
951
|
}, []);
|
|
563
|
-
const markDiscovered =
|
|
952
|
+
const markDiscovered = useCallback8((id) => {
|
|
564
953
|
const additions = Array.isArray(id) ? id : [id];
|
|
565
954
|
const merged = new Set(idsRef.current);
|
|
566
955
|
let changed = false;
|
|
@@ -575,24 +964,24 @@ function useDiscoveries(opts) {
|
|
|
575
964
|
setIds(merged);
|
|
576
965
|
storeRef.current.write([...merged]);
|
|
577
966
|
}, []);
|
|
578
|
-
const reset =
|
|
967
|
+
const reset = useCallback8(() => {
|
|
579
968
|
const empty = /* @__PURE__ */ new Set();
|
|
580
969
|
idsRef.current = empty;
|
|
581
970
|
setIds(empty);
|
|
582
971
|
storeRef.current.write([]);
|
|
583
972
|
}, []);
|
|
584
|
-
const isDiscovered =
|
|
585
|
-
return
|
|
973
|
+
const isDiscovered = useCallback8((id) => idsRef.current.has(id), []);
|
|
974
|
+
return useMemo8(
|
|
586
975
|
() => ({ discovered: ids, ready, isDiscovered, markDiscovered, reset }),
|
|
587
976
|
[ids, ready, isDiscovered, markDiscovered, reset]
|
|
588
977
|
);
|
|
589
978
|
}
|
|
590
979
|
|
|
591
980
|
// src/use-focus-trap.ts
|
|
592
|
-
import { useEffect as
|
|
981
|
+
import { useEffect as useEffect9, useRef as useRef10 } from "react";
|
|
593
982
|
function useFocusTrap(active) {
|
|
594
|
-
const ref =
|
|
595
|
-
|
|
983
|
+
const ref = useRef10(null);
|
|
984
|
+
useEffect9(() => {
|
|
596
985
|
if (!active) return;
|
|
597
986
|
const container = ref.current;
|
|
598
987
|
if (!container) return;
|
|
@@ -626,31 +1015,17 @@ function useFocusTrap(active) {
|
|
|
626
1015
|
return ref;
|
|
627
1016
|
}
|
|
628
1017
|
|
|
629
|
-
// src/use-prefers-reduced-motion.ts
|
|
630
|
-
import { useEffect as useEffect8, useState as useState6 } from "react";
|
|
631
|
-
function usePrefersReducedMotion() {
|
|
632
|
-
const [reduced, setReduced] = useState6(false);
|
|
633
|
-
useEffect8(() => {
|
|
634
|
-
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
635
|
-
setReduced(mq.matches);
|
|
636
|
-
const onChange = (e) => setReduced(e.matches);
|
|
637
|
-
mq.addEventListener("change", onChange);
|
|
638
|
-
return () => mq.removeEventListener("change", onChange);
|
|
639
|
-
}, []);
|
|
640
|
-
return reduced;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
1018
|
// src/glossary.tsx
|
|
644
1019
|
import {
|
|
645
1020
|
createContext,
|
|
646
|
-
useCallback as
|
|
1021
|
+
useCallback as useCallback9,
|
|
647
1022
|
useContext,
|
|
648
|
-
useEffect as
|
|
1023
|
+
useEffect as useEffect10,
|
|
649
1024
|
useId,
|
|
650
|
-
useLayoutEffect,
|
|
651
|
-
useMemo as
|
|
652
|
-
useRef as
|
|
653
|
-
useState as
|
|
1025
|
+
useLayoutEffect as useLayoutEffect2,
|
|
1026
|
+
useMemo as useMemo9,
|
|
1027
|
+
useRef as useRef11,
|
|
1028
|
+
useState as useState8
|
|
654
1029
|
} from "react";
|
|
655
1030
|
import { createPortal } from "react-dom";
|
|
656
1031
|
|
|
@@ -708,7 +1083,7 @@ function GlossaryProvider({
|
|
|
708
1083
|
theme = defaultTheme,
|
|
709
1084
|
children
|
|
710
1085
|
}) {
|
|
711
|
-
const value =
|
|
1086
|
+
const value = useMemo9(
|
|
712
1087
|
() => ({ byId: new Map(entries.map((e) => [e.id, e])), entries, theme }),
|
|
713
1088
|
[entries, theme]
|
|
714
1089
|
);
|
|
@@ -718,23 +1093,23 @@ function Term({ id, children, className }) {
|
|
|
718
1093
|
const ctx = useContext(GlossaryContext);
|
|
719
1094
|
const depth = useContext(DepthContext);
|
|
720
1095
|
const entry = ctx?.byId.get(id);
|
|
721
|
-
const [open, setOpen] =
|
|
722
|
-
const [pos, setPos] =
|
|
723
|
-
const openRef =
|
|
724
|
-
const wrapperRef =
|
|
725
|
-
const triggerRef =
|
|
726
|
-
const cardRef =
|
|
727
|
-
const openTimer =
|
|
728
|
-
const closeTimer =
|
|
1096
|
+
const [open, setOpen] = useState8(false);
|
|
1097
|
+
const [pos, setPos] = useState8(null);
|
|
1098
|
+
const openRef = useRef11(false);
|
|
1099
|
+
const wrapperRef = useRef11(null);
|
|
1100
|
+
const triggerRef = useRef11(null);
|
|
1101
|
+
const cardRef = useRef11(null);
|
|
1102
|
+
const openTimer = useRef11(0);
|
|
1103
|
+
const closeTimer = useRef11(0);
|
|
729
1104
|
const tipId = useId();
|
|
730
1105
|
const display = children ?? entry?.term ?? id;
|
|
731
|
-
const closeNow =
|
|
1106
|
+
const closeNow = useCallback9(() => {
|
|
732
1107
|
window.clearTimeout(openTimer.current);
|
|
733
1108
|
window.clearTimeout(closeTimer.current);
|
|
734
1109
|
openRef.current = false;
|
|
735
1110
|
setOpen(false);
|
|
736
1111
|
}, []);
|
|
737
|
-
const openNow =
|
|
1112
|
+
const openNow = useCallback9((focusCard = false) => {
|
|
738
1113
|
window.clearTimeout(closeTimer.current);
|
|
739
1114
|
window.clearTimeout(openTimer.current);
|
|
740
1115
|
openRef.current = true;
|
|
@@ -743,18 +1118,18 @@ function Term({ id, children, className }) {
|
|
|
743
1118
|
requestAnimationFrame(() => cardRef.current?.focus({ preventScroll: true }));
|
|
744
1119
|
}
|
|
745
1120
|
}, []);
|
|
746
|
-
const scheduleOpen =
|
|
1121
|
+
const scheduleOpen = useCallback9(() => {
|
|
747
1122
|
window.clearTimeout(closeTimer.current);
|
|
748
1123
|
if (openRef.current) return;
|
|
749
1124
|
window.clearTimeout(openTimer.current);
|
|
750
1125
|
openTimer.current = window.setTimeout(() => openNow(), OPEN_DELAY_MS);
|
|
751
1126
|
}, [openNow]);
|
|
752
|
-
const scheduleClose =
|
|
1127
|
+
const scheduleClose = useCallback9(() => {
|
|
753
1128
|
window.clearTimeout(openTimer.current);
|
|
754
1129
|
window.clearTimeout(closeTimer.current);
|
|
755
1130
|
closeTimer.current = window.setTimeout(closeNow, CLOSE_GRACE_MS);
|
|
756
1131
|
}, [closeNow]);
|
|
757
|
-
|
|
1132
|
+
useLayoutEffect2(() => {
|
|
758
1133
|
if (!open) {
|
|
759
1134
|
setPos(null);
|
|
760
1135
|
return;
|
|
@@ -791,7 +1166,7 @@ function Term({ id, children, className }) {
|
|
|
791
1166
|
if (raf) cancelAnimationFrame(raf);
|
|
792
1167
|
};
|
|
793
1168
|
}, [open]);
|
|
794
|
-
|
|
1169
|
+
useEffect10(() => {
|
|
795
1170
|
if (!open) return;
|
|
796
1171
|
const onPointerDown = (e) => {
|
|
797
1172
|
const target = e.target;
|
|
@@ -802,7 +1177,7 @@ function Term({ id, children, className }) {
|
|
|
802
1177
|
document.addEventListener("pointerdown", onPointerDown);
|
|
803
1178
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
804
1179
|
}, [open, closeNow]);
|
|
805
|
-
|
|
1180
|
+
useEffect10(() => () => {
|
|
806
1181
|
window.clearTimeout(openTimer.current);
|
|
807
1182
|
window.clearTimeout(closeTimer.current);
|
|
808
1183
|
}, []);
|
|
@@ -854,6 +1229,7 @@ function Term({ id, children, className }) {
|
|
|
854
1229
|
"span",
|
|
855
1230
|
{
|
|
856
1231
|
ref: wrapperRef,
|
|
1232
|
+
"data-stepwise": "",
|
|
857
1233
|
className: "relative inline",
|
|
858
1234
|
onMouseEnter: interactive ? scheduleOpen : void 0,
|
|
859
1235
|
onMouseLeave: interactive ? scheduleClose : void 0,
|
|
@@ -876,6 +1252,7 @@ function Term({ id, children, className }) {
|
|
|
876
1252
|
"span",
|
|
877
1253
|
{
|
|
878
1254
|
ref: cardRef,
|
|
1255
|
+
"data-stepwise": "",
|
|
879
1256
|
role: "tooltip",
|
|
880
1257
|
id: tipId,
|
|
881
1258
|
tabIndex: -1,
|
|
@@ -946,14 +1323,14 @@ function GlossaryAppendix({
|
|
|
946
1323
|
if (bare) {
|
|
947
1324
|
return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: body });
|
|
948
1325
|
}
|
|
949
|
-
return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: /* @__PURE__ */ jsxs("section", { "aria-label": title, className: cn("mt-16 border-t border-white/10 pt-10", className), children: [
|
|
1326
|
+
return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: /* @__PURE__ */ jsxs("section", { "data-stepwise": "", "aria-label": title, className: cn("mt-16 border-t border-white/10 pt-10", className), children: [
|
|
950
1327
|
/* @__PURE__ */ jsx("p", { className: cn("text-[11px] font-mono uppercase tracking-widest mb-6", theme.muted), children: title }),
|
|
951
1328
|
body
|
|
952
1329
|
] }) });
|
|
953
1330
|
}
|
|
954
1331
|
|
|
955
1332
|
// src/components.tsx
|
|
956
|
-
import { useEffect as
|
|
1333
|
+
import { useEffect as useEffect11, useState as useState9 } from "react";
|
|
957
1334
|
import { createPortal as createPortal2 } from "react-dom";
|
|
958
1335
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
959
1336
|
var TONE_SEGMENT_CLASS = defaultTheme.toneSegment;
|
|
@@ -975,7 +1352,7 @@ function CodeBlock({
|
|
|
975
1352
|
theme = defaultTheme,
|
|
976
1353
|
className
|
|
977
1354
|
}) {
|
|
978
|
-
return /* @__PURE__ */ jsx2("div", { className: cn(theme.codeBlock, className), children: lines.map((line, i) => {
|
|
1355
|
+
return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: cn(theme.codeBlock, className), children: lines.map((line, i) => {
|
|
979
1356
|
const commentStart = line.indexOf("//");
|
|
980
1357
|
if (commentStart === 0) {
|
|
981
1358
|
return /* @__PURE__ */ jsx2("div", { className: cn("whitespace-pre", theme.codeMuted), children: line }, i);
|
|
@@ -1022,7 +1399,7 @@ function StepScrubber({
|
|
|
1022
1399
|
}
|
|
1023
1400
|
};
|
|
1024
1401
|
if (count === 0) {
|
|
1025
|
-
return /* @__PURE__ */ jsx2("div", { className: cn("flex-1 flex items-center min-w-0", className), children: emptyHint && /* @__PURE__ */ jsx2("span", { className: cn("text-xs font-mono px-2", theme.muted), children: emptyHint }) });
|
|
1402
|
+
return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: cn("flex-1 flex items-center min-w-0", className), children: emptyHint && /* @__PURE__ */ jsx2("span", { className: cn("text-xs font-mono px-2", theme.muted), children: emptyHint }) });
|
|
1026
1403
|
}
|
|
1027
1404
|
const valueText = labels?.frame ? labels.frame(index) : titleAt?.(index) ?? `frame ${index + 1} of ${count}`;
|
|
1028
1405
|
const segment = (i) => /* @__PURE__ */ jsx2(
|
|
@@ -1064,6 +1441,7 @@ function StepScrubber({
|
|
|
1064
1441
|
return /* @__PURE__ */ jsx2(
|
|
1065
1442
|
"div",
|
|
1066
1443
|
{
|
|
1444
|
+
"data-stepwise": "",
|
|
1067
1445
|
role: "slider",
|
|
1068
1446
|
tabIndex: 0,
|
|
1069
1447
|
"aria-label": labels?.scrubber ?? "Frames",
|
|
@@ -1101,7 +1479,7 @@ function PlayerBar({
|
|
|
1101
1479
|
next: labels?.next ?? "Next step"
|
|
1102
1480
|
};
|
|
1103
1481
|
const playLabel = player.playing ? L.pause : player.total > 0 && player.atEnd ? L.replay : L.play;
|
|
1104
|
-
return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-2 p-2", className), children: [
|
|
1482
|
+
return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2 p-2", className), children: [
|
|
1105
1483
|
leading,
|
|
1106
1484
|
/* @__PURE__ */ jsx2(StepButton, { variant: "ghost", onClick: () => player.prev(), className: "h-7 px-2 text-xs", "aria-label": L.previous, children: "\u2190" }),
|
|
1107
1485
|
/* @__PURE__ */ jsx2(StepButton, { variant: "outline", onClick: () => player.toggle(onEmpty), className: "h-7 px-3.5 text-[11px]", children: playLabel }),
|
|
@@ -1163,6 +1541,7 @@ function ChapterNav({
|
|
|
1163
1541
|
return /* @__PURE__ */ jsx2(
|
|
1164
1542
|
"nav",
|
|
1165
1543
|
{
|
|
1544
|
+
"data-stepwise": "",
|
|
1166
1545
|
"aria-label": "Chapters",
|
|
1167
1546
|
className: cn(
|
|
1168
1547
|
"sticky top-20 md:top-24 z-30 rounded-sm shadow-[0_8px_24px_rgba(0,0,0,0.35)]",
|
|
@@ -1186,7 +1565,7 @@ function LessonPager({
|
|
|
1186
1565
|
className,
|
|
1187
1566
|
labels
|
|
1188
1567
|
}) {
|
|
1189
|
-
return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-2", className), children: [
|
|
1568
|
+
return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2", className), children: [
|
|
1190
1569
|
/* @__PURE__ */ jsxs2(
|
|
1191
1570
|
StepButton,
|
|
1192
1571
|
{
|
|
@@ -1223,8 +1602,8 @@ function TourSpotlight({
|
|
|
1223
1602
|
target,
|
|
1224
1603
|
theme = defaultTheme
|
|
1225
1604
|
}) {
|
|
1226
|
-
const [rect, setRect] =
|
|
1227
|
-
|
|
1605
|
+
const [rect, setRect] = useState9(null);
|
|
1606
|
+
useEffect11(() => {
|
|
1228
1607
|
if (!target) {
|
|
1229
1608
|
setRect(null);
|
|
1230
1609
|
return;
|
|
@@ -1256,7 +1635,7 @@ function TourSpotlight({
|
|
|
1256
1635
|
}, [target]);
|
|
1257
1636
|
if (!rect) return null;
|
|
1258
1637
|
const pad = 6;
|
|
1259
|
-
return /* @__PURE__ */ jsx2("div", { className: "fixed inset-0 z-40 pointer-events-none", children: /* @__PURE__ */ jsx2(
|
|
1638
|
+
return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: "fixed inset-0 z-40 pointer-events-none", children: /* @__PURE__ */ jsx2(
|
|
1260
1639
|
"div",
|
|
1261
1640
|
{
|
|
1262
1641
|
className: cn("absolute", theme.spotlight),
|
|
@@ -1294,6 +1673,7 @@ function TourCard({
|
|
|
1294
1673
|
"div",
|
|
1295
1674
|
{
|
|
1296
1675
|
ref: trapRef,
|
|
1676
|
+
"data-stepwise": "",
|
|
1297
1677
|
role: "dialog",
|
|
1298
1678
|
"aria-modal": "true",
|
|
1299
1679
|
"aria-label": `${L.title} \xB7 ${index + 1}/${total}`,
|
|
@@ -1352,10 +1732,10 @@ function ShortcutsDialog({
|
|
|
1352
1732
|
className,
|
|
1353
1733
|
theme = defaultTheme
|
|
1354
1734
|
}) {
|
|
1355
|
-
const [mounted, setMounted] =
|
|
1735
|
+
const [mounted, setMounted] = useState9(false);
|
|
1356
1736
|
const trapRef = useFocusTrap(open);
|
|
1357
|
-
|
|
1358
|
-
|
|
1737
|
+
useEffect11(() => setMounted(true), []);
|
|
1738
|
+
useEffect11(() => {
|
|
1359
1739
|
if (!open) return;
|
|
1360
1740
|
const onKey = (e) => {
|
|
1361
1741
|
if (e.key === "Escape") onClose();
|
|
@@ -1368,6 +1748,7 @@ function ShortcutsDialog({
|
|
|
1368
1748
|
/* @__PURE__ */ jsx2(
|
|
1369
1749
|
"div",
|
|
1370
1750
|
{
|
|
1751
|
+
"data-stepwise": "",
|
|
1371
1752
|
className: cn("fixed inset-0 z-50 flex items-center justify-center p-4", theme.overlay, className),
|
|
1372
1753
|
onClick: onClose,
|
|
1373
1754
|
children: /* @__PURE__ */ jsxs2(
|
|
@@ -1424,18 +1805,24 @@ export {
|
|
|
1424
1805
|
Term,
|
|
1425
1806
|
TourCard,
|
|
1426
1807
|
TourSpotlight,
|
|
1808
|
+
alignTracks,
|
|
1427
1809
|
cookieStore,
|
|
1810
|
+
createTrace,
|
|
1428
1811
|
defaultTheme,
|
|
1429
1812
|
flattenChapters,
|
|
1430
1813
|
localStorageStore,
|
|
1431
1814
|
memoryStore,
|
|
1432
1815
|
useChapters,
|
|
1433
1816
|
useDiscoveries,
|
|
1817
|
+
useFadeIn,
|
|
1434
1818
|
useFocusTrap,
|
|
1435
1819
|
useGlobalKeys,
|
|
1436
1820
|
useGuidedTour,
|
|
1821
|
+
useLessonStream,
|
|
1437
1822
|
usePrefersReducedMotion,
|
|
1438
1823
|
useStepPlayer,
|
|
1439
|
-
useSteps
|
|
1824
|
+
useSteps,
|
|
1825
|
+
useSyncedTracks,
|
|
1826
|
+
useTrace
|
|
1440
1827
|
};
|
|
1441
1828
|
//# sourceMappingURL=index.js.map
|