@bendyline/squisq-react 2.4.5 → 2.4.7

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/NOTICE.md CHANGED
@@ -6,12 +6,13 @@ Third-party components remain under their respective license terms.
6
6
 
7
7
  ## Runtime, peer, and bundled dependencies
8
8
 
9
- | Package | Version | License | Repository |
10
- | ------------------ | -------------------- | ------- | ------------------------------------- |
11
- | mermaid | 11.16.0 | MIT | https://github.com/mermaid-js/mermaid |
12
- | preact _(bundled)_ | 10.29.0 | MIT | https://github.com/preactjs/preact |
13
- | react _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
14
- | react-dom _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
9
+ | Package | Version | License | Repository |
10
+ | ----------------------------- | -------------------- | ----------------------------- | ------------------------------------------- |
11
+ | @fortawesome/fontawesome-free | 7.2.0 | CC-BY-4.0 AND OFL-1.1 AND MIT | https://github.com/FortAwesome/Font-Awesome |
12
+ | mermaid | 11.16.0 | MIT | https://github.com/mermaid-js/mermaid |
13
+ | preact _(bundled)_ | 10.29.0 | MIT | https://github.com/preactjs/preact |
14
+ | react _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
15
+ | react-dom _(peer)_ | ^18.0.0 \|\| ^19.0.0 | MIT | https://github.com/facebook/react |
15
16
 
16
17
  Mermaid is Copyright (c) 2014-2022 Knut Sveidqvist and is distributed under
17
18
  the MIT License. Preact is used by the standalone browser bundles. Exact
package/README.md CHANGED
@@ -91,9 +91,10 @@ useDocPlayback(doc, currentTime, { viewport, theme, onSeek });
91
91
 
92
92
  ## Standalone Player
93
93
 
94
- A self-contained global build is available for non-React environments. It
95
- exposes a `SquisqPlayer` global with `mount`, `getHandle`, `unmount`, and
96
- `version`. `mount` returns an instance handle.
94
+ A global build is available for non-React environments. It exposes a
95
+ `SquisqPlayer` global with `mount`, `getHandle`, `unmount`, and `version`.
96
+ `mount` returns an instance handle. Load the package stylesheet alongside the
97
+ raw global build so authored Font Awesome icons can use the shared webfonts.
97
98
 
98
99
  The default build is the light player and omits Mermaid's multi-megabyte parser
99
100
  and layout engines. Documents that contain Mermaid fences should use the full
@@ -108,6 +109,7 @@ top-level `window.seekTo` / `window.getDuration` properties.
108
109
  Interactive mount:
109
110
 
110
111
  ```html
112
+ <link rel="stylesheet" href="https://unpkg.com/@bendyline/squisq-react/dist/styles/index.css" />
111
113
  <script src="https://unpkg.com/@bendyline/squisq-react/dist/squisq-player.global.js"></script>
112
114
  <div id="player"></div>
113
115
  <script>
@@ -138,12 +140,13 @@ content. Embedded video, timed media, captions, audio, and document timing stay
138
140
  active; this makes the option suitable for compact MP4 and animated-GIF export.
139
141
 
140
142
  For build-time embedding, `@bendyline/squisq-react/standalone-source` exports
141
- the same bundle as a string constant (`PLAYER_BUNDLE`) used by
142
- `@bendyline/squisq-formats` and the CLI to produce single-file HTML exports.
143
+ the player as a self-contained string constant (`PLAYER_BUNDLE`) with the
144
+ shared icon styles composed once. It is used by `@bendyline/squisq-formats`
145
+ and the CLI to produce single-file HTML and rendered video exports.
143
146
 
144
147
  ## Styles
145
148
 
146
- Import the animation CSS for block transitions:
149
+ Import the package CSS for block transitions and Font Awesome inline icons:
147
150
 
148
151
  ```ts
149
152
  import '@bendyline/squisq-react/styles';
@@ -9,14 +9,14 @@ import {
9
9
  TreeLayer,
10
10
  VideoLayer,
11
11
  getTransitionClass
12
- } from "./chunk-TI5X7SBC.js";
12
+ } from "./chunk-MKWVJYUS.js";
13
13
  import {
14
14
  useAutoSurface
15
15
  } from "./chunk-TT6ENR6T.js";
16
16
  import {
17
17
  InlineVideoPlayer,
18
18
  MarkdownRenderer
19
- } from "./chunk-D7KN4CRG.js";
19
+ } from "./chunk-VMPQEUJH.js";
20
20
  import {
21
21
  useMediaUrl
22
22
  } from "./chunk-LR3AIGDD.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TT6ENR6T.js";
4
4
  import {
5
5
  MarkdownRenderer
6
- } from "./chunk-D7KN4CRG.js";
6
+ } from "./chunk-VMPQEUJH.js";
7
7
 
8
8
  // src/jsonView/useJsonViewTokens.ts
9
9
  import { useMemo } from "react";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BlockRenderer,
3
3
  LinearDocView
4
- } from "./chunk-EJMUO6VX.js";
4
+ } from "./chunk-3HYSWEKG.js";
5
5
  import {
6
6
  useAudioSync,
7
7
  useDocPlayback,
@@ -165,7 +165,7 @@ function MediaClipElement({
165
165
  return;
166
166
  }
167
167
  const target = Math.max(0, clip.sourceIn + (currentTime - clip.absoluteStart));
168
- if (renderMode || !isPlaying || Math.abs(el.currentTime - target) > DRIFT) {
168
+ if (el.dataset.captureSequential !== "true" && (renderMode || !isPlaying || Math.abs(el.currentTime - target) > DRIFT)) {
169
169
  try {
170
170
  el.currentTime = target;
171
171
  } catch {
@@ -175,7 +175,7 @@ function MediaClipElement({
175
175
  const p = el.play();
176
176
  if (p) p.catch(() => {
177
177
  });
178
- } else {
178
+ } else if (el.dataset.captureSequential !== "true") {
179
179
  el.pause();
180
180
  }
181
181
  }, [active, currentTime, isPlaying, renderMode, clip.sourceIn, clip.absoluteStart, src]);
@@ -1542,6 +1542,8 @@ function buildSegmentTitleMap(doc) {
1542
1542
  var DEFAULT_VIDEO_FRAME_TIMEOUT_MS = 2e3;
1543
1543
  var VIDEO_FRAME_READINESS_POLL_MS = 16;
1544
1544
  var VIDEO_TIME_TOLERANCE_SECONDS = 0.01;
1545
+ var MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS = 0.5;
1546
+ var SEQUENTIAL_CAPTURE_PLAYBACK_RATE = 1;
1545
1547
  function formatMediaTime(time) {
1546
1548
  return Number.isFinite(time) ? `${time.toFixed(3)}s` : String(time);
1547
1549
  }
@@ -1565,10 +1567,8 @@ function isVisiblyPresented(video) {
1565
1567
  }
1566
1568
  return true;
1567
1569
  }
1568
- function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
1570
+ function seekVideoByAssignment(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
1569
1571
  video.pause();
1570
- const alreadyReady = !video.seeking && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && isAtReachableMediaTime(video, targetTime);
1571
- if (alreadyReady) return Promise.resolve();
1572
1572
  return new Promise((resolve, reject) => {
1573
1573
  let settled = false;
1574
1574
  let videoFrameRequest = null;
@@ -1630,6 +1630,125 @@ function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIM
1630
1630
  }
1631
1631
  });
1632
1632
  }
1633
+ function advanceSequentialCaptureVideo(video, targetTime, timeoutMs) {
1634
+ return new Promise((resolve, reject) => {
1635
+ let settled = false;
1636
+ let timeout = null;
1637
+ const ownerDocument = video.ownerDocument;
1638
+ const isInactive = () => ownerDocument.visibilityState !== "visible";
1639
+ const clearWatchdog = () => {
1640
+ if (timeout === null) return;
1641
+ clearTimeout(timeout);
1642
+ timeout = null;
1643
+ };
1644
+ const cleanup = () => {
1645
+ clearWatchdog();
1646
+ clearInterval(readinessPoll);
1647
+ video.removeEventListener("timeupdate", check);
1648
+ video.removeEventListener("loadeddata", check);
1649
+ video.removeEventListener("canplay", check);
1650
+ video.removeEventListener("error", fail);
1651
+ ownerDocument.removeEventListener("visibilitychange", handleVisibilityChange);
1652
+ };
1653
+ const finish = () => {
1654
+ if (!settled) {
1655
+ settled = true;
1656
+ cleanup();
1657
+ resolve();
1658
+ }
1659
+ };
1660
+ const fallbackToSeek = () => {
1661
+ if (settled) return;
1662
+ settled = true;
1663
+ video.pause();
1664
+ cleanup();
1665
+ void seekVideoByAssignment(video, targetTime, timeoutMs).then(resolve, reject);
1666
+ };
1667
+ const fail = () => {
1668
+ if (settled) return;
1669
+ settled = true;
1670
+ video.pause();
1671
+ cleanup();
1672
+ reject(
1673
+ new Error(
1674
+ `Video frame did not advance to ${formatMediaTime(targetTime)} within ${timeoutMs}ms (currentTime=${formatMediaTime(video.currentTime)}, readyState=${video.readyState}).`
1675
+ )
1676
+ );
1677
+ };
1678
+ function check() {
1679
+ if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && video.currentTime >= reachableMediaTime(video, targetTime)) {
1680
+ video.pause();
1681
+ finish();
1682
+ }
1683
+ }
1684
+ const handlePlaybackFailure = () => {
1685
+ if (settled || isInactive()) return;
1686
+ fallbackToSeek();
1687
+ };
1688
+ const startPlayback = () => {
1689
+ if (settled || isInactive()) return;
1690
+ try {
1691
+ video.playbackRate = SEQUENTIAL_CAPTURE_PLAYBACK_RATE;
1692
+ if (video.paused) {
1693
+ void video.play().catch(handlePlaybackFailure);
1694
+ }
1695
+ } catch {
1696
+ handlePlaybackFailure();
1697
+ }
1698
+ };
1699
+ const armWatchdog = () => {
1700
+ clearWatchdog();
1701
+ if (settled || isInactive()) return;
1702
+ timeout = setTimeout(fail, timeoutMs);
1703
+ };
1704
+ const pauseWhileInactive = () => {
1705
+ video.pause();
1706
+ clearWatchdog();
1707
+ };
1708
+ const resumeWhenActive = () => {
1709
+ if (settled || isInactive()) return;
1710
+ check();
1711
+ if (settled) return;
1712
+ startPlayback();
1713
+ armWatchdog();
1714
+ };
1715
+ function handleVisibilityChange() {
1716
+ if (ownerDocument.visibilityState !== "visible") {
1717
+ pauseWhileInactive();
1718
+ return;
1719
+ }
1720
+ resumeWhenActive();
1721
+ }
1722
+ const readinessPoll = setInterval(check, VIDEO_FRAME_READINESS_POLL_MS);
1723
+ video.addEventListener("timeupdate", check);
1724
+ video.addEventListener("loadeddata", check);
1725
+ video.addEventListener("canplay", check);
1726
+ video.addEventListener("error", fail, { once: true });
1727
+ ownerDocument.addEventListener("visibilitychange", handleVisibilityChange);
1728
+ resumeWhenActive();
1729
+ });
1730
+ }
1731
+ function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIMEOUT_MS) {
1732
+ if (video.readyState >= HTMLMediaElement.HAVE_METADATA && video.videoWidth <= 0 && video.videoHeight <= 0) {
1733
+ video.pause();
1734
+ return Promise.resolve();
1735
+ }
1736
+ const reachableTargetTime = reachableMediaTime(video, targetTime);
1737
+ const step = reachableTargetTime - video.currentTime;
1738
+ if (video.dataset.captureSequential === "true") {
1739
+ if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && step <= VIDEO_TIME_TOLERANCE_SECONDS) {
1740
+ if (step < -VIDEO_TIME_TOLERANCE_SECONDS) video.pause();
1741
+ return Promise.resolve();
1742
+ }
1743
+ if (step > 0 && step <= MAX_SEQUENTIAL_CAPTURE_STEP_SECONDS && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
1744
+ return advanceSequentialCaptureVideo(video, targetTime, timeoutMs);
1745
+ }
1746
+ }
1747
+ video.pause();
1748
+ const alreadyReady = !video.seeking && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && isAtReachableMediaTime(video, targetTime);
1749
+ if (alreadyReady) return Promise.resolve();
1750
+ return seekVideoByAssignment(video, targetTime, timeoutMs);
1751
+ }
1633
1752
 
1634
1753
  // src/DocPlayer.tsx
1635
1754
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -1296,7 +1296,7 @@ function VideoLayer({
1296
1296
  const video = videoRef.current;
1297
1297
  if (!video || !hasStartedRef.current) return;
1298
1298
  const targetTime = gated ? content.clipStart : Math.min(content.clipEnd, content.clipStart + Math.max(0, blockTime - startAt));
1299
- if (Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
1299
+ if (video.dataset.captureSequential !== "true" && Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
1300
1300
  video.currentTime = targetTime;
1301
1301
  }
1302
1302
  if (gated) {
@@ -1313,7 +1313,7 @@ function VideoLayer({
1313
1313
  playPromise.catch(() => {
1314
1314
  });
1315
1315
  }
1316
- } else {
1316
+ } else if (video.dataset.captureSequential !== "true") {
1317
1317
  video.pause();
1318
1318
  }
1319
1319
  }, [isPlaying, gated, blockTime, startAt, src, content.clipStart, content.clipEnd]);
@@ -103,6 +103,16 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
103
103
  return /* @__PURE__ */ jsx3("br", {}, key);
104
104
  case "inlineMath":
105
105
  return /* @__PURE__ */ jsx3("code", { className: "squisq-md-inline-math", children: node.value }, key);
106
+ case "inlineIcon":
107
+ return /* @__PURE__ */ jsx3(
108
+ "i",
109
+ {
110
+ className: `fa-${node.family} fa-${node.name}`,
111
+ "data-icon": node.token,
112
+ "aria-hidden": "true"
113
+ },
114
+ key
115
+ );
106
116
  case "htmlInline":
107
117
  if (ctx.htmlPolicy === "strip") return null;
108
118
  return /* @__PURE__ */ jsx3("span", { className: "squisq-md-html-inline", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`, ctx) }, key);
package/dist/index.d.ts CHANGED
@@ -17,8 +17,9 @@ import '@bendyline/squisq/jsonForm';
17
17
  * Standalone Entry Point — IIFE bundle for self-contained HTML rendering.
18
18
  *
19
19
  * This file is the entry point for the standalone `squisq-player.iife.js` bundle.
20
- * It bundles Preact (via preact/compat), squisq core, and all rendering components
21
- * into a single self-contained script that can be loaded in any HTML page.
20
+ * It bundles Preact (via preact/compat), squisq core, rendering components, and
21
+ * animation CSS. Raw-script consumers load the package stylesheet for shared
22
+ * icon webfonts; standalone-source and the CLI compose those styles once.
22
23
  *
23
24
  * The bundle exposes a global `SquisqPlayer` object with methods to mount
24
25
  * interactive or static document views into any DOM element.
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  formatTime,
13
13
  resolveDocPlayerAppearance,
14
14
  useMediaClipDurations
15
- } from "./chunk-A4VLH2US.js";
15
+ } from "./chunk-KF4DTT46.js";
16
16
  import {
17
17
  BlockRenderer,
18
18
  CanvasSection,
@@ -20,7 +20,7 @@ import {
20
20
  PageSectionView,
21
21
  PageViewContext,
22
22
  usePageView
23
- } from "./chunk-EJMUO6VX.js";
23
+ } from "./chunk-3HYSWEKG.js";
24
24
  import {
25
25
  ImageLayer,
26
26
  MapLayer,
@@ -33,7 +33,7 @@ import {
33
33
  VideoLayer,
34
34
  getAnimationStyle,
35
35
  getTransitionClass
36
- } from "./chunk-TI5X7SBC.js";
36
+ } from "./chunk-MKWVJYUS.js";
37
37
  import {
38
38
  useModalDialog
39
39
  } from "./chunk-65POMKHR.js";
@@ -45,7 +45,7 @@ import {
45
45
  } from "./chunk-BOZJ655L.js";
46
46
  import {
47
47
  JsonView
48
- } from "./chunk-NANF7REM.js";
48
+ } from "./chunk-DJRYQQXB.js";
49
49
  import {
50
50
  useAutoSurface
51
51
  } from "./chunk-TT6ENR6T.js";
@@ -53,7 +53,7 @@ import {
53
53
  InlineAudioPlayer,
54
54
  InlineVideoPlayer,
55
55
  MarkdownRenderer
56
- } from "./chunk-D7KN4CRG.js";
56
+ } from "./chunk-VMPQEUJH.js";
57
57
  import {
58
58
  MermaidDiagram
59
59
  } from "./chunk-WLUZTUNZ.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  JsonView
3
- } from "../chunk-NANF7REM.js";
3
+ } from "../chunk-DJRYQQXB.js";
4
4
  import "../chunk-TT6ENR6T.js";
5
- import "../chunk-D7KN4CRG.js";
5
+ import "../chunk-VMPQEUJH.js";
6
6
  import "../chunk-WLUZTUNZ.js";
7
7
  import "../chunk-LR3AIGDD.js";
8
8
  export {
@@ -8,7 +8,7 @@ import {
8
8
  TextLayer,
9
9
  TreeLayer,
10
10
  VideoLayer
11
- } from "../chunk-TI5X7SBC.js";
11
+ } from "../chunk-MKWVJYUS.js";
12
12
  import "../chunk-WLUZTUNZ.js";
13
13
  import "../chunk-LR3AIGDD.js";
14
14
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MarkdownRenderer
3
- } from "../chunk-D7KN4CRG.js";
3
+ } from "../chunk-VMPQEUJH.js";
4
4
  import {
5
5
  MermaidDiagram
6
6
  } from "../chunk-WLUZTUNZ.js";
@@ -4,10 +4,10 @@ import {
4
4
  PageSectionView,
5
5
  PageViewContext,
6
6
  usePageView
7
- } from "../chunk-EJMUO6VX.js";
8
- import "../chunk-TI5X7SBC.js";
7
+ } from "../chunk-3HYSWEKG.js";
8
+ import "../chunk-MKWVJYUS.js";
9
9
  import "../chunk-TT6ENR6T.js";
10
- import "../chunk-D7KN4CRG.js";
10
+ import "../chunk-VMPQEUJH.js";
11
11
  import "../chunk-WLUZTUNZ.js";
12
12
  import "../chunk-LR3AIGDD.js";
13
13
  export {
@@ -10,17 +10,17 @@ import {
10
10
  MediaClipLayer,
11
11
  SocialCaptionOverlay,
12
12
  formatTime
13
- } from "../chunk-A4VLH2US.js";
13
+ } from "../chunk-KF4DTT46.js";
14
14
  import {
15
15
  BlockRenderer
16
- } from "../chunk-EJMUO6VX.js";
17
- import "../chunk-TI5X7SBC.js";
16
+ } from "../chunk-3HYSWEKG.js";
17
+ import "../chunk-MKWVJYUS.js";
18
18
  import "../chunk-BOZJ655L.js";
19
19
  import "../chunk-TT6ENR6T.js";
20
20
  import {
21
21
  InlineAudioPlayer,
22
22
  InlineVideoPlayer
23
- } from "../chunk-D7KN4CRG.js";
23
+ } from "../chunk-VMPQEUJH.js";
24
24
  import "../chunk-WLUZTUNZ.js";
25
25
  import "../chunk-LR3AIGDD.js";
26
26
  export {