@bendyline/squisq-editor-react 1.5.3 → 1.6.1

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.
Files changed (186) hide show
  1. package/README.md +57 -10
  2. package/dist/index.d.ts +1114 -52
  3. package/dist/index.js +18446 -8050
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles/fa-brands-400-AHOAZHCU.woff2 +0 -0
  6. package/dist/styles/fa-regular-400-VRZYIBIZ.woff2 +0 -0
  7. package/dist/styles/fa-solid-900-MDEYK55F.woff2 +0 -0
  8. package/dist/styles/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
  9. package/dist/styles/index.css +13867 -0
  10. package/package.json +15 -7
  11. package/src/BlockCardView.tsx +121 -0
  12. package/src/BlockPreviewPanel.tsx +69 -0
  13. package/src/BlockPropertiesPopover.tsx +191 -0
  14. package/src/EditorContext.tsx +165 -16
  15. package/src/EditorShell.tsx +265 -144
  16. package/src/FolderView.tsx +131 -0
  17. package/src/Icon.tsx +26 -0
  18. package/src/ImageEditor.tsx +69 -22
  19. package/src/OutlinePanel.tsx +64 -7
  20. package/src/PlainHtmlPreview.tsx +30 -3
  21. package/src/PreviewControls.tsx +491 -122
  22. package/src/PreviewPanel.tsx +15 -9
  23. package/src/RawEditor.tsx +179 -17
  24. package/src/RecorderEntry.tsx +9 -16
  25. package/src/TemplateAnnotation.ts +44 -0
  26. package/src/TemplatePicker.tsx +329 -54
  27. package/src/ThemeCustomizerPanel.tsx +30 -336
  28. package/src/ThemePicker.tsx +112 -3
  29. package/src/TimelineBlockPreview.tsx +37 -0
  30. package/src/TimelineTrack.tsx +671 -0
  31. package/src/Toolbar.tsx +544 -180
  32. package/src/Tooltip.tsx +22 -4
  33. package/src/TransitionPicker.tsx +351 -0
  34. package/src/VersionHistoryPanel.tsx +4 -16
  35. package/src/ViewMenuPanel.tsx +17 -14
  36. package/src/WysiwygEditor.tsx +161 -65
  37. package/src/__tests__/blockProperties.test.ts +92 -0
  38. package/src/__tests__/blockRange.test.ts +105 -0
  39. package/src/__tests__/buildPreviewDocTransition.test.ts +73 -0
  40. package/src/__tests__/codeContextSectionView.test.tsx +95 -0
  41. package/src/__tests__/codeContextZoneManager.test.ts +127 -0
  42. package/src/__tests__/createShapeLayer.test.ts +46 -0
  43. package/src/__tests__/diffContextSections.test.ts +39 -0
  44. package/src/__tests__/drawingShapeRoundTrip.test.ts +49 -0
  45. package/src/__tests__/editorShellCodeContext.test.tsx +86 -0
  46. package/src/__tests__/editorShellProps.test.tsx +96 -0
  47. package/src/__tests__/embeddedMedia.test.ts +48 -0
  48. package/src/__tests__/headingTransition.test.ts +138 -0
  49. package/src/__tests__/layoutChildRoundTrip.test.ts +71 -0
  50. package/src/__tests__/plainHtmlPreview.test.tsx +10 -8
  51. package/src/__tests__/previewControls.test.tsx +70 -0
  52. package/src/__tests__/recorderMediaInsert.test.ts +86 -0
  53. package/src/__tests__/templateAnnotationRoundTrip.test.ts +18 -0
  54. package/src/__tests__/templatePickerMetadata.test.ts +32 -0
  55. package/src/__tests__/timelineSource.test.ts +134 -0
  56. package/src/__tests__/tiptapBridge.test.ts +92 -0
  57. package/src/__tests__/tiptapBridgeConformance.test.ts +47 -0
  58. package/src/__tests__/tooltip.test.tsx +72 -0
  59. package/src/__tests__/transitionCatalog.test.ts +64 -0
  60. package/src/__tests__/useBlockNavigator.test.tsx +67 -0
  61. package/src/__tests__/useJsonEditorTokens.test.ts +59 -0
  62. package/src/__tests__/useMediaRecorder.test.ts +41 -0
  63. package/src/__tests__/useTimelineClock.test.ts +21 -0
  64. package/src/blockProperties.ts +88 -0
  65. package/src/blockRange.ts +132 -0
  66. package/src/buildPreviewDoc.ts +98 -9
  67. package/src/codeContext/CodeContextSectionView.tsx +124 -0
  68. package/src/codeContext/CodeContextZoneManager.ts +149 -0
  69. package/src/codeContext/CodeContextZones.tsx +121 -0
  70. package/src/codeContext/diffContextSections.ts +38 -0
  71. package/src/codeContext/types.ts +75 -0
  72. package/src/customTemplates/AddBin.tsx +126 -0
  73. package/src/customTemplates/CustomLayoutManager.tsx +233 -0
  74. package/src/customTemplates/CustomTemplateContext.tsx +182 -0
  75. package/src/customTemplates/LayerToolbar.tsx +580 -0
  76. package/src/customTemplates/ShapeGlyph.tsx +47 -0
  77. package/src/customTemplates/TemplateDesigner.tsx +430 -0
  78. package/src/customTemplates/__tests__/library.test.ts +88 -0
  79. package/src/customTemplates/__tests__/normalizePositions.test.ts +109 -0
  80. package/src/customTemplates/__tests__/shapeDefs.test.ts +49 -0
  81. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +95 -0
  82. package/src/customTemplates/designer.css +673 -0
  83. package/src/customTemplates/index.ts +31 -0
  84. package/src/customTemplates/library.ts +97 -0
  85. package/src/customTemplates/normalizePositions.ts +75 -0
  86. package/src/customTemplates/shapeDefs.ts +131 -0
  87. package/src/customTemplates/thumbnail.tsx +63 -0
  88. package/src/customTemplates/tokenDefs.ts +60 -0
  89. package/src/customTemplates/useDocCustomTemplates.ts +52 -0
  90. package/src/customTemplates/useMemoryLayerAdapter.ts +123 -0
  91. package/src/customThemes/CustomThemeContext.tsx +179 -0
  92. package/src/customThemes/CustomThemeDialog.tsx +286 -0
  93. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +64 -0
  94. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +47 -0
  95. package/src/customThemes/__tests__/customThemeLibrary.test.ts +51 -0
  96. package/src/customThemes/customThemeLibrary.ts +97 -0
  97. package/src/customThemes/index.ts +31 -0
  98. package/src/customThemes/themeControls.tsx +229 -0
  99. package/src/customThemes/themeDraft.ts +272 -0
  100. package/src/customThemes/useDocCustomThemes.ts +49 -0
  101. package/src/diagram/DiagramCanvas.tsx +240 -0
  102. package/src/diagram/DiagramExtension.ts +209 -0
  103. package/src/diagram/DiagramMaximizedOverlay.tsx +46 -0
  104. package/src/diagram/DiagramWidget.tsx +270 -0
  105. package/src/diagram/diagramCommands.ts +604 -0
  106. package/src/diagram/diagramConstants.ts +17 -0
  107. package/src/diagram/useDiagramData.ts +126 -0
  108. package/src/embeddedMedia.ts +78 -0
  109. package/src/frontmatter.ts +29 -0
  110. package/src/headingTransition.ts +231 -0
  111. package/src/imageEditor/CanvasSurface.tsx +383 -88
  112. package/src/imageEditor/PropertiesPanel.tsx +47 -1
  113. package/src/imageEditor/Toolbar.tsx +229 -16
  114. package/src/imageEditor/createShapeLayer.ts +280 -0
  115. package/src/imageEditor/icons.tsx +34 -114
  116. package/src/imageEditor/image-editor.css +54 -5
  117. package/src/imageEditor/state.ts +23 -3
  118. package/src/index.ts +109 -1
  119. package/src/jsonEditor/useJsonEditorTokens.ts +13 -43
  120. package/src/recorder/RecorderModal.tsx +120 -53
  121. package/src/recorder/RecorderPanel.tsx +2 -26
  122. package/src/recorder/hooks/useMediaRecorder.ts +17 -11
  123. package/src/recorder/insertMediaBlock.ts +30 -0
  124. package/src/resolveBlockVisual.ts +33 -0
  125. package/src/scene/Scene.tsx +540 -0
  126. package/src/scene/SceneBlockExtension.ts +198 -0
  127. package/src/scene/SceneBlockToolbar.tsx +201 -0
  128. package/src/scene/SceneBlockWidget.tsx +434 -0
  129. package/src/scene/ScenePropsBar.tsx +85 -0
  130. package/src/scene/SceneSelection.tsx +107 -0
  131. package/src/scene/SceneViewport.tsx +102 -0
  132. package/src/scene/ShapePalette.tsx +181 -0
  133. package/src/scene/__tests__/DiagramAdapter.test.ts +56 -0
  134. package/src/scene/__tests__/bezierEdit.test.ts +85 -0
  135. package/src/scene/__tests__/blockLayers.test.ts +57 -0
  136. package/src/scene/__tests__/shapeLayers.test.ts +106 -0
  137. package/src/scene/__tests__/useSceneHitTest.test.ts +90 -0
  138. package/src/scene/__tests__/useScenePanZoom.test.ts +103 -0
  139. package/src/scene/adapters/DiagramAdapter.ts +168 -0
  140. package/src/scene/adapters/DrawingAdapter.ts +415 -0
  141. package/src/scene/adapters/LayoutAdapter.ts +310 -0
  142. package/src/scene/adapters/blockLayers.ts +159 -0
  143. package/src/scene/commands/SceneCommand.ts +70 -0
  144. package/src/scene/commands/drawingCommands.ts +318 -0
  145. package/src/scene/commands/layoutCommands.ts +301 -0
  146. package/src/scene/hooks/useSceneHitTest.ts +105 -0
  147. package/src/scene/hooks/useScenePanZoom.ts +147 -0
  148. package/src/scene/hooks/useSceneSelection.ts +62 -0
  149. package/src/scene/index.ts +95 -0
  150. package/src/scene/layers/DiagramEdges.tsx +127 -0
  151. package/src/scene/layers/edgeGeometry.ts +77 -0
  152. package/src/scene/layers/nodeCard.tsx +145 -0
  153. package/src/scene/layers/renderLayer.tsx +70 -0
  154. package/src/scene/layers/shapeLayers.ts +201 -0
  155. package/src/scene/paths/bezierEdit.ts +208 -0
  156. package/src/scene/scene.css +649 -0
  157. package/src/scene/text/SceneTextOverlay.tsx +161 -0
  158. package/src/scene/text/sceneTextChannel.ts +40 -0
  159. package/src/scene/text/sceneTextConfig.ts +27 -0
  160. package/src/scene/text/sceneTiptap.ts +36 -0
  161. package/src/scene/text/useSceneTextEditing.ts +39 -0
  162. package/src/scene/tools/ConnectTool.ts +111 -0
  163. package/src/scene/tools/DrawingConnectTool.ts +161 -0
  164. package/src/scene/tools/PathTool.ts +158 -0
  165. package/src/scene/tools/PlaceTool.ts +47 -0
  166. package/src/scene/tools/SceneTool.ts +75 -0
  167. package/src/scene/tools/SelectTool.ts +284 -0
  168. package/src/scene/tools/ShapeTool.ts +144 -0
  169. package/src/scene/tools/TextTool.ts +72 -0
  170. package/src/scene/tools/TokenTool.ts +95 -0
  171. package/src/scene/tools/createDrawShapeTool.ts +82 -0
  172. package/src/styles/code-context.css +155 -0
  173. package/src/styles/diagram.css +183 -0
  174. package/src/styles/editor.css +1708 -150
  175. package/src/styles/folder-view.css +210 -0
  176. package/src/styles/image-edit-affordance.css +2 -2
  177. package/src/styles/index.css +5 -0
  178. package/src/timelineSource.ts +244 -0
  179. package/src/tiptapBridge.ts +115 -34
  180. package/src/tooltipPlacement.ts +13 -0
  181. package/src/transitionCatalog.ts +159 -0
  182. package/src/types/monaco-shims.d.ts +10 -0
  183. package/src/useBlockNavigator.ts +153 -0
  184. package/src/useMonacoLoader.ts +23 -1
  185. package/src/useTimelineClock.ts +76 -0
  186. package/src/utils/dropUtils.ts +1 -1
package/dist/index.d.ts CHANGED
@@ -1,19 +1,47 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode, CSSProperties, RefObject } from 'react';
4
- import { Doc, MediaProvider, Theme, ViewportPreset, ViewportConfig, SurfaceScheme, ImageEditDoc, ImageEditLayer } from '@bendyline/squisq/schemas';
5
- import { MarkdownDocument } from '@bendyline/squisq/markdown';
4
+ import { Doc, MediaProvider, Theme, ViewportPreset, ViewportConfig, CustomTemplateDefinition, SurfaceScheme, ImageEditDoc, ImageEditLayer } from '@bendyline/squisq/schemas';
5
+ import { MarkdownDocument, HeadingAttributes } from '@bendyline/squisq/markdown';
6
6
  import { ContentContainer } from '@bendyline/squisq/storage';
7
7
  import { DocumentVersionManager, SaveVersionOptions, SaveVersionResult, PrunePolicy } from '@bendyline/squisq/versions';
8
8
  import * as _tiptap_core from '@tiptap/core';
9
- import { Editor } from '@tiptap/core';
9
+ import { Editor, Extension } from '@tiptap/core';
10
+ import * as monaco_editor from 'monaco-editor';
10
11
  import { editor } from 'monaco-editor';
11
12
  import { IconFamily } from '@bendyline/squisq/icons';
12
13
  import { DisplayMode, CaptionStyle } from '@bendyline/squisq-react';
14
+ import { Editor as Editor$1 } from '@tiptap/react';
13
15
  import * as _tiptap_extension_heading from '@tiptap/extension-heading';
16
+ import { Node } from '@tiptap/pm/model';
14
17
  import { SquisqAnnotatedSchema, JsonFormValidator, JsonFormValidationError } from '@bendyline/squisq/jsonForm';
15
18
  import { ImageEditExportFormat, ImageEditVersionManager } from '@bendyline/squisq/imageEdit';
16
19
 
20
+ /**
21
+ * Configuration a Scene host (diagram / drawing / layout widget) supplies
22
+ * so the shared inline text editor knows how to read and write the text of
23
+ * a given layer. The Scene owns the editing UI (overlay + positioning);
24
+ * the host owns persistence (markdown heading vs. layout JSON blob).
25
+ */
26
+ type SceneTextLevel = 'inline' | 'block' | 'rich';
27
+
28
+ /**
29
+ * sceneTextChannel — a module singleton that bridges the canvas's inline
30
+ * text editor (which renders in a **detached React root** created by the
31
+ * Diagram/SceneBlock ProseMirror extensions, outside `<EditorProvider>`)
32
+ * to the provider, so the top formatting toolbar can target it.
33
+ *
34
+ * The active textbox's `SceneTextOverlay` publishes its Tiptap editor here
35
+ * on focus and clears it on blur/unmount; `EditorProvider` subscribes and
36
+ * mirrors the handle into `activeSceneText`. Singleton because only one
37
+ * canvas textbox can be focused at a time.
38
+ */
39
+
40
+ interface SceneTextHandle {
41
+ editor: Editor;
42
+ level: SceneTextLevel;
43
+ }
44
+
17
45
  /** Monaco standalone code editor instance type */
18
46
  type MonacoEditor = editor.IStandaloneCodeEditor;
19
47
  /**
@@ -63,7 +91,22 @@ interface DocumentLinkCandidate {
63
91
  */
64
92
  type DocumentLinkProvider = (query: string) => Promise<DocumentLinkCandidate[]>;
65
93
  type EditorView = 'raw' | 'wysiwyg' | 'preview';
66
- type EditorTheme = 'light' | 'dark';
94
+ /**
95
+ * Light/dark chrome mode for the editor shell (toolbar, tabs, status bar,
96
+ * side panes). This is the editor's *UI color scheme* — distinct from a
97
+ * Squisq `Theme` object, which styles the rendered document. Renamed from
98
+ * the former `EditorTheme` to remove that ambiguity.
99
+ */
100
+ type EditorColorScheme = 'light' | 'dark';
101
+ /**
102
+ * Document layout mode. `'document'` shows the whole markdown document in
103
+ * the active view (the historical behavior). `'block'` is the
104
+ * block-at-a-time view — one heading-defined block on a card at a time,
105
+ * with the editor scoped to just that block. `'timeline'` is block mode plus
106
+ * a horizontal timeline track for editing block durations and media slices.
107
+ * See {@link useBlockNavigator}.
108
+ */
109
+ type LayoutMode = 'document' | 'block' | 'timeline';
67
110
  /**
68
111
  * How much of the active Squisq theme the WYSIWYG editing surface
69
112
  * mirrors. `'fonts'` is the historical default — body and heading
@@ -90,8 +133,8 @@ interface EditorState {
90
133
  parseError: string | null;
91
134
  /** Whether a parse is pending */
92
135
  isParsing: boolean;
93
- /** Current color theme */
94
- theme: EditorTheme;
136
+ /** Current light/dark chrome color scheme for the editor shell. */
137
+ colorScheme: EditorColorScheme;
95
138
  /** Operating mode — 'markdown' for the full shell, 'code' for Monaco-only. */
96
139
  editorMode: EditorMode;
97
140
  /** Monaco language ID for the Raw editor. */
@@ -152,10 +195,47 @@ interface EditorState {
152
195
  * shell.
153
196
  */
154
197
  allowRecording: boolean;
198
+ /**
199
+ * Document layout mode. `'document'` (default) edits the whole document;
200
+ * `'block'` activates the block-at-a-time card view. Initialized from the
201
+ * EditorShell `layoutMode` prop; the View menu can toggle it at runtime.
202
+ */
203
+ layoutMode: LayoutMode;
204
+ /**
205
+ * The markdown the active text editor should bind to: the full source in
206
+ * `'document'` mode, or just the active block's slice in `'block'` mode.
207
+ * Editors read this instead of `markdownSource` so the same surfaces work
208
+ * in both layouts.
209
+ */
210
+ editorSource: string;
211
+ /** Number of navigable blocks (cards) in the current document. */
212
+ blockCount: number;
213
+ /** Index of the block currently shown on the card (block mode). */
214
+ activeBlockKey: number;
215
+ /** 1-based source line where the active block begins, or null. */
216
+ activeBlockStartLine: number | null;
155
217
  }
156
218
  interface EditorActions {
157
219
  /** Set markdown source and trigger re-parse */
158
220
  setMarkdownSource: (source: string) => void;
221
+ /**
222
+ * Write through the active editor channel. In `'document'` mode this is
223
+ * `setMarkdownSource`; in `'block'` mode it splices the edited block back
224
+ * into the full document. Editors call this instead of `setMarkdownSource`.
225
+ */
226
+ setEditorSource: (source: string) => void;
227
+ /** Switch between Document and Block-at-a-time layouts. */
228
+ setLayoutMode: (mode: LayoutMode) => void;
229
+ /** Show a block by index in block mode (clamped to range). */
230
+ goToBlock: (key: number) => void;
231
+ /** Show the block that owns a given 1-based source line (used by the outline). */
232
+ goToBlockByLine: (line: number) => void;
233
+ /** Move the card to the previous block. */
234
+ prevBlock: () => void;
235
+ /** Move the card to the next block. */
236
+ nextBlock: () => void;
237
+ /** Insert a new heading block after the active one and move to it. */
238
+ addBlock: () => void;
159
239
  /** Set markdown from a MarkdownDocument (e.g. from WYSIWYG) */
160
240
  setMarkdownDoc: (doc: MarkdownDocument) => void;
161
241
  /** Switch the active view */
@@ -164,8 +244,8 @@ interface EditorActions {
164
244
  setTiptapEditor: (editor: Editor | null) => void;
165
245
  /** Register / unregister the Monaco editor instance (called by RawEditor) */
166
246
  setMonacoEditor: (editor: MonacoEditor | null) => void;
167
- /** Set the color theme */
168
- setTheme: (theme: EditorTheme) => void;
247
+ /** Set the light/dark chrome color scheme for the editor shell. */
248
+ setColorScheme: (colorScheme: EditorColorScheme) => void;
169
249
  /** Show or hide the inline preview gutter at runtime (driven by the View menu). */
170
250
  setInlinePreviewVisible: (visible: boolean) => void;
171
251
  /** Show or hide the bottom status bar at runtime (driven by the View menu). */
@@ -201,6 +281,14 @@ interface EditorContextValue extends EditorState, EditorActions {
201
281
  tiptapEditor: Editor | null;
202
282
  /** The live Monaco editor instance (null when Raw is not mounted) */
203
283
  monacoEditor: MonacoEditor | null;
284
+ /**
285
+ * The focused canvas textbox editor, if any — a small Tiptap instance for
286
+ * a diagram/drawing/layout textbox being edited inline. Published via
287
+ * `sceneTextChannel` (the canvas renders in a detached React root). The
288
+ * top formatting toolbar retargets to this when set. `level` gates which
289
+ * buttons apply (`inline` = marks only; `rich` = headings/lists too).
290
+ */
291
+ activeSceneText: SceneTextHandle | null;
204
292
  /**
205
293
  * Workspace-scoped `ContentContainer` for this document — the folder
206
294
  * holding the doc, its `_files/` sidecar, sibling documents, and any
@@ -259,8 +347,8 @@ interface EditorProviderProps {
259
347
  initialView?: EditorView;
260
348
  /** Article ID used when generating the Doc */
261
349
  articleId?: string;
262
- /** Color theme */
263
- theme?: EditorTheme;
350
+ /** Light/dark chrome color scheme for the editor shell. */
351
+ colorScheme?: EditorColorScheme;
264
352
  /**
265
353
  * Workspace-scoped `ContentContainer` for this document — the folder
266
354
  * holding the doc, its `_files/` sidecar, sibling documents, and any
@@ -351,6 +439,12 @@ interface EditorProviderProps {
351
439
  * toolbar's View menu can change it at runtime.
352
440
  */
353
441
  themeInheritance?: ThemeInheritance;
442
+ /**
443
+ * Initial layout mode. Defaults to `'document'` (whole-document editing).
444
+ * `'block'` boots into the block-at-a-time card view. The toolbar's View
445
+ * menu can toggle it at runtime.
446
+ */
447
+ layoutMode?: LayoutMode;
354
448
  /**
355
449
  * Bundled view preferences — a serializable JSON blob covering all
356
450
  * runtime-toggleable view options. When provided, individual values
@@ -386,8 +480,86 @@ interface ViewPreferences {
386
480
  blockTags?: boolean;
387
481
  /** How much of the active Squisq theme the WYSIWYG surface mirrors. */
388
482
  themeInheritance?: ThemeInheritance;
483
+ /** Document vs. block-at-a-time layout. */
484
+ layoutMode?: LayoutMode;
485
+ }
486
+ declare function EditorProvider({ initialMarkdown, initialView, articleId, colorScheme: initialColorScheme, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, mediaProvider, imageDisplayMode, mentionProvider, documentLinkProvider, allowRecording, fileName, language, inlinePreview, showStatusBar, outline, blockTags, themeInheritance, layoutMode, viewPreferences, onViewPreferencesChange, children, }: EditorProviderProps): react_jsx_runtime.JSX.Element;
487
+
488
+ /**
489
+ * Host-supplied context sections rendered INSIDE the Monaco (raw/code)
490
+ * surface: collapsible markdown blurbs injected above anchor lines, plus an
491
+ * optional file-top summary. Squisq stays host-agnostic — anchors are plain
492
+ * line numbers, ids are opaque strings, links are intercepted via callback.
493
+ *
494
+ * Accessibility note: Monaco marks its view-zone layer `aria-hidden`, so
495
+ * sections are invisible to screen readers in v1.
496
+ */
497
+ /**
498
+ * One host-supplied markdown blurb anchored above a line of the code buffer.
499
+ * Rendered as a compact one-line strip that expands in place to the full
500
+ * markdown body.
501
+ */
502
+ interface CodeContextSection {
503
+ /**
504
+ * Stable identity used to reconcile zones across prop updates — e.g. a
505
+ * symbol id like 'resolveImportEdges@60'. Zones are diffed by id: same id +
506
+ * new line moves the zone; same id + new markdown re-renders in place; a
507
+ * new id creates a new zone.
508
+ */
509
+ id: string;
510
+ /**
511
+ * 1-based line the section renders ABOVE. Out-of-range values are clamped
512
+ * by Monaco. In editable buffers the zone rides Monaco's whitespace
513
+ * semantics — it shifts as lines are inserted/deleted above it and
514
+ * collapses onto the previous line if its anchor lines are deleted. Squisq
515
+ * never re-derives anchors from edits; the host re-supplies lines when it
516
+ * re-analyzes the file.
517
+ */
518
+ line: number;
519
+ /**
520
+ * Compact markdown for the collapsed strip. Rendered on one line (block
521
+ * structure flattened, overflow ellipsized). Links work here too and go
522
+ * through `onLinkClick`.
523
+ */
524
+ summaryMarkdown: string;
525
+ /**
526
+ * Full markdown body shown when expanded. Omit while still loading — the
527
+ * expanded view shows a muted loading row and fills in when a later prop
528
+ * update supplies it.
529
+ */
530
+ markdown?: string;
531
+ /** Start expanded. The user's toggle wins after first interaction. Default false. */
532
+ defaultExpanded?: boolean;
533
+ }
534
+ /** The full context dictionary passed to `EditorShell.codeContext`. */
535
+ interface CodeContext {
536
+ /**
537
+ * Section pinned above line 1 (file summary). Rendered before any line-1
538
+ * `sections` entry. `line` is implicit.
539
+ */
540
+ fileTop?: Omit<CodeContextSection, 'line'>;
541
+ /** Line-anchored sections. Array order is preserved for equal lines. */
542
+ sections?: CodeContextSection[];
543
+ /**
544
+ * Extra URI schemes section links may use (e.g. `['gezel-nav']`).
545
+ * http/https/mailto/tel are always allowed; executable schemes
546
+ * (javascript:, data:) are never allowed regardless.
547
+ */
548
+ linkSchemes?: readonly string[];
549
+ /**
550
+ * Intercepts link clicks inside sections, receiving the href exactly as
551
+ * authored in the markdown. Return `false` to let the browser's default
552
+ * navigation proceed; any other return (or void) suppresses it. Fragment
553
+ * links of the form `#L<digits>` are handled natively by squisq (reveal
554
+ * that line in the editor) and never reach this callback. When omitted:
555
+ * http(s)/mailto links open normally, custom-scheme links do nothing.
556
+ */
557
+ onLinkClick?: (href: string, meta: {
558
+ sectionId: string;
559
+ }) => boolean | undefined;
560
+ /** Notified on expand/collapse — lets hosts lazy-load bodies on first expand. */
561
+ onToggleSection?: (sectionId: string, expanded: boolean) => void;
389
562
  }
390
- declare function EditorProvider({ initialMarkdown, initialView, articleId, theme: initialTheme, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, mediaProvider, imageDisplayMode, mentionProvider, documentLinkProvider, allowRecording, fileName, language, inlinePreview, showStatusBar, outline, blockTags, themeInheritance, viewPreferences, onViewPreferencesChange, children, }: EditorProviderProps): react_jsx_runtime.JSX.Element;
391
563
 
392
564
  interface EditorShellProps {
393
565
  /** Initial markdown content */
@@ -401,8 +573,13 @@ interface EditorShellProps {
401
573
  basePath?: string;
402
574
  /** Called when markdown source changes */
403
575
  onChange?: (source: string) => void;
404
- /** Color theme: 'light' or 'dark' (default: 'light') */
405
- theme?: 'light' | 'dark';
576
+ /**
577
+ * Light/dark chrome color scheme for the editor shell — toolbar, tabs,
578
+ * status bar, and side panes (default: `'light'`). This is the editor's
579
+ * UI mode, **not** a Squisq `Theme` object; the rendered document's
580
+ * styling is controlled separately via `themeOverride` / `Doc.themeId`.
581
+ */
582
+ colorScheme?: 'light' | 'dark';
406
583
  /** Additional class name */
407
584
  className?: string;
408
585
  /** CSS height for the shell container (default: '100vh') */
@@ -497,6 +674,13 @@ interface EditorShellProps {
497
674
  * (Slack, Discord). When omitted, the editor behaves normally.
498
675
  */
499
676
  submitOnEnter?: () => void;
677
+ /**
678
+ * Host-supplied context dictionary rendered inside the Monaco (raw / code)
679
+ * surface: collapsible markdown sections injected above anchor lines, plus
680
+ * an optional file-top summary. See {@link CodeContext}. Ignored in
681
+ * WYSIWYG / preview / image surfaces.
682
+ */
683
+ codeContext?: CodeContext;
500
684
  /**
501
685
  * Let the WYSIWYG editing surface fill its container instead of rendering
502
686
  * as a centered 800px "page" column. Useful when embedding in chat
@@ -655,9 +839,11 @@ interface EditorShellProps {
655
839
  */
656
840
  outline?: boolean;
657
841
  /**
658
- * Width in pixels for the outline pane. Defaults to 240. Only takes
659
- * effect when {@link EditorShellProps.outline} is true (or the View
660
- * menu has toggled it on).
842
+ * Fixed width in pixels for the outline pane. When omitted, the pane sizes
843
+ * responsively never narrower than 260px, growing with the window and
844
+ * capped at 460px — so it stretches out when there's horizontal space to
845
+ * spare. Only takes effect when {@link EditorShellProps.outline} is true (or
846
+ * the View menu has toggled it on).
661
847
  */
662
848
  outlineWidth?: number;
663
849
  /**
@@ -702,7 +888,237 @@ interface EditorShellProps {
702
888
  * Complete markdown editor shell with toolbar, view switcher, and three
703
889
  * editing modes: Raw (Monaco), WYSIWYG (Tiptap), and Preview.
704
890
  */
705
- declare function EditorShell({ initialMarkdown, initialView, articleId, basePath, onChange, theme, className, height, minHeight, maxHeight, mediaProvider, workspaceContainer, container, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, showFilesToggle, toolbarSlotLeft, toolbarSlotAfterActions, toolbarSlotRight, showPlayTab, submitOnEnter, fullWidth, uxFont, thinMargins, showStatusBar, imageDisplayMode, fileName, language, mentionProvider, documentLinkProvider, allowRecording, placeholder, readOnly, imageSrc, imageAlt, imageMode, imageEditorContainer, onImageExport, inlinePreview, inlinePreviewWidth, outline, outlineWidth, blockTags, themeInheritance, viewPreferences, onViewPreferencesChange, themeOverride, }: EditorShellProps): react_jsx_runtime.JSX.Element;
891
+ declare function EditorShell({ initialMarkdown, initialView, articleId, basePath, onChange, colorScheme, className, height, minHeight, maxHeight, mediaProvider, workspaceContainer, container, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, showFilesToggle, toolbarSlotLeft, toolbarSlotAfterActions, toolbarSlotRight, showPlayTab, submitOnEnter, codeContext, fullWidth, uxFont, thinMargins, showStatusBar, imageDisplayMode, fileName, language, mentionProvider, documentLinkProvider, allowRecording, placeholder, readOnly, imageSrc, imageAlt, imageMode, imageEditorContainer, onImageExport, inlinePreview, inlinePreviewWidth, outline, outlineWidth, blockTags, themeInheritance, viewPreferences, onViewPreferencesChange, themeOverride, }: EditorShellProps): react_jsx_runtime.JSX.Element;
892
+
893
+ /**
894
+ * Bridges a host-supplied {@link CodeContext} onto the live Monaco editor:
895
+ * owns a {@link CodeContextZoneManager} per editor instance, reconciles zones
896
+ * when the context prop changes, and portals a `CodeContextSectionView` into
897
+ * each zone's dom node. Mounted by EditorShell in code mode; also exported
898
+ * for hosts composing a custom shell around `RawEditor`.
899
+ */
900
+ declare function CodeContextZones({ options }: {
901
+ options: CodeContext;
902
+ }): react_jsx_runtime.JSX.Element | null;
903
+
904
+ /**
905
+ * One entry in a {@link FolderView} — a file or subfolder. Host-defined
906
+ * `path` is opaque to this component: it's used as the React key and
907
+ * handed back verbatim to the open callbacks so the host can resolve it.
908
+ */
909
+ interface FolderEntry {
910
+ /** Display name (basename). */
911
+ name: string;
912
+ /** Full host path. Opaque here; passed back on open. */
913
+ path: string;
914
+ isDirectory: boolean;
915
+ }
916
+ interface FolderViewProps {
917
+ /** Folder display name (basename) shown in the header. */
918
+ name: string;
919
+ /** Immediate children of the folder — subfolders and files. */
920
+ entries: FolderEntry[];
921
+ /** Open a file entry. The host decides what "open" means. */
922
+ onOpenFile: (entry: FolderEntry) => void;
923
+ /** Drill into a subfolder. */
924
+ onOpenFolder: (entry: FolderEntry) => void;
925
+ /** Primary action — create a new document in this folder. */
926
+ onNewDocument: () => void;
927
+ /** Secondary action — create a new subfolder. Omit to hide the button. */
928
+ onNewFolder?: () => void;
929
+ /** Color theme (default `'light'`). */
930
+ theme?: 'light' | 'dark';
931
+ /** CSS height for the container (default `'100%'`). */
932
+ height?: string;
933
+ /** Override the per-entry icon. Defaults to FontAwesome file/folder glyphs. */
934
+ iconFor?: (entry: FolderEntry) => ReactNode;
935
+ }
936
+ /**
937
+ * FolderView — a standalone folder browser surface.
938
+ *
939
+ * Lists a folder's files and subfolders plus a prominent "New document"
940
+ * action (and an optional "New folder"). It's the companion to
941
+ * {@link EditorShell}: where the shell edits a single document, this
942
+ * presents the directory around it.
943
+ *
944
+ * Like the shell, it's host-agnostic — the consumer supplies the
945
+ * `entries` and the open / new callbacks, and FolderView owns only the
946
+ * presentation and theming. It holds no state and reads nothing from
947
+ * disk, so it composes into any storage backend.
948
+ */
949
+ declare function FolderView({ name, entries, onOpenFile, onOpenFolder, onNewDocument, onNewFolder, theme, height, iconFor, }: FolderViewProps): react_jsx_runtime.JSX.Element;
950
+
951
+ /**
952
+ * useBlockNavigator
953
+ *
954
+ * Standalone hook powering the block-at-a-time editing view. Given a
955
+ * `(source, setSource)` pair it slices the markdown into blocks (via
956
+ * `blockRange.ts`), tracks which block is active, and exposes a derived
957
+ * **content channel** (`editorSource` / `setEditorSource`) plus navigation.
958
+ *
959
+ * It depends only on its arguments — no `EditorContext`, no `EditorShell` —
960
+ * so any host (an embedded single-block editor, a chat composer, a review
961
+ * surface) can drive a block-at-a-time UI by calling it directly.
962
+ *
963
+ * When `enabled` is false the channel is an identity passthrough: the editors
964
+ * see and write the full source exactly as before.
965
+ */
966
+ interface BlockNavigator {
967
+ /** What the bound editor should show: the active slice (block mode) or the full source. */
968
+ editorSource: string;
969
+ /** What the bound editor writes through: splices back into the full source (block mode). */
970
+ setEditorSource: (s: string) => void;
971
+ /** Number of navigable blocks in the current source. */
972
+ blockCount: number;
973
+ /** Index of the active block (clamped into range). */
974
+ activeBlockKey: number;
975
+ /** Jump to a block by index (clamped). */
976
+ goToBlock: (key: number) => void;
977
+ /**
978
+ * Select the block that owns a given 1-based source line — used by the
979
+ * outline, which knows a heading's source line but not its slice index
980
+ * (slice order includes the optional preamble, so it needn't match
981
+ * `flattenBlocks` order).
982
+ */
983
+ goToBlockByLine: (line: number) => void;
984
+ /** 1-based source line where the active block begins (for outline highlight). */
985
+ activeBlockStartLine: number | null;
986
+ /** Move to the previous block (no-op at the start). */
987
+ prevBlock: () => void;
988
+ /** Move to the next block (no-op at the end). */
989
+ nextBlock: () => void;
990
+ /** Insert a new heading-defined block after the active one and move to it. */
991
+ addBlock: () => void;
992
+ }
993
+ interface UseBlockNavigatorOptions {
994
+ /**
995
+ * When false (the default), the channel passes through to the full source
996
+ * and navigation is inert. Hosts flip this on to enter block-at-a-time mode.
997
+ */
998
+ enabled?: boolean;
999
+ }
1000
+ declare function useBlockNavigator(source: string, setSource: (s: string) => void, opts?: UseBlockNavigatorOptions): BlockNavigator;
1001
+
1002
+ interface BlockCardViewProps {
1003
+ /** Total number of navigable blocks. */
1004
+ blockCount: number;
1005
+ /** Index of the block currently shown (0-based). */
1006
+ activeBlockKey: number;
1007
+ /** Move to the previous block. */
1008
+ onPrev: () => void;
1009
+ /** Move to the next block. */
1010
+ onNext: () => void;
1011
+ /** Insert a new block after the current one. Omit to hide the affordance. */
1012
+ onAdd?: () => void;
1013
+ /** The editor surface (or any content) for the active block. */
1014
+ children: ReactNode;
1015
+ /** Optional extra class for the outer container. */
1016
+ className?: string;
1017
+ }
1018
+ declare function BlockCardView({ blockCount, activeBlockKey, onPrev, onNext, onAdd, children, className, }: BlockCardViewProps): react_jsx_runtime.JSX.Element;
1019
+
1020
+ /**
1021
+ * blockRange
1022
+ *
1023
+ * Source-text-range slicing for the block-at-a-time editing view. Splits a
1024
+ * full markdown document into ordered, contiguous slices — one per
1025
+ * heading-defined block plus an optional leading preamble — so a single
1026
+ * block can be shown in isolation and edits spliced back into the parent.
1027
+ *
1028
+ * A block runs from its heading line through the character just before the
1029
+ * next heading at ANY depth (or EOF). Sub-headings therefore start their own
1030
+ * slices — they are NOT folded into their parent — which matches the
1031
+ * "don't see child blocks" requirement and the `slicePastHeading` boundary
1032
+ * in `blockSlice.ts`. Ranges are half-open `[startOffset, endOffset)` and
1033
+ * line-aligned, so trailing blank lines stay with the current block and
1034
+ * `spliceBlock(src, range, getBlockSlices(src)[i].text) === src` for every i.
1035
+ *
1036
+ * These are pure functions over a markdown string — no React, no editor
1037
+ * coupling — so any host can reuse them.
1038
+ */
1039
+ /** Half-open character range into the full source: `[startOffset, endOffset)`. */
1040
+ interface BlockRange {
1041
+ startOffset: number;
1042
+ endOffset: number;
1043
+ }
1044
+ /** One block's source text plus the range it occupies in the full document. */
1045
+ interface BlockSlice {
1046
+ text: string;
1047
+ range: BlockRange;
1048
+ }
1049
+ /**
1050
+ * Split `fullSource` into ordered block slices.
1051
+ *
1052
+ * - With no headings, the entire post-frontmatter body is a single slice
1053
+ * (so an empty or heading-less document still shows one editable card).
1054
+ * - With headings, a leading preamble slice is included only when the text
1055
+ * before the first heading has non-whitespace content. Each heading then
1056
+ * yields one slice spanning up to the next heading (any depth) or EOF.
1057
+ *
1058
+ * Frontmatter is never part of any slice — slices start at the body offset.
1059
+ */
1060
+ declare function getBlockSlices(fullSource: string): BlockSlice[];
1061
+ /** Replace the text in `range` with `newText`, returning the new full source. */
1062
+ declare function spliceBlock(fullSource: string, range: BlockRange, newText: string): string;
1063
+ /** Character offset of the start of 1-based `line` (clamps past EOF). */
1064
+ declare function lineToOffset(source: string, line: number): number;
1065
+ /** 1-based line number containing `offset`. */
1066
+ declare function offsetToLine(source: string, offset: number): number;
1067
+ /** Index of the slice whose range contains `offset`, or -1. */
1068
+ declare function sliceIndexAtOffset(slices: BlockSlice[], offset: number): number;
1069
+
1070
+ /**
1071
+ * TimelineTrack
1072
+ *
1073
+ * Horizontal timeline strip for the Timeline view. Shows every block as a bar
1074
+ * (width ∝ duration, x ∝ startTime) with its media clips as sub-bars below.
1075
+ * Clicking a block selects it (the editor above follows). Dragging a block's
1076
+ * right edge changes its duration; dragging its left edge changes the previous
1077
+ * block's duration (the boundary, since startTime is derived). Dragging a media
1078
+ * clip moves its `startAt`; dragging the clip's right edge changes its length;
1079
+ * double-clicking a clip toggles `spillover`. All edits are written back to the
1080
+ * markdown source via {@link timelineSource}.
1081
+ */
1082
+ interface TimelineTrackProps {
1083
+ height?: number;
1084
+ }
1085
+ declare function TimelineTrack({ height }: TimelineTrackProps): react_jsx_runtime.JSX.Element | null;
1086
+
1087
+ /**
1088
+ * timelineSource
1089
+ *
1090
+ * Line-level markdown rewrites for the timeline editor: set a block's
1091
+ * `duration` on its heading's Pandoc attribute block, and patch a media
1092
+ * clip's `startAt` / `clipStart` / `clipEnd` / `spillover` on its `{[audio …]}`
1093
+ * / `{[video …]}` annotation line. Both preserve everything else on the line
1094
+ * (template annotations, ids, classes, other params) by reusing the shared
1095
+ * tokenizers rather than regex-replacing values.
1096
+ */
1097
+ /** Format a seconds value compactly: integers bare, else up to 2 decimals. */
1098
+ declare function formatSeconds(seconds: number): string;
1099
+ /**
1100
+ * Set/insert a `duration` on the heading at 1-based `line`, written in the
1101
+ * squisq-native squiggly form — `{[duration=<seconds>]}` on its own, or
1102
+ * folded into an existing `{[template …]}` annotation. Preserves any `{#id}`,
1103
+ * classes, and other Pandoc params, and migrates a legacy Pandoc
1104
+ * `{duration=…}` to the squiggly form (dropping the stale Pandoc key so the
1105
+ * two can't disagree). Returns the new full source, or null when the line
1106
+ * isn't an ATX heading.
1107
+ */
1108
+ declare function setBlockDurationInSource(source: string, line: number, seconds: number): string | null;
1109
+ /** A patch to a media clip; numeric values are seconds, `null` removes the key. */
1110
+ interface MediaClipPatch {
1111
+ startAt?: number | null;
1112
+ clipStart?: number | null;
1113
+ clipEnd?: number | null;
1114
+ spillover?: boolean | null;
1115
+ }
1116
+ /**
1117
+ * Patch the `{[audio …]}` / `{[video …]}` annotation at 1-based `line`.
1118
+ * Preserves the template name and any params not in the patch. Returns the
1119
+ * new full source, or null when the line isn't a media annotation.
1120
+ */
1121
+ declare function setMediaClipInSource(source: string, line: number, patch: MediaClipPatch): string | null;
706
1122
 
707
1123
  /**
708
1124
  * fileKind
@@ -772,8 +1188,14 @@ declare function ImageViewer({ src, alt, className, theme }: ImageViewerProps):
772
1188
  * Syncs changes back to EditorContext on every keystroke (debounced).
773
1189
  */
774
1190
  interface RawEditorProps {
775
- /** Monaco editor theme (default: 'vs-dark') */
776
- theme?: string;
1191
+ /**
1192
+ * Monaco editor theme name (default: `'vs'`). Accepts Monaco's built-in
1193
+ * theme ids (`'vs'`, `'vs-dark'`, `'hc-black'`) — which are transparently
1194
+ * mapped to the Squisq-tinted variants — or any custom theme registered
1195
+ * via `monaco.editor.defineTheme`. This is the *code editor* color
1196
+ * theme, distinct from the shell's light/dark `colorScheme`.
1197
+ */
1198
+ monacoTheme?: string;
777
1199
  /** Show minimap (default: false) */
778
1200
  minimap?: boolean;
779
1201
  /** Font size in pixels (default: 14) */
@@ -794,7 +1216,7 @@ interface RawEditorProps {
794
1216
  * Raw markdown editor using Monaco Editor.
795
1217
  * Binds to the shared EditorContext for source synchronization.
796
1218
  */
797
- declare function RawEditor({ theme, minimap, fontSize, wordWrap, className, submitOnEnter, readOnly, }: RawEditorProps): react_jsx_runtime.JSX.Element;
1219
+ declare function RawEditor({ monacoTheme, minimap, fontSize, wordWrap, className, submitOnEnter, readOnly, }: RawEditorProps): react_jsx_runtime.JSX.Element;
798
1220
 
799
1221
  /**
800
1222
  * WysiwygEditor
@@ -994,16 +1416,6 @@ interface DocumentSettingsDialogProps {
994
1416
  }
995
1417
  declare function DocumentSettingsDialog({ markdownSource, onSave, onClose, }: DocumentSettingsDialogProps): react_jsx_runtime.JSX.Element;
996
1418
 
997
- /**
998
- * ThemePicker
999
- *
1000
- * Custom theme dropdown that replaces a plain `<select>` with a popover
1001
- * showing each theme as a card: the theme name rendered in the theme's
1002
- * own background / foreground / title font, plus three color swatches
1003
- * (primary, secondary, highlight). Used by both the play-mode preview
1004
- * toolbar and the Document Settings dialog so authors see a
1005
- * preview-on-hover style listing rather than a wall of names.
1006
- */
1007
1419
  interface ThemePickerProps {
1008
1420
  /** Currently selected theme id. Empty string represents "default". */
1009
1421
  value: string;
@@ -1024,9 +1436,100 @@ interface ThemePickerProps {
1024
1436
  variant?: 'compact' | 'full';
1025
1437
  /** Accessible label, e.g. "Theme". */
1026
1438
  ariaLabel?: string;
1439
+ /**
1440
+ * User-authored themes to list in a "Custom" group (doc + library, from
1441
+ * `useCustomThemes().allThemes`). When omitted, only built-ins show — so
1442
+ * the base-theme picker and other embeds stay built-ins only.
1443
+ */
1444
+ customThemes?: Theme[];
1445
+ /** When provided, renders a "+ Create custom theme" row that calls this. */
1446
+ onCreateCustom?: () => void;
1447
+ /** Per-custom-card edit affordance (opens the designer for that theme). */
1448
+ onEditCustom?: (id: string) => void;
1449
+ /** Per-custom-card delete affordance. */
1450
+ onDeleteCustom?: (id: string) => void;
1451
+ }
1452
+ declare function ThemePicker({ value, onChange, includeDefault, variant, ariaLabel, customThemes, onCreateCustom, onEditCustom, onDeleteCustom, }: ThemePickerProps): react_jsx_runtime.JSX.Element;
1453
+
1454
+ interface CustomThemeContextValue {
1455
+ /** Themes inlined into the current doc's frontmatter. */
1456
+ docThemes: Theme[];
1457
+ /** Themes the user has saved to their browser-local library. */
1458
+ libraryThemes: Theme[];
1459
+ /**
1460
+ * Convenience: the union of doc + library, with library entries skipped
1461
+ * when a doc theme of the same id already exists. Used by the picker so a
1462
+ * single "Custom" section covers both sources without duplicates.
1463
+ */
1464
+ allThemes: Theme[];
1465
+ /**
1466
+ * Persist a theme into the current doc. Replaces any existing entry with
1467
+ * the same `id`. Triggers the host's `onDocThemesChange` so the doc's
1468
+ * frontmatter ends up updated.
1469
+ */
1470
+ upsertDocTheme: (theme: Theme) => void;
1471
+ /** Persist a theme into the user's library (replaces by id). */
1472
+ upsertLibraryTheme: (theme: Theme) => void;
1473
+ /** Remove a doc-level theme by id. */
1474
+ removeDocTheme: (id: string) => void;
1475
+ /** Remove a library theme by id. */
1476
+ removeLibraryTheme: (id: string) => void;
1477
+ /**
1478
+ * Apply a theme to the current doc. If the theme is already in the doc,
1479
+ * this is a no-op. If it's only in the library, it's copied into the doc
1480
+ * so SSR / export work. Returns the resolved definition so callers can
1481
+ * also write the `squisq-theme` selection.
1482
+ */
1483
+ applyTheme: (theme: Theme) => Theme;
1484
+ }
1485
+ interface CustomThemeProviderProps {
1486
+ /** The current doc's custom themes (from `Doc.customThemes`). */
1487
+ docThemes: Theme[];
1488
+ /**
1489
+ * Callback the host uses to persist a new doc-theme list back into the doc
1490
+ * (e.g. by writing the encoded payload into the markdown's
1491
+ * `squisq-custom-themes` frontmatter key).
1492
+ */
1493
+ onDocThemesChange: (next: Theme[]) => void;
1494
+ children: ReactNode;
1027
1495
  }
1028
- declare function ThemePicker({ value, onChange, includeDefault, variant, ariaLabel, }: ThemePickerProps): react_jsx_runtime.JSX.Element;
1496
+ declare function CustomThemeProvider({ docThemes, onDocThemesChange, children, }: CustomThemeProviderProps): react_jsx_runtime.JSX.Element;
1497
+ /**
1498
+ * Hook returning the current context. Returns null when no provider is
1499
+ * mounted — callers can degrade to "no custom themes" rather than throwing,
1500
+ * since the feature is optional.
1501
+ */
1502
+ declare function useCustomThemes(): CustomThemeContextValue | null;
1029
1503
 
1504
+ /**
1505
+ * useDocCustomThemes — read + persist the active doc's custom themes (the
1506
+ * list inlined into the doc's frontmatter under `squisq-custom-themes`).
1507
+ *
1508
+ * The theme analog of `useDocCustomTemplates`. Both the CustomThemeProvider
1509
+ * and any manager surface need the same two things: the current
1510
+ * `Doc.customThemes` array and a way to write a new list back into the
1511
+ * markdown source. Centralizing the frontmatter encoding here keeps call
1512
+ * sites from drifting.
1513
+ */
1514
+
1515
+ interface DocCustomThemes {
1516
+ /** Custom themes inlined in the active doc's frontmatter. */
1517
+ docThemes: Theme[];
1518
+ /**
1519
+ * Persist a new custom-themes list back into the markdown source's
1520
+ * frontmatter so the doc round-trips through save/load. The whole list is
1521
+ * encoded as a single compact JSON string per the flat YAML frontmatter
1522
+ * parser's constraint.
1523
+ */
1524
+ onDocThemesChange: (next: Theme[]) => void;
1525
+ }
1526
+ declare function useDocCustomThemes(): DocCustomThemes;
1527
+
1528
+ /** Where a saved theme lands — mirrors `DesignerSaveTarget` for templates. */
1529
+ type ThemeSaveTarget = 'doc' | 'library';
1530
+
1531
+ /** Caption selection: off, or one of the two enabled styles. */
1532
+ type CaptionMode = 'off' | CaptionStyle;
1030
1533
  interface PreviewSettings {
1031
1534
  activePreset: ViewportPreset;
1032
1535
  setSelectedPreset: (preset: ViewportPreset | null) => void;
@@ -1038,8 +1541,30 @@ interface PreviewSettings {
1038
1541
  activeTheme: Theme;
1039
1542
  activeTransformStyle: string;
1040
1543
  setSelectedTransformStyle: (id: string | null) => void;
1544
+ /** The caption style used when captions are enabled. */
1041
1545
  activeCaptionStyle: CaptionStyle;
1042
- setSelectedCaptionStyle: (style: CaptionStyle | null) => void;
1546
+ /** Whether captions are shown at all (the 'off' arm of the tri-state). */
1547
+ activeCaptionsEnabled: boolean;
1548
+ /** Set the caption mode: 'off' hides captions, 'standard'/'social' enable
1549
+ * that style. The single entry point so the toggle buttons persist in one
1550
+ * frontmatter write. */
1551
+ setCaptionMode: (mode: CaptionMode) => void;
1552
+ /** User-authored themes (doc + browser library) for the picker's "Custom" group. */
1553
+ customThemes: Theme[];
1554
+ /** Open the custom-theme designer for a theme (or null to create a new one). */
1555
+ openThemeDesigner: (theme: Theme | null) => void;
1556
+ /** Remove a custom theme from the doc and the library. */
1557
+ deleteCustomTheme: (id: string) => void;
1558
+ /** Config for the docked theme designer, or null when closed. Rendered by
1559
+ * `<ThemeDesignerDock>` in the editor's content row. */
1560
+ themeDesigner: ThemeDesignerConfig | null;
1561
+ }
1562
+ /** Everything `<ThemeDesignerDock>` needs to render the designer pane. */
1563
+ interface ThemeDesignerConfig {
1564
+ value: Theme | null;
1565
+ onChange: (theme: Theme) => void;
1566
+ onSave: (theme: Theme, target: ThemeSaveTarget) => void;
1567
+ onClose: () => void;
1043
1568
  }
1044
1569
  declare function usePreviewSettings(): PreviewSettings;
1045
1570
  interface PreviewSettingsProviderProps {
@@ -1056,9 +1581,31 @@ interface PreviewSettingsProviderProps {
1056
1581
  declare function PreviewSettingsProvider({ doc, children, themeOverride, }: PreviewSettingsProviderProps): react_jsx_runtime.JSX.Element;
1057
1582
  /**
1058
1583
  * Inline preview controls rendered in the main toolbar row.
1059
- * On narrow viewports, collapses into a single settings button with a dropdown.
1584
+ *
1585
+ * Collapse is *progressive* (a priority-plus pattern): rather than switch the
1586
+ * whole row in and out at a fixed window-width breakpoint, the controls
1587
+ * measure how many of them actually fit in the width the toolbar gives them
1588
+ * and keep that many inline, folding the rest — from the low-priority end of
1589
+ * {@link CONTROL_KEYS} — into a single settings (gear) button's popover. As
1590
+ * the toolbar widens or narrows, controls migrate one at a time between the
1591
+ * inline row and the menu, so the available space is always well used and the
1592
+ * row never wraps onto a second line.
1060
1593
  */
1061
1594
  declare function PreviewToolbarControls(): react_jsx_runtime.JSX.Element;
1595
+ /**
1596
+ * Segmented display-mode switch (Video / Slideshow / Document / Page) rendered
1597
+ * as four connected buttons on the left of the Play toolbar — the prominent,
1598
+ * one-click counterpart to the old "Mode:" dropdown. Reads and writes the same
1599
+ * `activeDisplayMode` in preview settings.
1600
+ */
1601
+ declare function PreviewModeSwitch(): react_jsx_runtime.JSX.Element;
1602
+ /**
1603
+ * Segmented aspect-ratio switch (16:9 / 1:1 / 9:16 / 4:3) rendered as connected
1604
+ * icon buttons on the left of the Play toolbar, next to the mode switch — the
1605
+ * one-click counterpart to the old "Format:" dropdown. Reads and writes the
1606
+ * same `activePreset` in preview settings.
1607
+ */
1608
+ declare function PreviewFormatSwitch(): react_jsx_runtime.JSX.Element;
1062
1609
 
1063
1610
  /**
1064
1611
  * ViewSwitcher
@@ -1083,6 +1630,10 @@ interface ToolbarProps {
1083
1630
  onToggleFiles?: () => void;
1084
1631
  /** Content rendered at the left edge of the toolbar, before the view tabs. */
1085
1632
  slotLeft?: ReactNode;
1633
+ /** Content rendered immediately after the view tabs, on the left side of the
1634
+ * toolbar (before the formatting controls). Used for the preview mode
1635
+ * switch in Play view. */
1636
+ slotAfterTabs?: ReactNode;
1086
1637
  /** Content rendered after the formatting controls (in the middle area). */
1087
1638
  slotAfterActions?: ReactNode;
1088
1639
  /** Content rendered at the rightmost end of the toolbar, after all other elements. */
@@ -1099,7 +1650,7 @@ interface ToolbarProps {
1099
1650
  * - WYSIWYG: calls Tiptap chain commands (toggleBold, etc.)
1100
1651
  * - Raw: appends markdown syntax to the source
1101
1652
  */
1102
- declare function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAfterActions, slotRight, showPlayTab, }: ToolbarProps): react_jsx_runtime.JSX.Element;
1653
+ declare function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAfterTabs, slotAfterActions, slotRight, showPlayTab, }: ToolbarProps): react_jsx_runtime.JSX.Element;
1103
1654
 
1104
1655
  /**
1105
1656
  * VersionHistoryPanel
@@ -1128,17 +1679,12 @@ declare function VersionHistoryPanel(): react_jsx_runtime.JSX.Element | null;
1128
1679
  */
1129
1680
  declare function ViewMenuPanel(): react_jsx_runtime.JSX.Element;
1130
1681
 
1131
- /**
1132
- * OutlinePanel
1133
- *
1134
- * Left-side companion to the InlinePreviewGutter. Renders a hierarchical
1135
- * tree of the document's headings (h1 → h2 → h3 …) so the structure is
1136
- * graspable at a glance and the user can jump to any section. Works in
1137
- * BOTH the WYSIWYG and Markdown editor views — view-specific positioning
1138
- * lives in `useHeadingLayout`.
1139
- */
1140
1682
  interface OutlinePanelProps {
1141
- /** Width of the pane in pixels (default: 240). */
1683
+ /**
1684
+ * Fixed width of the pane in pixels. When omitted, the pane sizes
1685
+ * responsively from `--squisq-outline-width` (falling back to
1686
+ * {@link OUTLINE_RESPONSIVE_WIDTH}) so it stretches on wider screens.
1687
+ */
1142
1688
  width?: number;
1143
1689
  /** Optional CSS class for the outer container. */
1144
1690
  className?: string;
@@ -1164,7 +1710,7 @@ declare function ThemeCustomizerPanel({ value, onChange, onSave, onReset, }: The
1164
1710
  * so existing documents keep showing a friendly label without first
1165
1711
  * normalizing their annotations.
1166
1712
  */
1167
- declare function templateLabel(name: string): string;
1713
+ declare function templateLabel(name: string, customTemplates?: readonly CustomTemplateDefinition[]): string;
1168
1714
  interface TemplatePickerProps {
1169
1715
  value: string;
1170
1716
  onChange: (name: string) => void;
@@ -1176,8 +1722,192 @@ interface TemplatePickerProps {
1176
1722
  * single ungrouped grid (legacy behavior).
1177
1723
  */
1178
1724
  recommended?: readonly string[];
1725
+ /**
1726
+ * Optional callback fired when the user clicks the "+ New custom
1727
+ * template" card pinned at the top of the gallery. The host wires this
1728
+ * to open the modal `TemplateDesigner`. When omitted, the card is
1729
+ * hidden.
1730
+ */
1731
+ onOpenDesigner?: () => void;
1732
+ }
1733
+ declare function TemplatePicker({ value, onChange, compact, recommended, onOpenDesigner, }: TemplatePickerProps): react_jsx_runtime.JSX.Element;
1734
+
1735
+ /**
1736
+ * headingTransition
1737
+ *
1738
+ * Read and write a block's transition (`transition` / `transitionDirection` /
1739
+ * `transitionDuration`) on a heading, in both editing surfaces:
1740
+ *
1741
+ * - Markdown (Monaco): operate on the raw heading line string.
1742
+ * - WYSIWYG (Tiptap): operate on the heading node's `dataBlockAttrs` string
1743
+ * (the inner of the Pandoc `{…}` block, no braces — matching how
1744
+ * `tiptapBridge` stores and re-emits it).
1745
+ *
1746
+ * Transitions are stored in the Pandoc `{#id .class key=value}` attribute
1747
+ * block, NOT the `{[template …]}` annotation. That mirrors the canonical
1748
+ * serializer (`core/doc/docToMarkdown.ts` → `ensureTransitionAttributes`,
1749
+ * which always emits the `{…}` form) and `diagram/diagramCommands.ts`, so a
1750
+ * value set here round-trips through a Doc render without being duplicated
1751
+ * or moved. Reads still look at the `{[…]}` params too, so a hand-typed
1752
+ * `{[title transition=fade]}` shows up in the picker.
1753
+ *
1754
+ * All the brace-matching / tokenizing / serializing is delegated to the
1755
+ * shared core helpers so this stays in lockstep with the parser by import
1756
+ * rather than by copied regexes.
1757
+ */
1758
+ /** Raw (un-coerced) transition attribute values for one block. */
1759
+ interface TransitionFields {
1760
+ /** `transition` value. Empty string means "none" (`cut`). */
1761
+ type: string;
1762
+ /** `transitionDirection` value, or '' when unset. */
1763
+ direction: string;
1764
+ /** `transitionDuration` value (raw, e.g. `0.7` or `700ms`), or '' when unset. */
1765
+ duration: string;
1766
+ }
1767
+ declare const EMPTY_TRANSITION: TransitionFields;
1768
+ /**
1769
+ * Read the transition fields off a heading line. Looks in both the Pandoc
1770
+ * `{…}` block (canonical) and the `{[…]}` template params (hand-typed),
1771
+ * with the Pandoc block taking precedence. Returns the empty transition for
1772
+ * non-heading lines.
1773
+ */
1774
+ declare function readHeadingLineTransition(line: string): TransitionFields;
1775
+ /**
1776
+ * Return `line` with its transition rewritten from `next`, writing into the
1777
+ * Pandoc `{…}` block and leaving the `{[…]}` template annotation untouched.
1778
+ * Non-heading lines are returned unchanged.
1779
+ */
1780
+ declare function setHeadingLineTransition(line: string, next: TransitionFields): string;
1781
+ /**
1782
+ * Read the transition fields from a heading node's `dataBlockAttrs` (Pandoc
1783
+ * inner) plus `dataTemplateParams` (the `{[…]}` params). Pandoc wins.
1784
+ */
1785
+ declare function readBlockAttrsTransition(blockAttrsInner: string | null | undefined, templateParams: string | null | undefined): TransitionFields;
1786
+ /**
1787
+ * Rewrite the transition in a heading node's `dataBlockAttrs` inner string.
1788
+ * Returns the new inner (no braces), or null when the block carries no
1789
+ * attributes at all — matching how `tiptapBridge` stores `dataBlockAttrs`
1790
+ * (absent attribute → null, not `{}`).
1791
+ */
1792
+ declare function setBlockAttrsTransition(blockAttrsInner: string | null | undefined, next: TransitionFields): string | null;
1793
+
1794
+ interface TransitionPickerProps {
1795
+ value: TransitionFields;
1796
+ onChange: (next: TransitionFields) => void;
1179
1797
  }
1180
- declare function TemplatePicker({ value, onChange, compact, recommended }: TemplatePickerProps): react_jsx_runtime.JSX.Element;
1798
+ declare function TransitionPicker({ value, onChange }: TransitionPickerProps): react_jsx_runtime.JSX.Element;
1799
+
1800
+ /**
1801
+ * transitionCatalog
1802
+ *
1803
+ * Editor-facing, curated presentation of the block transition vocabulary.
1804
+ *
1805
+ * Core's `TRANSITION_TYPES` (packages/core/src/schemas/Transitions.ts) lists
1806
+ * ~80 names, including legacy aliases and near-duplicate spellings. This
1807
+ * catalog hand-picks the distinct, useful transitions, gives each a friendly
1808
+ * label, and groups them for the toolbar's transition flyout — the same
1809
+ * "core holds the truth, the editor holds the presentation" split the block
1810
+ * `TemplatePicker` uses.
1811
+ *
1812
+ * Every `value` here MUST be a real `TransitionType`; `transitionCatalog.test.ts`
1813
+ * enforces that so a renamed/removed core transition fails the build instead
1814
+ * of silently producing an invalid `transition=` annotation. The catalog is
1815
+ * intentionally NOT exhaustive — aliases and redundant spellings are omitted.
1816
+ */
1817
+ /**
1818
+ * How a transition takes a direction, driving which direction sub-control
1819
+ * the picker shows. Mirrors core's `getTransitionVisualClass` dispatch:
1820
+ * - `lrud`: left / right / up / down (push, wipe, cover, uncover, reveal, pan)
1821
+ * - `axis`: horizontal / vertical (split, blinds)
1822
+ * Entries without a model take no `transitionDirection`.
1823
+ */
1824
+ type DirectionModel = 'lrud' | 'axis';
1825
+ interface TransitionCatalogEntry {
1826
+ /** Canonical `transition=` value — must be a core `TransitionType`. */
1827
+ value: string;
1828
+ /** Friendly label shown in the flyout and trigger. */
1829
+ label: string;
1830
+ /** Direction model, when the transition is directional. */
1831
+ direction?: DirectionModel;
1832
+ }
1833
+ interface TransitionGroup {
1834
+ title: string;
1835
+ entries: TransitionCatalogEntry[];
1836
+ }
1837
+ declare const TRANSITION_GROUPS: readonly TransitionGroup[];
1838
+ /** Flat list of every catalog entry, in group order. */
1839
+ declare const TRANSITION_ENTRIES: readonly TransitionCatalogEntry[];
1840
+ /** Look up a catalog entry by its `transition=` value. */
1841
+ declare function findTransitionEntry(value: string): TransitionCatalogEntry | undefined;
1842
+ /**
1843
+ * Human label for a transition value. Returns 'None' for the empty value and
1844
+ * falls back to a camelCase-humanized form for any valid-but-uncurated type
1845
+ * (e.g. a hand-typed alias) so the trigger still reads sensibly.
1846
+ */
1847
+ declare function transitionLabel(value: string): string;
1848
+
1849
+ /**
1850
+ * blockProperties
1851
+ *
1852
+ * Generic read/write of a single block-meta key on a heading's Pandoc `{…}`
1853
+ * attribute block (stored as the `dataBlockAttrs` inner string in the WYSIWYG
1854
+ * heading node — no braces, matching `tiptapBridge`).
1855
+ *
1856
+ * The transition family (which spans three coupled keys) has its own helpers
1857
+ * in `headingTransition.ts`; this module covers the standalone scalar keys the
1858
+ * block-properties palette edits — `duration`, `startTime`, `x`, `y`, … — all
1859
+ * of which are plain `key=value` params. Parse/serialize is delegated to the
1860
+ * shared core helpers so quoting and ordering match the parser exactly.
1861
+ */
1862
+ /** Parse a `dataBlockAttrs` inner string into its flat `key → value` map. */
1863
+ declare function readBlockAttrsParams(inner: string | null | undefined): Record<string, string>;
1864
+ /** Read a single block-meta param, or '' when unset. */
1865
+ declare function readBlockAttrsValue(inner: string | null | undefined, key: string): string;
1866
+ /**
1867
+ * Set (or, when `value` is empty, remove) a single param in a `dataBlockAttrs`
1868
+ * inner string. Returns the new inner (no braces), or null when the block is
1869
+ * left with no attributes at all — matching how `tiptapBridge` stores an
1870
+ * absent attribute (null, not `{}`).
1871
+ */
1872
+ declare function setBlockAttrsValue(inner: string | null | undefined, key: string, value: string): string | null;
1873
+ /**
1874
+ * A concise, human-readable summary of a block's authored properties for the
1875
+ * on-canvas badge — e.g. `Doors · 1:30 start · 3:20 long`. Returns '' when no
1876
+ * properties are set (the badge then shows just its icon). Reads transition
1877
+ * from both the Pandoc block and the `{[…]}` params; timing from the block.
1878
+ */
1879
+ declare function summarizeBlockProps(blockAttrs: string | null | undefined, templateParams: string | null | undefined): string;
1880
+
1881
+ /**
1882
+ * BlockPropertiesPopover
1883
+ *
1884
+ * The on-canvas "block properties" palette — the sibling of the block-template
1885
+ * badge. Anchored at the `.squisq-props-badge` chip on a heading, it edits the
1886
+ * block's playback/animation metadata, all stored in the heading's Pandoc `{…}`
1887
+ * attribute block (`dataBlockAttrs`):
1888
+ *
1889
+ * - Transition (type / direction / duration) — reuses `TransitionPicker`
1890
+ * - Duration (`duration`) — how long the block is shown
1891
+ * - Start time (`startTime`) — timeline position
1892
+ *
1893
+ * The popover holds the `dataBlockAttrs` inner string as working state and
1894
+ * re-derives each control from it, so successive edits compose. Every change
1895
+ * serializes a new inner and bubbles up through `onChange`; the host applies it
1896
+ * to the heading node. Positioning/portal/outside-click mirror
1897
+ * `TemplateBadgePopover`.
1898
+ */
1899
+ interface BlockPropertiesPopoverProps {
1900
+ /** DOMRect of the badge that triggered the popover (viewport coords). */
1901
+ anchorRect: DOMRect;
1902
+ /** Current `dataBlockAttrs` inner (Pandoc), or null when unset. */
1903
+ blockAttrs: string | null;
1904
+ /** `dataTemplateParams`, so a hand-typed `{[… transition=]}` reads through. */
1905
+ templateParams: string | null;
1906
+ /** Apply a new `dataBlockAttrs` inner to the heading (null clears it). */
1907
+ onChange: (nextInner: string | null) => void;
1908
+ onClose: () => void;
1909
+ }
1910
+ declare function BlockPropertiesPopover({ anchorRect, blockAttrs, templateParams, onChange, onClose, }: BlockPropertiesPopoverProps): react.ReactPortal;
1181
1911
 
1182
1912
  interface InlinePreviewGutterProps {
1183
1913
  /** Width of the gutter in pixels (default: 320). */
@@ -1355,6 +2085,155 @@ declare function processTextFile(file: File): Promise<string>;
1355
2085
  */
1356
2086
  declare function processTextFiles(files: File[]): Promise<string>;
1357
2087
 
2088
+ /**
2089
+ * useMonacoLoader
2090
+ *
2091
+ * Idempotently dynamic-imports `monaco-editor` and points the
2092
+ * `@monaco-editor/react` singleton loader at the bundled copy. Replaces
2093
+ * the historical top-of-module `import * as monaco from 'monaco-editor';
2094
+ * loader.config({ monaco })` pattern, which forced every consumer of
2095
+ * `@bendyline/squisq-editor-react` — including ones that only import
2096
+ * `JsonEditor` or a type — to drag in monaco's ~9MB worth of language
2097
+ * services and workers at module evaluation time.
2098
+ *
2099
+ * Hosts that want the smallest possible bundle can keep aliasing
2100
+ * `monaco-editor` to a slim entry as before; the behavior is identical
2101
+ * once the dynamic import settles.
2102
+ *
2103
+ * The promise is cached at module scope so the first subscriber
2104
+ * anywhere in the app pays the import cost and every later subscriber
2105
+ * reuses the same settled value.
2106
+ */
2107
+ interface UseMonacoLoaderResult {
2108
+ /** The monaco namespace once loaded, or `null` while the import is in flight. */
2109
+ monaco: typeof monaco_editor | null;
2110
+ /** Flips to `true` after the import settles. Gate `<Editor>` / `<DiffEditor>` renders on this. */
2111
+ ready: boolean;
2112
+ }
2113
+ /**
2114
+ * Subscribe to the lazy-loaded monaco namespace. The first caller
2115
+ * triggers `import('monaco-editor')` and configures the
2116
+ * `@monaco-editor/react` loader; subsequent callers receive the same
2117
+ * cached value.
2118
+ */
2119
+ declare function useMonacoLoader(): UseMonacoLoaderResult;
2120
+
2121
+ interface CustomTemplateContextValue {
2122
+ /** Templates inlined into the current doc's frontmatter. */
2123
+ docTemplates: CustomTemplateDefinition[];
2124
+ /** Templates the user has saved to their browser-local library. */
2125
+ libraryTemplates: CustomTemplateDefinition[];
2126
+ /**
2127
+ * Convenience: the union of doc + library, with library entries
2128
+ * skipped when a doc template of the same name already exists.
2129
+ * Used by the picker so a single "Custom" section covers both
2130
+ * sources without duplicates.
2131
+ */
2132
+ allTemplates: CustomTemplateDefinition[];
2133
+ /**
2134
+ * Persist a template into the current doc. Replaces any existing
2135
+ * entry with the same `name`. Triggers the host's
2136
+ * `onDocTemplatesChange` so the doc's frontmatter ends up updated.
2137
+ */
2138
+ upsertDocTemplate: (def: CustomTemplateDefinition) => void;
2139
+ /** Persist a template into the user's library (replaces by name). */
2140
+ upsertLibraryTemplate: (def: CustomTemplateDefinition) => void;
2141
+ /** Remove a doc-level template by name. */
2142
+ removeDocTemplate: (name: string) => void;
2143
+ /** Remove a library template by name. */
2144
+ removeLibraryTemplate: (name: string) => void;
2145
+ /**
2146
+ * Apply a template to the current doc. If the template is already in
2147
+ * the doc, this is a no-op. If it's only in the library, it's
2148
+ * copied into the doc so SSR / export work. Returns the resolved
2149
+ * definition so callers can also write the heading annotation.
2150
+ */
2151
+ applyTemplate: (def: CustomTemplateDefinition) => CustomTemplateDefinition;
2152
+ }
2153
+ interface CustomTemplateProviderProps {
2154
+ /** The current doc's custom templates (from `Doc.customTemplates`). */
2155
+ docTemplates: CustomTemplateDefinition[];
2156
+ /**
2157
+ * Callback the host uses to persist a new doc-template list back
2158
+ * into the doc (e.g. by writing the encoded payload into the
2159
+ * markdown's `squisq-custom-templates` frontmatter key).
2160
+ */
2161
+ onDocTemplatesChange: (next: CustomTemplateDefinition[]) => void;
2162
+ children: ReactNode;
2163
+ }
2164
+ declare function CustomTemplateProvider({ docTemplates, onDocTemplatesChange, children, }: CustomTemplateProviderProps): react_jsx_runtime.JSX.Element;
2165
+ /**
2166
+ * Hook returning the current context. Returns null when no provider is
2167
+ * mounted — callers can degrade to "no custom templates" rather than
2168
+ * throwing, since the feature is optional.
2169
+ */
2170
+ declare function useCustomTemplates(): CustomTemplateContextValue | null;
2171
+
2172
+ /**
2173
+ * useDocCustomTemplates — read + persist the active doc's custom
2174
+ * templates (the list inlined into the doc's frontmatter under
2175
+ * `squisq-custom-templates`).
2176
+ *
2177
+ * Both the WYSIWYG editor and the standalone Custom Layout Manager need
2178
+ * the same two things: the current `Doc.customTemplates` array and a way
2179
+ * to write a new list back into the markdown source. Centralizing the
2180
+ * frontmatter encoding here keeps the two call sites from drifting.
2181
+ */
2182
+
2183
+ interface DocCustomTemplates {
2184
+ /** Custom templates inlined in the active doc's frontmatter. */
2185
+ docTemplates: CustomTemplateDefinition[];
2186
+ /**
2187
+ * Persist a new custom-templates list back into the markdown source's
2188
+ * frontmatter so the doc round-trips through save/load. The whole list
2189
+ * is encoded as a single base64-JSON string per the flat YAML
2190
+ * frontmatter parser's constraint.
2191
+ */
2192
+ onDocTemplatesChange: (next: CustomTemplateDefinition[]) => void;
2193
+ }
2194
+ declare function useDocCustomTemplates(): DocCustomTemplates;
2195
+
2196
+ /**
2197
+ * Read diagram nodes + edges from the live Tiptap state.
2198
+ *
2199
+ * For a given parent heading position, walks the diagram section's child
2200
+ * headings, builds synthetic `Block` objects from their text + Pandoc
2201
+ * attributes, runs `computeDiagramLayout` from core to fill in missing
2202
+ * positions, and returns the result in the shape React Flow consumes.
2203
+ *
2204
+ * The hook re-derives on every editor transaction — no caching layer
2205
+ * means there's nothing to invalidate when the user types or the markdown
2206
+ * is reloaded from disk.
2207
+ */
2208
+
2209
+ interface DiagramRFNode {
2210
+ id: string;
2211
+ position: {
2212
+ x: number;
2213
+ y: number;
2214
+ };
2215
+ data: {
2216
+ label: string;
2217
+ };
2218
+ type?: string;
2219
+ /** Per-node width override (from the heading's `w=` Pandoc param). */
2220
+ width?: number;
2221
+ /** Per-node height override (from the heading's `h=` Pandoc param). */
2222
+ height?: number;
2223
+ }
2224
+ interface DiagramRFEdge {
2225
+ id: string;
2226
+ source: string;
2227
+ target: string;
2228
+ label?: string;
2229
+ }
2230
+ interface DiagramData {
2231
+ nodes: DiagramRFNode[];
2232
+ edges: DiagramRFEdge[];
2233
+ warnings: string[];
2234
+ }
2235
+ declare function useDiagramData(editor: Editor$1, parentPos: number): DiagramData;
2236
+
1358
2237
  /**
1359
2238
  * Tiptap Bridge
1360
2239
  *
@@ -1421,6 +2300,172 @@ declare function buildPreviewDoc(doc: Doc): Doc;
1421
2300
  */
1422
2301
  declare const HeadingWithTemplate: _tiptap_core.Node<_tiptap_extension_heading.HeadingOptions, any>;
1423
2302
 
2303
+ /**
2304
+ * DiagramExtension — Tiptap/ProseMirror plugin that:
2305
+ *
2306
+ * 1. Mounts a React-Flow canvas (`DiagramWidget`) immediately after every
2307
+ * heading whose `dataTemplate === 'diagram'`.
2308
+ * 2. Hides the direct sub-headings of each diagram parent (until the next
2309
+ * equal-or-shallower heading) by tagging them with a `data-squisq-diagram-child`
2310
+ * attribute — CSS in `styles/diagram.css` does the actual hiding.
2311
+ *
2312
+ * Widgets are rendered as plain DOM nodes attached to a ProseMirror
2313
+ * `Decoration.widget`. React is mounted into the widget DOM with
2314
+ * `react-dom/client`'s `createRoot`, and unmounted on the widget's
2315
+ * `destroy` hook.
2316
+ */
2317
+
2318
+ interface DiagramExtensionOptions {
2319
+ /** When false, the extension is inert (no widgets, no decorations). */
2320
+ enabled?: boolean;
2321
+ }
2322
+ declare const DiagramExtension: Extension<DiagramExtensionOptions, any>;
2323
+
2324
+ type DiagramCommand = {
2325
+ kind: 'moveNode';
2326
+ nodeId: string;
2327
+ x: number;
2328
+ y: number;
2329
+ } | {
2330
+ kind: 'resizeNode';
2331
+ nodeId: string;
2332
+ width: number;
2333
+ height: number;
2334
+ } | {
2335
+ kind: 'addConnection';
2336
+ source: string;
2337
+ target: string;
2338
+ type?: string;
2339
+ } | {
2340
+ kind: 'removeConnection';
2341
+ source: string;
2342
+ target: string;
2343
+ type?: string;
2344
+ } | {
2345
+ kind: 'renameNode';
2346
+ nodeId: string;
2347
+ newLabel: string;
2348
+ } | {
2349
+ kind: 'addNode';
2350
+ x: number;
2351
+ y: number;
2352
+ } | {
2353
+ kind: 'removeNode';
2354
+ nodeId: string;
2355
+ };
2356
+ interface DiagramCanvasProps {
2357
+ nodes: DiagramRFNode[];
2358
+ edges: DiagramRFEdge[];
2359
+ onCommand: (cmd: DiagramCommand) => void;
2360
+ /** When true, render the maximize button. Click toggles `onToggleMaximize`. */
2361
+ showMaximize?: boolean;
2362
+ /** Whether the canvas is currently maximized (affects button icon). */
2363
+ maximized?: boolean;
2364
+ /** Callback when the maximize button is clicked. */
2365
+ onToggleMaximize?: () => void;
2366
+ /**
2367
+ * Active tool id, controlled by the host (DiagramWidget) so the tool
2368
+ * buttons can live in the shared toolbar above the canvas. Falls back
2369
+ * to internal state when omitted.
2370
+ */
2371
+ activeToolId?: string;
2372
+ onActiveToolIdChange?: (id: string) => void;
2373
+ /** Forwarded to the Scene so the host can drive a Delete action. */
2374
+ onSelectionChange?: (ids: ReadonlySet<string>) => void;
2375
+ }
2376
+ declare function DiagramCanvas({ nodes: incomingNodes, edges: incomingEdges, onCommand, showMaximize, maximized, onToggleMaximize, activeToolId: controlledToolId, onActiveToolIdChange, onSelectionChange, }: DiagramCanvasProps): react_jsx_runtime.JSX.Element;
2377
+
2378
+ interface DiagramWidgetProps {
2379
+ editor: Editor$1;
2380
+ /** Stable id derived from the parent heading (slug / `#id`). */
2381
+ headingKey: string;
2382
+ /** Position of the parent heading at widget-creation time. Used as a
2383
+ * fallback when the dynamic lookup fails (e.g. before the first
2384
+ * transaction). */
2385
+ fallbackParentPos: number;
2386
+ /** Host element used for portal targeting by the maximize overlay. */
2387
+ host?: HTMLElement | null;
2388
+ }
2389
+ declare function DiagramWidget({ editor, headingKey, fallbackParentPos, host }: DiagramWidgetProps): react_jsx_runtime.JSX.Element;
2390
+
2391
+ /**
2392
+ * Tiptap commands for diagram editing.
2393
+ *
2394
+ * Each command finds the relevant heading inside a diagram section (parent
2395
+ * heading + its direct sub-headings until the next equal-or-shallower
2396
+ * heading) and mutates either its `data-block-attrs` attribute or its
2397
+ * text content. All edits flow back into markdown via the existing
2398
+ * `tiptapBridge` round-trip — no parallel data store.
2399
+ */
2400
+
2401
+ interface HeadingLocation {
2402
+ /** Node start position in the doc (absolute). */
2403
+ pos: number;
2404
+ /** The heading PMNode. */
2405
+ node: Node;
2406
+ /** Parsed attributes derived from the heading's `data-block-attrs` (always defined). */
2407
+ attrs: HeadingAttributes;
2408
+ /** Computed id: explicit `#id` if set, otherwise the slugified heading text. */
2409
+ id: string;
2410
+ }
2411
+ /**
2412
+ * Find the diagram section that starts at `parentPos` (the position of the
2413
+ * parent heading with `dataTemplate === 'diagram'`). Returns the headings
2414
+ * that should appear as diagram nodes — defined as every heading at the
2415
+ * **shallowest** depth greater than the parent within the section, until
2416
+ * the next equal-or-shallower heading.
2417
+ *
2418
+ * Using the shallowest deeper depth (rather than a strict parentDepth + 1)
2419
+ * mirrors `markdownToDoc`'s stack behavior: when authors skip a level
2420
+ * (e.g. `# parent` + `### child`), those `###` headings are still treated
2421
+ * as direct children of the `#` parent. Any headings deeper than the
2422
+ * detected child depth are sub-sections of a node and are not surfaced as
2423
+ * separate diagram nodes.
2424
+ */
2425
+ declare function listDiagramChildren(editor: Editor$1, parentPos: number): HeadingLocation[];
2426
+ /**
2427
+ * Update a node's `x` / `y` attributes from a drag.
2428
+ *
2429
+ * Before writing the moved node, this also "freezes" any siblings that
2430
+ * lack an explicit position by snapshotting their currently-displayed
2431
+ * (auto-laid) coordinates. Without that, `computeDiagramLayout`'s grid
2432
+ * auto-placement is relative to the bounding box of pinned nodes — so
2433
+ * dragging one node would pull every unpinned sibling along behind it.
2434
+ * Freezing converts the implicit layout into explicit per-node
2435
+ * positions on the first interaction, after which each node moves
2436
+ * independently.
2437
+ */
2438
+ declare function moveNode(editor: Editor$1, parentPos: number, nodeId: string, x: number, y: number): boolean;
2439
+ /**
2440
+ * Add a connection from `sourceId` to `targetId` (optionally typed). No-op
2441
+ * if the same connection already exists.
2442
+ */
2443
+ declare function addConnection(editor: Editor$1, parentPos: number, sourceId: string, targetId: string, type?: string): boolean;
2444
+ /**
2445
+ * Remove a connection from `sourceId` to `targetId`. If `type` is provided,
2446
+ * only the matching-typed entry is removed; otherwise the first match
2447
+ * (regardless of type) is removed.
2448
+ */
2449
+ declare function removeConnection(editor: Editor$1, parentPos: number, sourceId: string, targetId: string, type?: string): boolean;
2450
+ /**
2451
+ * Replace a heading's text content (used when the user renames a node
2452
+ * via a double-click in the canvas).
2453
+ */
2454
+ declare function renameNode(editor: Editor$1, parentPos: number, nodeId: string, newText: string): boolean;
2455
+ /**
2456
+ * Insert a new heading node at the end of the diagram section. The new
2457
+ * heading carries `data-block-attrs` with the supplied id and position,
2458
+ * so the freshly-inserted node appears in React Flow at the expected
2459
+ * coordinates.
2460
+ */
2461
+ declare function addNode(editor: Editor$1, parentPos: number, id: string, label: string, x: number, y: number): boolean;
2462
+ /**
2463
+ * Remove a child node's heading (and any body content under it up to the
2464
+ * next heading). Also strips inbound `connectsTo` references on remaining
2465
+ * siblings so the diagram doesn't carry dangling targets.
2466
+ */
2467
+ declare function removeNode(editor: Editor$1, parentPos: number, nodeId: string): boolean;
2468
+
1424
2469
  interface JsonEditorProps {
1425
2470
  /** Schema describing the value's shape (with optional `squisq` hints). */
1426
2471
  schema: SquisqAnnotatedSchema;
@@ -1511,8 +2556,8 @@ type RecorderSource = 'mic' | 'camera' | 'screen' | 'screen+mic';
1511
2556
  /** Discriminated state describing what the recorder is currently doing. */
1512
2557
  type RecorderState = 'idle' | 'requesting' | 'ready' | 'recording' | 'stopping' | 'stopped' | 'error';
1513
2558
  interface UseMediaRecorderOptions {
1514
- /** Which capture pipeline to use. */
1515
- source: RecorderSource;
2559
+ /** Which capture pipeline to use (default: `'mic'`). */
2560
+ source?: RecorderSource;
1516
2561
  /**
1517
2562
  * Preferred MIME type override. When the browser supports it, this
1518
2563
  * wins over the default candidate list. When unset (or unsupported),
@@ -1535,6 +2580,13 @@ interface UseMediaRecorderOptions {
1535
2580
  * only); when unsupported the resulting stream simply omits it.
1536
2581
  */
1537
2582
  systemAudio?: boolean;
2583
+ /**
2584
+ * For `source === 'camera'`, whether to include the microphone track.
2585
+ * Defaults to `true` (camera + mic). Set `false` to capture silent
2586
+ * video. Ignored for other sources, whose mic handling is encoded in
2587
+ * the source itself (`'mic'`, `'screen+mic'`).
2588
+ */
2589
+ includeMicrophone?: boolean;
1538
2590
  }
1539
2591
  interface UseMediaRecorderResult {
1540
2592
  /** Current recorder state. */
@@ -1582,7 +2634,7 @@ interface UseMediaRecorderResult {
1582
2634
  * (e.g. headless tests) can resolve a format up front.
1583
2635
  */
1584
2636
  declare function getCaptureKind(source: RecorderSource): CaptureKind;
1585
- declare function useMediaRecorder(options: UseMediaRecorderOptions): UseMediaRecorderResult;
2637
+ declare function useMediaRecorder(options?: UseMediaRecorderOptions): UseMediaRecorderResult;
1586
2638
 
1587
2639
  interface RecorderModalProps {
1588
2640
  /** Required — recordings are written here. */
@@ -1876,16 +2928,17 @@ declare function ImageEditor(props: ImageEditorProps): react_jsx_runtime.JSX.Ele
1876
2928
  * version manager) lives in `useImageEditor.ts`.
1877
2929
  */
1878
2930
 
2931
+ type DOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
1879
2932
  /**
1880
2933
  * Layer payload accepted by the `add-layer` action — the `id` field is
1881
2934
  * optional and will be assigned by the underlying `addLayer` helper if
1882
2935
  * the caller doesn't supply one.
1883
2936
  */
1884
- type ImageEditLayerInput = ImageEditLayer | (Omit<ImageEditLayer, 'id'> & {
2937
+ type ImageEditLayerInput = ImageEditLayer | (DOmit<ImageEditLayer, 'id'> & {
1885
2938
  id?: string;
1886
2939
  });
1887
2940
  /** The currently active interaction tool. */
1888
- type ImageEditorTool = 'select' | 'text' | 'shape' | 'image' | 'crop';
2941
+ type ImageEditorTool = 'select' | 'text' | 'shape' | 'image' | 'crop' | 'zoom-rect';
1889
2942
  /** A pixel-space rectangle in canvas coordinates. */
1890
2943
  interface CanvasRect {
1891
2944
  x: number;
@@ -1900,6 +2953,12 @@ interface ImageEditorState {
1900
2953
  selectedLayerId: string | null;
1901
2954
  /** Active tool. */
1902
2955
  tool: ImageEditorTool;
2956
+ /**
2957
+ * The shape kind the shape tool will drop next (a drawing palette kind,
2958
+ * e.g. `'rectangle'`, `'diamond'`, `'arrow-right'`). Set when the user
2959
+ * picks from the shape palette; defaults to `'rectangle'`.
2960
+ */
2961
+ shapeKind: string;
1903
2962
  /**
1904
2963
  * Dirty flag — true when the in-memory doc has unsaved changes
1905
2964
  * relative to the last `markClean()` call. The hook uses this to
@@ -1915,6 +2974,9 @@ type ImageEditorAction = {
1915
2974
  } | {
1916
2975
  type: 'set-tool';
1917
2976
  tool: ImageEditorTool;
2977
+ } | {
2978
+ type: 'set-shape-kind';
2979
+ kind: string;
1918
2980
  } | {
1919
2981
  type: 'select';
1920
2982
  layerId: string | null;
@@ -1995,4 +3057,4 @@ interface UseImageEditorReturn {
1995
3057
  }
1996
3058
  declare function useImageEditor(options: UseImageEditorOptions): UseImageEditorReturn;
1997
3059
 
1998
- export { ALL_EMOJIS, type CameraStreamOptions, type CanvasRect, type CaptureKind, type DocumentLinkCandidate, type DocumentLinkProvider, DocumentSettingsDialog, type DocumentSettingsDialogProps, type DragContentType, type DropTarget, DropZoneOverlay, type DropZoneOverlayProps, EMOJI_CATEGORIES, type EditorActions, type EditorContextValue, type EditorMode, EditorProvider, type EditorProviderProps, EditorShell, type EditorShellProps, type EditorState, type EditorTheme, type EditorView, type EmojiCategory, type EmojiEntry, EmojiPicker, type EmojiPickerProps, type FileCategory, type FileKind, HeadingWithTemplate, type ImageDisplayMode, ImageEditor, type ImageEditorAction, type ImageEditorProps, type ImageEditorState, type ImageEditorTool, ImageViewer, type ImageViewerProps, InlinePreviewGutter, type InlinePreviewGutterProps, JsonEditor, type JsonEditorProps, MediaBin, type MediaBinProps, type MentionCandidate, type MentionProvider, OutlinePanel, type OutlinePanelProps, PlainHtmlPreview, type PlainHtmlPreviewProps, PreviewPanel, type PreviewPanelProps, type PreviewSettings, PreviewSettingsProvider, PreviewToolbarControls, RawEditor, type RawEditorProps, type RecordedBookmark, RecorderButton, type RecorderButtonProps, RecorderModal, type RecorderModalProps, RecorderPanel, type RecorderPanelProps, type RecorderSaveResult, type RecorderSource, type RecorderState, type ResolvedFormat, type ScreenStreamHandle, type ScreenStreamOptions, StatusBar, type StatusBarProps, TemplatePicker, ThemeCustomizerPanel, type ThemeCustomizerPanelProps, type ThemeInheritance, ThemePicker, type ThemePickerProps, type TimingJson, Toolbar, type ToolbarProps, TooltipLayer, type UseFileDropOptions, type UseFileDropResult, type UseImageEditorOptions, type UseImageEditorReturn, type UseMediaRecorderOptions, type UseMediaRecorderResult, VersionHistoryPanel, ViewMenuPanel, type ViewPreferences, ViewSwitcher, type ViewSwitcherProps, WysiwygEditor, type WysiwygEditorProps, buildFilename, buildPreviewDoc, buildTimingJson, classifyFile, detectLanguageFromFileName, encodeTimingJson, getCaptureKind, imageEditorReducer, initialImageEditorState, markdownToTiptap, partitionFiles, processMediaFiles, processTextFile, processTextFiles, requestCameraStream, requestMicStream, requestScreenStream, resolveFileKind, resolveFormat, searchEmojis, supportsDisplayMedia, supportsMediaRecorder, supportsUserMedia, templateLabel, timingPathFor, tiptapToMarkdown, useEditorContext, useFileDrop, useImageEditor, useMediaRecorder, usePreviewSettings, useStreamPreview };
3060
+ export { ALL_EMOJIS, BlockCardView, type BlockCardViewProps, type BlockNavigator, BlockPropertiesPopover, type BlockPropertiesPopoverProps, type BlockRange, type BlockSlice, type CameraStreamOptions, type CanvasRect, type CaptureKind, type CodeContext, type CodeContextSection, CodeContextZones, type CustomTemplateContextValue, CustomTemplateProvider, type CustomTemplateProviderProps, type CustomThemeContextValue, CustomThemeProvider, type CustomThemeProviderProps, DiagramCanvas, type DiagramCommand, type DiagramData, DiagramExtension, type DiagramRFEdge, type DiagramRFNode, DiagramWidget, type DirectionModel, type DocCustomTemplates, type DocCustomThemes, type DocumentLinkCandidate, type DocumentLinkProvider, DocumentSettingsDialog, type DocumentSettingsDialogProps, type DragContentType, type DropTarget, DropZoneOverlay, type DropZoneOverlayProps, EMOJI_CATEGORIES, EMPTY_TRANSITION, type EditorActions, type EditorColorScheme, type EditorContextValue, type EditorMode, EditorProvider, type EditorProviderProps, EditorShell, type EditorShellProps, type EditorState, type EditorView, type EmojiCategory, type EmojiEntry, EmojiPicker, type EmojiPickerProps, type FileCategory, type FileKind, type FolderEntry, FolderView, type FolderViewProps, HeadingWithTemplate, type ImageDisplayMode, ImageEditor, type ImageEditorAction, type ImageEditorProps, type ImageEditorState, type ImageEditorTool, ImageViewer, type ImageViewerProps, InlinePreviewGutter, type InlinePreviewGutterProps, JsonEditor, type JsonEditorProps, type LayoutMode, MediaBin, type MediaBinProps, type MediaClipPatch, type MentionCandidate, type MentionProvider, OutlinePanel, type OutlinePanelProps, PlainHtmlPreview, type PlainHtmlPreviewProps, PreviewFormatSwitch, PreviewModeSwitch, PreviewPanel, type PreviewPanelProps, type PreviewSettings, PreviewSettingsProvider, PreviewToolbarControls, RawEditor, type RawEditorProps, type RecordedBookmark, RecorderButton, type RecorderButtonProps, RecorderModal, type RecorderModalProps, RecorderPanel, type RecorderPanelProps, type RecorderSaveResult, type RecorderSource, type RecorderState, type ResolvedFormat, type ScreenStreamHandle, type ScreenStreamOptions, StatusBar, type StatusBarProps, TRANSITION_ENTRIES, TRANSITION_GROUPS, TemplatePicker, ThemeCustomizerPanel, type ThemeCustomizerPanelProps, type ThemeInheritance, ThemePicker, type ThemePickerProps, TimelineTrack, type TimelineTrackProps, type TimingJson, Toolbar, type ToolbarProps, TooltipLayer, type TransitionCatalogEntry, type TransitionFields, type TransitionGroup, TransitionPicker, type TransitionPickerProps, type UseBlockNavigatorOptions, type UseFileDropOptions, type UseFileDropResult, type UseImageEditorOptions, type UseImageEditorReturn, type UseMediaRecorderOptions, type UseMediaRecorderResult, type UseMonacoLoaderResult, VersionHistoryPanel, ViewMenuPanel, type ViewPreferences, ViewSwitcher, type ViewSwitcherProps, WysiwygEditor, type WysiwygEditorProps, addConnection, addNode, buildFilename, buildPreviewDoc, buildTimingJson, classifyFile, detectLanguageFromFileName, encodeTimingJson, findTransitionEntry, formatSeconds, getBlockSlices, getCaptureKind, imageEditorReducer, initialImageEditorState, lineToOffset, listDiagramChildren, markdownToTiptap, moveNode, offsetToLine, partitionFiles, processMediaFiles, processTextFile, processTextFiles, readBlockAttrsParams, readBlockAttrsTransition, readBlockAttrsValue, readHeadingLineTransition, removeConnection, removeNode, renameNode, requestCameraStream, requestMicStream, requestScreenStream, resolveFileKind, resolveFormat, searchEmojis, setBlockAttrsTransition, setBlockAttrsValue, setBlockDurationInSource, setHeadingLineTransition, setMediaClipInSource, sliceIndexAtOffset, spliceBlock, summarizeBlockProps, supportsDisplayMedia, supportsMediaRecorder, supportsUserMedia, templateLabel, timingPathFor, tiptapToMarkdown, transitionLabel, useBlockNavigator, useCustomTemplates, useCustomThemes, useDiagramData, useDocCustomTemplates, useDocCustomThemes, useEditorContext, useFileDrop, useImageEditor, useMediaRecorder, useMonacoLoader, usePreviewSettings, useStreamPreview };