@bendyline/squisq-react 2.5.0 → 2.6.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.
package/README.md CHANGED
@@ -57,6 +57,25 @@ state rather than crashing.
57
57
  | `MediaClipLayer` | Hidden `<audio>`/`<video>` elements for timed media clips |
58
58
  | `JsonView` | Read-only viewer for JSON values bound to a Squisq-annotated schema |
59
59
 
60
+ ### Fenced-code copy control
61
+
62
+ `MarkdownRenderer` and the linear document surfaces keep code-copy UI off by
63
+ default. Opt in with `showCodeCopyButton`. Web hosts can rely on
64
+ `navigator.clipboard`; Electron or native embeddings can provide their own
65
+ clipboard bridge:
66
+
67
+ ````tsx
68
+ <LinearDocView
69
+ markdown={'```\n$ node packages/tooling/dist/cli.mjs components\n```'}
70
+ showCodeCopyButton
71
+ onCopyCode={(code, { language }) => hostClipboard.writeText(code)}
72
+ />
73
+ ````
74
+
75
+ The same two props are available on `DocPlayer` (for linear mode), and on the
76
+ standalone static `mount()` options. The callback receives the exact fence
77
+ contents, without the backtick delimiters.
78
+
60
79
  ## Layers
61
80
 
62
81
  Blocks are composed of typed layers rendered as SVG:
@@ -0,0 +1,53 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Theme } from '@bendyline/squisq/schemas';
3
+ import { MarkdownBlockNode, HtmlPolicy } from '@bendyline/squisq/markdown';
4
+
5
+ interface CodeBlockCopyContext {
6
+ /** Authored fence language, when one was supplied. */
7
+ language?: string;
8
+ }
9
+ /**
10
+ * Host-owned clipboard adapter for fenced code blocks.
11
+ *
12
+ * The callback runs directly from the button's click handler so Electron
13
+ * bridges and browser Clipboard APIs retain their user-gesture context.
14
+ */
15
+ type CodeBlockCopyHandler = (code: string, context: CodeBlockCopyContext) => void | Promise<void>;
16
+ interface MarkdownRendererProps {
17
+ /** Block-level AST nodes to render */
18
+ nodes: MarkdownBlockNode[];
19
+ /** Optional CSS class for the wrapper element */
20
+ className?: string;
21
+ /**
22
+ * Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,
23
+ * event handlers, and executable URL schemes before rendering.
24
+ */
25
+ htmlPolicy?: HtmlPolicy;
26
+ /**
27
+ * Extra URL schemes to allow on links (e.g. a host app's internal
28
+ * navigation scheme it intercepts on click). Executable schemes are
29
+ * never allowed regardless. See {@link SanitizeUrlOptions}.
30
+ */
31
+ linkSchemes?: readonly string[];
32
+ /** Resolved Squisq theme inherited by embedded Mermaid diagrams. */
33
+ theme?: Theme;
34
+ /** Show a subtle Copy button on ordinary fenced code blocks (default: false). */
35
+ showCodeCopyButton?: boolean;
36
+ /**
37
+ * Optional host clipboard adapter. When omitted, enabled copy buttons use
38
+ * `navigator.clipboard.writeText`. Supply this for Electron, native shells,
39
+ * or hosts with their own clipboard permission/error handling.
40
+ */
41
+ onCopyCode?: CodeBlockCopyHandler;
42
+ }
43
+ /**
44
+ * Renders MarkdownBlockNode[] AST as React HTML elements.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * <MarkdownRenderer nodes={block.contents} />
49
+ * ```
50
+ */
51
+ declare function MarkdownRenderer({ nodes, className, htmlPolicy, linkSchemes, theme, showCodeCopyButton, onCopyCode, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
52
+
53
+ export { type CodeBlockCopyHandler as C, MarkdownRenderer as M, type CodeBlockCopyContext as a, type MarkdownRendererProps as b };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  BlockRenderer,
3
3
  LinearDocView
4
- } from "./chunk-THDXCSPC.js";
4
+ } from "./chunk-U6P7HBUY.js";
5
5
  import {
6
6
  useAudioSync,
7
7
  useDocPlayback,
8
8
  useMediaSchedule,
9
9
  useViewportOrientation
10
- } from "./chunk-BOZJ655L.js";
10
+ } from "./chunk-MVJQL2W2.js";
11
11
  import {
12
12
  useAutoSurface
13
13
  } from "./chunk-TT6ENR6T.js";
@@ -1859,7 +1859,9 @@ function DocPlayerContent({
1859
1859
  pipShape,
1860
1860
  pipPosition,
1861
1861
  enableSwipe = true,
1862
- globalKeyboardShortcuts = false
1862
+ globalKeyboardShortcuts = false,
1863
+ showCodeCopyButton = false,
1864
+ onCopyCode
1863
1865
  }) {
1864
1866
  const isSlideshowMode = displayMode === "slideshow";
1865
1867
  const isLinearMode = displayMode === "linear";
@@ -1876,12 +1878,17 @@ function DocPlayerContent({
1876
1878
  const params = new URLSearchParams(window.location.search);
1877
1879
  return params.get("debug") === "true";
1878
1880
  }, []);
1881
+ const syntheticDuration = useMemo3(
1882
+ () => audioMode === "synthetic" ? getDocPlaybackDuration(doc) : 0,
1883
+ [audioMode, doc]
1884
+ );
1879
1885
  const internalAudio = useAudioSync(
1880
1886
  audioRef,
1881
1887
  doc.audio,
1882
1888
  basePath,
1883
1889
  !externalAudioController,
1884
- audioMode
1890
+ audioMode,
1891
+ syntheticDuration
1885
1892
  );
1886
1893
  const audio = externalAudioController || internalAudio;
1887
1894
  useEffect5(() => {
@@ -2660,7 +2667,9 @@ function DocPlayerContent({
2660
2667
  viewport: activeViewport,
2661
2668
  theme,
2662
2669
  surface,
2663
- animationsEnabled
2670
+ animationsEnabled,
2671
+ showCodeCopyButton,
2672
+ onCopyCode
2664
2673
  }
2665
2674
  )
2666
2675
  }
@@ -16,7 +16,7 @@ function useMediaSchedule(schedule, currentTime) {
16
16
  }
17
17
 
18
18
  // src/hooks/useAudioSync.ts
19
- import { useState, useEffect, useRef, useCallback } from "react";
19
+ import { useState, useEffect, useMemo as useMemo2, useRef, useCallback } from "react";
20
20
  import { fetchResourceBytes, isResourceUrlAllowed } from "@bendyline/squisq/markdown";
21
21
 
22
22
  // src/hooks/AudioController.ts
@@ -61,7 +61,14 @@ function resolveAudioUrl(src, basePath) {
61
61
  if (!basePath) return src;
62
62
  return `${basePath.replace(/\/$/, "")}/${src.replace(/^\//, "")}`;
63
63
  }
64
- function useAudioSync(audioRef, audioTrack, basePath = "", enabled = true, mode = "media") {
64
+ function useAudioSync(audioRef, track, basePath = "", enabled = true, mode = "media", syntheticDuration = 0) {
65
+ const audioTrack = useMemo2(() => {
66
+ if (mode !== "synthetic" || track?.segments?.length || syntheticDuration <= 0) return track;
67
+ return {
68
+ ...track,
69
+ segments: [{ src: "", name: "synthetic", duration: syntheticDuration, startTime: 0 }]
70
+ };
71
+ }, [track, mode, syntheticDuration]);
65
72
  const resourcePolicy = useResourcePolicy();
66
73
  const [currentTime, setCurrentTime] = useState(0);
67
74
  const [isPlaying, setIsPlaying] = useState(false);
@@ -468,7 +475,7 @@ function useAudioSync(audioRef, audioTrack, basePath = "", enabled = true, mode
468
475
  }
469
476
 
470
477
  // src/hooks/useDocPlayback.ts
471
- import { useMemo as useMemo2, useCallback as useCallback2, useRef as useRef2 } from "react";
478
+ import { useMemo as useMemo3, useCallback as useCallback2, useRef as useRef2 } from "react";
472
479
  import {
473
480
  DEFAULT_THEME,
474
481
  getBlockAtTime,
@@ -489,7 +496,7 @@ function useDocPlayback(script, currentTime, options = {}) {
489
496
  onSeek,
490
497
  useAudioSegmentTiming = true
491
498
  } = options;
492
- const blocks = useMemo2(() => {
499
+ const blocks = useMemo3(() => {
493
500
  if (!script?.blocks) {
494
501
  return [];
495
502
  }
@@ -532,20 +539,20 @@ function useDocPlayback(script, currentTime, options = {}) {
532
539
  theme,
533
540
  useAudioSegmentTiming
534
541
  ]);
535
- const currentBlock = useMemo2(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
536
- const currentBlockIndex = useMemo2(
542
+ const currentBlock = useMemo3(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
543
+ const currentBlockIndex = useMemo3(
537
544
  () => currentBlock ? blocks.indexOf(currentBlock) : -1,
538
545
  [blocks, currentBlock]
539
546
  );
540
- const blockTime = useMemo2(() => {
547
+ const blockTime = useMemo3(() => {
541
548
  if (!currentBlock) return 0;
542
549
  return Math.max(0, currentTime - currentBlock.startTime);
543
550
  }, [currentBlock, currentTime]);
544
- const blockProgress = useMemo2(() => {
551
+ const blockProgress = useMemo3(() => {
545
552
  if (!currentBlock || currentBlock.duration === 0) return 0;
546
553
  return Math.min(1, blockTime / currentBlock.duration);
547
554
  }, [currentBlock, blockTime]);
548
- const docProgress = useMemo2(() => {
555
+ const docProgress = useMemo3(() => {
549
556
  if (!script || script.duration === 0) return 0;
550
557
  return Math.min(1, currentTime / script.duration);
551
558
  }, [script, currentTime]);
@@ -612,7 +619,7 @@ function useDocPlayback(script, currentTime, options = {}) {
612
619
  }
613
620
 
614
621
  // src/hooks/useViewportOrientation.ts
615
- import { useState as useState2, useEffect as useEffect2, useMemo as useMemo3 } from "react";
622
+ import { useState as useState2, useEffect as useEffect2, useMemo as useMemo4 } from "react";
616
623
  import {
617
624
  VIEWPORT_PRESETS as VIEWPORT_PRESETS2
618
625
  } from "@bendyline/squisq/doc";
@@ -661,11 +668,11 @@ function useViewportOrientation() {
661
668
  clearTimeout(timeoutId);
662
669
  };
663
670
  }, []);
664
- const orientation = useMemo3(
671
+ const orientation = useMemo4(
665
672
  () => getOrientationFromWindow(windowSize.width, windowSize.height),
666
673
  [windowSize.width, windowSize.height]
667
674
  );
668
- const viewport = useMemo3(() => getViewportForOrientation(orientation), [orientation]);
675
+ const viewport = useMemo4(() => getViewportForOrientation(orientation), [orientation]);
669
676
  return {
670
677
  viewport,
671
678
  orientation,
@@ -50,13 +50,72 @@ function InlineAudioPlayer({
50
50
  }
51
51
 
52
52
  // src/MarkdownRenderer.tsx
53
- import { Fragment } from "react";
53
+ import { Fragment, useEffect, useRef, useState } from "react";
54
54
  import {
55
55
  sanitizeHtmlNodes,
56
56
  sanitizeUrl
57
57
  } from "@bendyline/squisq/markdown";
58
58
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
59
59
  var DEFAULT_CTX = { htmlPolicy: "sanitize" };
60
+ var CODE_COPY_LABELS = {
61
+ idle: "Copy",
62
+ copying: "Copying\u2026",
63
+ copied: "Copied",
64
+ failed: "Copy failed"
65
+ };
66
+ async function writeCodeToClipboard(code, language, onCopyCode) {
67
+ if (onCopyCode) {
68
+ await onCopyCode(code, language === void 0 ? {} : { language });
69
+ return;
70
+ }
71
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
72
+ throw new Error("Clipboard access is unavailable in this host");
73
+ }
74
+ await navigator.clipboard.writeText(code);
75
+ }
76
+ function CodeBlock({
77
+ value,
78
+ language,
79
+ ctx
80
+ }) {
81
+ const [status, setStatus] = useState("idle");
82
+ const resetTimer = useRef();
83
+ useEffect(
84
+ () => () => {
85
+ if (resetTimer.current !== void 0) clearTimeout(resetTimer.current);
86
+ },
87
+ []
88
+ );
89
+ const pre = /* @__PURE__ */ jsx3("pre", { className: "squisq-md-code-block", children: /* @__PURE__ */ jsx3("code", { className: language ? `language-${language}` : void 0, children: value }) });
90
+ if (!ctx.showCodeCopyButton) return pre;
91
+ const handleCopy = async () => {
92
+ if (status === "copying") return;
93
+ setStatus("copying");
94
+ try {
95
+ await writeCodeToClipboard(value, language ?? void 0, ctx.onCopyCode);
96
+ setStatus("copied");
97
+ } catch {
98
+ setStatus("failed");
99
+ }
100
+ if (resetTimer.current !== void 0) clearTimeout(resetTimer.current);
101
+ resetTimer.current = setTimeout(() => setStatus("idle"), 1600);
102
+ };
103
+ return /* @__PURE__ */ jsxs("div", { className: "squisq-md-code-frame", children: [
104
+ pre,
105
+ /* @__PURE__ */ jsx3(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ className: "squisq-md-code-copy",
110
+ "data-copy-state": status,
111
+ disabled: status === "copying",
112
+ onClick: () => void handleCopy(),
113
+ "aria-label": "Copy code to clipboard",
114
+ children: CODE_COPY_LABELS[status]
115
+ }
116
+ )
117
+ ] });
118
+ }
60
119
  function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
61
120
  return nodes.map((node, i) => {
62
121
  const key = `${keyPrefix}i${i}`;
@@ -181,7 +240,7 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
181
240
  key
182
241
  );
183
242
  }
184
- return /* @__PURE__ */ jsx3("pre", { className: "squisq-md-code-block", children: /* @__PURE__ */ jsx3("code", { className: node.lang ? `language-${node.lang}` : void 0, children: node.value }) }, key);
243
+ return /* @__PURE__ */ jsx3(CodeBlock, { value: node.value, language: node.lang, ctx }, key);
185
244
  case "thematicBreak":
186
245
  return /* @__PURE__ */ jsx3("hr", { className: "squisq-md-hr" }, key);
187
246
  case "table":
@@ -393,10 +452,18 @@ function MarkdownRenderer({
393
452
  className,
394
453
  htmlPolicy = "sanitize",
395
454
  linkSchemes,
396
- theme
455
+ theme,
456
+ showCodeCopyButton = false,
457
+ onCopyCode
397
458
  }) {
398
459
  if (!nodes || nodes.length === 0) return null;
399
- return /* @__PURE__ */ jsx3("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", { htmlPolicy, linkSchemes, theme }) });
460
+ return /* @__PURE__ */ jsx3("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", {
461
+ htmlPolicy,
462
+ linkSchemes,
463
+ theme,
464
+ showCodeCopyButton,
465
+ onCopyCode
466
+ }) });
400
467
  }
401
468
 
402
469
  export {
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  InlineVideoPlayer,
18
18
  MarkdownRenderer
19
- } from "./chunk-VMPQEUJH.js";
19
+ } from "./chunk-TMCLQNLM.js";
20
20
  import {
21
21
  useMediaUrl
22
22
  } from "./chunk-LR3AIGDD.js";
@@ -165,7 +165,8 @@ var defaultValue = {
165
165
  viewport: VIEWPORT_PRESETS.landscape,
166
166
  renderContext: { theme: DEFAULT_THEME },
167
167
  animationsEnabled: true,
168
- imageDisplayMode: "inline"
168
+ imageDisplayMode: "inline",
169
+ showCodeCopyButton: false
169
170
  };
170
171
  var PageViewContext = createContext(defaultValue);
171
172
  function usePageView() {
@@ -438,11 +439,19 @@ function CardGridSection({ section }) {
438
439
  ] });
439
440
  }
440
441
  function ItemListSection({ section }) {
441
- const { theme } = usePageView();
442
+ const { theme, showCodeCopyButton, onCopyCode } = usePageView();
442
443
  const items = section.slots.items ?? [];
443
444
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
444
445
  section.slots.title && /* @__PURE__ */ jsx3("h2", { className: "squisq-page-section-title squisq-page-items-title", children: section.slots.title }),
445
- /* @__PURE__ */ jsx3("ol", { className: "squisq-page-items", children: items.map((item, i) => /* @__PURE__ */ jsx3("li", { children: item.markdown ? /* @__PURE__ */ jsx3(MarkdownRenderer, { nodes: item.markdown, theme }) : item.body }, i)) })
446
+ /* @__PURE__ */ jsx3("ol", { className: "squisq-page-items", children: items.map((item, i) => /* @__PURE__ */ jsx3("li", { children: item.markdown ? /* @__PURE__ */ jsx3(
447
+ MarkdownRenderer,
448
+ {
449
+ nodes: item.markdown,
450
+ theme,
451
+ showCodeCopyButton,
452
+ onCopyCode
453
+ }
454
+ ) : item.body }, i)) })
446
455
  ] });
447
456
  }
448
457
  function TimelineRailSection({ section }) {
@@ -470,12 +479,28 @@ function TableSection({ section }) {
470
479
  ] });
471
480
  }
472
481
  function ProseSection({ section, block }) {
473
- const { theme } = usePageView();
474
- const heading = section.slots.title !== void 0 && block?.sourceHeading ? /* @__PURE__ */ jsx3(MarkdownRenderer, { nodes: [block.sourceHeading], theme }) : null;
482
+ const { theme, showCodeCopyButton, onCopyCode } = usePageView();
483
+ const heading = section.slots.title !== void 0 && block?.sourceHeading ? /* @__PURE__ */ jsx3(
484
+ MarkdownRenderer,
485
+ {
486
+ nodes: [block.sourceHeading],
487
+ theme,
488
+ showCodeCopyButton,
489
+ onCopyCode
490
+ }
491
+ ) : null;
475
492
  const bodyNodes = section.slots.body?.markdown ?? block?.contents;
476
493
  return /* @__PURE__ */ jsxs2("div", { className: "squisq-page-prose", children: [
477
494
  heading,
478
- bodyNodes && bodyNodes.length > 0 && /* @__PURE__ */ jsx3(MarkdownRenderer, { nodes: bodyNodes, theme })
495
+ bodyNodes && bodyNodes.length > 0 && /* @__PURE__ */ jsx3(
496
+ MarkdownRenderer,
497
+ {
498
+ nodes: bodyNodes,
499
+ theme,
500
+ showCodeCopyButton,
501
+ onCopyCode
502
+ }
503
+ )
479
504
  ] });
480
505
  }
481
506
  function FooterSection({ section }) {
@@ -537,7 +562,7 @@ function sectionBody(entry, featureFlip) {
537
562
  }
538
563
  }
539
564
  function PageSectionView({ entry, featureFlip, isLeadProse }) {
540
- const { pageStyle, theme } = usePageView();
565
+ const { pageStyle, theme, showCodeCopyButton, onCopyCode } = usePageView();
541
566
  const { section } = entry;
542
567
  const richContent = section.slots.richContent?.markdown;
543
568
  const classes = [
@@ -566,7 +591,15 @@ function PageSectionView({ entry, featureFlip, isLeadProse }) {
566
591
  ...hintAttrs,
567
592
  children: /* @__PURE__ */ jsxs3("div", { className: "squisq-page-section-inner", children: [
568
593
  sectionBody(entry, featureFlip),
569
- richContent && richContent.length > 0 && /* @__PURE__ */ jsx4("div", { className: "squisq-page-rich-content", children: /* @__PURE__ */ jsx4(MarkdownRenderer, { nodes: richContent, theme }) })
594
+ richContent && richContent.length > 0 && /* @__PURE__ */ jsx4("div", { className: "squisq-page-rich-content", children: /* @__PURE__ */ jsx4(
595
+ MarkdownRenderer,
596
+ {
597
+ nodes: richContent,
598
+ theme,
599
+ showCodeCopyButton,
600
+ onCopyCode
601
+ }
602
+ ) })
570
603
  ] })
571
604
  }
572
605
  );
@@ -603,7 +636,9 @@ function LinearDocView({
603
636
  imageDisplayMode = "inline",
604
637
  globalKeyboardShortcuts = false,
605
638
  showCover = true,
606
- transformPage
639
+ transformPage,
640
+ showCodeCopyButton = false,
641
+ onCopyCode
607
642
  }) {
608
643
  const scrollRef = useRef(null);
609
644
  const activeViewport = viewport ?? VIEWPORT_PRESETS2.landscape;
@@ -650,7 +685,9 @@ function LinearDocView({
650
685
  viewport: activeViewport,
651
686
  renderContext,
652
687
  animationsEnabled,
653
- imageDisplayMode
688
+ imageDisplayMode,
689
+ showCodeCopyButton,
690
+ onCopyCode
654
691
  }),
655
692
  [
656
693
  activeTheme,
@@ -659,7 +696,9 @@ function LinearDocView({
659
696
  activeViewport,
660
697
  renderContext,
661
698
  animationsEnabled,
662
- imageDisplayMode
699
+ imageDisplayMode,
700
+ showCodeCopyButton,
701
+ onCopyCode
663
702
  ]
664
703
  );
665
704
  const { featureOrdinals, leadProseIndex } = useMemo2(() => {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TT6ENR6T.js";
4
4
  import {
5
5
  MarkdownRenderer
6
- } from "./chunk-VMPQEUJH.js";
6
+ } from "./chunk-TMCLQNLM.js";
7
7
 
8
8
  // src/jsonView/useJsonViewTokens.ts
9
9
  import { useMemo } from "react";
@@ -18,10 +18,14 @@ import { ResourcePolicy } from '@bendyline/squisq/markdown';
18
18
  * This is the HTML5 Audio implementation of the AudioController interface.
19
19
  * Hosts that drive audio through an external player (e.g. a native shell)
20
20
  * can supply their own AudioController to DocPlayer instead of this hook.
21
+ *
22
+ * In `synthetic` mode there is no audio element to follow, so a caller that
23
+ * has no segments passes `syntheticDuration` — the document's own timeline
24
+ * length — and the same segment machinery drives a timer instead.
21
25
  */
22
26
 
23
27
  type AudioSyncMode = 'media' | 'synthetic';
24
- declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>, audioTrack: AudioTrack | undefined, basePath?: string, enabled?: boolean, mode?: AudioSyncMode): AudioController;
28
+ declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>, track: AudioTrack | undefined, basePath?: string, enabled?: boolean, mode?: AudioSyncMode, syntheticDuration?: number): AudioController;
25
29
 
26
30
  interface ModalDialogOptions {
27
31
  /** The backdrop/portal root. Siblings of this branch are made inert. */
@@ -6,7 +6,7 @@ import {
6
6
  useDocPlayback,
7
7
  useMediaSchedule,
8
8
  useViewportOrientation
9
- } from "../chunk-BOZJ655L.js";
9
+ } from "../chunk-MVJQL2W2.js";
10
10
  import {
11
11
  useAutoSurface
12
12
  } from "../chunk-TT6ENR6T.js";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { SquisqRenderAPI, VideoPresentation as VideoPresentation$1, PipSize, PipShape, PipPosition } from './player/index.js';
2
2
  export { BlockMarker, BlockRenderer, CaptionMode, CaptionOverlay, CaptionStyle, ControlsLayout, DisplayMode, DocControlsBottom, DocControlsOverlay, DocControlsSidebar, DocControlsSlideshow, DocPlayer, DocPlayerProps, DocPlayerWithSidebar, DocProgressBar, InlineAudioPlayer, InlineAudioPlayerProps, InlineVideoPlayer, InlineVideoPlayerProps, MediaClipLayer, MediaClipLayerProps, PlaybackActions, PlaybackState, RenderAudioSegmentInfo, RenderBlockInfo, RenderCaptionInfo, RenderChapterInfo, SlideNavActions, SocialCaptionOverlay, formatTime } from './player/index.js';
3
3
  import { Theme, VideoPresentation, VideoPipSize, VideoPipShape, VideoPipPosition, Doc, ScheduledClip, MediaProvider } from '@bendyline/squisq/schemas';
4
- export { MarkdownRenderer, MermaidDiagram, MermaidDiagramProps } from './markdown/index.js';
4
+ import { C as CodeBlockCopyHandler } from './MarkdownRenderer-CerBKw-c.js';
5
+ export { a as CodeBlockCopyContext, M as MarkdownRenderer, b as MarkdownRendererProps } from './MarkdownRenderer-CerBKw-c.js';
6
+ export { MermaidDiagram, MermaidDiagramProps } from './markdown/index.js';
5
7
  export { CanvasSection, CanvasSectionProps, ImageDisplayMode, LinearDocView, LinearDocViewProps, PageSectionView, PageSectionViewProps, PageViewContext, PageViewContextValue, usePageView } from './page/index.js';
6
8
  export { ImageLayer, MapLayer, MermaidLayer, PathLayer, ShapeLayer, TableLayer, TextLayer, TreeLayer, VideoLayer } from './layers/index.js';
7
9
  import { CoverSlideTemplate, CoverSlidePlayback } from '@bendyline/squisq/doc';
@@ -84,6 +86,10 @@ interface MountOptions {
84
86
  animationsEnabled?: boolean;
85
87
  /** Caption style: 'standard' or 'social'. Omit or set to undefined for no captions. */
86
88
  captionStyle?: 'standard' | 'social';
89
+ /** Show a Copy button on fenced code blocks in static mode (default: false). */
90
+ showCodeCopyButton?: boolean;
91
+ /** Optional host clipboard adapter; otherwise the browser Clipboard API is used. */
92
+ onCopyCode?: CodeBlockCopyHandler;
87
93
  }
88
94
  /** Instance handle returned by {@link mount}. */
89
95
  interface SquisqPlayerHandle {
@@ -147,4 +153,4 @@ declare function resolveDocPlayerAppearance(doc: Doc, overrides?: DocPlayerAppea
147
153
  */
148
154
  declare function useMediaClipDurations(schedule: ScheduledClip[], basePath: string, mediaProviderOverride?: MediaProvider | null): Map<string, number>;
149
155
 
150
- export { type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useMediaClipDurations };
156
+ export { CodeBlockCopyHandler, type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useMediaClipDurations };
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  formatTime,
13
13
  resolveDocPlayerAppearance,
14
14
  useMediaClipDurations
15
- } from "./chunk-ZKNX3EJI.js";
15
+ } from "./chunk-GWD62KLD.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-THDXCSPC.js";
23
+ } from "./chunk-U6P7HBUY.js";
24
24
  import {
25
25
  ImageLayer,
26
26
  MapLayer,
@@ -42,10 +42,10 @@ import {
42
42
  useDocPlayback,
43
43
  useMediaSchedule,
44
44
  useViewportOrientation
45
- } from "./chunk-BOZJ655L.js";
45
+ } from "./chunk-MVJQL2W2.js";
46
46
  import {
47
47
  JsonView
48
- } from "./chunk-DJRYQQXB.js";
48
+ } from "./chunk-YKBVTYU4.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-VMPQEUJH.js";
56
+ } from "./chunk-TMCLQNLM.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-DJRYQQXB.js";
3
+ } from "../chunk-YKBVTYU4.js";
4
4
  import "../chunk-TT6ENR6T.js";
5
- import "../chunk-VMPQEUJH.js";
5
+ import "../chunk-TMCLQNLM.js";
6
6
  import "../chunk-WLUZTUNZ.js";
7
7
  import "../chunk-LR3AIGDD.js";
8
8
  export {
@@ -1,35 +1,7 @@
1
+ export { a as CodeBlockCopyContext, C as CodeBlockCopyHandler, M as MarkdownRenderer, b as MarkdownRendererProps } from '../MarkdownRenderer-CerBKw-c.js';
1
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
  import { Theme } from '@bendyline/squisq/schemas';
3
- import { MarkdownBlockNode, HtmlPolicy } from '@bendyline/squisq/markdown';
4
-
5
- interface MarkdownRendererProps {
6
- /** Block-level AST nodes to render */
7
- nodes: MarkdownBlockNode[];
8
- /** Optional CSS class for the wrapper element */
9
- className?: string;
10
- /**
11
- * Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,
12
- * event handlers, and executable URL schemes before rendering.
13
- */
14
- htmlPolicy?: HtmlPolicy;
15
- /**
16
- * Extra URL schemes to allow on links (e.g. a host app's internal
17
- * navigation scheme it intercepts on click). Executable schemes are
18
- * never allowed regardless. See {@link SanitizeUrlOptions}.
19
- */
20
- linkSchemes?: readonly string[];
21
- /** Resolved Squisq theme inherited by embedded Mermaid diagrams. */
22
- theme?: Theme;
23
- }
24
- /**
25
- * Renders MarkdownBlockNode[] AST as React HTML elements.
26
- *
27
- * @example
28
- * ```tsx
29
- * <MarkdownRenderer nodes={block.contents} />
30
- * ```
31
- */
32
- declare function MarkdownRenderer({ nodes, className, htmlPolicy, linkSchemes, theme, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
4
+ import '@bendyline/squisq/markdown';
33
5
 
34
6
  interface MermaidDiagramProps {
35
7
  source: string;
@@ -41,4 +13,4 @@ interface MermaidDiagramProps {
41
13
  /** Read-only Mermaid rendering for page bodies and slide layers. */
42
14
  declare function MermaidDiagram({ source, className, ariaLabel, theme, }: MermaidDiagramProps): react_jsx_runtime.JSX.Element;
43
15
 
44
- export { MarkdownRenderer, MermaidDiagram, type MermaidDiagramProps };
16
+ export { MermaidDiagram, type MermaidDiagramProps };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MarkdownRenderer
3
- } from "../chunk-VMPQEUJH.js";
3
+ } from "../chunk-TMCLQNLM.js";
4
4
  import {
5
5
  MermaidDiagram
6
6
  } from "../chunk-WLUZTUNZ.js";