@sphynxoid/stepwise 0.0.0-stage → 0.1.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.
@@ -0,0 +1,441 @@
1
+ import { P as PlayerEngine, a as TourStepInput, T as TourDriver, C as Chapter, S as StepTone, b as ShortcutItem } from './types-CPf9ZD_Z.js';
2
+ export { c as PlayerEngineApi, d as PlayerEngineHandle, e as StepwiseStep, f as TourContext } from './types-CPf9ZD_Z.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.
57
+ */
58
+ syncHash?: string;
59
+ /**
60
+ * Fired once when playback runs to the final frame on its own (built-in
61
+ * timer or engine `onEnd`). Cleared by any cursor movement (seek/step/
62
+ * reset/new timeline) — the next full playthrough fires it again.
63
+ */
64
+ onEnd?: () => void;
65
+ }
66
+ interface StepPlayer {
67
+ /** Total frames in the current timeline. */
68
+ total: number;
69
+ /** Raw frame counter; -1 means "no timeline / before first frame". */
70
+ frame: number;
71
+ /** Frame clamped into [0, total-1]; -1 when there is nothing to show. */
72
+ index: number;
73
+ playing: boolean;
74
+ speed: number;
75
+ /** Position flag: the cursor is parked on the final frame (seek counts too). */
76
+ atEnd: boolean;
77
+ /**
78
+ * Completion state: playback ran to the final frame on its own and nothing
79
+ * has moved the cursor since. Unlike `atEnd`, a seek to the last frame does
80
+ * not set it — `toggle()` at `ended` replays from frame 0.
81
+ */
82
+ ended: boolean;
83
+ /** Jump to frame 0 and play. */
84
+ start: () => void;
85
+ /** Clear the player (frame = -1, paused). */
86
+ reset: () => void;
87
+ seek: (frame: number) => void;
88
+ /** Step forward; pauses first. `onEmpty` fires when there is no timeline. */
89
+ next: (onEmpty?: () => void) => void;
90
+ /** Step backward; pauses first. */
91
+ prev: () => void;
92
+ /** Play/pause; replays from 0 at the end. `onEmpty` fires when there is no timeline. */
93
+ toggle: (onEmpty?: () => void) => void;
94
+ setPlaying: (playing: boolean) => void;
95
+ setSpeed: (speed: number) => void;
96
+ }
97
+ declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd }: StepPlayerOptions): StepPlayer;
98
+
99
+ interface GuidedTour {
100
+ /** Active step index, or null when the tour is not running. */
101
+ index: number | null;
102
+ current: TourStepInput | null;
103
+ active: boolean;
104
+ /**
105
+ * True while the active step's `gate` returns false: `next()` is a no-op
106
+ * until the gate opens. Present the `blocked` state (disable Next, pulse
107
+ * the waitHint) so the user knows what is being waited on.
108
+ */
109
+ blocked: boolean;
110
+ start: () => void;
111
+ end: () => void;
112
+ next: () => void;
113
+ back: () => void;
114
+ goto: (index: number) => void;
115
+ /** Jump to the step with this id. No-op when the id is unknown. */
116
+ gotoId: (id: string) => void;
117
+ }
118
+ interface GuidedTourOptions {
119
+ /**
120
+ * Alternate presenter (driver.js, Shepherd, …). When set, the hook drives it
121
+ * imperatively — show/hide per step, with transition controls wired — and
122
+ * the app should not render the built-in TourSpotlight/TourCard. Without a
123
+ * driver, the app renders the built-in presentation itself.
124
+ */
125
+ driver?: TourDriver;
126
+ }
127
+ /**
128
+ * Tour state whose steps are effects over app state: `onEnter` fires whenever
129
+ * the active step changes, reading the latest `steps` via ref so closures stay
130
+ * fresh without re-triggering the effect on every render. Composed on
131
+ * useSteps — a tour is a step sequence whose `next` past the end exits.
132
+ *
133
+ * On top of the linear cursor, steps may declare `when` (skip on arrival),
134
+ * `gate` (block `next` until true), and `next` (edge override by step id) —
135
+ * a small state machine where states are steps, edges are `next` functions,
136
+ * and guards are gates. The cursor itself stays a cursor: skipped steps are
137
+ * never landed on, and skipped/gated behavior is evaluated at transition
138
+ * time, not on every render.
139
+ */
140
+ declare function useGuidedTour(steps: TourStepInput[], opts?: GuidedTourOptions): GuidedTour;
141
+
142
+ interface FlatChapter {
143
+ chapter: Chapter;
144
+ depth: number;
145
+ /** Index of this entry's top-level ancestor within the flat list (itself at depth 0). */
146
+ rootIndex: number;
147
+ /** Index of the immediate parent within the flat list; -1 at depth 0. */
148
+ parentIndex: number;
149
+ }
150
+ /** Depth-first flattening — reading order for prev/next and scroll tracking. */
151
+ declare function flattenChapters(chapters: Chapter[], depth?: number): FlatChapter[];
152
+
153
+ interface ChaptersApi {
154
+ /** Index of the chapter currently in view (flattened order). */
155
+ active: number;
156
+ goTo: (index: number) => void;
157
+ next: () => void;
158
+ prev: () => void;
159
+ }
160
+
161
+ /**
162
+ * Scroll-tracked chapter navigation for multi-section pages. Active = the
163
+ * section containing the viewport center: a chapter becomes active when it
164
+ * reaches the middle of the screen, not the moment its top scrolls past an
165
+ * offset. Sections are resolved by DOM id; give each section CSS scroll-mt
166
+ * so `goTo`'s scrollIntoView clears any sticky header. Composed on useSteps —
167
+ * the scroll position is an external input driving the cursor.
168
+ */
169
+ declare function useChapters(chapters: Chapter[]): ChaptersApi;
170
+
171
+ type KeyMap = Record<string, (e: KeyboardEvent) => void>;
172
+ interface GlobalKeysOptions {
173
+ /** Element tags that swallow keys (except Escape). Defaults to form controls and buttons. */
174
+ ignoreTags?: string[];
175
+ /** Gate: bindings fire only while enabled. Boolean or function — checked per event. */
176
+ enabled?: boolean | (() => boolean);
177
+ }
178
+ /**
179
+ * Bind global keyboard shortcuts. Keys are matched against `event.key`
180
+ * (exact first, then lowercase). Handlers fire with preventDefault applied;
181
+ * Escape always fires, even from ignored targets, and is never prevented.
182
+ */
183
+ declare function useGlobalKeys(map: KeyMap, opts?: GlobalKeysOptions): void;
184
+
185
+ interface DiscoveryStore {
186
+ /** All discovered ids (best-effort; corrupted/absent storage reads as empty). */
187
+ read(): string[];
188
+ /** Persist the full set (write-through, not a delta). */
189
+ write(ids: string[]): void;
190
+ }
191
+ /** localStorage-backed store — the default. Survives reloads, per-browser. */
192
+ declare function localStorageStore(key?: string): DiscoveryStore;
193
+ /**
194
+ * Cookie-backed store — only when the SERVER must know during SSR (e.g. to
195
+ * render "NEW" badges without a hydration flash). Costs request bytes; prefer
196
+ * localStorage otherwise. Values are JSON-encoded and URI-escaped.
197
+ */
198
+ declare function cookieStore(key?: string): DiscoveryStore;
199
+ /** In-memory store — tests, storybook, SSR fallbacks, "always show" previews. */
200
+ declare function memoryStore(seed?: string[]): DiscoveryStore;
201
+ interface DiscoveriesOptions {
202
+ /**
203
+ * Storage seam. Defaults to localStorage. Swap in cookieStore when SSR
204
+ * markup must know, or your own store (backend sync for cross-device).
205
+ */
206
+ store?: DiscoveryStore;
207
+ /** Storage key used by the built-in stores. */
208
+ key?: string;
209
+ }
210
+ interface Discoveries {
211
+ /** Discovered ids. Empty until hydration — gate triggers on `ready`. */
212
+ discovered: Set<string>;
213
+ /** False during SSR and before the first client hydration pass. */
214
+ ready: boolean;
215
+ /** Ref-backed check for event handlers; prefer `discovered.has` in render. */
216
+ isDiscovered: (id: string) => boolean;
217
+ /** Record ids as discovered and persist the merged set. */
218
+ markDiscovered: (id: string | string[]) => void;
219
+ /** Forget everything (debug / "show me the tour again"). */
220
+ reset: () => void;
221
+ }
222
+ /**
223
+ * A persisted set of discovered ids, hydrated after mount (SSR-safe: the
224
+ * server renders the undiscovered state, the client corrects it in an
225
+ * effect — same pattern as usePrefersReducedMotion). Compose with
226
+ * useGuidedTour: `pending = features.filter(f => !discovered.has(f.id))`,
227
+ * auto-start the first pending tour, `markDiscovered` on completion.
228
+ */
229
+ declare function useDiscoveries(opts?: DiscoveriesOptions): Discoveries;
230
+
231
+ /**
232
+ * Traps Tab focus inside `ref.current` while `active`, focuses the container
233
+ * on activation, and restores the previously focused element on release.
234
+ * Returns a ref to attach to the focusable container (tabIndex={-1}).
235
+ */
236
+ declare function useFocusTrap(active: boolean): react.RefObject<HTMLDivElement | null>;
237
+
238
+ /**
239
+ * True when the user asks the OS to reduce motion. Consumers should disable
240
+ * transitions, smooth scrolls, and entry animations when this is true.
241
+ * SSR-safe: false on the server, corrected after mount.
242
+ */
243
+ declare function usePrefersReducedMotion(): boolean;
244
+
245
+ interface StepwiseTheme {
246
+ /** Timeline-segment styling per tone. */
247
+ toneSegment: Record<StepTone, string>;
248
+ /** Step-kind chip styling per tone. */
249
+ toneChip: Record<StepTone, string>;
250
+ /** Shared structural classes for internal buttons. */
251
+ buttonBase: string;
252
+ /** Per-variant appearance for internal buttons. */
253
+ buttonVariants: Record<"ghost" | "outline" | "primary", string>;
254
+ /**
255
+ * Panel chrome for nav bars, cards, dialogs, and tooltip popovers.
256
+ * Components pass their own border/background overrides after this token,
257
+ * so it stays a base, not a contract.
258
+ */
259
+ surface: string;
260
+ /** Full-screen dimmed backdrop behind modal surfaces. */
261
+ overlay: string;
262
+ /** Code block frame — its own background, border, and type scale. */
263
+ codeBlock: string;
264
+ /** Comment / dimmed lines inside the code block. */
265
+ codeMuted: string;
266
+ /** Keyboard chip (ShortcutsDialog). */
267
+ kbd: string;
268
+ /** Ring + rounding around the tour spotlight target. */
269
+ spotlight: string;
270
+ /** Dimmed meta text — counters, hints, empty states. */
271
+ muted: string;
272
+ }
273
+ declare const defaultTheme: StepwiseTheme;
274
+
275
+ interface GlossaryEntry {
276
+ id: string;
277
+ /** Display name, shown as the tooltip's heading. */
278
+ term: string;
279
+ /** Body — may embed <Term> references for nested tooltips. */
280
+ definition: ReactNode;
281
+ }
282
+ declare function GlossaryProvider({ entries, theme, children, }: {
283
+ entries: GlossaryEntry[];
284
+ /** Styling for tooltip cards (see StepwiseTheme). */
285
+ theme?: StepwiseTheme;
286
+ children: ReactNode;
287
+ }): react.JSX.Element;
288
+ declare function Term({ id, children, className }: {
289
+ id: string;
290
+ children?: ReactNode;
291
+ className?: string;
292
+ }): react.JSX.Element;
293
+ /**
294
+ * The glossary appendix: every entry rendered as an anchored row, placed
295
+ * after the last chapter. Entries default to the provider's declarations —
296
+ * define them once and the appendix follows automatically; pass `entries`
297
+ * explicitly to override or to render without a provider. Inside it, <Term>
298
+ * references resolve to in-page anchor links instead of tooltips.
299
+ *
300
+ * Customization hooks: `intro`/`footer` render arbitrary nodes around the
301
+ * grid, `renderEntry` replaces the whole row renderer, `className` and
302
+ * `entryClassName` style the section and each row. Opt out of the appendix
303
+ * entirely by not rendering it.
304
+ */
305
+ declare function GlossaryAppendix({ entries, title, intro, footer, columns, bare, className, entryClassName, renderEntry, }: {
306
+ /** Override the row list; defaults to the provider's entries. */
307
+ entries?: GlossaryEntry[];
308
+ title?: string;
309
+ /** Rendered between the title and the grid — an introduction, usage notes, scope, … */
310
+ intro?: ReactNode;
311
+ /** Rendered after the grid. */
312
+ footer?: ReactNode;
313
+ columns?: 1 | 2;
314
+ /** Omit the section chrome (title/border) — the host provides its own. */
315
+ bare?: boolean;
316
+ className?: string;
317
+ entryClassName?: string;
318
+ /** Replace the default row renderer entirely. */
319
+ renderEntry?: (entry: GlossaryEntry) => ReactNode;
320
+ }): react.JSX.Element | null;
321
+
322
+ declare const TONE_SEGMENT_CLASS: Record<StepTone, string>;
323
+ declare const TONE_CHIP_CLASS: Record<StepTone, string>;
324
+ declare function CodeBlock({ lines, theme, className, }: {
325
+ lines: string[];
326
+ theme?: StepwiseTheme;
327
+ className?: string;
328
+ }): react.JSX.Element;
329
+ interface ScrubberLabels {
330
+ /** aria-label for the slider. */
331
+ scrubber?: string;
332
+ /** aria-valuetext for the current position. Receives the frame index. */
333
+ frame?: (i: number) => string;
334
+ }
335
+ declare function StepScrubber({ count, index, toneAt, titleAt, onSeek, emptyHint, className, theme, labels, groupOf, }: {
336
+ count: number;
337
+ index: number;
338
+ toneAt?: (i: number) => StepTone;
339
+ titleAt?: (i: number) => string;
340
+ onSeek: (i: number) => void;
341
+ emptyHint?: string;
342
+ className?: string;
343
+ theme?: StepwiseTheme;
344
+ labels?: ScrubberLabels;
345
+ /**
346
+ * Groups consecutive segments into one proportional wrapper — a chapter
347
+ * with sub-chapters spans the width of its whole subtree and only the
348
+ * active inner segment fills, so a chapter reads as "halfway" while its
349
+ * children play. Segments must be group-contiguous (flattenChapters order).
350
+ */
351
+ groupOf?: (i: number) => number;
352
+ }): react.JSX.Element;
353
+ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, }: {
354
+ player: {
355
+ total: number;
356
+ index: number;
357
+ playing: boolean;
358
+ atEnd: boolean;
359
+ next: (onEmpty?: () => void) => void;
360
+ prev: () => void;
361
+ toggle: (onEmpty?: () => void) => void;
362
+ seek: (frame: number) => void;
363
+ };
364
+ toneAt?: (i: number) => StepTone;
365
+ titleAt?: (i: number) => string;
366
+ leading?: ReactNode;
367
+ /** Fired when the user plays/steps with no timeline loaded. */
368
+ onEmpty?: () => void;
369
+ emptyHint?: string;
370
+ className?: string;
371
+ theme?: StepwiseTheme;
372
+ /**
373
+ * Forwarded to the scrubber — group consecutive segments into one
374
+ * proportional wrapper (see StepScrubber).
375
+ */
376
+ groupOf?: (i: number) => number;
377
+ labels?: {
378
+ previous?: string;
379
+ play?: string;
380
+ pause?: string;
381
+ replay?: string;
382
+ next?: string;
383
+ };
384
+ }): react.JSX.Element;
385
+ declare function ChapterNav({ chapters, active, onNavigate, hint, className, theme, }: {
386
+ chapters: Chapter[];
387
+ active: number;
388
+ onNavigate: (i: number) => void;
389
+ hint?: string;
390
+ className?: string;
391
+ theme?: StepwiseTheme;
392
+ }): react.JSX.Element;
393
+ declare function LessonPager({ prev, next, onNavigate, className, labels, }: {
394
+ prev?: {
395
+ label: string;
396
+ hint?: string;
397
+ };
398
+ next?: {
399
+ label: string;
400
+ hint?: string;
401
+ };
402
+ onNavigate: (dir: "prev" | "next") => void;
403
+ className?: string;
404
+ labels?: {
405
+ previous?: string;
406
+ next?: string;
407
+ };
408
+ }): react.JSX.Element;
409
+ declare function TourSpotlight({ target, theme, }: {
410
+ target: string | null;
411
+ theme?: StepwiseTheme;
412
+ }): react.JSX.Element | null;
413
+ declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, }: {
414
+ step: TourStepInput;
415
+ index: number;
416
+ total: number;
417
+ onNext: () => void;
418
+ onBack: () => void;
419
+ onEnd: () => void;
420
+ labels?: {
421
+ title?: string;
422
+ back?: string;
423
+ next?: string;
424
+ finish?: string;
425
+ end?: string;
426
+ };
427
+ className?: string;
428
+ theme?: StepwiseTheme;
429
+ /** Disable the primary (Next/Finish) button — pair with tour.blocked. */
430
+ nextDisabled?: boolean;
431
+ }): react.JSX.Element;
432
+ declare function ShortcutsDialog({ open, onClose, items, title, className, theme, }: {
433
+ open: boolean;
434
+ onClose: () => void;
435
+ items: ShortcutItem[];
436
+ title?: string;
437
+ className?: string;
438
+ theme?: StepwiseTheme;
439
+ }): react.ReactPortal | null;
440
+
441
+ export { 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, PlayerBar, PlayerEngine, ShortcutItem, ShortcutsDialog, type StepPlayer, type StepPlayerOptions, StepScrubber, StepTone, type StepsCore, type StepwiseTheme, TONE_CHIP_CLASS, TONE_SEGMENT_CLASS, Term, TourCard, TourDriver, TourSpotlight, TourStepInput, type UseStepsOptions, cookieStore, defaultTheme, flattenChapters, localStorageStore, memoryStore, useChapters, useDiscoveries, useFocusTrap, useGlobalKeys, useGuidedTour, usePrefersReducedMotion, useStepPlayer, useSteps };