@bendyline/squisq-react 2.11.15 → 2.11.17

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,298 @@
1
+ import {
2
+ ImageLayer,
3
+ MapLayer,
4
+ MermaidLayer,
5
+ PathLayer,
6
+ ShapeLayer,
7
+ TableLayer,
8
+ TextLayer,
9
+ TreeLayer,
10
+ VideoLayer,
11
+ getTransitionClass
12
+ } from "./chunk-PZCWGBRQ.js";
13
+
14
+ // src/BlockRenderer.tsx
15
+ import { useId, useLayoutEffect, useRef, useState } from "react";
16
+ import { resolveTransitionDuration } from "@bendyline/squisq/schemas";
17
+ import { isGeometryBoundTemplate } from "@bendyline/squisq/doc";
18
+
19
+ // src/utils/textFit.ts
20
+ function largestFittingTextScale(fits, maximum = 2) {
21
+ if (maximum <= 1 || !fits(1)) return 1;
22
+ if (fits(maximum)) return maximum;
23
+ let low = 1;
24
+ let high = maximum;
25
+ for (let attempt = 0; attempt < 10; attempt += 1) {
26
+ const candidate = (low + high) / 2;
27
+ if (fits(candidate)) low = candidate;
28
+ else high = candidate;
29
+ }
30
+ return Math.max(1, Math.floor(low * 100) / 100);
31
+ }
32
+
33
+ // src/BlockRenderer.tsx
34
+ import { jsx, jsxs } from "react/jsx-runtime";
35
+ var DEFAULT_VIEWPORT = {
36
+ width: 1920,
37
+ height: 1080
38
+ };
39
+ var FIT_TOLERANCE_PX = 1;
40
+ function applyTextScale(targets, scale) {
41
+ for (const target of targets) {
42
+ if (target.dataset.squisqTextFit === "html") {
43
+ const baseFontSize = Number(target.dataset.squisqBaseFontSize);
44
+ if (Number.isFinite(baseFontSize)) target.style.fontSize = `${baseFontSize * scale}px`;
45
+ } else {
46
+ target.style.transform = `scale(${scale})`;
47
+ }
48
+ }
49
+ }
50
+ function cssPixels(value) {
51
+ const parsed = parseFloat(value);
52
+ return Number.isFinite(parsed) ? parsed : 0;
53
+ }
54
+ function htmlTargetFits(target) {
55
+ const content = target.querySelector('[data-squisq-text-content="true"]');
56
+ if (!content || content.scrollWidth <= 0 || content.scrollHeight <= 0) return false;
57
+ const view = target.ownerDocument.defaultView;
58
+ const computed = view?.getComputedStyle(target);
59
+ const horizontalInset = computed ? cssPixels(computed.paddingLeft) + cssPixels(computed.paddingRight) + cssPixels(computed.borderLeftWidth) + cssPixels(computed.borderRightWidth) : 0;
60
+ const verticalInset = computed ? cssPixels(computed.paddingTop) + cssPixels(computed.paddingBottom) + cssPixels(computed.borderTopWidth) + cssPixels(computed.borderBottomWidth) : 0;
61
+ const availableWidth = Math.max(0, target.clientWidth - horizontalInset);
62
+ const availableHeight = Math.max(0, target.clientHeight - verticalInset);
63
+ if (content.scrollWidth > availableWidth + FIT_TOLERANCE_PX || content.scrollHeight > availableHeight + FIT_TOLERANCE_PX) {
64
+ return false;
65
+ }
66
+ for (const scrollRegion of content.querySelectorAll("[data-squisq-table-scroll]")) {
67
+ if (scrollRegion.clientWidth > 0 && scrollRegion.scrollWidth > scrollRegion.clientWidth + FIT_TOLERANCE_PX || scrollRegion.clientHeight > 0 && scrollRegion.scrollHeight > scrollRegion.clientHeight + FIT_TOLERANCE_PX) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+ function svgTargetFits(target, scale) {
74
+ const content = target.querySelector('[data-squisq-text-content="true"]');
75
+ if (!content || typeof content.getBBox !== "function") return false;
76
+ try {
77
+ const box = content.getBBox();
78
+ const availableWidth = Number(target.dataset.squisqFitWidth);
79
+ const availableHeight = Number(target.dataset.squisqFitHeight);
80
+ return box.width > 0 && box.height > 0 && box.width * scale <= availableWidth + FIT_TOLERANCE_PX && box.height * scale <= availableHeight + FIT_TOLERANCE_PX;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ function renderedTextRects(targets) {
86
+ return targets.map(
87
+ (target) => target.querySelector('[data-squisq-text-content="true"]')?.getBoundingClientRect()
88
+ ).filter((rect) => !!rect && rect.width > 0 && rect.height > 0);
89
+ }
90
+ function overlapPairs(rects) {
91
+ const pairs = /* @__PURE__ */ new Set();
92
+ for (let left = 0; left < rects.length; left += 1) {
93
+ for (let right = left + 1; right < rects.length; right += 1) {
94
+ const a = rects[left];
95
+ const b = rects[right];
96
+ if (a.left < b.right - FIT_TOLERANCE_PX && a.right > b.left + FIT_TOLERANCE_PX && a.top < b.bottom - FIT_TOLERANCE_PX && a.bottom > b.top + FIT_TOLERANCE_PX) {
97
+ pairs.add(`${left}:${right}`);
98
+ }
99
+ }
100
+ }
101
+ return pairs;
102
+ }
103
+ function BlockRenderer({
104
+ block,
105
+ blockTime,
106
+ basePath,
107
+ isEntering = false,
108
+ isExiting = false,
109
+ transition,
110
+ viewport = DEFAULT_VIEWPORT,
111
+ isPlaying,
112
+ muted = false,
113
+ animationsEnabled = true,
114
+ theme,
115
+ tableContentRenderer,
116
+ growTextToFit = false
117
+ }) {
118
+ let transitionClass = "";
119
+ const transitionStyle = {};
120
+ const activeTransition = animationsEnabled ? transition ?? block.transition : void 0;
121
+ if (activeTransition && isEntering) {
122
+ transitionClass = getTransitionClass(activeTransition.type, true, activeTransition.direction);
123
+ transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
124
+ } else if (activeTransition && isExiting) {
125
+ transitionClass = getTransitionClass(activeTransition.type, false, activeTransition.direction);
126
+ transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
127
+ }
128
+ const instanceId = useId().replace(/:/g, "");
129
+ const clipId = `vb-clip-${instanceId}-${block.id}`;
130
+ const svgRef = useRef(null);
131
+ const [textScale, setTextScale] = useState(1);
132
+ const growText = growTextToFit && !isGeometryBoundTemplate(block.template);
133
+ useLayoutEffect(() => {
134
+ const svg = svgRef.current;
135
+ let cancelled = false;
136
+ if (!svg) return;
137
+ const measure = () => {
138
+ if (cancelled) return;
139
+ const targets = Array.from(svg.querySelectorAll("[data-squisq-text-fit]"));
140
+ if (!growText || targets.length === 0) {
141
+ applyTextScale(targets, 1);
142
+ setTextScale(1);
143
+ return;
144
+ }
145
+ applyTextScale(targets, 1);
146
+ const baselineOverlaps = overlapPairs(renderedTextRects(targets));
147
+ const fits = (scale) => {
148
+ applyTextScale(targets, scale);
149
+ if (targets.some(
150
+ (target) => target.dataset.squisqTextFit === "html" ? !htmlTargetFits(target) : !svgTargetFits(target, scale)
151
+ )) {
152
+ return false;
153
+ }
154
+ const candidateOverlaps = overlapPairs(renderedTextRects(targets));
155
+ for (const pair of candidateOverlaps) {
156
+ if (!baselineOverlaps.has(pair)) return false;
157
+ }
158
+ return true;
159
+ };
160
+ const nextScale = largestFittingTextScale(fits);
161
+ applyTextScale(targets, nextScale);
162
+ setTextScale((current) => current === nextScale ? current : nextScale);
163
+ };
164
+ measure();
165
+ const fonts = svg.ownerDocument?.fonts;
166
+ if (fonts && fonts.status !== "loaded") void fonts.ready.then(measure);
167
+ return () => {
168
+ cancelled = true;
169
+ };
170
+ }, [block.layers, growText, viewport.height, viewport.width]);
171
+ return /* @__PURE__ */ jsxs(
172
+ "svg",
173
+ {
174
+ ref: svgRef,
175
+ className: `block-svg ${transitionClass}`,
176
+ style: transitionStyle,
177
+ viewBox: `0 0 ${viewport.width} ${viewport.height}`,
178
+ preserveAspectRatio: "xMidYMid meet",
179
+ overflow: "hidden",
180
+ "data-block-id": block.id,
181
+ "data-squisq-text-scale": growText ? textScale : void 0,
182
+ children: [
183
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: viewport.width, height: viewport.height }) }) }),
184
+ /* @__PURE__ */ jsx("g", { clipPath: `url(#${clipId})`, children: (block.layers ?? []).map((layer) => /* @__PURE__ */ jsx(
185
+ LayerRenderer,
186
+ {
187
+ block,
188
+ layer,
189
+ basePath,
190
+ viewport,
191
+ blockTime,
192
+ isPlaying,
193
+ muted,
194
+ animationsEnabled,
195
+ theme,
196
+ tableContentRenderer,
197
+ textScale
198
+ },
199
+ layer.id
200
+ )) })
201
+ ]
202
+ }
203
+ );
204
+ }
205
+ function LayerRenderer({
206
+ block,
207
+ layer,
208
+ basePath,
209
+ viewport,
210
+ blockTime,
211
+ isPlaying,
212
+ muted,
213
+ animationsEnabled,
214
+ theme,
215
+ tableContentRenderer,
216
+ textScale
217
+ }) {
218
+ const renderedLayer = animationsEnabled || !layer.animation ? layer : { ...layer, animation: void 0 };
219
+ switch (renderedLayer.type) {
220
+ case "image":
221
+ return /* @__PURE__ */ jsx(
222
+ ImageLayer,
223
+ {
224
+ layer: renderedLayer,
225
+ basePath,
226
+ viewport,
227
+ blockTime,
228
+ animationsEnabled
229
+ }
230
+ );
231
+ case "text":
232
+ return /* @__PURE__ */ jsx(
233
+ TextLayer,
234
+ {
235
+ layer: renderedLayer,
236
+ viewport,
237
+ blockTime,
238
+ textScale
239
+ }
240
+ );
241
+ case "shape":
242
+ return /* @__PURE__ */ jsx(ShapeLayer, { layer: renderedLayer, viewport, blockTime });
243
+ case "path":
244
+ return /* @__PURE__ */ jsx(PathLayer, { layer: renderedLayer, viewport, blockTime });
245
+ case "map":
246
+ return /* @__PURE__ */ jsx(
247
+ MapLayer,
248
+ {
249
+ layer: renderedLayer,
250
+ basePath,
251
+ viewport,
252
+ blockTime
253
+ }
254
+ );
255
+ case "video":
256
+ return /* @__PURE__ */ jsx(
257
+ VideoLayer,
258
+ {
259
+ layer: renderedLayer,
260
+ basePath,
261
+ viewport,
262
+ blockTime,
263
+ isPlaying,
264
+ muted
265
+ }
266
+ );
267
+ case "table":
268
+ return /* @__PURE__ */ jsx(
269
+ TableLayer,
270
+ {
271
+ block,
272
+ layer: renderedLayer,
273
+ viewport,
274
+ blockTime,
275
+ contentRenderer: tableContentRenderer
276
+ }
277
+ );
278
+ case "tree":
279
+ return /* @__PURE__ */ jsx(TreeLayer, { layer: renderedLayer, viewport, blockTime });
280
+ case "mermaid":
281
+ return /* @__PURE__ */ jsx(
282
+ MermaidLayer,
283
+ {
284
+ layer: renderedLayer,
285
+ viewport,
286
+ blockTime,
287
+ theme
288
+ }
289
+ );
290
+ default:
291
+ console.warn(`Unknown layer type: ${renderedLayer.type}`);
292
+ return null;
293
+ }
294
+ }
295
+
296
+ export {
297
+ BlockRenderer
298
+ };
@@ -1,28 +1,30 @@
1
1
  import {
2
- BlockRenderer,
3
2
  LinearDocView
4
- } from "./chunk-P3FJ3YKS.js";
5
- import {
6
- isCaptureDrivenVideo,
7
- seekVideoToFrame
8
- } from "./chunk-WHVSQ42V.js";
3
+ } from "./chunk-2VSD55NT.js";
9
4
  import {
10
5
  useAudioSync,
11
6
  useDocPlayback,
12
7
  useMediaSchedule,
13
8
  useViewportOrientation
14
- } from "./chunk-U2FIZWAL.js";
9
+ } from "./chunk-SVVPYDV3.js";
15
10
  import {
16
11
  useAutoSurface
17
12
  } from "./chunk-TT6ENR6T.js";
18
13
  import {
19
14
  MarkdownRenderer
20
- } from "./chunk-6PUMLLJH.js";
15
+ } from "./chunk-2FZUFQO3.js";
16
+ import {
17
+ BlockRenderer
18
+ } from "./chunk-5FJZDX7H.js";
19
+ import {
20
+ isCaptureDrivenVideo,
21
+ seekVideoToFrame
22
+ } from "./chunk-PZCWGBRQ.js";
21
23
  import {
22
24
  useMediaProvider,
23
25
  useMediaUrl,
24
26
  useResourcePolicy
25
- } from "./chunk-LR3AIGDD.js";
27
+ } from "./chunk-63G5OJWY.js";
26
28
 
27
29
  // src/MediaClipLayer.tsx
28
30
  import { useEffect, useRef } from "react";
@@ -2833,6 +2835,7 @@ function DocPlayerContent({
2833
2835
  blocks: expandedBlocks,
2834
2836
  suppressOutgoingForNextBlock
2835
2837
  } = useDocPlayback(doc, currentTime, {
2838
+ basePath,
2836
2839
  viewport: activeViewport,
2837
2840
  theme: effectiveTheme,
2838
2841
  motion: motionProp,
@@ -0,0 +1,123 @@
1
+ // src/hooks/MediaContext.tsx
2
+ import { createContext, useContext, useState, useEffect, useMemo } from "react";
3
+ import {
4
+ DEFAULT_INTERACTIVE_RESOURCE_POLICY,
5
+ isResourceUrlAllowed
6
+ } from "@bendyline/squisq/markdown";
7
+ var MediaContext = createContext(null);
8
+ var ResourcePolicyContext = createContext(
9
+ DEFAULT_INTERACTIVE_RESOURCE_POLICY
10
+ );
11
+ function useResourcePolicy() {
12
+ return useContext(ResourcePolicyContext);
13
+ }
14
+ function useMediaProvider() {
15
+ return useContext(MediaContext);
16
+ }
17
+ function useMediaUrl(relativePath, basePath) {
18
+ const provider = useMediaProvider();
19
+ const resourcePolicy = useResourcePolicy();
20
+ const safePath = typeof relativePath === "string" ? relativePath : "";
21
+ const isAbsolute = !safePath || /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(safePath);
22
+ const fallback = useMemo(() => {
23
+ const candidate = isAbsolute ? safePath : `${basePath.replace(/\/$/, "")}/${safePath}`;
24
+ return isResourceUrlAllowed(candidate, resourcePolicy) ? candidate : "";
25
+ }, [isAbsolute, safePath, basePath, resourcePolicy]);
26
+ const needsProvider = !isAbsolute && !!provider;
27
+ const [url, setUrl] = useState(fallback);
28
+ useEffect(() => {
29
+ if (!needsProvider) {
30
+ setUrl(fallback);
31
+ return;
32
+ }
33
+ let cancelled = false;
34
+ setUrl(fallback);
35
+ provider.resolveUrl(safePath).then(
36
+ (resolved) => {
37
+ if (!cancelled) {
38
+ setUrl(isResourceUrlAllowed(resolved, resourcePolicy) ? resolved : "");
39
+ }
40
+ },
41
+ () => {
42
+ if (!cancelled) setUrl(fallback);
43
+ }
44
+ );
45
+ return () => {
46
+ cancelled = true;
47
+ };
48
+ }, [needsProvider, provider, safePath, fallback, resourcePolicy]);
49
+ return needsProvider ? url : fallback;
50
+ }
51
+
52
+ // src/mermaid/mermaidRuntime.ts
53
+ import { buildMermaidThemeVariables } from "@bendyline/squisq/schemas";
54
+ import { DEFAULT_THEME } from "@bendyline/squisq/doc";
55
+ var mermaidPromise = null;
56
+ var MERMAID_BASE_CONFIG = {
57
+ startOnLoad: false,
58
+ securityLevel: "strict",
59
+ suppressErrorRendering: true,
60
+ deterministicIds: false,
61
+ logLevel: "error"
62
+ };
63
+ async function loadMermaid() {
64
+ if (!mermaidPromise) {
65
+ mermaidPromise = import("mermaid").then(({ default: mermaid }) => mermaid).catch((error) => {
66
+ mermaidPromise = null;
67
+ throw error;
68
+ });
69
+ }
70
+ return mermaidPromise;
71
+ }
72
+ function enqueue(run) {
73
+ const host = globalThis;
74
+ const previous = host.__squisqMermaidWorkQueue ?? Promise.resolve();
75
+ const result = previous.then(run, run);
76
+ host.__squisqMermaidWorkQueue = result.then(
77
+ () => void 0,
78
+ () => void 0
79
+ );
80
+ return result;
81
+ }
82
+ function renderMermaidSvg(id, source, container, theme = DEFAULT_THEME) {
83
+ return enqueue(async () => {
84
+ const mermaid = await loadMermaid();
85
+ mermaid.initialize({
86
+ ...MERMAID_BASE_CONFIG,
87
+ theme: "base",
88
+ themeVariables: buildMermaidThemeVariables(theme)
89
+ });
90
+ const { svg, diagramType } = await mermaid.render(id, source, container);
91
+ return { svg, diagramType };
92
+ });
93
+ }
94
+ function renderMermaidSvgForExport(id, source, theme = DEFAULT_THEME, fontFamily) {
95
+ return enqueue(async () => {
96
+ const mermaid = await loadMermaid();
97
+ const themeVariables = buildMermaidThemeVariables(theme);
98
+ mermaid.initialize({
99
+ ...MERMAID_BASE_CONFIG,
100
+ theme: "base",
101
+ themeVariables: fontFamily ? { ...themeVariables, fontFamily } : themeVariables,
102
+ htmlLabels: false,
103
+ journey: { textPlacement: "tspan" }
104
+ });
105
+ const { svg } = await mermaid.render(id, source);
106
+ return svg;
107
+ });
108
+ }
109
+ function mermaidRenderErrorMessage(error) {
110
+ const raw = error instanceof Error ? error.message : String(error);
111
+ return raw.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).slice(0, 6).join("\n") || "Unknown Mermaid rendering error.";
112
+ }
113
+
114
+ export {
115
+ MediaContext,
116
+ ResourcePolicyContext,
117
+ useResourcePolicy,
118
+ useMediaProvider,
119
+ useMediaUrl,
120
+ renderMermaidSvg,
121
+ renderMermaidSvgForExport,
122
+ mermaidRenderErrorMessage
123
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TT6ENR6T.js";
4
4
  import {
5
5
  MarkdownRenderer
6
- } from "./chunk-6PUMLLJH.js";
6
+ } from "./chunk-2FZUFQO3.js";
7
7
 
8
8
  // src/jsonView/useJsonViewTokens.ts
9
9
  import { useMemo } from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LinearDocView
3
- } from "./chunk-P3FJ3YKS.js";
3
+ } from "./chunk-2VSD55NT.js";
4
4
 
5
5
  // src/PaginatedDocView.tsx
6
6
  import {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MermaidDiagram
3
- } from "./chunk-WLUZTUNZ.js";
3
+ } from "./chunk-QXZXQPL3.js";
4
4
  import {
5
5
  useMediaUrl,
6
6
  useResourcePolicy
7
- } from "./chunk-LR3AIGDD.js";
7
+ } from "./chunk-63G5OJWY.js";
8
8
 
9
9
  // src/utils/animationUtils.ts
10
10
  import {
@@ -1,62 +1,18 @@
1
+ import {
2
+ mermaidRenderErrorMessage,
3
+ renderMermaidSvg
4
+ } from "./chunk-63G5OJWY.js";
5
+
1
6
  // src/mermaid/MermaidDiagram.tsx
2
7
  import { useEffect, useState } from "react";
3
- import { DEFAULT_THEME as DEFAULT_THEME2 } from "@bendyline/squisq/doc";
4
-
5
- // src/mermaid/mermaidRuntime.ts
6
- import { buildMermaidThemeVariables } from "@bendyline/squisq/schemas";
7
8
  import { DEFAULT_THEME } from "@bendyline/squisq/doc";
8
- var mermaidPromise = null;
9
- var MERMAID_BASE_CONFIG = {
10
- startOnLoad: false,
11
- securityLevel: "strict",
12
- suppressErrorRendering: true,
13
- deterministicIds: false,
14
- logLevel: "error"
15
- };
16
- async function loadMermaid() {
17
- if (!mermaidPromise) {
18
- mermaidPromise = import("mermaid").then(({ default: mermaid }) => mermaid).catch((error) => {
19
- mermaidPromise = null;
20
- throw error;
21
- });
22
- }
23
- return mermaidPromise;
24
- }
25
- function enqueue(run) {
26
- const host = globalThis;
27
- const previous = host.__squisqMermaidWorkQueue ?? Promise.resolve();
28
- const result = previous.then(run, run);
29
- host.__squisqMermaidWorkQueue = result.then(
30
- () => void 0,
31
- () => void 0
32
- );
33
- return result;
34
- }
35
- function renderMermaidSvg(id, source, container, theme = DEFAULT_THEME) {
36
- return enqueue(async () => {
37
- const mermaid = await loadMermaid();
38
- mermaid.initialize({
39
- ...MERMAID_BASE_CONFIG,
40
- theme: "base",
41
- themeVariables: buildMermaidThemeVariables(theme)
42
- });
43
- const { svg, diagramType } = await mermaid.render(id, source, container);
44
- return { svg, diagramType };
45
- });
46
- }
47
- function mermaidRenderErrorMessage(error) {
48
- const raw = error instanceof Error ? error.message : String(error);
49
- return raw.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).slice(0, 6).join("\n") || "Unknown Mermaid rendering error.";
50
- }
51
-
52
- // src/mermaid/MermaidDiagram.tsx
53
9
  import { jsx, jsxs } from "react/jsx-runtime";
54
10
  var renderSequence = 0;
55
11
  function MermaidDiagram({
56
12
  source,
57
13
  className,
58
14
  ariaLabel = "Mermaid diagram",
59
- theme = DEFAULT_THEME2
15
+ theme = DEFAULT_THEME
60
16
  }) {
61
17
  const [svg, setSvg] = useState("");
62
18
  const [error, setError] = useState("");