@bendyline/squisq-react 1.4.0 → 1.4.2
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 +57 -23
- package/dist/index.d.ts +81 -21
- package/dist/index.js +461 -178
- package/dist/index.js.map +1 -1
- package/dist/squisq-player.css +1 -1
- package/dist/squisq-player.css.map +1 -1
- package/dist/squisq-player.global.js +49 -17
- package/dist/squisq-player.global.js.map +1 -1
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +2263 -0
- package/package.json +8 -5
- package/src/DocControlsSlideshow.tsx +13 -2
- package/src/DocPlayer.tsx +255 -58
- package/src/DocPlayerWithSidebar.tsx +21 -9
- package/src/LinearDocView.tsx +59 -10
- package/src/MarkdownRenderer.tsx +52 -35
- package/src/__tests__/DocPlayer.test.tsx +85 -6
- package/src/__tests__/DocPlayerStylesSentinel.test.tsx +41 -0
- package/src/__tests__/LinearDocView.test.tsx +53 -1
- package/src/__tests__/MarkdownRenderer.test.tsx +18 -0
- package/src/__tests__/useJsonViewTokens.test.ts +41 -0
- package/src/__tests__/useSlideSwipe.test.ts +81 -0
- package/src/hooks/{AudioProvider.ts → AudioController.ts} +3 -3
- package/src/hooks/index.ts +7 -2
- package/src/hooks/useAudioSync.ts +5 -4
- package/src/hooks/useSlideSwipe.ts +265 -0
- package/src/index.ts +1 -1
- package/src/jsonView/useJsonViewTokens.ts +6 -31
- package/src/standalone-entry.tsx +1 -1
- package/src/styles/doc-animations.css +46 -0
- package/src/types.ts +6 -0
package/README.md
CHANGED
|
@@ -22,37 +22,54 @@ import { DocPlayer } from '@bendyline/squisq-react';
|
|
|
22
22
|
import '@bendyline/squisq-react/styles';
|
|
23
23
|
|
|
24
24
|
function App() {
|
|
25
|
-
return <DocPlayer
|
|
25
|
+
return <DocPlayer markdown={'# Hello\n\nWelcome to Squisq.'} />;
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
`DocPlayer` accepts **either** raw `markdown` (parsed and converted internally)
|
|
30
|
+
**or** a parsed `doc`; if you already have a `Doc`, pass `<DocPlayer doc={doc} />`.
|
|
31
|
+
`basePath` is optional (default `'.'`) and is the base URL for resolving relative
|
|
32
|
+
media paths. With neither `markdown` nor `doc`, the player renders a themed empty
|
|
33
|
+
state rather than crashing.
|
|
34
|
+
|
|
35
|
+
> **v1.5 breaking changes:** the old `script` prop is now `doc`, and the
|
|
36
|
+
> `audioProvider` prop is now `audioController` (its type `AudioProvider` was
|
|
37
|
+
> renamed to `AudioController`). `LinearDocView` gained the same `markdown` prop.
|
|
38
|
+
|
|
29
39
|
## Components
|
|
30
40
|
|
|
31
|
-
| Component | Description
|
|
32
|
-
| ---------------------- |
|
|
33
|
-
| `DocPlayer` | Main document player with timed playback, audio sync, and controls
|
|
34
|
-
| `LinearDocView` | Scroll-based linear rendering of all blocks
|
|
35
|
-
| `BlockRenderer` | SVG-based renderer for a single block
|
|
36
|
-
| `MarkdownRenderer` | Renders Squisq markdown as a visual document
|
|
37
|
-
| `DocPlayerWithSidebar` | DocPlayer with a sidebar navigation panel
|
|
38
|
-
| `CaptionOverlay` | Timed caption/subtitle overlay
|
|
39
|
-
| `DocProgressBar` | Playback progress indicator
|
|
40
|
-
| `DocControlsOverlay` | Floating playback controls
|
|
41
|
-
| `DocControlsBottom` | Bottom-bar playback controls
|
|
42
|
-
| `DocControlsSidebar` | Sidebar navigation controls
|
|
43
|
-
| `DocControlsSlideshow` | Slideshow-style navigation controls
|
|
41
|
+
| Component | Description |
|
|
42
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
43
|
+
| `DocPlayer` | Main document player with timed playback, audio sync, and controls |
|
|
44
|
+
| `LinearDocView` | Scroll-based linear rendering of all blocks |
|
|
45
|
+
| `BlockRenderer` | SVG-based renderer for a single block |
|
|
46
|
+
| `MarkdownRenderer` | Renders Squisq markdown as a visual document |
|
|
47
|
+
| `DocPlayerWithSidebar` | DocPlayer with a sidebar navigation panel |
|
|
48
|
+
| `CaptionOverlay` | Timed caption/subtitle overlay |
|
|
49
|
+
| `DocProgressBar` | Playback progress indicator |
|
|
50
|
+
| `DocControlsOverlay` | Floating playback controls |
|
|
51
|
+
| `DocControlsBottom` | Bottom-bar playback controls |
|
|
52
|
+
| `DocControlsSidebar` | Sidebar navigation controls |
|
|
53
|
+
| `DocControlsSlideshow` | Slideshow-style navigation controls |
|
|
54
|
+
| `SocialCaptionOverlay` | Large centered word-by-word (TikTok/Reels-style) captions |
|
|
55
|
+
| `InlineVideoPlayer` | Native `<video>` wrapper resolving `src`/`poster` via MediaContext |
|
|
56
|
+
| `InlineAudioPlayer` | Native `<audio>` wrapper resolving `src` via MediaContext |
|
|
57
|
+
| `MediaClipLayer` | Hidden `<audio>`/`<video>` elements for timed media clips |
|
|
58
|
+
| `JsonView` | Read-only viewer for JSON values bound to a Squisq-annotated schema |
|
|
44
59
|
|
|
45
60
|
## Layers
|
|
46
61
|
|
|
47
62
|
Blocks are composed of typed layers rendered as SVG:
|
|
48
63
|
|
|
49
|
-
| Layer | Description
|
|
50
|
-
| ------------ |
|
|
51
|
-
| `ImageLayer` | Background and foreground images
|
|
52
|
-
| `TextLayer` | Styled text with positioning and animation
|
|
53
|
-
| `ShapeLayer` | SVG shapes (rectangles, circles, lines)
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
64
|
+
| Layer | Description |
|
|
65
|
+
| ------------ | --------------------------------------------- |
|
|
66
|
+
| `ImageLayer` | Background and foreground images |
|
|
67
|
+
| `TextLayer` | Styled text with positioning and animation |
|
|
68
|
+
| `ShapeLayer` | SVG shapes (rectangles, circles, lines) |
|
|
69
|
+
| `PathLayer` | Freeform SVG path drawing |
|
|
70
|
+
| `VideoLayer` | Embedded video with playback sync |
|
|
71
|
+
| `TableLayer` | HTML table embedded via SVG `<foreignObject>` |
|
|
72
|
+
| `MapLayer` | Tile-based map rendering |
|
|
56
73
|
|
|
57
74
|
## Hooks
|
|
58
75
|
|
|
@@ -60,21 +77,33 @@ Blocks are composed of typed layers rendered as SVG:
|
|
|
60
77
|
| ---------------------------------- | ------------------------------------------------------------------ |
|
|
61
78
|
| `useDocPlayback` | Core playback state machine — timing, block transitions, scripting |
|
|
62
79
|
| `useAudioSync` | Synchronizes audio playback with doc timeline |
|
|
80
|
+
| `useMediaSchedule` | Resolves which timed media clips are active at the current time |
|
|
63
81
|
| `useViewportOrientation` | Tracks viewport orientation for responsive layouts |
|
|
82
|
+
| `useAutoSurface` | Live light/dark surface detection via `prefers-color-scheme` |
|
|
64
83
|
| `useMediaProvider` / `useMediaUrl` | Media URL resolution via `MediaContext` |
|
|
65
84
|
|
|
66
85
|
## Standalone Player
|
|
67
86
|
|
|
68
|
-
A self-contained global build is available for non-React environments
|
|
87
|
+
A self-contained global build is available for non-React environments. It
|
|
88
|
+
exposes a `SquisqPlayer` global with `mount`, `mountStatic`, `unmount`, and
|
|
89
|
+
`version`:
|
|
69
90
|
|
|
70
91
|
```html
|
|
71
92
|
<script src="https://unpkg.com/@bendyline/squisq-react/dist/squisq-player.global.js"></script>
|
|
72
93
|
<div id="player"></div>
|
|
73
94
|
<script>
|
|
74
|
-
|
|
95
|
+
// docJson is a Doc (e.g. produced by markdownToDoc and serialized)
|
|
96
|
+
SquisqPlayer.mount(document.getElementById('player'), docJson, {
|
|
97
|
+
mode: 'slideshow', // or 'static' for a scrollable document view
|
|
98
|
+
basePath: '/',
|
|
99
|
+
});
|
|
75
100
|
</script>
|
|
76
101
|
```
|
|
77
102
|
|
|
103
|
+
For build-time embedding, `@bendyline/squisq-react/standalone-source` exports
|
|
104
|
+
the same bundle as a string constant (`PLAYER_BUNDLE`) — used by
|
|
105
|
+
`@bendyline/squisq-formats` and the CLI to produce single-file HTML exports.
|
|
106
|
+
|
|
78
107
|
## Styles
|
|
79
108
|
|
|
80
109
|
Import the animation CSS for block transitions:
|
|
@@ -83,6 +112,11 @@ Import the animation CSS for block transitions:
|
|
|
83
112
|
import '@bendyline/squisq-react/styles';
|
|
84
113
|
```
|
|
85
114
|
|
|
115
|
+
## Full API Reference
|
|
116
|
+
|
|
117
|
+
See [docs/API.md](https://github.com/bendyline/squisq/blob/main/docs/API.md)
|
|
118
|
+
for the complete prop tables, hook signatures, and types.
|
|
119
|
+
|
|
86
120
|
## Related Packages
|
|
87
121
|
|
|
88
122
|
| Package | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { RefObject } from 'react';
|
|
|
8
8
|
import { SquisqAnnotatedSchema } from '@bendyline/squisq/jsonForm';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* AudioController - Abstraction for audio playback in DocPlayer
|
|
12
12
|
*
|
|
13
13
|
* This module defines an interface for audio playback operations that can have
|
|
14
14
|
* different implementations depending on the runtime environment:
|
|
@@ -52,7 +52,7 @@ interface AudioActions {
|
|
|
52
52
|
/** Restart from beginning */
|
|
53
53
|
restart: () => Promise<void>;
|
|
54
54
|
}
|
|
55
|
-
type
|
|
55
|
+
type AudioController = AudioState & AudioActions;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Doc Player Control Types
|
|
@@ -131,6 +131,12 @@ interface PlaybackState$1 {
|
|
|
131
131
|
currentSegmentName: string | null;
|
|
132
132
|
/** Current block data (for extracting image info, etc.) */
|
|
133
133
|
currentBlock: Block | null;
|
|
134
|
+
/** Optional display label for non-block slides, e.g. the managed cover. */
|
|
135
|
+
currentSlideLabel?: string;
|
|
136
|
+
/** Optional human-facing slide number, separate from internal nav index. */
|
|
137
|
+
currentSlideNumber?: number;
|
|
138
|
+
/** Optional human-facing slide total, separate from internal nav total. */
|
|
139
|
+
totalSlideNumber?: number;
|
|
134
140
|
}
|
|
135
141
|
/** Playback actions exposed to external control components */
|
|
136
142
|
interface PlaybackActions$1 {
|
|
@@ -209,10 +215,20 @@ type SquisqWindow = Window & typeof globalThis & Partial<SquisqRenderAPI>;
|
|
|
209
215
|
declare function formatTime(seconds: number): string;
|
|
210
216
|
|
|
211
217
|
interface DocPlayerProps {
|
|
212
|
-
/**
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
/**
|
|
219
|
+
* The Doc to play. Wins over `markdown` when both are provided.
|
|
220
|
+
* When neither `doc` nor `markdown` is given, the player renders a
|
|
221
|
+
* minimal themed empty state instead of crashing.
|
|
222
|
+
*/
|
|
223
|
+
doc?: Doc;
|
|
224
|
+
/**
|
|
225
|
+
* Markdown source to play. When `doc` is absent, the markdown is parsed
|
|
226
|
+
* and converted to a Doc via `markdownToDoc(parseMarkdown(markdown))`.
|
|
227
|
+
* Ignored when `doc` is provided.
|
|
228
|
+
*/
|
|
229
|
+
markdown?: string;
|
|
230
|
+
/** Base path for resolving media URLs (default: `'.'`) */
|
|
231
|
+
basePath?: string;
|
|
216
232
|
/** Render mode for video capture (hides controls, exposes seekTo) */
|
|
217
233
|
renderMode?: boolean;
|
|
218
234
|
/** Auto-play when loaded */
|
|
@@ -221,8 +237,8 @@ interface DocPlayerProps {
|
|
|
221
237
|
onEnded?: () => void;
|
|
222
238
|
/** Callback for time updates */
|
|
223
239
|
onTimeUpdate?: (time: number) => void;
|
|
224
|
-
/** Optional audio
|
|
225
|
-
|
|
240
|
+
/** Optional audio controller (if not provided, uses default HTML5 audio) */
|
|
241
|
+
audioController?: AudioController;
|
|
226
242
|
/** Show built-in controls (default: true). Set to false for custom controls. */
|
|
227
243
|
showControls?: boolean;
|
|
228
244
|
/** Show only the progress bar/scrubber at bottom (no other controls).
|
|
@@ -270,11 +286,29 @@ interface DocPlayerProps {
|
|
|
270
286
|
* template-annotated sections as inline SVG cards. No audio, no timeline.
|
|
271
287
|
*/
|
|
272
288
|
displayMode?: DisplayMode;
|
|
289
|
+
/**
|
|
290
|
+
* Whether to synthesize and show the managed cover slide from
|
|
291
|
+
* `doc.startBlock`. Defaults to true for existing documents.
|
|
292
|
+
*/
|
|
293
|
+
showCoverSlide?: boolean;
|
|
273
294
|
/** Caption display style (default: 'standard').
|
|
274
295
|
* 'social' shows large centered words with the active word highlighted. */
|
|
275
296
|
captionStyle?: CaptionStyle;
|
|
297
|
+
/**
|
|
298
|
+
* Enable drag-to-swipe slide navigation in slideshow mode (default: true).
|
|
299
|
+
* When enabled, press-and-drag on a slide advances/rewinds on release past a
|
|
300
|
+
* threshold (or a quick flick), and snaps back otherwise. Only applies when
|
|
301
|
+
* `displayMode === 'slideshow'` and not in render/headless mode.
|
|
302
|
+
*/
|
|
303
|
+
enableSwipe?: boolean;
|
|
276
304
|
}
|
|
277
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Front-door component: resolves the `doc` / `markdown` props into a Doc
|
|
307
|
+
* and renders a themed empty state when neither is provided. The playback
|
|
308
|
+
* machinery lives in `DocPlayerContent` so its hook order never changes
|
|
309
|
+
* when a doc appears or disappears.
|
|
310
|
+
*/
|
|
311
|
+
declare function DocPlayer(props: DocPlayerProps): react_jsx_runtime.JSX.Element;
|
|
278
312
|
|
|
279
313
|
/** Default viewport dimensions (1080p landscape) - for backwards compatibility */
|
|
280
314
|
declare const VIEWPORT: {
|
|
@@ -364,12 +398,15 @@ interface DocControlsSlideshowProps {
|
|
|
364
398
|
declare function DocControlsSlideshow({ state, slideNav }: DocControlsSlideshowProps): react_jsx_runtime.JSX.Element;
|
|
365
399
|
|
|
366
400
|
interface DocPlayerWithSidebarProps {
|
|
367
|
-
|
|
368
|
-
|
|
401
|
+
/** The Doc to play */
|
|
402
|
+
doc: Doc;
|
|
403
|
+
/** Base path for resolving media URLs (default: `'.'`) */
|
|
404
|
+
basePath?: string;
|
|
369
405
|
autoPlay?: boolean;
|
|
370
406
|
onEnded?: () => void;
|
|
371
407
|
onTimeUpdate?: (time: number) => void;
|
|
372
|
-
|
|
408
|
+
/** Optional audio controller (if not provided, uses default HTML5 audio) */
|
|
409
|
+
audioController?: AudioController;
|
|
373
410
|
muted?: boolean;
|
|
374
411
|
captionsEnabled?: boolean;
|
|
375
412
|
isFullscreen?: boolean;
|
|
@@ -378,8 +415,15 @@ interface DocPlayerWithSidebarProps {
|
|
|
378
415
|
forceViewport?: ViewportConfig$1;
|
|
379
416
|
/** Called when playing state changes */
|
|
380
417
|
onPlayingChange?: (isPlaying: boolean) => void;
|
|
418
|
+
/**
|
|
419
|
+
* Theme for rendering. Forwarded to the inner DocPlayer so the sidebar
|
|
420
|
+
* (portrait) layout matches the default (landscape) layout — without it the
|
|
421
|
+
* inner player falls back to DEFAULT_THEME, whose dark text is unreadable
|
|
422
|
+
* over a hero cover image.
|
|
423
|
+
*/
|
|
424
|
+
theme?: Theme;
|
|
381
425
|
}
|
|
382
|
-
declare function DocPlayerWithSidebar({
|
|
426
|
+
declare function DocPlayerWithSidebar({ doc, basePath, autoPlay, onEnded, onTimeUpdate, audioController, muted, captionsEnabled, isFullscreen, onFullscreenToggle, forceViewport, onPlayingChange, theme, }: DocPlayerWithSidebarProps): react_jsx_runtime.JSX.Element;
|
|
383
427
|
|
|
384
428
|
interface DocProgressBarProps {
|
|
385
429
|
state: PlaybackState$1;
|
|
@@ -402,6 +446,12 @@ interface MarkdownRendererProps {
|
|
|
402
446
|
* event handlers, and executable URL schemes before rendering.
|
|
403
447
|
*/
|
|
404
448
|
htmlPolicy?: HtmlPolicy;
|
|
449
|
+
/**
|
|
450
|
+
* Extra URL schemes to allow on links (e.g. a host app's internal
|
|
451
|
+
* navigation scheme it intercepts on click). Executable schemes are
|
|
452
|
+
* never allowed regardless. See {@link SanitizeUrlOptions}.
|
|
453
|
+
*/
|
|
454
|
+
linkSchemes?: readonly string[];
|
|
405
455
|
}
|
|
406
456
|
/**
|
|
407
457
|
* Renders MarkdownBlockNode[] AST as React HTML elements.
|
|
@@ -411,11 +461,20 @@ interface MarkdownRendererProps {
|
|
|
411
461
|
* <MarkdownRenderer nodes={block.contents} />
|
|
412
462
|
* ```
|
|
413
463
|
*/
|
|
414
|
-
declare function MarkdownRenderer({ nodes, className, htmlPolicy, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
464
|
+
declare function MarkdownRenderer({ nodes, className, htmlPolicy, linkSchemes, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
415
465
|
|
|
416
466
|
interface LinearDocViewProps {
|
|
417
|
-
/**
|
|
418
|
-
|
|
467
|
+
/**
|
|
468
|
+
* The Doc to render. Wins over `markdown` when both are provided.
|
|
469
|
+
* When neither `doc` nor `markdown` is given, an empty container renders.
|
|
470
|
+
*/
|
|
471
|
+
doc?: Doc;
|
|
472
|
+
/**
|
|
473
|
+
* Markdown source to render. When `doc` is absent, the markdown is parsed
|
|
474
|
+
* and converted to a Doc via `markdownToDoc(parseMarkdown(markdown))`.
|
|
475
|
+
* Ignored when `doc` is provided.
|
|
476
|
+
*/
|
|
477
|
+
markdown?: string;
|
|
419
478
|
/** Base path for resolving media URLs (images, etc.) */
|
|
420
479
|
basePath?: string;
|
|
421
480
|
/** Viewport config for SVG card rendering (default: landscape) */
|
|
@@ -463,7 +522,7 @@ type ImageDisplayMode = 'inline' | 'thumbnail';
|
|
|
463
522
|
* <LinearDocView doc={doc} basePath="/media/" />
|
|
464
523
|
* ```
|
|
465
524
|
*/
|
|
466
|
-
declare function LinearDocView({ doc, basePath, viewport, className, theme, surface, thinMargins, imageDisplayMode, }: LinearDocViewProps): react_jsx_runtime.JSX.Element;
|
|
525
|
+
declare function LinearDocView({ doc, markdown, basePath, viewport, className, theme, surface, thinMargins, imageDisplayMode, }: LinearDocViewProps): react_jsx_runtime.JSX.Element;
|
|
467
526
|
|
|
468
527
|
interface InlineVideoPlayerProps {
|
|
469
528
|
/** Source path — resolved through MediaContext when relative. */
|
|
@@ -613,11 +672,12 @@ declare function MediaClipLayer({ schedule, currentTime, isPlaying, basePath, re
|
|
|
613
672
|
* Handles multiple audio segments (MP3 files) by tracking which segment
|
|
614
673
|
* is currently playing and calculating the overall timeline position.
|
|
615
674
|
*
|
|
616
|
-
* This is the HTML5 Audio implementation of the
|
|
617
|
-
*
|
|
675
|
+
* This is the HTML5 Audio implementation of the AudioController interface.
|
|
676
|
+
* Hosts that drive audio through an external player (e.g. a native shell)
|
|
677
|
+
* can supply their own AudioController to DocPlayer instead of this hook.
|
|
618
678
|
*/
|
|
619
679
|
|
|
620
|
-
declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>, audioTrack: AudioTrack | undefined, basePath?: string):
|
|
680
|
+
declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>, audioTrack: AudioTrack | undefined, basePath?: string): AudioController;
|
|
621
681
|
|
|
622
682
|
/**
|
|
623
683
|
* useMediaSchedule
|
|
@@ -763,4 +823,4 @@ interface JsonViewProps {
|
|
|
763
823
|
}
|
|
764
824
|
declare function JsonView(props: JsonViewProps): react_jsx_runtime.JSX.Element;
|
|
765
825
|
|
|
766
|
-
export { type AudioActions, type
|
|
826
|
+
export { type AudioActions, type AudioController, type AudioState, type BlockMarker, BlockRenderer, type CaptionMode, CaptionOverlay, type CaptionStyle, type ControlsLayout, type DisplayMode, DocControlsBottom, DocControlsOverlay, DocControlsSidebar, DocControlsSlideshow, DocPlayer, DocPlayerWithSidebar, DocProgressBar, type ImageDisplayMode, ImageLayer, InlineAudioPlayer, type InlineAudioPlayerProps, InlineVideoPlayer, type InlineVideoPlayerProps, JsonView, type JsonViewProps, LinearDocView, type LinearDocViewProps, MapLayer, MarkdownRenderer, MediaClipLayer, type MediaClipLayerProps, MediaContext, type MediaScheduleController, PathLayer, type PlaybackActions$1 as PlaybackActions, type PlaybackState$1 as PlaybackState, type RenderAudioSegmentInfo, type RenderBlockInfo, type RenderCaptionInfo, type RenderChapterInfo, ShapeLayer, type SlideNavActions, SocialCaptionOverlay, type SquisqRenderAPI, type SquisqWindow, TableLayer, TextLayer, VIEWPORT, VideoLayer, formatTime, useAudioSync, useAutoSurface, useDocPlayback, useMediaProvider, useMediaSchedule, useMediaUrl, useViewportOrientation };
|