@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.
@@ -1,6 +1,8 @@
1
1
  import {
2
+ renderMermaidSvg,
3
+ useMediaProvider,
2
4
  useResourcePolicy
3
- } from "./chunk-LR3AIGDD.js";
5
+ } from "./chunk-63G5OJWY.js";
4
6
 
5
7
  // src/hooks/useMediaSchedule.ts
6
8
  import { useMemo } from "react";
@@ -475,7 +477,7 @@ function useAudioSync(audioRef, track, basePath = "", enabled = true, mode = "me
475
477
  }
476
478
 
477
479
  // src/hooks/useDocPlayback.ts
478
- import { useMemo as useMemo3, useCallback as useCallback2, useRef as useRef2 } from "react";
480
+ import { useMemo as useMemo4, useCallback as useCallback2, useRef as useRef2 } from "react";
479
481
  import { resolveMotionForDoc } from "@bendyline/squisq/schemas";
480
482
  import {
481
483
  DEFAULT_THEME,
@@ -485,11 +487,145 @@ import {
485
487
  } from "@bendyline/squisq/schemas";
486
488
  import {
487
489
  expandDocBlocks,
488
- flattenRenderableBlocks,
490
+ flattenRenderableBlocks as flattenRenderableBlocks2,
489
491
  isTemplateBlock,
490
492
  resolvePersistentLayers,
491
493
  VIEWPORT_PRESETS
492
494
  } from "@bendyline/squisq/doc";
495
+
496
+ // src/hooks/useFeatureMediaAspectRatios.ts
497
+ import { useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
498
+ import {
499
+ extractImages,
500
+ extractEmbeddedVideos,
501
+ flattenRenderableBlocks
502
+ } from "@bendyline/squisq/doc";
503
+ import { isResourceUrlAllowed as isResourceUrlAllowed2 } from "@bendyline/squisq/markdown";
504
+ var EMPTY_RATIOS = {};
505
+ var ratioCache = /* @__PURE__ */ new Map();
506
+ var diagramSequence = 0;
507
+ function probeAspectRatio(kind, url, report) {
508
+ let element;
509
+ let size;
510
+ if (kind === "image") {
511
+ const image = new Image();
512
+ element = image;
513
+ size = () => [image.naturalWidth, image.naturalHeight];
514
+ } else {
515
+ const video = document.createElement("video");
516
+ video.preload = "metadata";
517
+ video.muted = true;
518
+ element = video;
519
+ size = () => [video.videoWidth, video.videoHeight];
520
+ }
521
+ const event = kind === "image" ? "load" : "loadedmetadata";
522
+ let released = false;
523
+ const cleanup = () => {
524
+ if (released) return;
525
+ released = true;
526
+ clearTimeout(timeout);
527
+ element.removeEventListener(event, loaded);
528
+ element.removeEventListener("error", cleanup);
529
+ element.removeAttribute("src");
530
+ if (element instanceof HTMLVideoElement) element.load();
531
+ };
532
+ const loaded = () => {
533
+ const [width, height] = size();
534
+ if (width > 0 && height > 0) report(width / height);
535
+ cleanup();
536
+ };
537
+ const timeout = setTimeout(cleanup, 8e3);
538
+ element.addEventListener(event, loaded);
539
+ element.addEventListener("error", cleanup);
540
+ element.src = url;
541
+ return cleanup;
542
+ }
543
+ function useFeatureMediaAspectRatios(blocks, basePath, theme) {
544
+ const provider = useMediaProvider();
545
+ const policy = useResourcePolicy();
546
+ const sourceKey = useMemo3(() => {
547
+ const sources = /* @__PURE__ */ new Map();
548
+ for (const block of flattenRenderableBlocks(blocks ?? [])) {
549
+ if (block.summaryLayout !== "feature") continue;
550
+ for (const image of extractImages(block.contents))
551
+ sources.set(image.src, { kind: "image", source: image.src });
552
+ for (const video of extractEmbeddedVideos(block.contents))
553
+ sources.set(video.src, { kind: "video", source: video.src });
554
+ for (const node of block.contents ?? []) {
555
+ if (node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")
556
+ sources.set(node.value, { kind: "mermaid", source: node.value });
557
+ }
558
+ }
559
+ return JSON.stringify([...sources.values()]);
560
+ }, [blocks]);
561
+ const scope = useMemo3(
562
+ () => ({ sourceKey, provider, policy, basePath, theme }),
563
+ [sourceKey, provider, policy, basePath, theme]
564
+ );
565
+ const [result, setResult] = useState2();
566
+ useEffect2(() => {
567
+ if (typeof document === "undefined") return;
568
+ let current = true;
569
+ const cleanups = [];
570
+ const ratios = {};
571
+ const report = (source, ratio) => {
572
+ if (!current || !Number.isFinite(ratio) || ratio <= 0) return;
573
+ ratios[source] = ratio;
574
+ setResult({ scope, ratios: { ...ratios } });
575
+ };
576
+ const sources = JSON.parse(sourceKey);
577
+ for (const { kind, source } of sources) {
578
+ if (kind === "mermaid") {
579
+ void renderMermaidSvg(
580
+ `squisq-feature-measure-${++diagramSequence}`,
581
+ source,
582
+ void 0,
583
+ theme
584
+ ).then(({ svg }) => {
585
+ if (!current) return;
586
+ const viewBox = new DOMParser().parseFromString(svg, "image/svg+xml").documentElement.getAttribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
587
+ if (viewBox?.length === 4 && viewBox[2] > 0 && viewBox[3] > 0)
588
+ report(source, viewBox[2] / viewBox[3]);
589
+ }).catch(() => {
590
+ });
591
+ continue;
592
+ }
593
+ const resolve = async () => {
594
+ const absolute = /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(source);
595
+ const fallback = absolute ? source : `${basePath.replace(/\/$/, "")}/${source}`;
596
+ let url = fallback;
597
+ if (!absolute && provider) {
598
+ try {
599
+ url = await provider.resolveUrl(source);
600
+ } catch {
601
+ }
602
+ }
603
+ if (!current || !isResourceUrlAllowed2(url, policy)) return;
604
+ const cacheKey = `${kind}:${url}`;
605
+ const cached = ratioCache.get(cacheKey);
606
+ if (cached !== void 0) {
607
+ report(source, cached);
608
+ return;
609
+ }
610
+ cleanups.push(
611
+ probeAspectRatio(kind, url, (ratio) => {
612
+ if (!current) return;
613
+ ratioCache.set(cacheKey, ratio);
614
+ report(source, ratio);
615
+ })
616
+ );
617
+ };
618
+ void resolve();
619
+ }
620
+ return () => {
621
+ current = false;
622
+ cleanups.forEach((cleanup) => cleanup());
623
+ };
624
+ }, [sourceKey, provider, policy, basePath, theme, scope]);
625
+ return result?.scope === scope ? result.ratios : EMPTY_RATIOS;
626
+ }
627
+
628
+ // src/hooks/useDocPlayback.ts
493
629
  function useDocPlayback(script, currentTime, options = {}) {
494
630
  const {
495
631
  viewport = VIEWPORT_PRESETS.landscape,
@@ -497,14 +633,20 @@ function useDocPlayback(script, currentTime, options = {}) {
497
633
  onSeek,
498
634
  useAudioSegmentTiming = true,
499
635
  deterministicTransitions = false,
500
- motion: motionOverride
636
+ motion: motionOverride,
637
+ basePath = "."
501
638
  } = options;
502
- const blocks = useMemo3(() => {
639
+ const mediaAspectRatios = useFeatureMediaAspectRatios(
640
+ script?.blocks,
641
+ basePath,
642
+ theme ?? DEFAULT_THEME
643
+ );
644
+ const blocks = useMemo4(() => {
503
645
  if (!script?.blocks) {
504
646
  return [];
505
647
  }
506
648
  const hasChildren = script.blocks.some((b) => b.children && b.children.length > 0);
507
- const flatBlocks = hasChildren ? flattenRenderableBlocks(script.blocks) : script.blocks;
649
+ const flatBlocks = hasChildren ? flattenRenderableBlocks2(script.blocks) : script.blocks;
508
650
  const hasTemplates = flatBlocks.some(isTemplateBlock);
509
651
  const resolvedTheme = theme ?? DEFAULT_THEME;
510
652
  const persistentLayers = resolvePersistentLayers(
@@ -519,6 +661,7 @@ function useDocPlayback(script, currentTime, options = {}) {
519
661
  const expanded = expandDocBlocks(flatBlocks, {
520
662
  audioSegments,
521
663
  viewport,
664
+ mediaAspectRatios,
522
665
  persistentLayers,
523
666
  theme,
524
667
  motion: resolveMotionForDoc(
@@ -546,24 +689,25 @@ function useDocPlayback(script, currentTime, options = {}) {
546
689
  script?.motion,
547
690
  script?.frontmatter,
548
691
  motionOverride,
692
+ mediaAspectRatios,
549
693
  viewport,
550
694
  theme,
551
695
  useAudioSegmentTiming
552
696
  ]);
553
- const currentBlock = useMemo3(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
554
- const currentBlockIndex = useMemo3(
697
+ const currentBlock = useMemo4(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
698
+ const currentBlockIndex = useMemo4(
555
699
  () => currentBlock ? blocks.indexOf(currentBlock) : -1,
556
700
  [blocks, currentBlock]
557
701
  );
558
- const blockTime = useMemo3(() => {
702
+ const blockTime = useMemo4(() => {
559
703
  if (!currentBlock) return 0;
560
704
  return Math.max(0, currentTime - currentBlock.startTime);
561
705
  }, [currentBlock, currentTime]);
562
- const blockProgress = useMemo3(() => {
706
+ const blockProgress = useMemo4(() => {
563
707
  if (!currentBlock || currentBlock.duration === 0) return 0;
564
708
  return Math.min(1, blockTime / currentBlock.duration);
565
709
  }, [currentBlock, blockTime]);
566
- const docProgress = useMemo3(() => {
710
+ const docProgress = useMemo4(() => {
567
711
  if (!script || script.duration === 0) return 0;
568
712
  return Math.min(1, currentTime / script.duration);
569
713
  }, [script, currentTime]);
@@ -630,7 +774,7 @@ function useDocPlayback(script, currentTime, options = {}) {
630
774
  }
631
775
 
632
776
  // src/hooks/useViewportOrientation.ts
633
- import { useState as useState2, useEffect as useEffect2, useMemo as useMemo4 } from "react";
777
+ import { useState as useState3, useEffect as useEffect3, useMemo as useMemo5 } from "react";
634
778
  import {
635
779
  VIEWPORT_PRESETS as VIEWPORT_PRESETS2
636
780
  } from "@bendyline/squisq/doc";
@@ -656,11 +800,11 @@ function getViewportForOrientation(orientation) {
656
800
  }
657
801
  }
658
802
  function useViewportOrientation() {
659
- const [windowSize, setWindowSize] = useState2(() => ({
803
+ const [windowSize, setWindowSize] = useState3(() => ({
660
804
  width: typeof window !== "undefined" ? window.innerWidth : 1920,
661
805
  height: typeof window !== "undefined" ? window.innerHeight : 1080
662
806
  }));
663
- useEffect2(() => {
807
+ useEffect3(() => {
664
808
  if (typeof window === "undefined") return;
665
809
  const handleResize = () => {
666
810
  setWindowSize({
@@ -679,11 +823,11 @@ function useViewportOrientation() {
679
823
  clearTimeout(timeoutId);
680
824
  };
681
825
  }, []);
682
- const orientation = useMemo4(
826
+ const orientation = useMemo5(
683
827
  () => getOrientationFromWindow(windowSize.width, windowSize.height),
684
828
  [windowSize.width, windowSize.height]
685
829
  );
686
- const viewport = useMemo4(() => getViewportForOrientation(orientation), [orientation]);
830
+ const viewport = useMemo5(() => getViewportForOrientation(orientation), [orientation]);
687
831
  return {
688
832
  viewport,
689
833
  orientation,
@@ -0,0 +1,61 @@
1
+ import { Theme } from '@bendyline/squisq/schemas';
2
+
3
+ /**
4
+ * PNG pictures of diagrams, for document exports (DOCX, EPUB, PDF, PPTX).
5
+ *
6
+ * Browser-only. A diagram is drawn as SVG, the SVG is loaded as an image,
7
+ * and the image is painted onto a canvas and read back as PNG. That last
8
+ * step fails in some browsers once the SVG holds `<foreignObject>`, so every
9
+ * path here produces plain SVG: Mermaid with SVG-text labels, Squisq
10
+ * diagrams through `BlockRenderer` without their slide chrome, and file
11
+ * trees as monospace text. A picture also cannot load the page's web fonts,
12
+ * so text is set in a system font stack.
13
+ *
14
+ * `createDiagramPictureRenderer` matches the `DiagramRasterizer` that
15
+ * `@bendyline/squisq-formats/diagrams` takes.
16
+ */
17
+
18
+ /** A PNG and the size it should display at, in CSS pixels. */
19
+ interface DiagramPicturePng {
20
+ data: Uint8Array;
21
+ width: number;
22
+ height: number;
23
+ }
24
+ interface DiagramPictureOptions {
25
+ /** Colors for the diagram. Default: Squisq's default (light) theme. */
26
+ theme?: Theme;
27
+ /** Pixel density of the PNG relative to its display size. Default: 2. */
28
+ scale?: number;
29
+ /** Widest display size in CSS pixels. Default: 680. */
30
+ maxWidth?: number;
31
+ /** Background painted behind the diagram. Default: white. */
32
+ background?: string;
33
+ }
34
+ /** The diagram description `@bendyline/squisq-formats/diagrams` passes. */
35
+ interface DiagramToPicture {
36
+ kind: 'mermaid' | 'diagram' | 'timeline' | 'tree' | 'container';
37
+ markdown: string;
38
+ source: string;
39
+ }
40
+ /** System fonts every platform has, since a picture can't load web fonts. */
41
+ declare const EXPORT_FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
42
+ /** Paint `svg` (viewBox already set) at `width` × `height` CSS pixels and read back PNG bytes. */
43
+ declare function svgToPng(svg: SVGSVGElement, width: number, height: number, options?: DiagramPictureOptions): Promise<DiagramPicturePng>;
44
+ /** A Mermaid diagram as a PNG at its natural size (capped at `maxWidth`). */
45
+ declare function renderMermaidPicture(source: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng>;
46
+ /** A file-tree fence as monospace text lines, exactly as written. */
47
+ declare function renderTreePicture(source: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng | null>;
48
+ /**
49
+ * A Squisq-rendered diagram — an ASCII diagram or timeline fence, or a
50
+ * drawing, layout, or diagram heading block — as a PNG cropped to its
51
+ * content, or null when it has nothing to draw.
52
+ */
53
+ declare function renderSquisqDiagramPicture(markdown: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng | null>;
54
+ /**
55
+ * A renderer for `rasterizeDiagrams` from `@bendyline/squisq-formats/diagrams`:
56
+ * Mermaid, ASCII diagrams and timelines, file trees, and drawing, layout,
57
+ * and diagram blocks.
58
+ */
59
+ declare function createDiagramPictureRenderer(options?: DiagramPictureOptions): (diagram: DiagramToPicture) => Promise<DiagramPicturePng | null>;
60
+
61
+ export { type DiagramPictureOptions, type DiagramPicturePng, type DiagramToPicture, EXPORT_FONT_STACK, createDiagramPictureRenderer, renderMermaidPicture, renderSquisqDiagramPicture, renderTreePicture, svgToPng };
@@ -0,0 +1,213 @@
1
+ import {
2
+ BlockRenderer
3
+ } from "../chunk-5FJZDX7H.js";
4
+ import "../chunk-PZCWGBRQ.js";
5
+ import "../chunk-QXZXQPL3.js";
6
+ import {
7
+ renderMermaidSvgForExport
8
+ } from "../chunk-63G5OJWY.js";
9
+
10
+ // src/export/diagramPictures.ts
11
+ import { createElement } from "react";
12
+ import { createRoot } from "react-dom/client";
13
+ import { VIEWPORT_PRESETS } from "@bendyline/squisq/schemas";
14
+ import {
15
+ DEFAULT_THEME,
16
+ flattenRenderableBlocks,
17
+ markdownToDoc,
18
+ materializeBlockLayers
19
+ } from "@bendyline/squisq/doc";
20
+ import { parseMarkdown } from "@bendyline/squisq/markdown";
21
+ var EXPORT_FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
22
+ var EXPORT_MONO_STACK = "ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace";
23
+ var CANVAS_UNITS_PER_CSS_PIXEL = 2.5;
24
+ var PADDING = 16;
25
+ var SVG_NS = "http://www.w3.org/2000/svg";
26
+ var pictureCount = 0;
27
+ function settings(options) {
28
+ return {
29
+ theme: options.theme ?? DEFAULT_THEME,
30
+ scale: options.scale ?? 2,
31
+ maxWidth: options.maxWidth ?? 680,
32
+ background: options.background ?? "#ffffff"
33
+ };
34
+ }
35
+ async function svgToPng(svg, width, height, options = {}) {
36
+ const { scale, background } = settings(options);
37
+ const pixelWidth = Math.max(1, Math.round(width * scale));
38
+ const pixelHeight = Math.max(1, Math.round(height * scale));
39
+ const clone = svg.cloneNode(true);
40
+ clone.setAttribute("xmlns", SVG_NS);
41
+ clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
42
+ clone.setAttribute("width", String(pixelWidth));
43
+ clone.setAttribute("height", String(pixelHeight));
44
+ clone.removeAttribute("style");
45
+ const xml = new XMLSerializer().serializeToString(clone);
46
+ const image = new Image();
47
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
48
+ await image.decode();
49
+ const canvas = document.createElement("canvas");
50
+ canvas.width = pixelWidth;
51
+ canvas.height = pixelHeight;
52
+ const context = canvas.getContext("2d");
53
+ if (!context) throw new Error("No 2D canvas is available to draw the diagram.");
54
+ context.fillStyle = background;
55
+ context.fillRect(0, 0, pixelWidth, pixelHeight);
56
+ context.drawImage(image, 0, 0, pixelWidth, pixelHeight);
57
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
58
+ if (!blob) throw new Error("The diagram could not be encoded as PNG.");
59
+ return {
60
+ data: new Uint8Array(await blob.arrayBuffer()),
61
+ width: Math.round(width),
62
+ height: Math.round(height)
63
+ };
64
+ }
65
+ function parseViewBox(svg) {
66
+ const values = (svg.getAttribute("viewBox") ?? "").trim().split(/[\s,]+/u).map(Number);
67
+ if (values.length !== 4 || values.some((value) => !Number.isFinite(value))) return null;
68
+ const [x = 0, y = 0, width = 0, height = 0] = values;
69
+ return width > 0 && height > 0 ? { x, y, width, height } : null;
70
+ }
71
+ async function renderMermaidPicture(source, options = {}) {
72
+ const { theme, maxWidth } = settings(options);
73
+ const markup = await renderMermaidSvgForExport(
74
+ `squisq-export-mermaid-${String(++pictureCount)}`,
75
+ source,
76
+ theme,
77
+ EXPORT_FONT_STACK
78
+ );
79
+ const template = document.createElement("template");
80
+ template.innerHTML = markup;
81
+ const svg = template.content.querySelector("svg");
82
+ if (!svg) throw new Error("Mermaid produced no SVG.");
83
+ const box = parseViewBox(svg);
84
+ if (!box) throw new Error("The Mermaid SVG has no size.");
85
+ const style = document.createElementNS(SVG_NS, "style");
86
+ style.textContent = `.edgeLabel text, .edgeLabel tspan { fill: ${theme.colors.text} !important; }`;
87
+ svg.append(style);
88
+ const width = Math.min(maxWidth, box.width);
89
+ return svgToPng(svg, width, box.height * width / box.width, options);
90
+ }
91
+ async function renderTreePicture(source, options = {}) {
92
+ const { theme, maxWidth } = settings(options);
93
+ const lines = source.replace(/\s+$/u, "").split("\n");
94
+ if (lines.length === 0) return null;
95
+ const fontSize = 14;
96
+ const lineHeight = 20;
97
+ const longest = Math.max(...lines.map((line) => [...line].length));
98
+ const width = Math.min(maxWidth, Math.ceil(longest * fontSize * 0.62) + 2 * PADDING);
99
+ const height = lines.length * lineHeight + 2 * PADDING;
100
+ const svg = document.createElementNS(SVG_NS, "svg");
101
+ svg.setAttribute("viewBox", `0 0 ${String(width)} ${String(height)}`);
102
+ const text = document.createElementNS(SVG_NS, "text");
103
+ text.setAttribute("font-family", EXPORT_MONO_STACK);
104
+ text.setAttribute("font-size", String(fontSize));
105
+ text.setAttribute("fill", theme.colors.text);
106
+ text.setAttribute("xml:space", "preserve");
107
+ lines.forEach((line, index) => {
108
+ const span = document.createElementNS(SVG_NS, "tspan");
109
+ span.setAttribute("x", String(PADDING));
110
+ span.setAttribute("y", String(PADDING + (index + 0.75) * lineHeight));
111
+ span.textContent = line;
112
+ text.append(span);
113
+ });
114
+ svg.append(text);
115
+ return svgToPng(svg, width, height, options);
116
+ }
117
+ function isChrome(layer) {
118
+ const id = layer.id;
119
+ if (id === "bg" || id === "title" || id.endsWith("-title") || id.endsWith("-description")) {
120
+ return true;
121
+ }
122
+ if (id.endsWith("-empty")) return true;
123
+ return layer.type === "shape" && typeof layer.content.fill === "string" && layer.content.fill.includes("gradient(");
124
+ }
125
+ function withExportFont(layer) {
126
+ if (layer.type !== "text") return layer;
127
+ return {
128
+ ...layer,
129
+ content: { ...layer.content, style: { ...layer.content.style, fontFamily: EXPORT_FONT_STACK } }
130
+ };
131
+ }
132
+ async function settle() {
133
+ await document.fonts?.ready;
134
+ for (let frame = 0; frame < 2; frame++) {
135
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
136
+ }
137
+ }
138
+ async function renderSquisqDiagramPicture(markdown, options = {}) {
139
+ const { theme, maxWidth } = settings(options);
140
+ const doc = markdownToDoc(parseMarkdown(markdown), { generateCoverBlock: false });
141
+ const block = flattenRenderableBlocks(doc.blocks)[0];
142
+ if (!block) return null;
143
+ const viewport = VIEWPORT_PRESETS.landscape;
144
+ const { layers } = materializeBlockLayers(block, { theme, viewport, persistentLayers: false });
145
+ const kept = layers.filter((layer) => !isChrome(layer)).map(withExportFont);
146
+ if (kept.length === 0) return null;
147
+ const host = document.createElement("div");
148
+ host.setAttribute("aria-hidden", "true");
149
+ host.style.cssText = `position:fixed;left:-${String(viewport.width * 2)}px;top:0;width:${String(
150
+ viewport.width
151
+ )}px;height:${String(viewport.height)}px;pointer-events:none;`;
152
+ document.body.append(host);
153
+ const root = createRoot(host);
154
+ try {
155
+ const pictureBlock = {
156
+ id: `squisq-export-${String(++pictureCount)}`,
157
+ startTime: 0,
158
+ duration: 0,
159
+ audioSegment: 0,
160
+ layers: kept
161
+ };
162
+ root.render(
163
+ createElement(BlockRenderer, {
164
+ block: pictureBlock,
165
+ blockTime: 0,
166
+ basePath: "",
167
+ viewport,
168
+ animationsEnabled: false,
169
+ theme
170
+ })
171
+ );
172
+ await settle();
173
+ const svg = host.querySelector("svg.block-svg");
174
+ if (!svg) return null;
175
+ const box = svg.getBBox();
176
+ if (box.width <= 0 || box.height <= 0) return null;
177
+ const x = box.x - PADDING;
178
+ const y = box.y - PADDING;
179
+ const contentWidth = box.width + 2 * PADDING;
180
+ const contentHeight = box.height + 2 * PADDING;
181
+ const cropped = svg.cloneNode(true);
182
+ cropped.setAttribute(
183
+ "viewBox",
184
+ `${String(x)} ${String(y)} ${String(contentWidth)} ${String(contentHeight)}`
185
+ );
186
+ cropped.removeAttribute("clip-path");
187
+ const width = Math.min(maxWidth, contentWidth / CANVAS_UNITS_PER_CSS_PIXEL);
188
+ return await svgToPng(cropped, width, contentHeight * width / contentWidth, options);
189
+ } finally {
190
+ root.unmount();
191
+ host.remove();
192
+ }
193
+ }
194
+ function createDiagramPictureRenderer(options = {}) {
195
+ return async (diagram) => {
196
+ switch (diagram.kind) {
197
+ case "mermaid":
198
+ return renderMermaidPicture(diagram.source, options);
199
+ case "tree":
200
+ return renderTreePicture(diagram.source, options);
201
+ default:
202
+ return renderSquisqDiagramPicture(diagram.markdown, options);
203
+ }
204
+ };
205
+ }
206
+ export {
207
+ EXPORT_FONT_STACK,
208
+ createDiagramPictureRenderer,
209
+ renderMermaidPicture,
210
+ renderSquisqDiagramPicture,
211
+ renderTreePicture,
212
+ svgToPng
213
+ };
@@ -114,6 +114,8 @@ interface PlaybackActions {
114
114
  suppressOutgoingForNextBlock: (blockId: string) => void;
115
115
  }
116
116
  interface UseDocPlaybackOptions {
117
+ /** Fallback media root for intrinsic feature metadata. */
118
+ basePath?: string;
117
119
  /** Target viewport used to materialize template blocks. */
118
120
  viewport?: ViewportConfig;
119
121
  /** Active theme used for materialization and transition defaults. */
@@ -6,7 +6,7 @@ import {
6
6
  useDocPlayback,
7
7
  useMediaSchedule,
8
8
  useViewportOrientation
9
- } from "../chunk-U2FIZWAL.js";
9
+ } from "../chunk-SVVPYDV3.js";
10
10
  import {
11
11
  useAutoSurface
12
12
  } from "../chunk-TT6ENR6T.js";
@@ -16,7 +16,7 @@ import {
16
16
  useMediaProvider,
17
17
  useMediaUrl,
18
18
  useResourcePolicy
19
- } from "../chunk-LR3AIGDD.js";
19
+ } from "../chunk-63G5OJWY.js";
20
20
  export {
21
21
  MediaContext,
22
22
  ResourcePolicyContext,
package/dist/index.js CHANGED
@@ -15,31 +15,17 @@ import {
15
15
  formatTime,
16
16
  resolveDocPlayerAppearance,
17
17
  useMediaClipDurations
18
- } from "./chunk-VUMNXXK4.js";
18
+ } from "./chunk-622IA3KB.js";
19
19
  import {
20
20
  PaginatedDocView
21
- } from "./chunk-7T26IKW2.js";
21
+ } from "./chunk-PSAO2CTF.js";
22
22
  import {
23
- BlockRenderer,
24
23
  CanvasSection,
25
24
  LinearDocView,
26
25
  PageSectionView,
27
26
  PageViewContext,
28
27
  usePageView
29
- } from "./chunk-P3FJ3YKS.js";
30
- import {
31
- ImageLayer,
32
- MapLayer,
33
- MermaidLayer,
34
- PathLayer,
35
- ShapeLayer,
36
- TableLayer,
37
- TextLayer,
38
- TreeLayer,
39
- VideoLayer,
40
- getAnimationStyle,
41
- getTransitionClass
42
- } from "./chunk-WHVSQ42V.js";
28
+ } from "./chunk-2VSD55NT.js";
43
29
  import {
44
30
  useModalDialog
45
31
  } from "./chunk-65POMKHR.js";
@@ -48,11 +34,11 @@ import {
48
34
  useDocPlayback,
49
35
  useMediaSchedule,
50
36
  useViewportOrientation
51
- } from "./chunk-U2FIZWAL.js";
37
+ } from "./chunk-SVVPYDV3.js";
52
38
  import {
53
39
  JsonView,
54
40
  createJsonFormFenceRenderer
55
- } from "./chunk-KDTKUE53.js";
41
+ } from "./chunk-L4LIIOU2.js";
56
42
  import {
57
43
  useAutoSurface
58
44
  } from "./chunk-TT6ENR6T.js";
@@ -64,17 +50,33 @@ import {
64
50
  MarkdownRenderer,
65
51
  useAnimatedImage,
66
52
  useFenceRenderers
67
- } from "./chunk-6PUMLLJH.js";
53
+ } from "./chunk-2FZUFQO3.js";
54
+ import {
55
+ BlockRenderer
56
+ } from "./chunk-5FJZDX7H.js";
57
+ import {
58
+ ImageLayer,
59
+ MapLayer,
60
+ MermaidLayer,
61
+ PathLayer,
62
+ ShapeLayer,
63
+ TableLayer,
64
+ TextLayer,
65
+ TreeLayer,
66
+ VideoLayer,
67
+ getAnimationStyle,
68
+ getTransitionClass
69
+ } from "./chunk-PZCWGBRQ.js";
68
70
  import {
69
71
  MermaidDiagram
70
- } from "./chunk-WLUZTUNZ.js";
72
+ } from "./chunk-QXZXQPL3.js";
71
73
  import {
72
74
  MediaContext,
73
75
  ResourcePolicyContext,
74
76
  useMediaProvider,
75
77
  useMediaUrl,
76
78
  useResourcePolicy
77
- } from "./chunk-LR3AIGDD.js";
79
+ } from "./chunk-63G5OJWY.js";
78
80
  export {
79
81
  AnimatedImageControls,
80
82
  BlockRenderer,
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  JsonView,
3
3
  createJsonFormFenceRenderer
4
- } from "../chunk-KDTKUE53.js";
4
+ } from "../chunk-L4LIIOU2.js";
5
5
  import "../chunk-TT6ENR6T.js";
6
- import "../chunk-6PUMLLJH.js";
7
- import "../chunk-WLUZTUNZ.js";
8
- import "../chunk-LR3AIGDD.js";
6
+ import "../chunk-2FZUFQO3.js";
7
+ import "../chunk-QXZXQPL3.js";
8
+ import "../chunk-63G5OJWY.js";
9
9
  export {
10
10
  JsonView,
11
11
  createJsonFormFenceRenderer
@@ -8,9 +8,9 @@ import {
8
8
  TextLayer,
9
9
  TreeLayer,
10
10
  VideoLayer
11
- } from "../chunk-WHVSQ42V.js";
12
- import "../chunk-WLUZTUNZ.js";
13
- import "../chunk-LR3AIGDD.js";
11
+ } from "../chunk-PZCWGBRQ.js";
12
+ import "../chunk-QXZXQPL3.js";
13
+ import "../chunk-63G5OJWY.js";
14
14
  export {
15
15
  ImageLayer,
16
16
  MapLayer,