@bendyline/squisq-react 2.2.0 → 2.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.
@@ -0,0 +1,410 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Block, Doc, Theme, SurfaceScheme, Transition, CaptionTrack, ViewportConfig as ViewportConfig$1, ScheduledClip } from '@bendyline/squisq/schemas';
3
+ import { ViewportConfig } from '@bendyline/squisq/doc';
4
+ import { a as AudioController } from '../AudioController-DwMsPe38.js';
5
+
6
+ /**
7
+ * Doc Player Control Types
8
+ *
9
+ * Shared type definitions for doc player controls across different
10
+ * layout modes (overlay, sidebar, bottom). These interfaces allow
11
+ * control components to be decoupled from the core DocPlayer.
12
+ *
13
+ * Related Files:
14
+ * - DocPlayer.tsx — Core player component
15
+ * - DocProgressBar.tsx — Extracted progress bar
16
+ * - DocControlsOverlay.tsx — Overlay controls (default)
17
+ * - DocControlsSidebar.tsx — Vertical sidebar controls
18
+ * - DocControlsBottom.tsx — Horizontal bottom strip controls
19
+ */
20
+
21
+ /** Layout mode for doc player controls */
22
+ type ControlsLayout = 'overlay' | 'sidebar' | 'bottom';
23
+ /**
24
+ * Display mode for DocPlayer.
25
+ *
26
+ * - `'video'` — Traditional video-style playback with play/pause, scrub bar,
27
+ * and time-based auto-advance. Default mode.
28
+ * - `'slideshow'` — PowerPoint-style presentation with prev/next navigation.
29
+ * Blocks are treated as static slides that only change on user click.
30
+ * No auto-advance, no scrub bar.
31
+ * - `'linear'` — Long-scrolling document view. Renders the full markdown as
32
+ * readable HTML with template-annotated sections shown as inline SVG cards.
33
+ * No audio, no timeline, no controls — just a scrollable page.
34
+ * - `'page'` — Plain semantic HTML preview matching what the
35
+ * `markdownDocToPlainHtml` export produces. No SquisqPlayer, no SVG
36
+ * cards — just `<h1>`/`<p>`/`<ul>` etc. inside a sandboxed iframe.
37
+ * Use when you want a WYSIWYG view of the simple HTML export.
38
+ * - `'narrate'` — Teleprompter/performance surface. DocPlayer does not
39
+ * implement this mode; it is owned by the editor package
40
+ * (`@bendyline/squisq-editor-react`), which renders its own
41
+ * voice-paced teleprompter view for it.
42
+ */
43
+ type DisplayMode = 'video' | 'slideshow' | 'linear' | 'page' | 'narrate';
44
+ /**
45
+ * Caption display style.
46
+ *
47
+ * - `'standard'` — Traditional broadcast-style captions: small white text
48
+ * on a semi-transparent black badge at the top of the player.
49
+ * - `'social'` — Social media-style (Instagram/TikTok): large centered words
50
+ * showing 3-5 words at a time with the active word highlighted in the
51
+ * theme's primary color. Font and colors pulled from the active theme.
52
+ */
53
+ type CaptionStyle = 'standard' | 'social';
54
+ /**
55
+ * Caption display mode — combines enable/disable with style selection.
56
+ * The CC button cycles through: off → standard → social → off.
57
+ */
58
+ type CaptionMode = 'off' | 'standard' | 'social';
59
+ /** Slide navigation actions for slideshow display mode */
60
+ interface SlideNavActions {
61
+ /** Navigate to the next slide */
62
+ nextSlide: () => void;
63
+ /** Navigate to the previous slide */
64
+ prevSlide: () => void;
65
+ /** Navigate to a specific slide by index (0-based) */
66
+ goToSlide: (index: number) => void;
67
+ }
68
+ /** Playback state exposed to external control components */
69
+ interface PlaybackState {
70
+ isPlaying: boolean;
71
+ currentTime: number;
72
+ totalDuration: number;
73
+ /** Whether the managed cover is the visual currently shown. */
74
+ isCoverVisible?: boolean;
75
+ currentBlockIndex: number;
76
+ totalBlocks: number;
77
+ docProgress: number;
78
+ hasCaptions: boolean;
79
+ captionsEnabled: boolean;
80
+ /** Current caption display mode (off, standard, social). */
81
+ captionMode: CaptionMode;
82
+ isFullscreen?: boolean;
83
+ /** Current audio segment index (0-based) */
84
+ currentSegmentIndex: number;
85
+ /** Current audio segment name (e.g., 'intro', 'history') */
86
+ currentSegmentName: string | null;
87
+ /** Current block data (for extracting image info, etc.) */
88
+ currentBlock: Block | null;
89
+ /** Optional display label for non-block slides, e.g. the managed cover. */
90
+ currentSlideLabel?: string;
91
+ /** Optional human-facing slide number, separate from internal nav index. */
92
+ currentSlideNumber?: number;
93
+ /** Optional human-facing slide total, separate from internal nav total. */
94
+ totalSlideNumber?: number;
95
+ }
96
+ /** Playback actions exposed to external control components */
97
+ interface PlaybackActions {
98
+ toggle: () => void;
99
+ restart: () => void;
100
+ seekTo: (time: number) => void;
101
+ setCaptionsEnabled: (enabled: boolean) => void;
102
+ /** Cycle caption mode: off → standard → social → off */
103
+ cycleCaptionMode: () => void;
104
+ toggleFullscreen?: () => void;
105
+ }
106
+ /** Block marker data for the progress bar */
107
+ interface BlockMarker {
108
+ block: Block;
109
+ index: number;
110
+ position: number;
111
+ title: string;
112
+ /** True if this block is the first in a new audio segment (section boundary) */
113
+ isSectionStart: boolean;
114
+ }
115
+ /** Block metadata returned by SquisqRenderAPI.getBlocks() */
116
+ interface RenderBlockInfo {
117
+ id: string;
118
+ template: string;
119
+ startTime: number;
120
+ duration: number;
121
+ }
122
+ /** Audio segment info returned by SquisqRenderAPI.getAudioSegments() */
123
+ interface RenderAudioSegmentInfo {
124
+ src: string;
125
+ name: string;
126
+ duration: number;
127
+ startTime: number;
128
+ }
129
+ /** Caption phrase info returned by SquisqRenderAPI.getCaptions() */
130
+ interface RenderCaptionInfo {
131
+ text: string;
132
+ startTime: number;
133
+ endTime: number;
134
+ }
135
+ /** Chapter marker returned by SquisqRenderAPI.getChapters() */
136
+ interface RenderChapterInfo {
137
+ title: string;
138
+ startTime: number;
139
+ duration: number;
140
+ }
141
+ /**
142
+ * Instance-scoped API created in render mode and debug mode.
143
+ * React hosts receive it via `DocPlayer.onRenderAPIReady`; standalone hosts
144
+ * receive it from their mount handle.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const handle = SquisqPlayer.getHandle(rootElement);
149
+ * const api = await handle?.renderAPI;
150
+ * await api?.seekTo(5.0);
151
+ * ```
152
+ */
153
+ interface SquisqRenderAPI {
154
+ seekTo: (time: number) => Promise<void>;
155
+ getDuration: () => number;
156
+ getBlocks: () => RenderBlockInfo[];
157
+ getAudioSegments: () => RenderAudioSegmentInfo[];
158
+ getCaptions: () => RenderCaptionInfo[];
159
+ getChapters: () => RenderChapterInfo[];
160
+ showCover: () => Promise<void>;
161
+ hideCover: () => Promise<void>;
162
+ hasCoverBlock: () => boolean;
163
+ }
164
+ /** Format time in seconds to MM:SS string */
165
+ declare function formatTime(seconds: number): string;
166
+
167
+ interface DocPlayerProps {
168
+ /** The Doc to play. Wins over `markdown` when both are provided. */
169
+ doc?: Doc;
170
+ /** Markdown source to convert when `doc` is absent. */
171
+ markdown?: string;
172
+ /** Base path for resolving media URLs (default: `'.'`). */
173
+ basePath?: string;
174
+ /** Render mode for deterministic video capture. */
175
+ renderMode?: boolean;
176
+ /** Render slide transitions and per-layer animations (default: true). */
177
+ animationsEnabled?: boolean;
178
+ /** Receives the instance-scoped render API, and `null` on cleanup. */
179
+ onRenderAPIReady?: (api: SquisqRenderAPI | null) => void;
180
+ autoPlay?: boolean;
181
+ onEnded?: () => void;
182
+ onTimeUpdate?: (time: number) => void;
183
+ /** Optional host-owned audio controller. */
184
+ audioController?: AudioController;
185
+ /** Explicit synthetic clock for timed documents that intentionally have no audio asset. */
186
+ audioMode?: 'media' | 'synthetic';
187
+ showControls?: boolean;
188
+ showScrubber?: boolean;
189
+ muted?: boolean;
190
+ captionsEnabled?: boolean;
191
+ onCaptionsToggle?: (enabled: boolean) => void;
192
+ onPlaybackStateChange?: (state: PlaybackState) => void;
193
+ onControlsReady?: (controls: PlaybackActions & {
194
+ play: () => void;
195
+ pause: () => void;
196
+ }) => void;
197
+ isFullscreen?: boolean;
198
+ onFullscreenToggle?: () => void;
199
+ onBlockMarkers?: (markers: BlockMarker[]) => void;
200
+ forceViewport?: ViewportConfig;
201
+ theme?: Theme;
202
+ surface?: SurfaceScheme | 'auto';
203
+ /** Video, manual slideshow, or long-scrolling linear rendition. */
204
+ displayMode?: DisplayMode;
205
+ showCoverSlide?: boolean;
206
+ coverVisible?: boolean;
207
+ captionStyle?: CaptionStyle;
208
+ enableSwipe?: boolean;
209
+ globalKeyboardShortcuts?: boolean;
210
+ }
211
+
212
+ /**
213
+ * Front-door component: resolves the `doc` / `markdown` props into a Doc
214
+ * and renders a themed empty state when neither is provided. The playback
215
+ * machinery lives in `DocPlayerContent` so its hook order never changes
216
+ * when a doc appears or disappears.
217
+ */
218
+ declare function DocPlayer(props: DocPlayerProps): react_jsx_runtime.JSX.Element;
219
+
220
+ /** Viewport configuration type */
221
+ interface ViewportDimensions {
222
+ width: number;
223
+ height: number;
224
+ }
225
+ interface BlockRendererProps {
226
+ /** The block to render */
227
+ block: Block;
228
+ /** Current time relative to block start (seconds) */
229
+ blockTime: number;
230
+ /** Base path for resolving media URLs */
231
+ basePath: string;
232
+ /** Whether this block is entering (for transition) */
233
+ isEntering?: boolean;
234
+ /** Whether this block is exiting (for transition) */
235
+ isExiting?: boolean;
236
+ /** Transition to apply. Defaults to block.transition. */
237
+ transition?: Transition;
238
+ /** Viewport dimensions (defaults to 1920x1080 landscape) */
239
+ viewport?: ViewportDimensions;
240
+ /** Whether the doc is currently playing (controls video playback) */
241
+ isPlaying?: boolean;
242
+ /**
243
+ * Whether to render block transitions and layer animations (default: true).
244
+ * Disabling this only removes authored/render-style motion; timed video
245
+ * layers continue to advance normally.
246
+ */
247
+ animationsEnabled?: boolean;
248
+ /** Resolved theme inherited by rich-media renderers such as Mermaid. */
249
+ theme?: Theme;
250
+ }
251
+ declare function BlockRenderer({ block, blockTime, basePath, isEntering, isExiting, transition, viewport, isPlaying, animationsEnabled, theme, }: BlockRendererProps): react_jsx_runtime.JSX.Element;
252
+
253
+ interface CaptionOverlayProps {
254
+ /** Caption track with timestamped phrases */
255
+ captions: CaptionTrack | undefined;
256
+ /** Current playback time in seconds */
257
+ currentTime: number;
258
+ /** Whether captions are enabled */
259
+ enabled?: boolean;
260
+ /** Font size in pixels for standard style (default: 16) */
261
+ fontSize?: number;
262
+ /** Caption display style (default: 'standard') */
263
+ captionStyle?: CaptionStyle;
264
+ /** Theme for social-style caption colors and fonts */
265
+ theme?: Theme;
266
+ /** Viewport config for social-style font scaling */
267
+ viewport?: ViewportConfig$1;
268
+ }
269
+ declare function CaptionOverlay({ captions, currentTime, enabled, fontSize, captionStyle, theme, viewport, }: CaptionOverlayProps): react_jsx_runtime.JSX.Element;
270
+
271
+ interface SocialCaptionOverlayProps {
272
+ captions: CaptionTrack | undefined;
273
+ currentTime: number;
274
+ enabled?: boolean;
275
+ theme?: Theme;
276
+ viewport?: ViewportConfig$1;
277
+ }
278
+ declare function SocialCaptionOverlay({ captions, currentTime, enabled, theme, viewport, }: SocialCaptionOverlayProps): react_jsx_runtime.JSX.Element | null;
279
+
280
+ interface DocControlsOverlayProps {
281
+ state: PlaybackState;
282
+ actions: PlaybackActions;
283
+ blockMarkers: BlockMarker[];
284
+ expandedBlocks: Block[];
285
+ getBlockTitle?: (block: Block) => string;
286
+ }
287
+ declare function DocControlsOverlay({ state, actions, blockMarkers, expandedBlocks, getBlockTitle, }: DocControlsOverlayProps): react_jsx_runtime.JSX.Element;
288
+
289
+ interface DocControlsBottomProps {
290
+ state: PlaybackState;
291
+ actions: PlaybackActions;
292
+ blockMarkers: BlockMarker[];
293
+ expandedBlocks: Block[];
294
+ getBlockTitle?: (block: Block) => string;
295
+ }
296
+ declare function DocControlsBottom({ state, actions, blockMarkers, expandedBlocks, getBlockTitle, }: DocControlsBottomProps): react_jsx_runtime.JSX.Element;
297
+
298
+ interface DocControlsSidebarProps {
299
+ state: PlaybackState;
300
+ actions: PlaybackActions;
301
+ }
302
+ declare function DocControlsSidebar({ state, actions }: DocControlsSidebarProps): react_jsx_runtime.JSX.Element;
303
+
304
+ interface SlideshowPickerItem {
305
+ /** Stable identifier for the slide/block. */
306
+ id: string;
307
+ /** Human-facing slide number, or a special label such as "Cover". */
308
+ label: string;
309
+ /** Short description shown in the slide picker. */
310
+ summary: string;
311
+ }
312
+ interface DocControlsSlideshowProps {
313
+ state: PlaybackState;
314
+ slideNav: SlideNavActions;
315
+ /** Slides available for direct navigation from the counter popover. */
316
+ slides?: readonly SlideshowPickerItem[];
317
+ /** Controlled open state for hosts that expose their own picker shortcut. */
318
+ pickerOpen?: boolean;
319
+ /** Called whenever the slide picker should open or close. */
320
+ onPickerOpenChange?: (open: boolean) => void;
321
+ }
322
+ declare function DocControlsSlideshow({ state, slideNav, slides, pickerOpen, onPickerOpenChange, }: DocControlsSlideshowProps): react_jsx_runtime.JSX.Element;
323
+
324
+ interface DocPlayerWithSidebarProps {
325
+ /** The Doc to play */
326
+ doc: Doc;
327
+ /** Base path for resolving media URLs (default: `'.'`) */
328
+ basePath?: string;
329
+ autoPlay?: boolean;
330
+ onEnded?: () => void;
331
+ onTimeUpdate?: (time: number) => void;
332
+ /** Optional audio controller (if not provided, uses default HTML5 audio) */
333
+ audioController?: AudioController;
334
+ /** Whether to render slide transitions and per-layer animations (default: true). */
335
+ animationsEnabled?: boolean;
336
+ muted?: boolean;
337
+ captionsEnabled?: boolean;
338
+ isFullscreen?: boolean;
339
+ onFullscreenToggle?: () => void;
340
+ /** Force a specific viewport preset, bypassing window-based orientation detection. */
341
+ forceViewport?: ViewportConfig$1;
342
+ /** Called when playing state changes */
343
+ onPlayingChange?: (isPlaying: boolean) => void;
344
+ /**
345
+ * Theme for rendering. Forwarded to the inner DocPlayer so the sidebar
346
+ * (portrait) layout matches the default (landscape) layout — without it the
347
+ * inner player falls back to DEFAULT_THEME, whose dark text is unreadable
348
+ * over a hero cover image.
349
+ */
350
+ theme?: Theme;
351
+ }
352
+ declare function DocPlayerWithSidebar({ doc, basePath, autoPlay, onEnded, onTimeUpdate, audioController, animationsEnabled, muted, captionsEnabled, isFullscreen, onFullscreenToggle, forceViewport, onPlayingChange, theme, }: DocPlayerWithSidebarProps): react_jsx_runtime.JSX.Element;
353
+
354
+ interface DocProgressBarProps {
355
+ state: PlaybackState;
356
+ actions: PlaybackActions;
357
+ blockMarkers: BlockMarker[];
358
+ /** All expanded blocks for hover lookup */
359
+ expandedBlocks: Block[];
360
+ /** Optional: get block title for hover tooltip */
361
+ getBlockTitle?: (block: Block) => string;
362
+ }
363
+ declare function DocProgressBar({ state, actions, blockMarkers, expandedBlocks, getBlockTitle, }: DocProgressBarProps): react_jsx_runtime.JSX.Element;
364
+
365
+ interface InlineVideoPlayerProps {
366
+ /** Source path — resolved through MediaContext when relative. */
367
+ src: string;
368
+ /** Base path used when no MediaProvider is in context. */
369
+ basePath?: string;
370
+ /** Optional explicit width (pixels or CSS length). */
371
+ width?: number | string;
372
+ /** Optional explicit height (pixels or CSS length). */
373
+ height?: number | string;
374
+ /** Optional poster image src — also resolved through MediaContext. */
375
+ poster?: string;
376
+ /** Whether to show native controls. Defaults to true. */
377
+ controls?: boolean;
378
+ /** `preload` attribute passthrough. Defaults to `'metadata'`. */
379
+ preload?: 'none' | 'metadata' | 'auto';
380
+ /** Extra className on the wrapper. */
381
+ className?: string;
382
+ }
383
+ declare function InlineVideoPlayer({ src, basePath, width, height, poster, controls, preload, className, }: InlineVideoPlayerProps): react_jsx_runtime.JSX.Element | null;
384
+
385
+ interface InlineAudioPlayerProps {
386
+ /** Source path — resolved through MediaContext when relative. */
387
+ src: string;
388
+ /** Base path used when no MediaProvider is in context. */
389
+ basePath?: string;
390
+ /** Whether to show native controls. Defaults to true. */
391
+ controls?: boolean;
392
+ /** `preload` attribute passthrough. Defaults to `'metadata'`. */
393
+ preload?: 'none' | 'metadata' | 'auto';
394
+ /** Extra className on the wrapper. */
395
+ className?: string;
396
+ }
397
+ declare function InlineAudioPlayer({ src, basePath, controls, preload, className, }: InlineAudioPlayerProps): react_jsx_runtime.JSX.Element | null;
398
+
399
+ interface MediaClipLayerProps {
400
+ schedule: ScheduledClip[];
401
+ currentTime: number;
402
+ isPlaying: boolean;
403
+ basePath: string;
404
+ renderMode?: boolean;
405
+ /** Silence every scheduled clip during live playback. */
406
+ muted?: boolean;
407
+ }
408
+ declare function MediaClipLayer({ schedule, currentTime, isPlaying, basePath, renderMode, muted, }: MediaClipLayerProps): react_jsx_runtime.JSX.Element | null;
409
+
410
+ export { type BlockMarker, BlockRenderer, type CaptionMode, CaptionOverlay, type CaptionStyle, type ControlsLayout, type DisplayMode, DocControlsBottom, DocControlsOverlay, DocControlsSidebar, DocControlsSlideshow, DocPlayer, type DocPlayerProps, DocPlayerWithSidebar, DocProgressBar, InlineAudioPlayer, type InlineAudioPlayerProps, InlineVideoPlayer, type InlineVideoPlayerProps, MediaClipLayer, type MediaClipLayerProps, type PlaybackActions, type PlaybackState, type RenderAudioSegmentInfo, type RenderBlockInfo, type RenderCaptionInfo, type RenderChapterInfo, type SlideNavActions, SocialCaptionOverlay, type SquisqRenderAPI, formatTime };
@@ -0,0 +1,41 @@
1
+ import {
2
+ CaptionOverlay,
3
+ DocControlsBottom,
4
+ DocControlsOverlay,
5
+ DocControlsSidebar,
6
+ DocControlsSlideshow,
7
+ DocPlayer,
8
+ DocPlayerWithSidebar,
9
+ DocProgressBar,
10
+ MediaClipLayer,
11
+ SocialCaptionOverlay,
12
+ formatTime
13
+ } from "../chunk-7FVQ7T3I.js";
14
+ import {
15
+ BlockRenderer
16
+ } from "../chunk-TQH6RTTV.js";
17
+ import "../chunk-XYS7HMP4.js";
18
+ import "../chunk-NWZQGZIJ.js";
19
+ import "../chunk-TT6ENR6T.js";
20
+ import {
21
+ InlineAudioPlayer,
22
+ InlineVideoPlayer
23
+ } from "../chunk-D7KN4CRG.js";
24
+ import "../chunk-WLUZTUNZ.js";
25
+ import "../chunk-LR3AIGDD.js";
26
+ export {
27
+ BlockRenderer,
28
+ CaptionOverlay,
29
+ DocControlsBottom,
30
+ DocControlsOverlay,
31
+ DocControlsSidebar,
32
+ DocControlsSlideshow,
33
+ DocPlayer,
34
+ DocPlayerWithSidebar,
35
+ DocProgressBar,
36
+ InlineAudioPlayer,
37
+ InlineVideoPlayer,
38
+ MediaClipLayer,
39
+ SocialCaptionOverlay,
40
+ formatTime
41
+ };