@sphynxoid/stepwise 0.2.0 → 0.3.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 +66 -2
- 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 +223 -20
- package/dist/index.js +429 -112
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/{types-IT_9L8On.d.ts → types-CNMxISXe.d.ts} +34 -5
- package/package.json +108 -101
package/dist/index.js
CHANGED
|
@@ -66,17 +66,23 @@ function useStepPlayer({
|
|
|
66
66
|
engine,
|
|
67
67
|
syncHash,
|
|
68
68
|
onEnd,
|
|
69
|
-
respectReducedMotion = true
|
|
69
|
+
respectReducedMotion = true,
|
|
70
|
+
holdAt,
|
|
71
|
+
follow = false
|
|
70
72
|
}) {
|
|
71
73
|
const core = useSteps(total);
|
|
72
74
|
const { go, next: coreNext, prev: corePrev, first } = core;
|
|
73
75
|
const [started, setStarted] = useState2(autoPlay && total > 0);
|
|
74
|
-
const [playing, setPlaying] = useState2(autoPlay && total > 0);
|
|
76
|
+
const [playing, setPlaying] = useState2(autoPlay && (total > 0 || follow));
|
|
75
77
|
const [speed, setSpeed] = useState2(1);
|
|
76
78
|
const [ended, setEnded] = useState2(false);
|
|
77
79
|
const endedRef = useRef2(false);
|
|
78
80
|
const onEndRef = useRef2(onEnd);
|
|
79
81
|
onEndRef.current = onEnd;
|
|
82
|
+
const holdAtRef = useRef2(holdAt);
|
|
83
|
+
holdAtRef.current = holdAt;
|
|
84
|
+
const [held, setHeld] = useState2(null);
|
|
85
|
+
const releasedRef = useRef2(/* @__PURE__ */ new Set());
|
|
80
86
|
const markEnded = useCallback2(() => {
|
|
81
87
|
if (endedRef.current) return;
|
|
82
88
|
endedRef.current = true;
|
|
@@ -90,6 +96,7 @@ function useStepPlayer({
|
|
|
90
96
|
}, []);
|
|
91
97
|
const index = started && core.index >= 0 ? core.index : -1;
|
|
92
98
|
const atEnd = total > 0 && index >= 0 && index >= total - 1;
|
|
99
|
+
const live = follow && held === null && (started ? playing || atEnd : playing);
|
|
93
100
|
const totalRef = useRef2(total);
|
|
94
101
|
totalRef.current = total;
|
|
95
102
|
const cursorRef = useRef2(core.index);
|
|
@@ -98,6 +105,10 @@ function useStepPlayer({
|
|
|
98
105
|
playingRef.current = playing;
|
|
99
106
|
const atEndRef = useRef2(atEnd);
|
|
100
107
|
atEndRef.current = atEnd;
|
|
108
|
+
const heldRef = useRef2(held);
|
|
109
|
+
heldRef.current = held;
|
|
110
|
+
const startedRef = useRef2(started);
|
|
111
|
+
startedRef.current = started;
|
|
101
112
|
const engineRef = useRef2(engine);
|
|
102
113
|
engineRef.current = engine;
|
|
103
114
|
const engineApiRef = useRef2({});
|
|
@@ -135,6 +146,8 @@ function useStepPlayer({
|
|
|
135
146
|
setStarted(true);
|
|
136
147
|
setPlaying(true);
|
|
137
148
|
clearEnded();
|
|
149
|
+
setHeld(null);
|
|
150
|
+
releasedRef.current = /* @__PURE__ */ new Set();
|
|
138
151
|
first();
|
|
139
152
|
if (engineRef.current) {
|
|
140
153
|
engineApiRef.current.seek?.(0);
|
|
@@ -145,6 +158,8 @@ function useStepPlayer({
|
|
|
145
158
|
setStarted(false);
|
|
146
159
|
setPlaying(false);
|
|
147
160
|
clearEnded();
|
|
161
|
+
setHeld(null);
|
|
162
|
+
releasedRef.current = /* @__PURE__ */ new Set();
|
|
148
163
|
if (engineRef.current) engineApiRef.current.pause?.();
|
|
149
164
|
}, [clearEnded]);
|
|
150
165
|
const seek = useCallback2(
|
|
@@ -154,6 +169,7 @@ function useStepPlayer({
|
|
|
154
169
|
setStarted(true);
|
|
155
170
|
setPlaying(false);
|
|
156
171
|
clearEnded();
|
|
172
|
+
setHeld(null);
|
|
157
173
|
go(Math.max(0, Math.min(f, t - 1)));
|
|
158
174
|
if (engineRef.current) {
|
|
159
175
|
engineApiRef.current.pause?.();
|
|
@@ -172,6 +188,7 @@ function useStepPlayer({
|
|
|
172
188
|
}
|
|
173
189
|
setStarted(true);
|
|
174
190
|
clearEnded();
|
|
191
|
+
setHeld(null);
|
|
175
192
|
const target = Math.min(cursorRef.current + 1, t - 1);
|
|
176
193
|
coreNext();
|
|
177
194
|
if (engineRef.current) {
|
|
@@ -184,6 +201,7 @@ function useStepPlayer({
|
|
|
184
201
|
const prev = useCallback2(() => {
|
|
185
202
|
setPlaying(false);
|
|
186
203
|
clearEnded();
|
|
204
|
+
setHeld(null);
|
|
187
205
|
const target = Math.max(0, cursorRef.current - 1);
|
|
188
206
|
corePrev();
|
|
189
207
|
if (engineRef.current) {
|
|
@@ -204,6 +222,10 @@ function useStepPlayer({
|
|
|
204
222
|
const nextPlaying = !playingRef.current;
|
|
205
223
|
setPlaying(nextPlaying);
|
|
206
224
|
setStarted(true);
|
|
225
|
+
if (nextPlaying) {
|
|
226
|
+
releasedRef.current.add(cursorRef.current);
|
|
227
|
+
setHeld(null);
|
|
228
|
+
}
|
|
207
229
|
if (engineRef.current) {
|
|
208
230
|
if (nextPlaying) engineApiRef.current.play?.();
|
|
209
231
|
else engineApiRef.current.pause?.();
|
|
@@ -211,6 +233,36 @@ function useStepPlayer({
|
|
|
211
233
|
},
|
|
212
234
|
[start]
|
|
213
235
|
);
|
|
236
|
+
const release = useCallback2(() => {
|
|
237
|
+
const h = heldRef.current;
|
|
238
|
+
if (h === null) return;
|
|
239
|
+
releasedRef.current.add(h);
|
|
240
|
+
setHeld(null);
|
|
241
|
+
setPlaying(true);
|
|
242
|
+
}, []);
|
|
243
|
+
const jump = useCallback2(
|
|
244
|
+
(f) => {
|
|
245
|
+
const t = totalRef.current;
|
|
246
|
+
if (t === 0) return;
|
|
247
|
+
setStarted(true);
|
|
248
|
+
go(Math.max(0, Math.min(f, t - 1)));
|
|
249
|
+
if (engineRef.current) engineApiRef.current.seek?.(f);
|
|
250
|
+
},
|
|
251
|
+
[go]
|
|
252
|
+
);
|
|
253
|
+
const goLive = useCallback2(() => {
|
|
254
|
+
const t = totalRef.current;
|
|
255
|
+
if (t === 0) return;
|
|
256
|
+
setStarted(true);
|
|
257
|
+
setPlaying(false);
|
|
258
|
+
clearEnded();
|
|
259
|
+
setHeld(null);
|
|
260
|
+
go(t - 1);
|
|
261
|
+
if (engineRef.current) {
|
|
262
|
+
engineApiRef.current.pause?.();
|
|
263
|
+
engineApiRef.current.seek?.(t - 1);
|
|
264
|
+
}
|
|
265
|
+
}, [go, clearEnded]);
|
|
214
266
|
const changeSpeed = useCallback2((s) => {
|
|
215
267
|
setSpeed(s);
|
|
216
268
|
if (engineRef.current) engineApiRef.current.rate?.(s);
|
|
@@ -218,6 +270,21 @@ function useStepPlayer({
|
|
|
218
270
|
useEffect2(() => {
|
|
219
271
|
clearEnded();
|
|
220
272
|
}, [total, clearEnded]);
|
|
273
|
+
const prevTotal = useRef2(total);
|
|
274
|
+
useEffect2(() => {
|
|
275
|
+
const before = prevTotal.current;
|
|
276
|
+
prevTotal.current = total;
|
|
277
|
+
if (!follow || engineRef.current || total <= before) return;
|
|
278
|
+
if (heldRef.current !== null) return;
|
|
279
|
+
if (!startedRef.current) {
|
|
280
|
+
if (playingRef.current) setStarted(true);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (playingRef.current || cursorRef.current >= before - 1) setPlaying(true);
|
|
284
|
+
}, [total, follow]);
|
|
285
|
+
useEffect2(() => {
|
|
286
|
+
if (held !== null && held !== core.index) setHeld(null);
|
|
287
|
+
}, [held, core.index]);
|
|
221
288
|
useEffect2(() => {
|
|
222
289
|
if (engineRef.current) return;
|
|
223
290
|
if (!playing || !started || total === 0) return;
|
|
@@ -226,30 +293,48 @@ function useStepPlayer({
|
|
|
226
293
|
setPlaying(false);
|
|
227
294
|
return;
|
|
228
295
|
}
|
|
296
|
+
if (holdAtRef.current?.(core.index) && !releasedRef.current.has(core.index)) {
|
|
297
|
+
setHeld(core.index);
|
|
298
|
+
setPlaying(false);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
229
301
|
const id = setTimeout(() => coreNext(), baseDelayMs / speed);
|
|
230
302
|
return () => clearTimeout(id);
|
|
231
303
|
}, [playing, started, core.index, coreNext, total, speed, baseDelayMs, markEnded]);
|
|
232
304
|
const lastWrittenHash = useRef2("");
|
|
305
|
+
const pendingRead = useRef2(null);
|
|
233
306
|
const readHash = useCallback2(() => {
|
|
234
|
-
|
|
307
|
+
const t = totalRef.current;
|
|
308
|
+
if (!syncHash || typeof window === "undefined" || t === 0) return;
|
|
235
309
|
const hash = window.location.hash;
|
|
236
310
|
if (hash === lastWrittenHash.current) return;
|
|
237
311
|
const value = readHashParam(hash, syncHash);
|
|
238
312
|
if (value === null || !/^\d+$/.test(value)) return;
|
|
239
313
|
const f = Number(value);
|
|
240
|
-
if (f <
|
|
314
|
+
if (f < t) {
|
|
315
|
+
pendingRead.current = f;
|
|
241
316
|
setStarted(true);
|
|
242
317
|
go(f);
|
|
243
318
|
}
|
|
244
|
-
}, [syncHash,
|
|
319
|
+
}, [syncHash, go]);
|
|
320
|
+
const linkedRead = useRef2(false);
|
|
245
321
|
useEffect2(() => {
|
|
246
322
|
readHash();
|
|
323
|
+
linkedRead.current = totalRef.current > 0;
|
|
247
324
|
const onHashChange = () => readHash();
|
|
248
325
|
window.addEventListener("hashchange", onHashChange);
|
|
249
326
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
250
327
|
}, [readHash]);
|
|
328
|
+
useEffect2(() => {
|
|
329
|
+
if (linkedRead.current || total === 0) return;
|
|
330
|
+
linkedRead.current = true;
|
|
331
|
+
readHash();
|
|
332
|
+
}, [total, readHash]);
|
|
251
333
|
useEffect2(() => {
|
|
252
334
|
if (!syncHash || index < 0 || typeof window === "undefined") return;
|
|
335
|
+
const pending2 = pendingRead.current;
|
|
336
|
+
pendingRead.current = null;
|
|
337
|
+
if (pending2 !== null && pending2 !== index) return;
|
|
253
338
|
const next2 = writeHashParam(window.location.hash, syncHash, String(index));
|
|
254
339
|
if (window.location.hash !== next2) {
|
|
255
340
|
lastWrittenHash.current = next2;
|
|
@@ -265,6 +350,11 @@ function useStepPlayer({
|
|
|
265
350
|
speed,
|
|
266
351
|
atEnd,
|
|
267
352
|
ended,
|
|
353
|
+
held,
|
|
354
|
+
release,
|
|
355
|
+
jump,
|
|
356
|
+
live,
|
|
357
|
+
goLive,
|
|
268
358
|
start,
|
|
269
359
|
reset,
|
|
270
360
|
seek,
|
|
@@ -274,7 +364,7 @@ function useStepPlayer({
|
|
|
274
364
|
setPlaying,
|
|
275
365
|
setSpeed: changeSpeed
|
|
276
366
|
}),
|
|
277
|
-
[total, index, playing, speed, atEnd, ended, start, reset, seek, next, prev, toggle, changeSpeed]
|
|
367
|
+
[total, index, playing, speed, atEnd, ended, held, release, jump, live, goLive, start, reset, seek, next, prev, toggle, changeSpeed]
|
|
278
368
|
);
|
|
279
369
|
}
|
|
280
370
|
function hashParts(hash) {
|
|
@@ -298,18 +388,139 @@ function writeHashParam(hash, key, value) {
|
|
|
298
388
|
}
|
|
299
389
|
|
|
300
390
|
// src/use-trace.ts
|
|
301
|
-
import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
391
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useReducer, useRef as useRef3 } from "react";
|
|
392
|
+
|
|
393
|
+
// src/reanchor.ts
|
|
394
|
+
function reanchor(oldSteps, oldIndex, newSteps, keyOf) {
|
|
395
|
+
if (newSteps.length === 0) return -1;
|
|
396
|
+
if (oldIndex < 0 || oldSteps.length === 0) return 0;
|
|
397
|
+
const positions = /* @__PURE__ */ new Map();
|
|
398
|
+
newSteps.forEach((s, i) => {
|
|
399
|
+
const k = keyOf(s, i);
|
|
400
|
+
if (k !== void 0 && !positions.has(k)) positions.set(k, i);
|
|
401
|
+
});
|
|
402
|
+
for (let i = Math.min(oldIndex, oldSteps.length - 1); i >= 0; i--) {
|
|
403
|
+
const k = keyOf(oldSteps[i], i);
|
|
404
|
+
if (k === void 0) continue;
|
|
405
|
+
const j = positions.get(k);
|
|
406
|
+
if (j !== void 0) return j;
|
|
407
|
+
}
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
var stepKey = (step) => step !== null && typeof step === "object" && typeof step.key === "string" ? step.key : void 0;
|
|
411
|
+
|
|
412
|
+
// src/use-trace.ts
|
|
413
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
|
|
302
414
|
function useTrace(steps, opts) {
|
|
303
|
-
const
|
|
415
|
+
const { keyOf, holdAt, ...playerOpts } = opts ?? {};
|
|
304
416
|
const stepsRef = useRef3(steps);
|
|
417
|
+
const player = useStepPlayer({
|
|
418
|
+
...playerOpts,
|
|
419
|
+
total: steps.length,
|
|
420
|
+
holdAt: holdAt ? (f) => holdAt(f, stepsRef.current[f]) : void 0
|
|
421
|
+
});
|
|
305
422
|
stepsRef.current = steps;
|
|
306
|
-
const
|
|
423
|
+
const keyFn = keyOf ?? stepKey;
|
|
424
|
+
const shownRef = useRef3({ steps, index: player.index });
|
|
425
|
+
const shown = shownRef.current;
|
|
426
|
+
let anchored = null;
|
|
427
|
+
if (shown.steps !== steps && shown.index >= 0) {
|
|
428
|
+
const i = shown.index;
|
|
429
|
+
const k = i < shown.steps.length ? keyFn(shown.steps[i], i) : void 0;
|
|
430
|
+
if (k !== void 0 && i < steps.length && keyFn(steps[i], i) === k) anchored = i;
|
|
431
|
+
else if (steps.some((s, j) => keyFn(s, j) !== void 0)) anchored = reanchor(shown.steps, i, steps, keyFn);
|
|
432
|
+
}
|
|
433
|
+
const index = anchored ?? player.index;
|
|
434
|
+
const { jump } = player;
|
|
435
|
+
const [, rerender] = useReducer((n) => n + 1, 0);
|
|
436
|
+
useIsomorphicLayoutEffect(() => {
|
|
437
|
+
shownRef.current = { steps, index };
|
|
438
|
+
if (anchored === null) return;
|
|
439
|
+
if (anchored >= 0 && anchored !== player.index) jump(anchored);
|
|
440
|
+
rerender();
|
|
441
|
+
});
|
|
442
|
+
const frame = steps.length === 0 ? -1 : Math.max(0, Math.min(index, steps.length - 1));
|
|
307
443
|
const step = frame >= 0 ? steps[frame] : void 0;
|
|
308
444
|
const toneAt = useCallback3((i) => stepsRef.current[i]?.tone ?? "neutral", []);
|
|
309
445
|
const titleAt = useCallback3((i) => stepsRef.current[i]?.label ?? `step ${i + 1}`, []);
|
|
310
446
|
return useMemo3(() => ({ player, step, frame, toneAt, titleAt }), [player, step, frame, toneAt, titleAt]);
|
|
311
447
|
}
|
|
312
448
|
|
|
449
|
+
// src/use-simulation.ts
|
|
450
|
+
import { useMemo as useMemo4 } from "react";
|
|
451
|
+
|
|
452
|
+
// src/simulation.ts
|
|
453
|
+
function hashSeed(seed) {
|
|
454
|
+
let h = 2166136261;
|
|
455
|
+
for (let i = 0; i < seed.length; i++) {
|
|
456
|
+
h ^= seed.charCodeAt(i);
|
|
457
|
+
h = Math.imul(h, 16777619);
|
|
458
|
+
}
|
|
459
|
+
return h >>> 0;
|
|
460
|
+
}
|
|
461
|
+
function createRng(seed = 0) {
|
|
462
|
+
const base = typeof seed === "string" ? hashSeed(seed) : seed >>> 0;
|
|
463
|
+
let state = base;
|
|
464
|
+
const next = () => {
|
|
465
|
+
state = state + 1831565813 >>> 0;
|
|
466
|
+
let t = state;
|
|
467
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
468
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
469
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
470
|
+
};
|
|
471
|
+
const int = (min, max) => min + Math.floor(next() * (max - min + 1));
|
|
472
|
+
return {
|
|
473
|
+
seed: base,
|
|
474
|
+
next,
|
|
475
|
+
int,
|
|
476
|
+
pick(items) {
|
|
477
|
+
if (items.length === 0) throw new RangeError("rng.pick: empty array");
|
|
478
|
+
return items[int(0, items.length - 1)];
|
|
479
|
+
},
|
|
480
|
+
shuffle(items) {
|
|
481
|
+
const out = items.slice();
|
|
482
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
483
|
+
const j = int(0, i);
|
|
484
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
485
|
+
}
|
|
486
|
+
return out;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function runSimulation(simulation, input, opts) {
|
|
491
|
+
const maxSteps = opts?.maxSteps ?? 1e4;
|
|
492
|
+
const rng = createRng(opts?.seed ?? 0);
|
|
493
|
+
const steps = [];
|
|
494
|
+
let truncated = false;
|
|
495
|
+
let error = null;
|
|
496
|
+
try {
|
|
497
|
+
for (const step of simulation(input, { rng })) {
|
|
498
|
+
if (steps.length >= maxSteps) {
|
|
499
|
+
truncated = true;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
steps.push(step);
|
|
503
|
+
}
|
|
504
|
+
} catch (e) {
|
|
505
|
+
error = e;
|
|
506
|
+
}
|
|
507
|
+
return { steps, truncated, error, seed: rng.seed };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/use-simulation.ts
|
|
511
|
+
function useSimulation(simulation, input, opts) {
|
|
512
|
+
const { seed, maxSteps, ...traceOpts } = opts ?? {};
|
|
513
|
+
const result = useMemo4(
|
|
514
|
+
() => runSimulation(simulation, input, { seed, maxSteps }),
|
|
515
|
+
[simulation, input, seed, maxSteps]
|
|
516
|
+
);
|
|
517
|
+
const trace = useTrace(result.steps, traceOpts);
|
|
518
|
+
return useMemo4(
|
|
519
|
+
() => ({ ...trace, steps: result.steps, truncated: result.truncated, error: result.error, seed: result.seed }),
|
|
520
|
+
[trace, result]
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
313
524
|
// src/create-trace.ts
|
|
314
525
|
function createTrace() {
|
|
315
526
|
const frames = [];
|
|
@@ -332,7 +543,7 @@ function createTrace() {
|
|
|
332
543
|
}
|
|
333
544
|
|
|
334
545
|
// src/use-synced-tracks.ts
|
|
335
|
-
import { useCallback as useCallback4, useMemo as
|
|
546
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef4 } from "react";
|
|
336
547
|
function alignTracks(tracks, key) {
|
|
337
548
|
const names = Object.keys(tracks);
|
|
338
549
|
const lens = names.map((n) => tracks[n].length);
|
|
@@ -420,7 +631,7 @@ function useSyncedTracks(tracks, opts) {
|
|
|
420
631
|
const frame = rows.length === 0 ? -1 : Math.max(0, Math.min(player.index, rows.length - 1));
|
|
421
632
|
const row = frame >= 0 ? rows[frame] : void 0;
|
|
422
633
|
const aligned = cache.current;
|
|
423
|
-
const view =
|
|
634
|
+
const view = useMemo5(() => {
|
|
424
635
|
const out = {};
|
|
425
636
|
aligned.names.forEach((n, t) => {
|
|
426
637
|
const list = aligned.arrays[t];
|
|
@@ -455,20 +666,20 @@ function useSyncedTracks(tracks, opts) {
|
|
|
455
666
|
},
|
|
456
667
|
[moved]
|
|
457
668
|
);
|
|
458
|
-
return
|
|
669
|
+
return useMemo5(
|
|
459
670
|
() => ({ player, frame, total: rows.length, tracks: view, rows, toneAt, titleAt }),
|
|
460
671
|
[player, frame, rows, view, toneAt, titleAt]
|
|
461
672
|
);
|
|
462
673
|
}
|
|
463
674
|
|
|
464
675
|
// src/use-lesson-stream.ts
|
|
465
|
-
import { useCallback as useCallback5, useEffect as
|
|
676
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useLayoutEffect as useLayoutEffect2, useMemo as useMemo6, useRef as useRef5, useState as useState4 } from "react";
|
|
466
677
|
|
|
467
678
|
// src/use-prefers-reduced-motion.ts
|
|
468
|
-
import { useEffect as
|
|
679
|
+
import { useEffect as useEffect4, useState as useState3 } from "react";
|
|
469
680
|
function usePrefersReducedMotion() {
|
|
470
681
|
const [reduced, setReduced] = useState3(false);
|
|
471
|
-
|
|
682
|
+
useEffect4(() => {
|
|
472
683
|
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
473
684
|
setReduced(mq.matches);
|
|
474
685
|
const onChange = (e) => setReduced(e.matches);
|
|
@@ -479,7 +690,7 @@ function usePrefersReducedMotion() {
|
|
|
479
690
|
}
|
|
480
691
|
|
|
481
692
|
// src/use-lesson-stream.ts
|
|
482
|
-
var useIsoLayoutEffect = typeof window !== "undefined" ?
|
|
693
|
+
var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect2 : useEffect5;
|
|
483
694
|
function useLessonStream(count, opts = {}) {
|
|
484
695
|
const { radius = 1, approachBelow = 600, approachAbove = 300, scrollToSeed = true } = opts;
|
|
485
696
|
const last = count - 1;
|
|
@@ -529,7 +740,7 @@ function useLessonStream(count, opts = {}) {
|
|
|
529
740
|
setRange(grown);
|
|
530
741
|
}, []);
|
|
531
742
|
const scheduleRef = useRef5(null);
|
|
532
|
-
|
|
743
|
+
useEffect5(() => {
|
|
533
744
|
if (count === 0) return;
|
|
534
745
|
let raf = 0;
|
|
535
746
|
const update = () => {
|
|
@@ -572,7 +783,7 @@ function useLessonStream(count, opts = {}) {
|
|
|
572
783
|
if (raf) cancelAnimationFrame(raf);
|
|
573
784
|
};
|
|
574
785
|
}, [count, expandTo]);
|
|
575
|
-
|
|
786
|
+
useEffect5(() => {
|
|
576
787
|
scheduleRef.current?.();
|
|
577
788
|
}, [first, lastMounted]);
|
|
578
789
|
const firstRef = useRef5(first);
|
|
@@ -592,7 +803,7 @@ function useLessonStream(count, opts = {}) {
|
|
|
592
803
|
blocks.current.get(seed)?.scrollIntoView({ block: "start", behavior: "auto" });
|
|
593
804
|
}, []);
|
|
594
805
|
const [nav, setNav] = useState4(null);
|
|
595
|
-
|
|
806
|
+
useEffect5(() => {
|
|
596
807
|
if (!nav) return;
|
|
597
808
|
const raf = requestAnimationFrame(
|
|
598
809
|
() => blocks.current.get(nav.target)?.scrollIntoView({ behavior: reducedRef.current ? "auto" : "smooth", block: "start" })
|
|
@@ -611,12 +822,12 @@ function useLessonStream(count, opts = {}) {
|
|
|
611
822
|
);
|
|
612
823
|
const next = useCallback5(() => goTo(activeRef.current + 1), [goTo]);
|
|
613
824
|
const prev = useCallback5(() => goTo(activeRef.current - 1), [goTo]);
|
|
614
|
-
const mounted =
|
|
825
|
+
const mounted = useMemo6(() => {
|
|
615
826
|
const out = [];
|
|
616
827
|
for (let i = first; i <= lastMounted; i++) out.push(i);
|
|
617
828
|
return out;
|
|
618
829
|
}, [first, lastMounted]);
|
|
619
|
-
return
|
|
830
|
+
return useMemo6(
|
|
620
831
|
() => ({
|
|
621
832
|
active: core.index,
|
|
622
833
|
seed,
|
|
@@ -634,7 +845,7 @@ function useLessonStream(count, opts = {}) {
|
|
|
634
845
|
function useFadeIn(initiallyVisible = false) {
|
|
635
846
|
const reduced = usePrefersReducedMotion();
|
|
636
847
|
const [shown, setShown] = useState4(initiallyVisible);
|
|
637
|
-
|
|
848
|
+
useEffect5(() => {
|
|
638
849
|
if (shown) return;
|
|
639
850
|
if (reduced) {
|
|
640
851
|
setShown(true);
|
|
@@ -646,26 +857,105 @@ function useFadeIn(initiallyVisible = false) {
|
|
|
646
857
|
return shown;
|
|
647
858
|
}
|
|
648
859
|
|
|
860
|
+
// src/use-evaluator.ts
|
|
861
|
+
import { useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef6, useState as useState5 } from "react";
|
|
862
|
+
|
|
863
|
+
// src/evaluate.ts
|
|
864
|
+
function toVerdict(result) {
|
|
865
|
+
return typeof result === "boolean" ? { status: result ? "pass" : "fail" } : result;
|
|
866
|
+
}
|
|
867
|
+
var pass = (feedback) => ({ status: "pass", feedback });
|
|
868
|
+
var fail = (feedback) => ({ status: "fail", feedback });
|
|
869
|
+
var pending = (feedback) => ({ status: "pending", feedback });
|
|
870
|
+
|
|
871
|
+
// src/use-evaluator.ts
|
|
872
|
+
var IDLE = { status: "idle", feedback: void 0, error: null };
|
|
873
|
+
function useEvaluator(evaluator, ctx, opts) {
|
|
874
|
+
const mode = opts?.mode ?? "auto";
|
|
875
|
+
const [state, setState] = useState5(IDLE);
|
|
876
|
+
const [attempts, setAttempts] = useState5(0);
|
|
877
|
+
const evaluatorRef = useRef6(evaluator);
|
|
878
|
+
evaluatorRef.current = evaluator;
|
|
879
|
+
const ctxRef = useRef6(ctx);
|
|
880
|
+
ctxRef.current = ctx;
|
|
881
|
+
const runRef = useRef6(null);
|
|
882
|
+
useEffect6(() => () => runRef.current?.abort(), []);
|
|
883
|
+
const run = useCallback6(async () => {
|
|
884
|
+
runRef.current?.abort();
|
|
885
|
+
const controller = new AbortController();
|
|
886
|
+
runRef.current = controller;
|
|
887
|
+
let result;
|
|
888
|
+
try {
|
|
889
|
+
result = evaluatorRef.current(ctxRef.current, controller.signal);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
setState({ status: "fail", feedback: void 0, error });
|
|
892
|
+
return { status: "fail" };
|
|
893
|
+
}
|
|
894
|
+
if (!(result instanceof Promise)) {
|
|
895
|
+
const verdict = toVerdict(result);
|
|
896
|
+
setState({ status: verdict.status, feedback: verdict.feedback, error: null });
|
|
897
|
+
return verdict;
|
|
898
|
+
}
|
|
899
|
+
setState((s) => ({ status: "pending", feedback: s.feedback, error: null }));
|
|
900
|
+
try {
|
|
901
|
+
const verdict = toVerdict(await result);
|
|
902
|
+
if (controller.signal.aborted) return null;
|
|
903
|
+
setState({ status: verdict.status, feedback: verdict.feedback, error: null });
|
|
904
|
+
return verdict;
|
|
905
|
+
} catch (error) {
|
|
906
|
+
if (controller.signal.aborted) return null;
|
|
907
|
+
setState({ status: "fail", feedback: void 0, error });
|
|
908
|
+
return { status: "fail" };
|
|
909
|
+
}
|
|
910
|
+
}, []);
|
|
911
|
+
const check = useCallback6(() => {
|
|
912
|
+
setAttempts((n) => n + 1);
|
|
913
|
+
return run();
|
|
914
|
+
}, [run]);
|
|
915
|
+
const reset = useCallback6(() => {
|
|
916
|
+
runRef.current?.abort();
|
|
917
|
+
runRef.current = null;
|
|
918
|
+
setState(IDLE);
|
|
919
|
+
setAttempts(0);
|
|
920
|
+
}, []);
|
|
921
|
+
useEffect6(() => {
|
|
922
|
+
if (mode === "auto") {
|
|
923
|
+
void run();
|
|
924
|
+
} else {
|
|
925
|
+
runRef.current?.abort();
|
|
926
|
+
setState(IDLE);
|
|
927
|
+
}
|
|
928
|
+
}, [ctx, mode, run]);
|
|
929
|
+
return useMemo7(
|
|
930
|
+
() => ({ ...state, passed: state.status === "pass", attempts, check, reset }),
|
|
931
|
+
[state, attempts, check, reset]
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
649
935
|
// src/use-guided-tour.ts
|
|
650
|
-
import { useCallback as
|
|
936
|
+
import { useCallback as useCallback7, useEffect as useEffect7, useMemo as useMemo8, useRef as useRef7, useState as useState6 } from "react";
|
|
937
|
+
function gateVerdict(step) {
|
|
938
|
+
const r = step?.gate?.();
|
|
939
|
+
return r === void 0 ? { status: "pass", feedback: void 0 } : toVerdict(r);
|
|
940
|
+
}
|
|
651
941
|
function useGuidedTour(steps, opts) {
|
|
652
|
-
const [running, setRunning] =
|
|
653
|
-
const runningRef =
|
|
942
|
+
const [running, setRunning] = useState6(false);
|
|
943
|
+
const runningRef = useRef7(running);
|
|
654
944
|
runningRef.current = running;
|
|
655
945
|
const core = useSteps(steps.length);
|
|
656
|
-
const stepsRef =
|
|
946
|
+
const stepsRef = useRef7(steps);
|
|
657
947
|
stepsRef.current = steps;
|
|
658
|
-
const driverRef =
|
|
948
|
+
const driverRef = useRef7(opts?.driver);
|
|
659
949
|
driverRef.current = opts?.driver;
|
|
660
|
-
const coreGoRef =
|
|
950
|
+
const coreGoRef = useRef7(core.go);
|
|
661
951
|
coreGoRef.current = core.go;
|
|
662
|
-
const cursorRef =
|
|
952
|
+
const cursorRef = useRef7(core.index);
|
|
663
953
|
cursorRef.current = core.index;
|
|
664
|
-
const isSkipped =
|
|
954
|
+
const isSkipped = useCallback7(
|
|
665
955
|
(i) => stepsRef.current[i]?.when?.() === false,
|
|
666
956
|
[]
|
|
667
957
|
);
|
|
668
|
-
const settle =
|
|
958
|
+
const settle = useCallback7(
|
|
669
959
|
(start2, dir) => {
|
|
670
960
|
const len = stepsRef.current.length;
|
|
671
961
|
let i = start2;
|
|
@@ -677,7 +967,7 @@ function useGuidedTour(steps, opts) {
|
|
|
677
967
|
},
|
|
678
968
|
[isSkipped]
|
|
679
969
|
);
|
|
680
|
-
const arrive =
|
|
970
|
+
const arrive = useCallback7(
|
|
681
971
|
(i, dir) => {
|
|
682
972
|
const target = settle(i, dir);
|
|
683
973
|
if (target === null) {
|
|
@@ -689,14 +979,14 @@ function useGuidedTour(steps, opts) {
|
|
|
689
979
|
},
|
|
690
980
|
[settle]
|
|
691
981
|
);
|
|
692
|
-
const goTo =
|
|
982
|
+
const goTo = useCallback7(
|
|
693
983
|
(i) => {
|
|
694
984
|
if (stepsRef.current.length === 0) return;
|
|
695
985
|
arrive(Math.max(0, Math.min(i, stepsRef.current.length - 1)), 1);
|
|
696
986
|
},
|
|
697
987
|
[arrive]
|
|
698
988
|
);
|
|
699
|
-
const goToId =
|
|
989
|
+
const goToId = useCallback7(
|
|
700
990
|
(id) => {
|
|
701
991
|
const i = stepsRef.current.findIndex((s) => s.id === id);
|
|
702
992
|
if (i >= 0) goTo(i);
|
|
@@ -704,11 +994,11 @@ function useGuidedTour(steps, opts) {
|
|
|
704
994
|
[goTo]
|
|
705
995
|
);
|
|
706
996
|
const index = running ? core.index : null;
|
|
707
|
-
|
|
997
|
+
useEffect7(() => {
|
|
708
998
|
if (!running) return;
|
|
709
999
|
stepsRef.current[core.index]?.onEnter?.();
|
|
710
1000
|
}, [running, core.index]);
|
|
711
|
-
|
|
1001
|
+
useEffect7(() => {
|
|
712
1002
|
const driver = driverRef.current;
|
|
713
1003
|
if (!driver) return;
|
|
714
1004
|
if (!running) {
|
|
@@ -728,13 +1018,13 @@ function useGuidedTour(steps, opts) {
|
|
|
728
1018
|
end: () => setRunning(false)
|
|
729
1019
|
});
|
|
730
1020
|
}, [running, core.index, goTo]);
|
|
731
|
-
|
|
732
|
-
const start =
|
|
1021
|
+
useEffect7(() => () => driverRef.current?.hide(), []);
|
|
1022
|
+
const start = useCallback7(() => {
|
|
733
1023
|
if (stepsRef.current.length === 0) return;
|
|
734
1024
|
arrive(0, 1);
|
|
735
1025
|
}, [arrive]);
|
|
736
|
-
const end =
|
|
737
|
-
const resolveNext =
|
|
1026
|
+
const end = useCallback7(() => setRunning(false), []);
|
|
1027
|
+
const resolveNext = useCallback7((i) => {
|
|
738
1028
|
const list = stepsRef.current;
|
|
739
1029
|
const edge = list[i]?.next?.();
|
|
740
1030
|
if (edge != null) {
|
|
@@ -743,10 +1033,10 @@ function useGuidedTour(steps, opts) {
|
|
|
743
1033
|
}
|
|
744
1034
|
return i + 1;
|
|
745
1035
|
}, []);
|
|
746
|
-
const next =
|
|
1036
|
+
const next = useCallback7(() => {
|
|
747
1037
|
if (!runningRef.current) return;
|
|
748
1038
|
const i = cursorRef.current;
|
|
749
|
-
if (stepsRef.current[i]
|
|
1039
|
+
if (gateVerdict(stepsRef.current[i]).status !== "pass") return;
|
|
750
1040
|
const target = resolveNext(i);
|
|
751
1041
|
if (target > stepsRef.current.length - 1) {
|
|
752
1042
|
setRunning(false);
|
|
@@ -754,22 +1044,24 @@ function useGuidedTour(steps, opts) {
|
|
|
754
1044
|
}
|
|
755
1045
|
arrive(target, 1);
|
|
756
1046
|
}, [arrive, resolveNext]);
|
|
757
|
-
const back =
|
|
1047
|
+
const back = useCallback7(() => {
|
|
758
1048
|
if (!runningRef.current) return;
|
|
759
1049
|
const i = cursorRef.current;
|
|
760
1050
|
if (i <= 0) return;
|
|
761
1051
|
arrive(i - 1, -1);
|
|
762
1052
|
}, [arrive]);
|
|
763
1053
|
const current = running ? stepsRef.current[core.index] ?? null : null;
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1054
|
+
const verdict = current ? gateVerdict(current) : null;
|
|
1055
|
+
const blocked = verdict !== null && verdict.status !== "pass";
|
|
1056
|
+
const feedback = verdict?.feedback;
|
|
1057
|
+
return useMemo8(
|
|
1058
|
+
() => ({ index, current, active: running, blocked, feedback, start, end, next, back, goto: goTo, gotoId: goToId }),
|
|
1059
|
+
[index, current, running, blocked, feedback, start, end, next, back, goTo, goToId]
|
|
768
1060
|
);
|
|
769
1061
|
}
|
|
770
1062
|
|
|
771
1063
|
// src/use-chapters.ts
|
|
772
|
-
import { useCallback as
|
|
1064
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState7 } from "react";
|
|
773
1065
|
|
|
774
1066
|
// src/flatten.ts
|
|
775
1067
|
function flattenChapters(chapters, depth = 0) {
|
|
@@ -788,17 +1080,17 @@ function flattenChapters(chapters, depth = 0) {
|
|
|
788
1080
|
|
|
789
1081
|
// src/use-chapters.ts
|
|
790
1082
|
function useChapters(chapters) {
|
|
791
|
-
const [mounted, setMounted] =
|
|
792
|
-
const flat =
|
|
793
|
-
const flatRef =
|
|
1083
|
+
const [mounted, setMounted] = useState7(false);
|
|
1084
|
+
const flat = useMemo9(() => flattenChapters(chapters), [chapters]);
|
|
1085
|
+
const flatRef = useRef8(flat);
|
|
794
1086
|
flatRef.current = flat;
|
|
795
|
-
const [active, setActive] =
|
|
796
|
-
const activeRef =
|
|
1087
|
+
const [active, setActive] = useState7(0);
|
|
1088
|
+
const activeRef = useRef8(0);
|
|
797
1089
|
activeRef.current = active;
|
|
798
1090
|
const core = useSteps(flat.length, { initialIndex: 0 });
|
|
799
|
-
const coreGoRef =
|
|
1091
|
+
const coreGoRef = useRef8(core.go);
|
|
800
1092
|
coreGoRef.current = core.go;
|
|
801
|
-
|
|
1093
|
+
useEffect8(() => {
|
|
802
1094
|
if (!mounted) return;
|
|
803
1095
|
let raf = 0;
|
|
804
1096
|
const update = () => {
|
|
@@ -836,8 +1128,8 @@ function useChapters(chapters) {
|
|
|
836
1128
|
if (raf) cancelAnimationFrame(raf);
|
|
837
1129
|
};
|
|
838
1130
|
}, [mounted, flat.length]);
|
|
839
|
-
|
|
840
|
-
const goTo =
|
|
1131
|
+
useEffect8(() => setMounted(true), []);
|
|
1132
|
+
const goTo = useCallback8((i) => {
|
|
841
1133
|
const entry = flatRef.current[i];
|
|
842
1134
|
if (!entry) return;
|
|
843
1135
|
setActive(i);
|
|
@@ -846,25 +1138,25 @@ function useChapters(chapters) {
|
|
|
846
1138
|
if (!el) return;
|
|
847
1139
|
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
848
1140
|
}, []);
|
|
849
|
-
const next =
|
|
1141
|
+
const next = useCallback8(() => {
|
|
850
1142
|
if (activeRef.current < flatRef.current.length - 1) goTo(activeRef.current + 1);
|
|
851
1143
|
}, [goTo]);
|
|
852
|
-
const prev =
|
|
1144
|
+
const prev = useCallback8(() => {
|
|
853
1145
|
if (activeRef.current > 0) goTo(activeRef.current - 1);
|
|
854
1146
|
}, [goTo]);
|
|
855
|
-
return
|
|
1147
|
+
return useMemo9(() => ({ active: core.index, goTo, next, prev }), [core.index, goTo, next, prev]);
|
|
856
1148
|
}
|
|
857
1149
|
|
|
858
1150
|
// src/use-global-keys.ts
|
|
859
|
-
import { useEffect as
|
|
1151
|
+
import { useEffect as useEffect9, useRef as useRef9 } from "react";
|
|
860
1152
|
var DEFAULT_IGNORED_TAGS = ["INPUT", "TEXTAREA", "SELECT", "BUTTON"];
|
|
861
1153
|
function useGlobalKeys(map, opts) {
|
|
862
|
-
const mapRef =
|
|
1154
|
+
const mapRef = useRef9(map);
|
|
863
1155
|
mapRef.current = map;
|
|
864
|
-
const ignoreRef =
|
|
865
|
-
const enabledRef =
|
|
1156
|
+
const ignoreRef = useRef9(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
|
|
1157
|
+
const enabledRef = useRef9(opts?.enabled ?? true);
|
|
866
1158
|
enabledRef.current = opts?.enabled ?? true;
|
|
867
|
-
|
|
1159
|
+
useEffect9(() => {
|
|
868
1160
|
const onKey = (e) => {
|
|
869
1161
|
const gate = enabledRef.current;
|
|
870
1162
|
const enabled = typeof gate === "function" ? gate() : gate;
|
|
@@ -882,7 +1174,7 @@ function useGlobalKeys(map, opts) {
|
|
|
882
1174
|
}
|
|
883
1175
|
|
|
884
1176
|
// src/use-discoveries.ts
|
|
885
|
-
import { useCallback as
|
|
1177
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo10, useRef as useRef10, useState as useState8 } from "react";
|
|
886
1178
|
function localStorageStore(key = "stepwise.discoveries") {
|
|
887
1179
|
return {
|
|
888
1180
|
read() {
|
|
@@ -933,23 +1225,23 @@ function memoryStore(seed = []) {
|
|
|
933
1225
|
};
|
|
934
1226
|
}
|
|
935
1227
|
function useDiscoveries(opts) {
|
|
936
|
-
const defaultStoreRef =
|
|
937
|
-
const storeRef =
|
|
1228
|
+
const defaultStoreRef = useRef10(null);
|
|
1229
|
+
const storeRef = useRef10(null);
|
|
938
1230
|
if (opts?.store) {
|
|
939
1231
|
storeRef.current = opts.store;
|
|
940
1232
|
} else {
|
|
941
1233
|
if (!defaultStoreRef.current) defaultStoreRef.current = localStorageStore(opts?.key);
|
|
942
1234
|
storeRef.current = defaultStoreRef.current;
|
|
943
1235
|
}
|
|
944
|
-
const [ids, setIds] =
|
|
945
|
-
const [ready, setReady] =
|
|
946
|
-
const idsRef =
|
|
1236
|
+
const [ids, setIds] = useState8(() => /* @__PURE__ */ new Set());
|
|
1237
|
+
const [ready, setReady] = useState8(false);
|
|
1238
|
+
const idsRef = useRef10(ids);
|
|
947
1239
|
idsRef.current = ids;
|
|
948
|
-
|
|
1240
|
+
useEffect10(() => {
|
|
949
1241
|
setIds(new Set(storeRef.current.read()));
|
|
950
1242
|
setReady(true);
|
|
951
1243
|
}, []);
|
|
952
|
-
const markDiscovered =
|
|
1244
|
+
const markDiscovered = useCallback9((id) => {
|
|
953
1245
|
const additions = Array.isArray(id) ? id : [id];
|
|
954
1246
|
const merged = new Set(idsRef.current);
|
|
955
1247
|
let changed = false;
|
|
@@ -964,24 +1256,24 @@ function useDiscoveries(opts) {
|
|
|
964
1256
|
setIds(merged);
|
|
965
1257
|
storeRef.current.write([...merged]);
|
|
966
1258
|
}, []);
|
|
967
|
-
const reset =
|
|
1259
|
+
const reset = useCallback9(() => {
|
|
968
1260
|
const empty = /* @__PURE__ */ new Set();
|
|
969
1261
|
idsRef.current = empty;
|
|
970
1262
|
setIds(empty);
|
|
971
1263
|
storeRef.current.write([]);
|
|
972
1264
|
}, []);
|
|
973
|
-
const isDiscovered =
|
|
974
|
-
return
|
|
1265
|
+
const isDiscovered = useCallback9((id) => idsRef.current.has(id), []);
|
|
1266
|
+
return useMemo10(
|
|
975
1267
|
() => ({ discovered: ids, ready, isDiscovered, markDiscovered, reset }),
|
|
976
1268
|
[ids, ready, isDiscovered, markDiscovered, reset]
|
|
977
1269
|
);
|
|
978
1270
|
}
|
|
979
1271
|
|
|
980
1272
|
// src/use-focus-trap.ts
|
|
981
|
-
import { useEffect as
|
|
1273
|
+
import { useEffect as useEffect11, useRef as useRef11 } from "react";
|
|
982
1274
|
function useFocusTrap(active) {
|
|
983
|
-
const ref =
|
|
984
|
-
|
|
1275
|
+
const ref = useRef11(null);
|
|
1276
|
+
useEffect11(() => {
|
|
985
1277
|
if (!active) return;
|
|
986
1278
|
const container = ref.current;
|
|
987
1279
|
if (!container) return;
|
|
@@ -1018,14 +1310,14 @@ function useFocusTrap(active) {
|
|
|
1018
1310
|
// src/glossary.tsx
|
|
1019
1311
|
import {
|
|
1020
1312
|
createContext,
|
|
1021
|
-
useCallback as
|
|
1313
|
+
useCallback as useCallback10,
|
|
1022
1314
|
useContext,
|
|
1023
|
-
useEffect as
|
|
1315
|
+
useEffect as useEffect12,
|
|
1024
1316
|
useId,
|
|
1025
|
-
useLayoutEffect as
|
|
1026
|
-
useMemo as
|
|
1027
|
-
useRef as
|
|
1028
|
-
useState as
|
|
1317
|
+
useLayoutEffect as useLayoutEffect3,
|
|
1318
|
+
useMemo as useMemo11,
|
|
1319
|
+
useRef as useRef12,
|
|
1320
|
+
useState as useState9
|
|
1029
1321
|
} from "react";
|
|
1030
1322
|
import { createPortal } from "react-dom";
|
|
1031
1323
|
|
|
@@ -1083,7 +1375,7 @@ function GlossaryProvider({
|
|
|
1083
1375
|
theme = defaultTheme,
|
|
1084
1376
|
children
|
|
1085
1377
|
}) {
|
|
1086
|
-
const value =
|
|
1378
|
+
const value = useMemo11(
|
|
1087
1379
|
() => ({ byId: new Map(entries.map((e) => [e.id, e])), entries, theme }),
|
|
1088
1380
|
[entries, theme]
|
|
1089
1381
|
);
|
|
@@ -1093,23 +1385,23 @@ function Term({ id, children, className }) {
|
|
|
1093
1385
|
const ctx = useContext(GlossaryContext);
|
|
1094
1386
|
const depth = useContext(DepthContext);
|
|
1095
1387
|
const entry = ctx?.byId.get(id);
|
|
1096
|
-
const [open, setOpen] =
|
|
1097
|
-
const [pos, setPos] =
|
|
1098
|
-
const openRef =
|
|
1099
|
-
const wrapperRef =
|
|
1100
|
-
const triggerRef =
|
|
1101
|
-
const cardRef =
|
|
1102
|
-
const openTimer =
|
|
1103
|
-
const closeTimer =
|
|
1388
|
+
const [open, setOpen] = useState9(false);
|
|
1389
|
+
const [pos, setPos] = useState9(null);
|
|
1390
|
+
const openRef = useRef12(false);
|
|
1391
|
+
const wrapperRef = useRef12(null);
|
|
1392
|
+
const triggerRef = useRef12(null);
|
|
1393
|
+
const cardRef = useRef12(null);
|
|
1394
|
+
const openTimer = useRef12(0);
|
|
1395
|
+
const closeTimer = useRef12(0);
|
|
1104
1396
|
const tipId = useId();
|
|
1105
1397
|
const display = children ?? entry?.term ?? id;
|
|
1106
|
-
const closeNow =
|
|
1398
|
+
const closeNow = useCallback10(() => {
|
|
1107
1399
|
window.clearTimeout(openTimer.current);
|
|
1108
1400
|
window.clearTimeout(closeTimer.current);
|
|
1109
1401
|
openRef.current = false;
|
|
1110
1402
|
setOpen(false);
|
|
1111
1403
|
}, []);
|
|
1112
|
-
const openNow =
|
|
1404
|
+
const openNow = useCallback10((focusCard = false) => {
|
|
1113
1405
|
window.clearTimeout(closeTimer.current);
|
|
1114
1406
|
window.clearTimeout(openTimer.current);
|
|
1115
1407
|
openRef.current = true;
|
|
@@ -1118,18 +1410,18 @@ function Term({ id, children, className }) {
|
|
|
1118
1410
|
requestAnimationFrame(() => cardRef.current?.focus({ preventScroll: true }));
|
|
1119
1411
|
}
|
|
1120
1412
|
}, []);
|
|
1121
|
-
const scheduleOpen =
|
|
1413
|
+
const scheduleOpen = useCallback10(() => {
|
|
1122
1414
|
window.clearTimeout(closeTimer.current);
|
|
1123
1415
|
if (openRef.current) return;
|
|
1124
1416
|
window.clearTimeout(openTimer.current);
|
|
1125
1417
|
openTimer.current = window.setTimeout(() => openNow(), OPEN_DELAY_MS);
|
|
1126
1418
|
}, [openNow]);
|
|
1127
|
-
const scheduleClose =
|
|
1419
|
+
const scheduleClose = useCallback10(() => {
|
|
1128
1420
|
window.clearTimeout(openTimer.current);
|
|
1129
1421
|
window.clearTimeout(closeTimer.current);
|
|
1130
1422
|
closeTimer.current = window.setTimeout(closeNow, CLOSE_GRACE_MS);
|
|
1131
1423
|
}, [closeNow]);
|
|
1132
|
-
|
|
1424
|
+
useLayoutEffect3(() => {
|
|
1133
1425
|
if (!open) {
|
|
1134
1426
|
setPos(null);
|
|
1135
1427
|
return;
|
|
@@ -1166,7 +1458,7 @@ function Term({ id, children, className }) {
|
|
|
1166
1458
|
if (raf) cancelAnimationFrame(raf);
|
|
1167
1459
|
};
|
|
1168
1460
|
}, [open]);
|
|
1169
|
-
|
|
1461
|
+
useEffect12(() => {
|
|
1170
1462
|
if (!open) return;
|
|
1171
1463
|
const onPointerDown = (e) => {
|
|
1172
1464
|
const target = e.target;
|
|
@@ -1177,7 +1469,7 @@ function Term({ id, children, className }) {
|
|
|
1177
1469
|
document.addEventListener("pointerdown", onPointerDown);
|
|
1178
1470
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
1179
1471
|
}, [open, closeNow]);
|
|
1180
|
-
|
|
1472
|
+
useEffect12(() => () => {
|
|
1181
1473
|
window.clearTimeout(openTimer.current);
|
|
1182
1474
|
window.clearTimeout(closeTimer.current);
|
|
1183
1475
|
}, []);
|
|
@@ -1330,7 +1622,7 @@ function GlossaryAppendix({
|
|
|
1330
1622
|
}
|
|
1331
1623
|
|
|
1332
1624
|
// src/components.tsx
|
|
1333
|
-
import { useEffect as
|
|
1625
|
+
import { useEffect as useEffect13, useState as useState10 } from "react";
|
|
1334
1626
|
import { createPortal as createPortal2 } from "react-dom";
|
|
1335
1627
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1336
1628
|
var TONE_SEGMENT_CLASS = defaultTheme.toneSegment;
|
|
@@ -1469,15 +1761,19 @@ function PlayerBar({
|
|
|
1469
1761
|
className,
|
|
1470
1762
|
theme = defaultTheme,
|
|
1471
1763
|
groupOf,
|
|
1472
|
-
labels
|
|
1764
|
+
labels,
|
|
1765
|
+
live
|
|
1473
1766
|
}) {
|
|
1474
1767
|
const L = {
|
|
1475
1768
|
previous: labels?.previous ?? "Previous step",
|
|
1476
1769
|
play: labels?.play ?? "Play",
|
|
1477
1770
|
pause: labels?.pause ?? "Pause",
|
|
1478
1771
|
replay: labels?.replay ?? "Replay",
|
|
1479
|
-
next: labels?.next ?? "Next step"
|
|
1772
|
+
next: labels?.next ?? "Next step",
|
|
1773
|
+
live: labels?.live ?? "Live",
|
|
1774
|
+
goLive: labels?.goLive ?? "Go live"
|
|
1480
1775
|
};
|
|
1776
|
+
const behind = Math.max(0, player.total - 1 - Math.max(player.index, 0));
|
|
1481
1777
|
const playLabel = player.playing ? L.pause : player.total > 0 && player.atEnd ? L.replay : L.play;
|
|
1482
1778
|
return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2 p-2", className), children: [
|
|
1483
1779
|
leading,
|
|
@@ -1497,7 +1793,15 @@ function PlayerBar({
|
|
|
1497
1793
|
groupOf
|
|
1498
1794
|
}
|
|
1499
1795
|
),
|
|
1500
|
-
/* @__PURE__ */ jsx2("span", { className: cn("text-[10px] font-mono w-11 text-right shrink-0", theme.muted), children: player.total > 0 ? `${player.index + 1}/${player.total}` : "\u2014" })
|
|
1796
|
+
/* @__PURE__ */ jsx2("span", { className: cn("text-[10px] font-mono w-11 text-right shrink-0", theme.muted), children: player.total > 0 ? `${player.index + 1}/${player.total}` : "\u2014" }),
|
|
1797
|
+
live && (player.live || behind === 0 ? /* @__PURE__ */ jsxs2("span", { role: "status", className: "flex items-center gap-1 text-[10px] font-mono uppercase text-primary shrink-0", children: [
|
|
1798
|
+
/* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: cn("h-1.5 w-1.5 rounded-full bg-primary", player.playing && "motion-safe:animate-pulse") }),
|
|
1799
|
+
L.live
|
|
1800
|
+
] }) : /* @__PURE__ */ jsxs2(StepButton, { variant: "ghost", onClick: () => player.goLive?.(), className: "h-7 px-2 text-[10px] font-mono shrink-0", children: [
|
|
1801
|
+
L.goLive,
|
|
1802
|
+
" +",
|
|
1803
|
+
behind
|
|
1804
|
+
] }))
|
|
1501
1805
|
] });
|
|
1502
1806
|
}
|
|
1503
1807
|
function ChapterNav({
|
|
@@ -1602,8 +1906,8 @@ function TourSpotlight({
|
|
|
1602
1906
|
target,
|
|
1603
1907
|
theme = defaultTheme
|
|
1604
1908
|
}) {
|
|
1605
|
-
const [rect, setRect] =
|
|
1606
|
-
|
|
1909
|
+
const [rect, setRect] = useState10(null);
|
|
1910
|
+
useEffect13(() => {
|
|
1607
1911
|
if (!target) {
|
|
1608
1912
|
setRect(null);
|
|
1609
1913
|
return;
|
|
@@ -1659,7 +1963,8 @@ function TourCard({
|
|
|
1659
1963
|
labels,
|
|
1660
1964
|
className,
|
|
1661
1965
|
theme = defaultTheme,
|
|
1662
|
-
nextDisabled = false
|
|
1966
|
+
nextDisabled = false,
|
|
1967
|
+
feedback
|
|
1663
1968
|
}) {
|
|
1664
1969
|
const L = {
|
|
1665
1970
|
title: labels?.title ?? "Guided tour",
|
|
@@ -1707,6 +2012,7 @@ function TourCard({
|
|
|
1707
2012
|
/* @__PURE__ */ jsx2("p", { className: "text-sm font-bold text-foreground/95 mb-1.5", children: step.title }),
|
|
1708
2013
|
/* @__PURE__ */ jsx2("div", { className: "text-[12px] font-mono text-foreground/70 leading-relaxed", children: step.body }),
|
|
1709
2014
|
step.waitHint && /* @__PURE__ */ jsx2("p", { className: "text-[11px] font-mono text-accent mt-2 animate-pulse", children: step.waitHint }),
|
|
2015
|
+
/* @__PURE__ */ jsx2("div", { "aria-live": "polite", children: feedback != null && feedback !== false && /* @__PURE__ */ jsx2("p", { className: "text-[11px] font-mono text-foreground/70 mt-2", children: feedback }) }),
|
|
1710
2016
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 mt-3 pt-3 border-t border-white/10", children: [
|
|
1711
2017
|
/* @__PURE__ */ jsx2(StepButton, { variant: "outline", onClick: onBack, disabled: index === 0, className: "h-7 px-3 text-[11px]", children: L.back }),
|
|
1712
2018
|
/* @__PURE__ */ jsx2(
|
|
@@ -1732,10 +2038,10 @@ function ShortcutsDialog({
|
|
|
1732
2038
|
className,
|
|
1733
2039
|
theme = defaultTheme
|
|
1734
2040
|
}) {
|
|
1735
|
-
const [mounted, setMounted] =
|
|
2041
|
+
const [mounted, setMounted] = useState10(false);
|
|
1736
2042
|
const trapRef = useFocusTrap(open);
|
|
1737
|
-
|
|
1738
|
-
|
|
2043
|
+
useEffect13(() => setMounted(true), []);
|
|
2044
|
+
useEffect13(() => {
|
|
1739
2045
|
if (!open) return;
|
|
1740
2046
|
const onKey = (e) => {
|
|
1741
2047
|
if (e.key === "Escape") onClose();
|
|
@@ -1807,22 +2113,33 @@ export {
|
|
|
1807
2113
|
TourSpotlight,
|
|
1808
2114
|
alignTracks,
|
|
1809
2115
|
cookieStore,
|
|
2116
|
+
createRng,
|
|
1810
2117
|
createTrace,
|
|
1811
2118
|
defaultTheme,
|
|
2119
|
+
fail,
|
|
1812
2120
|
flattenChapters,
|
|
1813
2121
|
localStorageStore,
|
|
1814
2122
|
memoryStore,
|
|
2123
|
+
pass,
|
|
2124
|
+
pending,
|
|
2125
|
+
readHashParam,
|
|
2126
|
+
reanchor,
|
|
2127
|
+
runSimulation,
|
|
2128
|
+
toVerdict,
|
|
1815
2129
|
useChapters,
|
|
1816
2130
|
useDiscoveries,
|
|
2131
|
+
useEvaluator,
|
|
1817
2132
|
useFadeIn,
|
|
1818
2133
|
useFocusTrap,
|
|
1819
2134
|
useGlobalKeys,
|
|
1820
2135
|
useGuidedTour,
|
|
1821
2136
|
useLessonStream,
|
|
1822
2137
|
usePrefersReducedMotion,
|
|
2138
|
+
useSimulation,
|
|
1823
2139
|
useStepPlayer,
|
|
1824
2140
|
useSteps,
|
|
1825
2141
|
useSyncedTracks,
|
|
1826
|
-
useTrace
|
|
2142
|
+
useTrace,
|
|
2143
|
+
writeHashParam
|
|
1827
2144
|
};
|
|
1828
2145
|
//# sourceMappingURL=index.js.map
|