@sphynxoid/stepwise 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +150 -3
- package/dist/adapters/driver-js.d.ts +23 -0
- package/dist/adapters/driver-js.js +45 -0
- package/dist/adapters/driver-js.js.map +1 -0
- package/dist/adapters/gsap.d.ts +23 -0
- package/dist/adapters/gsap.js +51 -0
- package/dist/adapters/gsap.js.map +1 -0
- package/dist/adapters/scroll.d.ts +23 -0
- package/dist/adapters/scroll.js +71 -0
- package/dist/adapters/scroll.js.map +1 -0
- package/dist/index.d.ts +640 -0
- package/dist/index.js +1828 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/types-IT_9L8On.d.ts +93 -0
- package/package.json +101 -6
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { P as PlayerEngine, S as StepwiseStep, a as StepTone, b as TourStepInput, T as TourDriver, C as Chapter, c as ShortcutItem } from './types-IT_9L8On.js';
|
|
2
|
+
export { d as PlayerEngineApi, e as PlayerEngineHandle, f as TourContext } from './types-IT_9L8On.js';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
interface UseStepsOptions {
|
|
7
|
+
/** Initial focus. Clamped. Defaults to 0. */
|
|
8
|
+
initialIndex?: number;
|
|
9
|
+
/** Fired whenever the focus index changes. */
|
|
10
|
+
onChange?: (index: number) => void;
|
|
11
|
+
}
|
|
12
|
+
interface StepsCore {
|
|
13
|
+
/** Number of steps in the sequence. */
|
|
14
|
+
count: number;
|
|
15
|
+
/** Clamped focus: 0..count-1, or -1 when the sequence is empty. */
|
|
16
|
+
index: number;
|
|
17
|
+
atStart: boolean;
|
|
18
|
+
atEnd: boolean;
|
|
19
|
+
canNext: boolean;
|
|
20
|
+
canPrev: boolean;
|
|
21
|
+
/** Clamped jump. No-op on an empty sequence. */
|
|
22
|
+
go: (index: number) => void;
|
|
23
|
+
/** Clamped forward step. */
|
|
24
|
+
next: () => void;
|
|
25
|
+
/** Clamped backward step. */
|
|
26
|
+
prev: () => void;
|
|
27
|
+
/** Jump to the first step. */
|
|
28
|
+
first: () => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The core primitive every stepwise machine composes: a clamped cursor over an
|
|
32
|
+
* ordered sequence. Frames in a trace, chapters in a lesson, lessons in a
|
|
33
|
+
* notebook, and tour steps are all the same thing at this layer — timers,
|
|
34
|
+
* effects, and scroll-tracking are policies layered on top by consumers.
|
|
35
|
+
*
|
|
36
|
+
* All callbacks are referentially stable; `count` may change dynamically (a
|
|
37
|
+
* new timeline, a mounted window) and the cursor re-clamps automatically.
|
|
38
|
+
*/
|
|
39
|
+
declare function useSteps<T>(items: readonly T[] | number, opts?: UseStepsOptions): StepsCore;
|
|
40
|
+
|
|
41
|
+
interface StepPlayerOptions {
|
|
42
|
+
/** Number of frames in the current timeline (0 = no timeline loaded). */
|
|
43
|
+
total: number;
|
|
44
|
+
/** Delay per frame at 1× speed (built-in timer engine). */
|
|
45
|
+
baseDelayMs?: number;
|
|
46
|
+
/** Start playing immediately (used when a timeline exists on first render). */
|
|
47
|
+
autoPlay?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Alternate frame source (GSAP timeline, scroll progress, …). Replaces the
|
|
50
|
+
* built-in timer. Memoize the engine per timeline — identity changes
|
|
51
|
+
* reconnect it.
|
|
52
|
+
*/
|
|
53
|
+
engine?: PlayerEngine;
|
|
54
|
+
/**
|
|
55
|
+
* Deep-link the current frame into the URL hash (`#<key>=<index>`), opt-in.
|
|
56
|
+
* The initial hash is honored once on mount. Only this key is written —
|
|
57
|
+
* other `&`-separated hash parts (anchors, other players' keys) are kept.
|
|
58
|
+
*/
|
|
59
|
+
syncHash?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Fired once when playback runs to the final frame on its own (built-in
|
|
62
|
+
* timer or engine `onEnd`). Cleared by any cursor movement (seek/step/
|
|
63
|
+
* reset/new timeline) — the next full playthrough fires it again.
|
|
64
|
+
*/
|
|
65
|
+
onEnd?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Honor `prefers-reduced-motion: reduce` by not auto-advancing: with
|
|
68
|
+
* `autoPlay`, the player still lands on frame 0 but stays paused until the
|
|
69
|
+
* user presses play. Default true.
|
|
70
|
+
*/
|
|
71
|
+
respectReducedMotion?: boolean;
|
|
72
|
+
}
|
|
73
|
+
interface StepPlayer {
|
|
74
|
+
/** Total frames in the current timeline. */
|
|
75
|
+
total: number;
|
|
76
|
+
/** Raw frame counter; -1 means "no timeline / before first frame". */
|
|
77
|
+
frame: number;
|
|
78
|
+
/** Frame clamped into [0, total-1]; -1 when there is nothing to show. */
|
|
79
|
+
index: number;
|
|
80
|
+
playing: boolean;
|
|
81
|
+
speed: number;
|
|
82
|
+
/** Position flag: the cursor is parked on the final frame (seek counts too). */
|
|
83
|
+
atEnd: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Completion state: playback ran to the final frame on its own and nothing
|
|
86
|
+
* has moved the cursor since. Unlike `atEnd`, a seek to the last frame does
|
|
87
|
+
* not set it — `toggle()` at `ended` replays from frame 0.
|
|
88
|
+
*/
|
|
89
|
+
ended: boolean;
|
|
90
|
+
/** Jump to frame 0 and play. */
|
|
91
|
+
start: () => void;
|
|
92
|
+
/** Clear the player (frame = -1, paused). */
|
|
93
|
+
reset: () => void;
|
|
94
|
+
seek: (frame: number) => void;
|
|
95
|
+
/** Step forward; pauses first. `onEmpty` fires when there is no timeline. */
|
|
96
|
+
next: (onEmpty?: () => void) => void;
|
|
97
|
+
/** Step backward; pauses first. */
|
|
98
|
+
prev: () => void;
|
|
99
|
+
/** Play/pause; replays from 0 at the end. `onEmpty` fires when there is no timeline. */
|
|
100
|
+
toggle: (onEmpty?: () => void) => void;
|
|
101
|
+
setPlaying: (playing: boolean) => void;
|
|
102
|
+
setSpeed: (speed: number) => void;
|
|
103
|
+
}
|
|
104
|
+
declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd, respectReducedMotion, }: StepPlayerOptions): StepPlayer;
|
|
105
|
+
|
|
106
|
+
interface TracePlayer<S extends StepwiseStep> {
|
|
107
|
+
/** The underlying player — pass it to PlayerBar. */
|
|
108
|
+
player: StepPlayer;
|
|
109
|
+
/**
|
|
110
|
+
* The frame to render. Never undefined for a non-empty trace: before the
|
|
111
|
+
* player starts (`player.index === -1`) this is frame 0, not the last one.
|
|
112
|
+
*/
|
|
113
|
+
step: S | undefined;
|
|
114
|
+
/** Index of `step` in the trace (0 before start; -1 for an empty trace). */
|
|
115
|
+
frame: number;
|
|
116
|
+
/** Stable `toneAt` for PlayerBar / StepScrubber. */
|
|
117
|
+
toneAt: (i: number) => StepTone;
|
|
118
|
+
/** Stable `titleAt` for PlayerBar / StepScrubber. */
|
|
119
|
+
titleAt: (i: number) => string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A player bound to a concrete trace: the steps array is the timeline, and
|
|
123
|
+
* the hook resolves the frame to render (frame 0 before start — invariant
|
|
124
|
+
* "index -1 means not started"), plus scrubber accessors that read the
|
|
125
|
+
* latest steps through a ref. Replaces the per-page
|
|
126
|
+
* `player.index >= 0 ? … : 0` boilerplate.
|
|
127
|
+
*
|
|
128
|
+
* const trace = useTrace(STEPS, { autoPlay: true });
|
|
129
|
+
* <Viz arr={trace.step.arr} />
|
|
130
|
+
* <PlayerBar player={trace.player} toneAt={trace.toneAt} titleAt={trace.titleAt} />
|
|
131
|
+
*/
|
|
132
|
+
declare function useTrace<S extends StepwiseStep>(steps: readonly S[], opts?: Omit<StepPlayerOptions, "total">): TracePlayer<S>;
|
|
133
|
+
|
|
134
|
+
interface TraceBuilder<Data extends object> {
|
|
135
|
+
/** Append one frame. Returns the builder, so calls chain. */
|
|
136
|
+
step(step: StepwiseStep<Data>): TraceBuilder<Data>;
|
|
137
|
+
/** Frames appended so far. */
|
|
138
|
+
readonly length: number;
|
|
139
|
+
/** The most recently appended frame (handy for "carry the state forward"). */
|
|
140
|
+
last(): StepwiseStep<Data> | undefined;
|
|
141
|
+
/** Snapshot the trace. A fresh array each call — the builder stays usable. */
|
|
142
|
+
build(): StepwiseStep<Data>[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Typed trace builder. Authors run their algorithm at module scope and record
|
|
146
|
+
* frames as they go — no IIFE, no pre-declared `const STEPS = []` that a
|
|
147
|
+
* component might read before it is filled (invariant 6):
|
|
148
|
+
*
|
|
149
|
+
* const t = createTrace<{ arr: number[] }>();
|
|
150
|
+
* for (…) t.step({ tone: "write", label: "swap", arr: [...arr] });
|
|
151
|
+
* export const STEPS = t.build();
|
|
152
|
+
*
|
|
153
|
+
* Pure data — no React, safe on the server.
|
|
154
|
+
*/
|
|
155
|
+
declare function createTrace<Data extends object = Record<never, never>>(): TraceBuilder<Data>;
|
|
156
|
+
|
|
157
|
+
/** Named traces to play side by side: `{ bubble: stepsA, insertion: stepsB }`. */
|
|
158
|
+
type Tracks = Record<string, readonly StepwiseStep<object>[]>;
|
|
159
|
+
/**
|
|
160
|
+
* Sync-point key for a step. Steps with the same key in two or more tracks
|
|
161
|
+
* land on the same aligned frame; `null`/`undefined` marks a free step. The
|
|
162
|
+
* n-th occurrence of a key in one track pairs with the n-th in another.
|
|
163
|
+
*/
|
|
164
|
+
type TrackKeyFn<T extends Tracks = Tracks> = (step: T[keyof T][number], index: number, track: keyof T & string) => string | null | undefined;
|
|
165
|
+
/** One track's slot in one aligned frame. */
|
|
166
|
+
interface AlignedCell {
|
|
167
|
+
/** Index into the track's own steps; -1 only for an empty track. */
|
|
168
|
+
index: number;
|
|
169
|
+
/**
|
|
170
|
+
* The track did not advance on this frame: it is holding its last frame
|
|
171
|
+
* (finished, or waiting for a sync point), or waiting at frame 0 for its
|
|
172
|
+
* first sync point.
|
|
173
|
+
*/
|
|
174
|
+
held: boolean;
|
|
175
|
+
}
|
|
176
|
+
type AlignedRow<K extends string = string> = Record<K, AlignedCell>;
|
|
177
|
+
/**
|
|
178
|
+
* Align several traces into one timeline. Pure — use it directly for server
|
|
179
|
+
* rendering or tests; `useSyncedTracks` memoizes it per track identity.
|
|
180
|
+
*
|
|
181
|
+
* - No `key` (hold): frame i shows step i of every track; a shorter track
|
|
182
|
+
* holds its last frame once it runs out. Length = the longest track.
|
|
183
|
+
* - With `key`: tracks advance together frame by frame, but a track whose
|
|
184
|
+
* next step is a sync point holds until every other track that shares the
|
|
185
|
+
* point has reached it, so matching steps share a frame. Keys must occur
|
|
186
|
+
* in the same relative order across tracks; where they conflict, that
|
|
187
|
+
* frame degrades to lockstep instead of deadlocking.
|
|
188
|
+
*/
|
|
189
|
+
declare function alignTracks<T extends Tracks>(tracks: T, key?: TrackKeyFn<T>): AlignedRow<keyof T & string>[];
|
|
190
|
+
interface TrackView<S> {
|
|
191
|
+
/** The step to render for this track (undefined only for an empty track). */
|
|
192
|
+
step: S | undefined;
|
|
193
|
+
/** Index into the track's own steps; -1 for an empty track. */
|
|
194
|
+
index: number;
|
|
195
|
+
/** The track did not advance on this frame (see `AlignedCell.held`). */
|
|
196
|
+
held: boolean;
|
|
197
|
+
/** The track is on its final step. */
|
|
198
|
+
done: boolean;
|
|
199
|
+
/** Length of the track's own trace. */
|
|
200
|
+
length: number;
|
|
201
|
+
}
|
|
202
|
+
interface SyncedTracksOptions<T extends Tracks = Tracks> extends Omit<StepPlayerOptions, "total"> {
|
|
203
|
+
/**
|
|
204
|
+
* Sync-point alignment (see `alignTracks`). Omit to hold shorter tracks on
|
|
205
|
+
* their last frame. Read when the tracks change — pass the same tracks to
|
|
206
|
+
* keep the alignment memoized; an inline function is fine.
|
|
207
|
+
*/
|
|
208
|
+
key?: TrackKeyFn<T>;
|
|
209
|
+
}
|
|
210
|
+
interface SyncedTracks<T extends Tracks> {
|
|
211
|
+
/** One player over the aligned timeline — pass it to PlayerBar. */
|
|
212
|
+
player: StepPlayer;
|
|
213
|
+
/** Aligned frame to render: 0 before the player starts, -1 when all tracks are empty. */
|
|
214
|
+
frame: number;
|
|
215
|
+
/** Number of aligned frames. */
|
|
216
|
+
total: number;
|
|
217
|
+
/** Per-track view of `frame`. */
|
|
218
|
+
tracks: {
|
|
219
|
+
[K in keyof T]: TrackView<T[K][number]>;
|
|
220
|
+
};
|
|
221
|
+
/** The full alignment table (stable while the tracks are). */
|
|
222
|
+
rows: readonly AlignedRow<keyof T & string>[];
|
|
223
|
+
/** Stable scrubber accessors: the most severe tone among tracks that moved. */
|
|
224
|
+
toneAt: (i: number) => StepTone;
|
|
225
|
+
/** Stable scrubber accessor: `track: label` for every track that moved. */
|
|
226
|
+
titleAt: (i: number) => string;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Comparison mode: several traces of the same input (two engines, two
|
|
230
|
+
* algorithms) under ONE player. The cursor walks the aligned timeline from
|
|
231
|
+
* `alignTracks`; each track resolves its own step, so payload types stay
|
|
232
|
+
* per-track. Like `useTrace`, frame 0 renders before playback starts.
|
|
233
|
+
*
|
|
234
|
+
* const cmp = useSyncedTracks({ bubble: BUBBLE, insertion: INSERTION }, { autoPlay: true });
|
|
235
|
+
* <Lane step={cmp.tracks.bubble.step} held={cmp.tracks.bubble.held} />
|
|
236
|
+
* <PlayerBar player={cmp.player} toneAt={cmp.toneAt} titleAt={cmp.titleAt} />
|
|
237
|
+
*/
|
|
238
|
+
declare function useSyncedTracks<T extends Tracks>(tracks: T, opts?: SyncedTracksOptions<T>): SyncedTracks<T>;
|
|
239
|
+
|
|
240
|
+
interface LessonStreamOptions {
|
|
241
|
+
/** Lesson to open on (deep link). Clamped. Default 0. */
|
|
242
|
+
seed?: number;
|
|
243
|
+
/** Lessons mounted on each side of the seed initially. Default 1. */
|
|
244
|
+
radius?: number;
|
|
245
|
+
/** Load the next lesson when the last mounted one's bottom is within this many px below the viewport. Default 600. */
|
|
246
|
+
approachBelow?: number;
|
|
247
|
+
/** Load the previous lesson when the first mounted one's top is below `-approachAbove`. Default 300. */
|
|
248
|
+
approachAbove?: number;
|
|
249
|
+
/** Scroll the seed lesson into view before first paint (seed > 0). Default true. */
|
|
250
|
+
scrollToSeed?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Fired when the scroll-tracked active lesson changes (not on mount) — sync
|
|
253
|
+
* the URL / document title here. Read through a ref; inline is fine.
|
|
254
|
+
*/
|
|
255
|
+
onActiveChange?: (index: number) => void;
|
|
256
|
+
}
|
|
257
|
+
interface LessonStream {
|
|
258
|
+
/** Lesson containing the viewport center (the seed until the first measure). */
|
|
259
|
+
active: number;
|
|
260
|
+
/** The seed lesson — SSR'd visible; every other block should fade in. */
|
|
261
|
+
seed: number;
|
|
262
|
+
/** Indices to render, in order (the mounted window). */
|
|
263
|
+
mounted: number[];
|
|
264
|
+
/** The window reaches the last lesson — render an end-of-stream footer. */
|
|
265
|
+
complete: boolean;
|
|
266
|
+
/** Stable callback ref for lesson `i`'s block element (cached per index). */
|
|
267
|
+
refFor: (index: number) => (el: HTMLElement | null) => void;
|
|
268
|
+
/** Mount through lesson `i` (if needed) and scroll to it. */
|
|
269
|
+
goTo: (index: number) => void;
|
|
270
|
+
next: () => void;
|
|
271
|
+
prev: () => void;
|
|
272
|
+
reducedMotion: boolean;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Continuous reader for a sequence of lessons (a notebook): renders a growing
|
|
276
|
+
* window of lessons around the seed instead of all of them, and tracks the
|
|
277
|
+
* active one by viewport-center containment (invariant 1).
|
|
278
|
+
*
|
|
279
|
+
* - The lesson after the window mounts as the reader approaches its place;
|
|
280
|
+
* the one before mounts near the top, and a layout effect compensates the
|
|
281
|
+
* scroll so prepended content never shoves the reader down.
|
|
282
|
+
* - Navigation (`goTo`) expansions are NOT compensated — their scrollIntoView
|
|
283
|
+
* is absolute, and compensating them displaced the landing.
|
|
284
|
+
* - The cursor is a useSteps core, so it composes with ChapterNav
|
|
285
|
+
* (`active` + `goTo`) and LessonPager (`prev`/`next`).
|
|
286
|
+
*
|
|
287
|
+
* Headless: the consumer renders `mounted.map(i => <Block ref={refFor(i)} />)`
|
|
288
|
+
* and owns the content; pair blocks with `useFadeIn(i === seed)`.
|
|
289
|
+
*/
|
|
290
|
+
declare function useLessonStream(count: number, opts?: LessonStreamOptions): LessonStream;
|
|
291
|
+
/**
|
|
292
|
+
* Mount fade for streamed blocks: `false` on the first client render, `true`
|
|
293
|
+
* a frame later (immediately with reduced motion). Pass `initiallyVisible`
|
|
294
|
+
* for server-rendered blocks (the stream's seed) so they never flash.
|
|
295
|
+
*/
|
|
296
|
+
declare function useFadeIn(initiallyVisible?: boolean): boolean;
|
|
297
|
+
|
|
298
|
+
interface GuidedTour {
|
|
299
|
+
/** Active step index, or null when the tour is not running. */
|
|
300
|
+
index: number | null;
|
|
301
|
+
current: TourStepInput | null;
|
|
302
|
+
active: boolean;
|
|
303
|
+
/**
|
|
304
|
+
* True while the active step's `gate` returns false: `next()` is a no-op
|
|
305
|
+
* until the gate opens. Present the `blocked` state (disable Next, pulse
|
|
306
|
+
* the waitHint) so the user knows what is being waited on.
|
|
307
|
+
*/
|
|
308
|
+
blocked: boolean;
|
|
309
|
+
start: () => void;
|
|
310
|
+
end: () => void;
|
|
311
|
+
next: () => void;
|
|
312
|
+
back: () => void;
|
|
313
|
+
goto: (index: number) => void;
|
|
314
|
+
/** Jump to the step with this id. No-op when the id is unknown. */
|
|
315
|
+
gotoId: (id: string) => void;
|
|
316
|
+
}
|
|
317
|
+
interface GuidedTourOptions {
|
|
318
|
+
/**
|
|
319
|
+
* Alternate presenter (driver.js, Shepherd, …). When set, the hook drives it
|
|
320
|
+
* imperatively — show/hide per step, with transition controls wired — and
|
|
321
|
+
* the app should not render the built-in TourSpotlight/TourCard. Without a
|
|
322
|
+
* driver, the app renders the built-in presentation itself.
|
|
323
|
+
*/
|
|
324
|
+
driver?: TourDriver;
|
|
325
|
+
}
|
|
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
|
+
declare function useGuidedTour(steps: TourStepInput[], opts?: GuidedTourOptions): GuidedTour;
|
|
340
|
+
|
|
341
|
+
interface FlatChapter {
|
|
342
|
+
chapter: Chapter;
|
|
343
|
+
depth: number;
|
|
344
|
+
/** Index of this entry's top-level ancestor within the flat list (itself at depth 0). */
|
|
345
|
+
rootIndex: number;
|
|
346
|
+
/** Index of the immediate parent within the flat list; -1 at depth 0. */
|
|
347
|
+
parentIndex: number;
|
|
348
|
+
}
|
|
349
|
+
/** Depth-first flattening — reading order for prev/next and scroll tracking. */
|
|
350
|
+
declare function flattenChapters(chapters: Chapter[], depth?: number): FlatChapter[];
|
|
351
|
+
|
|
352
|
+
interface ChaptersApi {
|
|
353
|
+
/** Index of the chapter currently in view (flattened order). */
|
|
354
|
+
active: number;
|
|
355
|
+
goTo: (index: number) => void;
|
|
356
|
+
next: () => void;
|
|
357
|
+
prev: () => void;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Scroll-tracked chapter navigation for multi-section pages. Active = the
|
|
362
|
+
* section containing the viewport center: a chapter becomes active when it
|
|
363
|
+
* reaches the middle of the screen, not the moment its top scrolls past an
|
|
364
|
+
* offset. Sections are resolved by DOM id; give each section CSS scroll-mt
|
|
365
|
+
* so `goTo`'s scrollIntoView clears any sticky header. Composed on useSteps —
|
|
366
|
+
* the scroll position is an external input driving the cursor.
|
|
367
|
+
*/
|
|
368
|
+
declare function useChapters(chapters: Chapter[]): ChaptersApi;
|
|
369
|
+
|
|
370
|
+
type KeyMap = Record<string, (e: KeyboardEvent) => void>;
|
|
371
|
+
interface GlobalKeysOptions {
|
|
372
|
+
/** Element tags that swallow keys (except Escape). Defaults to form controls and buttons. */
|
|
373
|
+
ignoreTags?: string[];
|
|
374
|
+
/** Gate: bindings fire only while enabled. Boolean or function — checked per event. */
|
|
375
|
+
enabled?: boolean | (() => boolean);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Bind global keyboard shortcuts. Keys are matched against `event.key`
|
|
379
|
+
* (exact first, then lowercase). Handlers fire with preventDefault applied;
|
|
380
|
+
* Escape always fires, even from ignored targets, and is never prevented.
|
|
381
|
+
*/
|
|
382
|
+
declare function useGlobalKeys(map: KeyMap, opts?: GlobalKeysOptions): void;
|
|
383
|
+
|
|
384
|
+
interface DiscoveryStore {
|
|
385
|
+
/** All discovered ids (best-effort; corrupted/absent storage reads as empty). */
|
|
386
|
+
read(): string[];
|
|
387
|
+
/** Persist the full set (write-through, not a delta). */
|
|
388
|
+
write(ids: string[]): void;
|
|
389
|
+
}
|
|
390
|
+
/** localStorage-backed store — the default. Survives reloads, per-browser. */
|
|
391
|
+
declare function localStorageStore(key?: string): DiscoveryStore;
|
|
392
|
+
/**
|
|
393
|
+
* Cookie-backed store — only when the SERVER must know during SSR (e.g. to
|
|
394
|
+
* render "NEW" badges without a hydration flash). Costs request bytes; prefer
|
|
395
|
+
* localStorage otherwise. Values are JSON-encoded and URI-escaped.
|
|
396
|
+
*/
|
|
397
|
+
declare function cookieStore(key?: string): DiscoveryStore;
|
|
398
|
+
/** In-memory store — tests, storybook, SSR fallbacks, "always show" previews. */
|
|
399
|
+
declare function memoryStore(seed?: string[]): DiscoveryStore;
|
|
400
|
+
interface DiscoveriesOptions {
|
|
401
|
+
/**
|
|
402
|
+
* Storage seam. Defaults to localStorage. Swap in cookieStore when SSR
|
|
403
|
+
* markup must know, or your own store (backend sync for cross-device).
|
|
404
|
+
*/
|
|
405
|
+
store?: DiscoveryStore;
|
|
406
|
+
/** Storage key used by the built-in stores. */
|
|
407
|
+
key?: string;
|
|
408
|
+
}
|
|
409
|
+
interface Discoveries {
|
|
410
|
+
/** Discovered ids. Empty until hydration — gate triggers on `ready`. */
|
|
411
|
+
discovered: Set<string>;
|
|
412
|
+
/** False during SSR and before the first client hydration pass. */
|
|
413
|
+
ready: boolean;
|
|
414
|
+
/** Ref-backed check for event handlers; prefer `discovered.has` in render. */
|
|
415
|
+
isDiscovered: (id: string) => boolean;
|
|
416
|
+
/** Record ids as discovered and persist the merged set. */
|
|
417
|
+
markDiscovered: (id: string | string[]) => void;
|
|
418
|
+
/** Forget everything (debug / "show me the tour again"). */
|
|
419
|
+
reset: () => void;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* A persisted set of discovered ids, hydrated after mount (SSR-safe: the
|
|
423
|
+
* server renders the undiscovered state, the client corrects it in an
|
|
424
|
+
* effect — same pattern as usePrefersReducedMotion). Compose with
|
|
425
|
+
* useGuidedTour: `pending = features.filter(f => !discovered.has(f.id))`,
|
|
426
|
+
* auto-start the first pending tour, `markDiscovered` on completion.
|
|
427
|
+
*/
|
|
428
|
+
declare function useDiscoveries(opts?: DiscoveriesOptions): Discoveries;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Traps Tab focus inside `ref.current` while `active`, focuses the container
|
|
432
|
+
* on activation, and restores the previously focused element on release.
|
|
433
|
+
* Returns a ref to attach to the focusable container (tabIndex={-1}).
|
|
434
|
+
*/
|
|
435
|
+
declare function useFocusTrap(active: boolean): react.RefObject<HTMLDivElement | null>;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* True when the user asks the OS to reduce motion. Consumers should disable
|
|
439
|
+
* transitions, smooth scrolls, and entry animations when this is true.
|
|
440
|
+
* SSR-safe: false on the server, corrected after mount.
|
|
441
|
+
*/
|
|
442
|
+
declare function usePrefersReducedMotion(): boolean;
|
|
443
|
+
|
|
444
|
+
interface StepwiseTheme {
|
|
445
|
+
/** Timeline-segment styling per tone. */
|
|
446
|
+
toneSegment: Record<StepTone, string>;
|
|
447
|
+
/** Step-kind chip styling per tone. */
|
|
448
|
+
toneChip: Record<StepTone, string>;
|
|
449
|
+
/** Shared structural classes for internal buttons. */
|
|
450
|
+
buttonBase: string;
|
|
451
|
+
/** Per-variant appearance for internal buttons. */
|
|
452
|
+
buttonVariants: Record<"ghost" | "outline" | "primary", string>;
|
|
453
|
+
/**
|
|
454
|
+
* Panel chrome for nav bars, cards, dialogs, and tooltip popovers.
|
|
455
|
+
* Components pass their own border/background overrides after this token,
|
|
456
|
+
* so it stays a base, not a contract.
|
|
457
|
+
*/
|
|
458
|
+
surface: string;
|
|
459
|
+
/** Full-screen dimmed backdrop behind modal surfaces. */
|
|
460
|
+
overlay: string;
|
|
461
|
+
/** Code block frame — its own background, border, and type scale. */
|
|
462
|
+
codeBlock: string;
|
|
463
|
+
/** Comment / dimmed lines inside the code block. */
|
|
464
|
+
codeMuted: string;
|
|
465
|
+
/** Keyboard chip (ShortcutsDialog). */
|
|
466
|
+
kbd: string;
|
|
467
|
+
/** Ring + rounding around the tour spotlight target. */
|
|
468
|
+
spotlight: string;
|
|
469
|
+
/** Dimmed meta text — counters, hints, empty states. */
|
|
470
|
+
muted: string;
|
|
471
|
+
}
|
|
472
|
+
declare const defaultTheme: StepwiseTheme;
|
|
473
|
+
|
|
474
|
+
interface GlossaryEntry {
|
|
475
|
+
id: string;
|
|
476
|
+
/** Display name, shown as the tooltip's heading. */
|
|
477
|
+
term: string;
|
|
478
|
+
/** Body — may embed <Term> references for nested tooltips. */
|
|
479
|
+
definition: ReactNode;
|
|
480
|
+
}
|
|
481
|
+
declare function GlossaryProvider({ entries, theme, children, }: {
|
|
482
|
+
entries: GlossaryEntry[];
|
|
483
|
+
/** Styling for tooltip cards (see StepwiseTheme). */
|
|
484
|
+
theme?: StepwiseTheme;
|
|
485
|
+
children: ReactNode;
|
|
486
|
+
}): react.JSX.Element;
|
|
487
|
+
declare function Term({ id, children, className }: {
|
|
488
|
+
id: string;
|
|
489
|
+
children?: ReactNode;
|
|
490
|
+
className?: string;
|
|
491
|
+
}): react.JSX.Element;
|
|
492
|
+
/**
|
|
493
|
+
* The glossary appendix: every entry rendered as an anchored row, placed
|
|
494
|
+
* after the last chapter. Entries default to the provider's declarations —
|
|
495
|
+
* define them once and the appendix follows automatically; pass `entries`
|
|
496
|
+
* explicitly to override or to render without a provider. Inside it, <Term>
|
|
497
|
+
* references resolve to in-page anchor links instead of tooltips.
|
|
498
|
+
*
|
|
499
|
+
* Customization hooks: `intro`/`footer` render arbitrary nodes around the
|
|
500
|
+
* grid, `renderEntry` replaces the whole row renderer, `className` and
|
|
501
|
+
* `entryClassName` style the section and each row. Opt out of the appendix
|
|
502
|
+
* entirely by not rendering it.
|
|
503
|
+
*/
|
|
504
|
+
declare function GlossaryAppendix({ entries, title, intro, footer, columns, bare, className, entryClassName, renderEntry, }: {
|
|
505
|
+
/** Override the row list; defaults to the provider's entries. */
|
|
506
|
+
entries?: GlossaryEntry[];
|
|
507
|
+
title?: string;
|
|
508
|
+
/** Rendered between the title and the grid — an introduction, usage notes, scope, … */
|
|
509
|
+
intro?: ReactNode;
|
|
510
|
+
/** Rendered after the grid. */
|
|
511
|
+
footer?: ReactNode;
|
|
512
|
+
columns?: 1 | 2;
|
|
513
|
+
/** Omit the section chrome (title/border) — the host provides its own. */
|
|
514
|
+
bare?: boolean;
|
|
515
|
+
className?: string;
|
|
516
|
+
entryClassName?: string;
|
|
517
|
+
/** Replace the default row renderer entirely. */
|
|
518
|
+
renderEntry?: (entry: GlossaryEntry) => ReactNode;
|
|
519
|
+
}): react.JSX.Element | null;
|
|
520
|
+
|
|
521
|
+
declare const TONE_SEGMENT_CLASS: Record<StepTone, string>;
|
|
522
|
+
declare const TONE_CHIP_CLASS: Record<StepTone, string>;
|
|
523
|
+
declare function CodeBlock({ lines, theme, className, }: {
|
|
524
|
+
lines: string[];
|
|
525
|
+
theme?: StepwiseTheme;
|
|
526
|
+
className?: string;
|
|
527
|
+
}): react.JSX.Element;
|
|
528
|
+
interface ScrubberLabels {
|
|
529
|
+
/** aria-label for the slider. */
|
|
530
|
+
scrubber?: string;
|
|
531
|
+
/** aria-valuetext for the current position. Receives the frame index. */
|
|
532
|
+
frame?: (i: number) => string;
|
|
533
|
+
}
|
|
534
|
+
declare function StepScrubber({ count, index, toneAt, titleAt, onSeek, emptyHint, className, theme, labels, groupOf, }: {
|
|
535
|
+
count: number;
|
|
536
|
+
index: number;
|
|
537
|
+
toneAt?: (i: number) => StepTone;
|
|
538
|
+
titleAt?: (i: number) => string;
|
|
539
|
+
onSeek: (i: number) => void;
|
|
540
|
+
emptyHint?: string;
|
|
541
|
+
className?: string;
|
|
542
|
+
theme?: StepwiseTheme;
|
|
543
|
+
labels?: ScrubberLabels;
|
|
544
|
+
/**
|
|
545
|
+
* Groups consecutive segments into one proportional wrapper — a chapter
|
|
546
|
+
* with sub-chapters spans the width of its whole subtree and only the
|
|
547
|
+
* active inner segment fills, so a chapter reads as "halfway" while its
|
|
548
|
+
* children play. Segments must be group-contiguous (flattenChapters order).
|
|
549
|
+
*/
|
|
550
|
+
groupOf?: (i: number) => number;
|
|
551
|
+
}): react.JSX.Element;
|
|
552
|
+
declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, }: {
|
|
553
|
+
player: {
|
|
554
|
+
total: number;
|
|
555
|
+
index: number;
|
|
556
|
+
playing: boolean;
|
|
557
|
+
atEnd: boolean;
|
|
558
|
+
next: (onEmpty?: () => void) => void;
|
|
559
|
+
prev: () => void;
|
|
560
|
+
toggle: (onEmpty?: () => void) => void;
|
|
561
|
+
seek: (frame: number) => void;
|
|
562
|
+
};
|
|
563
|
+
toneAt?: (i: number) => StepTone;
|
|
564
|
+
titleAt?: (i: number) => string;
|
|
565
|
+
leading?: ReactNode;
|
|
566
|
+
/** Fired when the user plays/steps with no timeline loaded. */
|
|
567
|
+
onEmpty?: () => void;
|
|
568
|
+
emptyHint?: string;
|
|
569
|
+
className?: string;
|
|
570
|
+
theme?: StepwiseTheme;
|
|
571
|
+
/**
|
|
572
|
+
* Forwarded to the scrubber — group consecutive segments into one
|
|
573
|
+
* proportional wrapper (see StepScrubber).
|
|
574
|
+
*/
|
|
575
|
+
groupOf?: (i: number) => number;
|
|
576
|
+
labels?: {
|
|
577
|
+
previous?: string;
|
|
578
|
+
play?: string;
|
|
579
|
+
pause?: string;
|
|
580
|
+
replay?: string;
|
|
581
|
+
next?: string;
|
|
582
|
+
};
|
|
583
|
+
}): react.JSX.Element;
|
|
584
|
+
declare function ChapterNav({ chapters, active, onNavigate, hint, className, theme, }: {
|
|
585
|
+
chapters: Chapter[];
|
|
586
|
+
active: number;
|
|
587
|
+
onNavigate: (i: number) => void;
|
|
588
|
+
hint?: string;
|
|
589
|
+
className?: string;
|
|
590
|
+
theme?: StepwiseTheme;
|
|
591
|
+
}): react.JSX.Element;
|
|
592
|
+
declare function LessonPager({ prev, next, onNavigate, className, labels, }: {
|
|
593
|
+
prev?: {
|
|
594
|
+
label: string;
|
|
595
|
+
hint?: string;
|
|
596
|
+
};
|
|
597
|
+
next?: {
|
|
598
|
+
label: string;
|
|
599
|
+
hint?: string;
|
|
600
|
+
};
|
|
601
|
+
onNavigate: (dir: "prev" | "next") => void;
|
|
602
|
+
className?: string;
|
|
603
|
+
labels?: {
|
|
604
|
+
previous?: string;
|
|
605
|
+
next?: string;
|
|
606
|
+
};
|
|
607
|
+
}): react.JSX.Element;
|
|
608
|
+
declare function TourSpotlight({ target, theme, }: {
|
|
609
|
+
target: string | null;
|
|
610
|
+
theme?: StepwiseTheme;
|
|
611
|
+
}): react.JSX.Element | null;
|
|
612
|
+
declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, }: {
|
|
613
|
+
step: TourStepInput;
|
|
614
|
+
index: number;
|
|
615
|
+
total: number;
|
|
616
|
+
onNext: () => void;
|
|
617
|
+
onBack: () => void;
|
|
618
|
+
onEnd: () => void;
|
|
619
|
+
labels?: {
|
|
620
|
+
title?: string;
|
|
621
|
+
back?: string;
|
|
622
|
+
next?: string;
|
|
623
|
+
finish?: string;
|
|
624
|
+
end?: string;
|
|
625
|
+
};
|
|
626
|
+
className?: string;
|
|
627
|
+
theme?: StepwiseTheme;
|
|
628
|
+
/** Disable the primary (Next/Finish) button — pair with tour.blocked. */
|
|
629
|
+
nextDisabled?: boolean;
|
|
630
|
+
}): react.JSX.Element;
|
|
631
|
+
declare function ShortcutsDialog({ open, onClose, items, title, className, theme, }: {
|
|
632
|
+
open: boolean;
|
|
633
|
+
onClose: () => void;
|
|
634
|
+
items: ShortcutItem[];
|
|
635
|
+
title?: string;
|
|
636
|
+
className?: string;
|
|
637
|
+
theme?: StepwiseTheme;
|
|
638
|
+
}): react.ReactPortal | null;
|
|
639
|
+
|
|
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 };
|