@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/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @sphynxoid/stepwise
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Interactive explainers for React: step-through players, guided tours, scroll-driven lessons and glossary tooltips, all on one cursor.**
|
|
4
|
+
|
|
5
|
+
**[Live demos](https://stepwise.thesphynx.net)** · [API reference](https://stepwise.thesphynx.net/docs) · [StackBlitz starter](https://stackblitz.com/github/TheSphynxoid/stepwise/tree/master/examples/vite-starter) · [GitHub](https://github.com/TheSphynxoid/stepwise)
|
|
6
|
+
|
|
7
|
+
<img src="https://raw.githubusercontent.com/TheSphynxoid/stepwise/master/docs/assets/hero.gif" alt="A quicksort trace playing in a stepwise PlayerBar" width="516">
|
|
4
8
|
|
|
5
9
|
Frames in a trace, chapters in a lesson, lessons in a notebook, and tour steps are the same thing at the core: a clamped cursor over an ordered sequence. Timers, tour effects, scroll tracking, and third-party presenters are policies and adapters layered on top.
|
|
6
10
|
|
|
@@ -45,7 +49,7 @@ Tokens: `--stepwise-background`, `-foreground`, `-card`, `-popover`, `-primary`,
|
|
|
45
49
|
**2. Tailwind + shadcn/ui — copy the components in with the shadcn CLI:**
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
|
-
npx shadcn@latest add https://stepwise
|
|
52
|
+
npx shadcn@latest add https://stepwise.thesphynx.net/r/player-bar.json
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
Items: `player-bar`, `code-block`, `tour`, `glossary`, `chapter-nav`, `lesson-pager`, `shortcuts-dialog` — or `stepwise` for all of them. The presentation source lands in `components/stepwise/` (plus `lib/stepwise-theme.ts`), where Tailwind scans it automatically; hooks and types stay imported from this package. Colors come from your shadcn tokens.
|
|
@@ -86,6 +90,66 @@ export function Demo() {
|
|
|
86
90
|
|
|
87
91
|
`useTrace` binds a player to a typed trace: `step` is the frame to render (frame 0 before playback starts), and `toneAt` / `titleAt` feed the scrubber. Under it, `useStepPlayer` gives you a timer-driven playhead with play/pause/seek, speed control, and optional `#frame=2` deep links — or hand it an `engine` to drive the playhead from GSAP or scroll instead. Two end-state flavors: `atEnd` (cursor parked on the last frame, seeks included) and `ended` (playback ran to the finish, via the `onEnd` callback option) — clearing on any cursor movement, so a completion summary knows the demo actually finished. `autoPlay` respects `prefers-reduced-motion` (lands on frame 0, paused; opt out with `respectReducedMotion: false`), and `syncHash` writes only its own key, so it coexists with anchors and other players' keys.
|
|
88
92
|
|
|
93
|
+
## Live simulations
|
|
94
|
+
|
|
95
|
+
A trace doesn't have to be recorded by hand. Write the algorithm as a generator, give each step a `key` naming its moment, and `useSimulation` reruns it whenever the input changes, keeping the playhead on the same moment (mid-playback too):
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useSimulation, type Simulation, type StepwiseStep } from "@sphynxoid/stepwise";
|
|
99
|
+
|
|
100
|
+
type Frame = StepwiseStep<{ arr: number[] }>;
|
|
101
|
+
|
|
102
|
+
const shuffleThenSort: Simulation<number[], Frame> = function* (input, { rng }) {
|
|
103
|
+
const a = rng.shuffle(input); // seeded: same input + seed → same trace
|
|
104
|
+
yield { key: "shuffled", tone: "neutral", label: "shuffled", arr: a };
|
|
105
|
+
for (let i = 1; i < a.length; i++) {
|
|
106
|
+
for (let j = i; j > 0 && a[j - 1] > a[j]; j--) {
|
|
107
|
+
[a[j - 1], a[j]] = [a[j], a[j - 1]];
|
|
108
|
+
yield { key: `swap ${i}.${j}`, tone: "write", label: `swap at ${j}`, arr: [...a] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const sim = useSimulation(shuffleThenSort, input, { seed: 7, autoPlay: true, syncHash: "frame" });
|
|
114
|
+
// sim.step, sim.player, sim.toneAt, sim.titleAt: like useTrace, plus sim.steps / truncated / error
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
How re-anchoring decides: walk back from the old position to the latest step whose key exists in the new trace, and land there (`reanchor` is exported for custom players). `runSimulation` is the pure, server-safe runner, with a `maxSteps` cap so a runaway generator can't hang the page.
|
|
118
|
+
|
|
119
|
+
## Following a live timeline
|
|
120
|
+
|
|
121
|
+
When frames arrive while the reader watches (a log, a stream, an agent's steps, a game's moves), add `follow`: a reader at the edge keeps playing into new frames at the normal pace, and one who scrubbed back to inspect something stays there until they choose to return.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const trace = useTrace(events, { autoPlay: true, follow: true }); // events grows over time
|
|
125
|
+
// trace.player.live: at the edge? trace.player.goLive(): back to it
|
|
126
|
+
<PlayerBar player={trace.player} toneAt={trace.toneAt} titleAt={trace.titleAt} live />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`PlayerBar live` shows "Live" at the edge and "Go live +N" when the reader is behind. Checkpoints still hold, and with reduced motion nothing starts on its own.
|
|
130
|
+
|
|
131
|
+
## Checkpoints and evaluators
|
|
132
|
+
|
|
133
|
+
Pause autoplay where the reader should think, then grade their answer:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
const sim = useSimulation(sort, input, {
|
|
137
|
+
autoPlay: true,
|
|
138
|
+
holdAt: (_, step) => step?.kind === "compare", // autoplay stops here; player.held is set
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const answer = useEvaluator(
|
|
142
|
+
(guess: "swap" | "keep" | null) =>
|
|
143
|
+
guess === truth ? pass("Right!") : fail("Compare the two highlighted bars."),
|
|
144
|
+
guess,
|
|
145
|
+
{ mode: "manual" } // judge on check(); "auto" judges every change
|
|
146
|
+
);
|
|
147
|
+
// <button onClick={answer.check}>Check</button> {answer.feedback}
|
|
148
|
+
// {answer.passed && <button onClick={sim.player.release}>Continue</button>}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
An `Evaluator` returns a boolean, a `Verdict` (`pass(feedback)`, `fail(feedback)`, `pending()`), or a promise of either; async checks report `pending`, and stale results are dropped. Tour gates accept verdicts too: anything but pass blocks Next, and `tour.feedback` (→ `TourCard feedback`) tells the reader what's missing.
|
|
152
|
+
|
|
89
153
|
## The three scales
|
|
90
154
|
|
|
91
155
|
| Scale | Hook | Presentation |
|
package/dist/adapters/gsap.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PlayerEngine, S as StepwiseStep, a as StepTone, b as TourStepInput, T as TourDriver, C as Chapter,
|
|
2
|
-
export {
|
|
1
|
+
import { P as PlayerEngine, S as StepwiseStep, a as StepTone, V as VerdictStatus, b as Verdict, E as Evaluator, c as TourStepInput, T as TourDriver, C as Chapter, d as ShortcutItem } from './types-CNMxISXe.js';
|
|
2
|
+
export { e as EvaluatorResult, f as PlayerEngineApi, g as PlayerEngineHandle, h as TourContext, i as fail, p as pass, j as pending, t as toVerdict } from './types-CNMxISXe.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
5
|
|
|
@@ -69,6 +69,24 @@ interface StepPlayerOptions {
|
|
|
69
69
|
* user presses play. Default true.
|
|
70
70
|
*/
|
|
71
71
|
respectReducedMotion?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Checkpoints: when it returns true for the frame playback just reached,
|
|
74
|
+
* the built-in timer pauses there and reports `held` until `release()` —
|
|
75
|
+
* "stop and predict what happens next". Manual stepping and seeks are never
|
|
76
|
+
* held (the reader keeps control). Ignored when a custom `engine` drives
|
|
77
|
+
* playback. Each checkpoint holds once per playthrough.
|
|
78
|
+
*/
|
|
79
|
+
holdAt?: (frame: number) => boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Follow the live edge of a growing timeline (a stream, a log, a game's
|
|
82
|
+
* move history). When frames are appended while the reader is live
|
|
83
|
+
* (playing, or parked on the last frame), playback continues through them
|
|
84
|
+
* at the normal pace. A reader who scrubbed back or paused mid-trace stays
|
|
85
|
+
* put — `live` turns false and `goLive()` returns them. With `autoPlay`, a
|
|
86
|
+
* player that mounts empty starts on the first frames. Checkpoints still
|
|
87
|
+
* hold; a custom `engine` owns its own timing and ignores this.
|
|
88
|
+
*/
|
|
89
|
+
follow?: boolean;
|
|
72
90
|
}
|
|
73
91
|
interface StepPlayer {
|
|
74
92
|
/** Total frames in the current timeline. */
|
|
@@ -87,6 +105,28 @@ interface StepPlayer {
|
|
|
87
105
|
* not set it — `toggle()` at `ended` replays from frame 0.
|
|
88
106
|
*/
|
|
89
107
|
ended: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The checkpoint frame playback is paused on (see `holdAt`), or null.
|
|
110
|
+
* Cleared by `release()` or any cursor movement.
|
|
111
|
+
*/
|
|
112
|
+
held: number | null;
|
|
113
|
+
/** Release the current checkpoint and resume playing. No-op when not held. */
|
|
114
|
+
release: () => void;
|
|
115
|
+
/**
|
|
116
|
+
* Move the cursor without touching playback state: playing stays playing,
|
|
117
|
+
* completion and checkpoints are kept. For re-anchoring after the timeline
|
|
118
|
+
* changes underneath (`useTrace` with keyed steps) — user-facing moves
|
|
119
|
+
* should use `seek`.
|
|
120
|
+
*/
|
|
121
|
+
jump: (frame: number) => void;
|
|
122
|
+
/**
|
|
123
|
+
* With `follow`: the reader is at the live edge (playing toward it, parked
|
|
124
|
+
* on the last frame, or waiting to start), so appended frames will play.
|
|
125
|
+
* Always false without `follow`.
|
|
126
|
+
*/
|
|
127
|
+
live: boolean;
|
|
128
|
+
/** Move to the last frame (paused, so new frames play from there with `follow`). */
|
|
129
|
+
goLive: () => void;
|
|
90
130
|
/** Jump to frame 0 and play. */
|
|
91
131
|
start: () => void;
|
|
92
132
|
/** Clear the player (frame = -1, paused). */
|
|
@@ -101,8 +141,26 @@ interface StepPlayer {
|
|
|
101
141
|
setPlaying: (playing: boolean) => void;
|
|
102
142
|
setSpeed: (speed: number) => void;
|
|
103
143
|
}
|
|
104
|
-
declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd, respectReducedMotion, }: StepPlayerOptions): StepPlayer;
|
|
144
|
+
declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd, respectReducedMotion, holdAt, follow, }: StepPlayerOptions): StepPlayer;
|
|
145
|
+
/** Value of `key` in the hash, or null when absent. */
|
|
146
|
+
declare function readHashParam(hash: string, key: string): string | null;
|
|
147
|
+
/** The hash with `key` set to `value` — replaced in place, else appended. */
|
|
148
|
+
declare function writeHashParam(hash: string, key: string, value: string): string;
|
|
105
149
|
|
|
150
|
+
interface TraceOptions<S extends StepwiseStep> extends Omit<StepPlayerOptions, "total" | "holdAt"> {
|
|
151
|
+
/**
|
|
152
|
+
* Checkpoints (see `StepPlayerOptions.holdAt`), with the step at hand:
|
|
153
|
+
* `holdAt: (_, step) => step?.kind === "compare"`. Always sees the current
|
|
154
|
+
* trace, also after it's recomputed.
|
|
155
|
+
*/
|
|
156
|
+
holdAt?: (frame: number, step: S | undefined) => boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Step identity used to keep the playhead in place when `steps` changes
|
|
159
|
+
* (see `reanchor`). Defaults to each step's `key` field. Traces without
|
|
160
|
+
* keys keep the old behavior: the same index, clamped.
|
|
161
|
+
*/
|
|
162
|
+
keyOf?: (step: S, index: number) => string | undefined;
|
|
163
|
+
}
|
|
106
164
|
interface TracePlayer<S extends StepwiseStep> {
|
|
107
165
|
/** The underlying player — pass it to PlayerBar. */
|
|
108
166
|
player: StepPlayer;
|
|
@@ -128,8 +186,110 @@ interface TracePlayer<S extends StepwiseStep> {
|
|
|
128
186
|
* const trace = useTrace(STEPS, { autoPlay: true });
|
|
129
187
|
* <Viz arr={trace.step.arr} />
|
|
130
188
|
* <PlayerBar player={trace.player} toneAt={trace.toneAt} titleAt={trace.titleAt} />
|
|
189
|
+
*
|
|
190
|
+
* `steps` may change (new input, new seed — see `useSimulation`). Keyed
|
|
191
|
+
* steps re-anchor: the playhead stays on the same logical step, and playback
|
|
192
|
+
* state is untouched.
|
|
131
193
|
*/
|
|
132
|
-
declare function useTrace<S extends StepwiseStep>(steps: readonly S[], opts?:
|
|
194
|
+
declare function useTrace<S extends StepwiseStep>(steps: readonly S[], opts?: TraceOptions<S>): TracePlayer<S>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Where should the playhead go when the trace changes underneath it?
|
|
198
|
+
*
|
|
199
|
+
* Walk back from the old position to the most recent step whose key still
|
|
200
|
+
* exists in the new trace, and land on that step there. So editing an input
|
|
201
|
+
* keeps you on "the same moment" of the algorithm when it still happens, and
|
|
202
|
+
* otherwise on the last moment both runs share — never somewhere unrelated.
|
|
203
|
+
* With no shared key at all, the playhead returns to frame 0.
|
|
204
|
+
*
|
|
205
|
+
* Pure: `useTrace` calls it during render, and it's exported for custom
|
|
206
|
+
* players. Keys come from `keyOf`, by default each step's `key` field; a
|
|
207
|
+
* step without a key never matches.
|
|
208
|
+
*/
|
|
209
|
+
declare function reanchor<A, B>(oldSteps: readonly A[], oldIndex: number, newSteps: readonly B[], keyOf: (step: A | B, index: number) => string | undefined): number;
|
|
210
|
+
|
|
211
|
+
/** Deterministic pseudo-random source (mulberry32). */
|
|
212
|
+
interface Rng {
|
|
213
|
+
/** The seed this generator was created from (numbers pass through; strings are hashed). */
|
|
214
|
+
readonly seed: number;
|
|
215
|
+
/** Uniform float in [0, 1). */
|
|
216
|
+
next(): number;
|
|
217
|
+
/** Uniform integer in [min, max], both inclusive. */
|
|
218
|
+
int(min: number, max: number): number;
|
|
219
|
+
/** A uniformly chosen element. Throws on an empty array. */
|
|
220
|
+
pick<T>(items: readonly T[]): T;
|
|
221
|
+
/** A shuffled copy (Fisher-Yates); the input is not modified. */
|
|
222
|
+
shuffle<T>(items: readonly T[]): T[];
|
|
223
|
+
}
|
|
224
|
+
declare function createRng(seed?: number | string): Rng;
|
|
225
|
+
interface SimulationContext {
|
|
226
|
+
/** Seeded randomness. Use it instead of Math.random to keep runs replayable. */
|
|
227
|
+
rng: Rng;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* A simulation turns an input into a trace. Write it as a generator that
|
|
231
|
+
* yields one step per moment worth showing (or return an array):
|
|
232
|
+
*
|
|
233
|
+
* function* bubble(input: number[]): Generator<SortStep> {
|
|
234
|
+
* const arr = [...input];
|
|
235
|
+
* for (let pass = 0; …) {
|
|
236
|
+
* yield { key: `p${pass}:j${j}`, tone: "neutral", label: "compare", arr: [...arr] };
|
|
237
|
+
* }
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* Yield snapshots (copies), not live references: every step is kept.
|
|
241
|
+
* Give steps a `key` so the playhead stays put when the input changes.
|
|
242
|
+
*/
|
|
243
|
+
type Simulation<Input, S extends StepwiseStep> = (input: Input, ctx: SimulationContext) => Iterable<S>;
|
|
244
|
+
interface SimulationRunOptions {
|
|
245
|
+
/** Seed for `ctx.rng`. Default 0. */
|
|
246
|
+
seed?: number | string;
|
|
247
|
+
/**
|
|
248
|
+
* Safety cap: stop collecting after this many steps and report
|
|
249
|
+
* `truncated` (a non-terminating simulation shouldn't hang the page).
|
|
250
|
+
* Default 10 000.
|
|
251
|
+
*/
|
|
252
|
+
maxSteps?: number;
|
|
253
|
+
}
|
|
254
|
+
interface SimulationResult<S> {
|
|
255
|
+
steps: S[];
|
|
256
|
+
/** True when `maxSteps` cut the run short. */
|
|
257
|
+
truncated: boolean;
|
|
258
|
+
/** What the simulation threw, if it did; `steps` holds what it yielded before. */
|
|
259
|
+
error: unknown;
|
|
260
|
+
/** The numeric seed actually used (strings are hashed). */
|
|
261
|
+
seed: number;
|
|
262
|
+
}
|
|
263
|
+
/** Run a simulation to completion (or the cap). Pure; safe at module scope and on the server. */
|
|
264
|
+
declare function runSimulation<Input, S extends StepwiseStep>(simulation: Simulation<Input, S>, input: Input, opts?: SimulationRunOptions): SimulationResult<S>;
|
|
265
|
+
|
|
266
|
+
interface SimulationOptions<S extends StepwiseStep> extends TraceOptions<S>, SimulationRunOptions {
|
|
267
|
+
}
|
|
268
|
+
interface SimulationPlayer<S extends StepwiseStep> extends TracePlayer<S> {
|
|
269
|
+
/** The computed trace. */
|
|
270
|
+
steps: readonly S[];
|
|
271
|
+
/** True when `maxSteps` cut the run short. */
|
|
272
|
+
truncated: boolean;
|
|
273
|
+
/** What the simulation threw, if anything (the trace holds the steps before it). */
|
|
274
|
+
error: unknown;
|
|
275
|
+
/** The numeric seed used for this run. */
|
|
276
|
+
seed: number;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* A live trace: run `simulation` on `input` and play the result. Change the
|
|
280
|
+
* input (or the seed) and the trace is recomputed; keyed steps keep the
|
|
281
|
+
* playhead on the same logical moment, and a playing player keeps playing.
|
|
282
|
+
*
|
|
283
|
+
* const [input, setInput] = useState([5, 2, 8, 1]);
|
|
284
|
+
* const sim = useSimulation(bubbleSort, input, { autoPlay: true, syncHash: "frame" });
|
|
285
|
+
* <Bars arr={sim.step!.arr} />
|
|
286
|
+
* <PlayerBar player={sim.player} toneAt={sim.toneAt} titleAt={sim.titleAt} />
|
|
287
|
+
*
|
|
288
|
+
* The trace is recomputed when `simulation`, `input`, `seed` or `maxSteps`
|
|
289
|
+
* change identity: define the simulation at module scope, and keep object
|
|
290
|
+
* inputs in state (or memoize them) rather than rebuilding them per render.
|
|
291
|
+
*/
|
|
292
|
+
declare function useSimulation<Input, S extends StepwiseStep>(simulation: Simulation<Input, S>, input: Input, opts?: SimulationOptions<S>): SimulationPlayer<S>;
|
|
133
293
|
|
|
134
294
|
interface TraceBuilder<Data extends object> {
|
|
135
295
|
/** Append one frame. Returns the builder, so calls chain. */
|
|
@@ -295,6 +455,45 @@ declare function useLessonStream(count: number, opts?: LessonStreamOptions): Les
|
|
|
295
455
|
*/
|
|
296
456
|
declare function useFadeIn(initiallyVisible?: boolean): boolean;
|
|
297
457
|
|
|
458
|
+
interface EvaluatorOptions {
|
|
459
|
+
/**
|
|
460
|
+
* "auto" (default) re-judges whenever `ctx` changes: a gate that opens the
|
|
461
|
+
* moment the reader does the thing. "manual" judges only on `check()`: a
|
|
462
|
+
* quiz with a Check button; changing `ctx` afterwards clears the verdict.
|
|
463
|
+
*/
|
|
464
|
+
mode?: "auto" | "manual";
|
|
465
|
+
}
|
|
466
|
+
interface EvaluatorState {
|
|
467
|
+
/** "idle" until the first verdict (manual mode, or after `reset`). */
|
|
468
|
+
status: VerdictStatus | "idle";
|
|
469
|
+
feedback: ReactNode;
|
|
470
|
+
/** status === "pass". */
|
|
471
|
+
passed: boolean;
|
|
472
|
+
/** How many times `check()` ran, for "show a hint after two tries". */
|
|
473
|
+
attempts: number;
|
|
474
|
+
/** What the evaluator threw (the verdict is then "fail"). */
|
|
475
|
+
error: unknown;
|
|
476
|
+
/** Judge the current context now. Resolves with the verdict, or null if superseded. */
|
|
477
|
+
check: () => Promise<Verdict | null>;
|
|
478
|
+
/** Back to "idle", attempts to 0, any running check abandoned. */
|
|
479
|
+
reset: () => void;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Run an evaluator against a context and keep its verdict as state. Async
|
|
483
|
+
* evaluators report "pending" while they run; a newer check (or a context
|
|
484
|
+
* change) aborts the older one and drops its late result, so the verdict
|
|
485
|
+
* always describes the latest context. `check` and `reset` are
|
|
486
|
+
* referentially stable.
|
|
487
|
+
*
|
|
488
|
+
* const answer = useEvaluator(
|
|
489
|
+
* (guess: string | null) => (guess === truth ? pass("Right!") : fail("Look at the pivot again.")),
|
|
490
|
+
* guess,
|
|
491
|
+
* { mode: "manual" }
|
|
492
|
+
* );
|
|
493
|
+
* <button onClick={answer.check}>Check</button> {answer.feedback}
|
|
494
|
+
*/
|
|
495
|
+
declare function useEvaluator<Ctx>(evaluator: Evaluator<Ctx>, ctx: Ctx, opts?: EvaluatorOptions): EvaluatorState;
|
|
496
|
+
|
|
298
497
|
interface GuidedTour {
|
|
299
498
|
/** Active step index, or null when the tour is not running. */
|
|
300
499
|
index: number | null;
|
|
@@ -306,6 +505,11 @@ interface GuidedTour {
|
|
|
306
505
|
* the waitHint) so the user knows what is being waited on.
|
|
307
506
|
*/
|
|
308
507
|
blocked: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* The active step's gate feedback, when its gate returns a `Verdict` with
|
|
510
|
+
* one ("Stage at least one file"). Pass it to `TourCard`'s `feedback`.
|
|
511
|
+
*/
|
|
512
|
+
feedback: ReactNode;
|
|
309
513
|
start: () => void;
|
|
310
514
|
end: () => void;
|
|
311
515
|
next: () => void;
|
|
@@ -323,19 +527,6 @@ interface GuidedTourOptions {
|
|
|
323
527
|
*/
|
|
324
528
|
driver?: TourDriver;
|
|
325
529
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Tour state whose steps are effects over app state: `onEnter` fires whenever
|
|
328
|
-
* the active step changes, reading the latest `steps` via ref so closures stay
|
|
329
|
-
* fresh without re-triggering the effect on every render. Composed on
|
|
330
|
-
* useSteps — a tour is a step sequence whose `next` past the end exits.
|
|
331
|
-
*
|
|
332
|
-
* On top of the linear cursor, steps may declare `when` (skip on arrival),
|
|
333
|
-
* `gate` (block `next` until true), and `next` (edge override by step id) —
|
|
334
|
-
* a small state machine where states are steps, edges are `next` functions,
|
|
335
|
-
* and guards are gates. The cursor itself stays a cursor: skipped steps are
|
|
336
|
-
* never landed on, and skipped/gated behavior is evaluated at transition
|
|
337
|
-
* time, not on every render.
|
|
338
|
-
*/
|
|
339
530
|
declare function useGuidedTour(steps: TourStepInput[], opts?: GuidedTourOptions): GuidedTour;
|
|
340
531
|
|
|
341
532
|
interface FlatChapter {
|
|
@@ -549,7 +740,7 @@ declare function StepScrubber({ count, index, toneAt, titleAt, onSeek, emptyHint
|
|
|
549
740
|
*/
|
|
550
741
|
groupOf?: (i: number) => number;
|
|
551
742
|
}): react.JSX.Element;
|
|
552
|
-
declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, }: {
|
|
743
|
+
declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, live, }: {
|
|
553
744
|
player: {
|
|
554
745
|
total: number;
|
|
555
746
|
index: number;
|
|
@@ -559,10 +750,18 @@ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHin
|
|
|
559
750
|
prev: () => void;
|
|
560
751
|
toggle: (onEmpty?: () => void) => void;
|
|
561
752
|
seek: (frame: number) => void;
|
|
753
|
+
live?: boolean;
|
|
754
|
+
goLive?: () => void;
|
|
562
755
|
};
|
|
563
756
|
toneAt?: (i: number) => StepTone;
|
|
564
757
|
titleAt?: (i: number) => string;
|
|
565
758
|
leading?: ReactNode;
|
|
759
|
+
/**
|
|
760
|
+
* Show the live-edge badge for a following player (`follow` option):
|
|
761
|
+
* "Live" at the edge, "Go live" with the frame count when the reader is
|
|
762
|
+
* behind.
|
|
763
|
+
*/
|
|
764
|
+
live?: boolean;
|
|
566
765
|
/** Fired when the user plays/steps with no timeline loaded. */
|
|
567
766
|
onEmpty?: () => void;
|
|
568
767
|
emptyHint?: string;
|
|
@@ -579,6 +778,8 @@ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHin
|
|
|
579
778
|
pause?: string;
|
|
580
779
|
replay?: string;
|
|
581
780
|
next?: string;
|
|
781
|
+
live?: string;
|
|
782
|
+
goLive?: string;
|
|
582
783
|
};
|
|
583
784
|
}): react.JSX.Element;
|
|
584
785
|
declare function ChapterNav({ chapters, active, onNavigate, hint, className, theme, }: {
|
|
@@ -609,7 +810,7 @@ declare function TourSpotlight({ target, theme, }: {
|
|
|
609
810
|
target: string | null;
|
|
610
811
|
theme?: StepwiseTheme;
|
|
611
812
|
}): react.JSX.Element | null;
|
|
612
|
-
declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, }: {
|
|
813
|
+
declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, feedback, }: {
|
|
613
814
|
step: TourStepInput;
|
|
614
815
|
index: number;
|
|
615
816
|
total: number;
|
|
@@ -627,6 +828,8 @@ declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, c
|
|
|
627
828
|
theme?: StepwiseTheme;
|
|
628
829
|
/** Disable the primary (Next/Finish) button — pair with tour.blocked. */
|
|
629
830
|
nextDisabled?: boolean;
|
|
831
|
+
/** Gate feedback ("Stage a file first") — pair with tour.feedback. Announced politely. */
|
|
832
|
+
feedback?: ReactNode;
|
|
630
833
|
}): react.JSX.Element;
|
|
631
834
|
declare function ShortcutsDialog({ open, onClose, items, title, className, theme, }: {
|
|
632
835
|
open: boolean;
|
|
@@ -637,4 +840,4 @@ declare function ShortcutsDialog({ open, onClose, items, title, className, theme
|
|
|
637
840
|
theme?: StepwiseTheme;
|
|
638
841
|
}): react.ReactPortal | null;
|
|
639
842
|
|
|
640
|
-
export { type AlignedCell, type AlignedRow, Chapter, ChapterNav, type ChaptersApi, CodeBlock, type Discoveries, type DiscoveriesOptions, type DiscoveryStore, type FlatChapter, type GlobalKeysOptions, GlossaryAppendix, type GlossaryEntry, GlossaryProvider, type GuidedTour, type GuidedTourOptions, type KeyMap, LessonPager, type LessonStream, type LessonStreamOptions, PlayerBar, PlayerEngine, ShortcutItem, ShortcutsDialog, type StepPlayer, type StepPlayerOptions, StepScrubber, StepTone, type StepsCore, StepwiseStep, type StepwiseTheme, type SyncedTracks, type SyncedTracksOptions, TONE_CHIP_CLASS, TONE_SEGMENT_CLASS, Term, TourCard, TourDriver, TourSpotlight, TourStepInput, type TraceBuilder, type TracePlayer, type TrackKeyFn, type TrackView, type Tracks, type UseStepsOptions, alignTracks, cookieStore, createTrace, defaultTheme, flattenChapters, localStorageStore, memoryStore, useChapters, useDiscoveries, useFadeIn, useFocusTrap, useGlobalKeys, useGuidedTour, useLessonStream, usePrefersReducedMotion, useStepPlayer, useSteps, useSyncedTracks, useTrace };
|
|
843
|
+
export { type AlignedCell, type AlignedRow, Chapter, ChapterNav, type ChaptersApi, CodeBlock, type Discoveries, type DiscoveriesOptions, type DiscoveryStore, Evaluator, type EvaluatorOptions, type EvaluatorState, type FlatChapter, type GlobalKeysOptions, GlossaryAppendix, type GlossaryEntry, GlossaryProvider, type GuidedTour, type GuidedTourOptions, type KeyMap, LessonPager, type LessonStream, type LessonStreamOptions, PlayerBar, PlayerEngine, type Rng, ShortcutItem, ShortcutsDialog, type Simulation, type SimulationContext, type SimulationOptions, type SimulationPlayer, type SimulationResult, type SimulationRunOptions, type StepPlayer, type StepPlayerOptions, StepScrubber, StepTone, type StepsCore, StepwiseStep, type StepwiseTheme, type SyncedTracks, type SyncedTracksOptions, TONE_CHIP_CLASS, TONE_SEGMENT_CLASS, Term, TourCard, TourDriver, TourSpotlight, TourStepInput, type TraceBuilder, type TraceOptions, type TracePlayer, type TrackKeyFn, type TrackView, type Tracks, type UseStepsOptions, Verdict, VerdictStatus, alignTracks, cookieStore, createRng, createTrace, defaultTheme, flattenChapters, localStorageStore, memoryStore, readHashParam, reanchor, runSimulation, useChapters, useDiscoveries, useEvaluator, useFadeIn, useFocusTrap, useGlobalKeys, useGuidedTour, useLessonStream, usePrefersReducedMotion, useSimulation, useStepPlayer, useSteps, useSyncedTracks, useTrace, writeHashParam };
|