@bendyline/squisq-editor-react 1.6.2 → 2.0.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.
Files changed (200) hide show
  1. package/README.md +12 -3
  2. package/dist/index.d.ts +1229 -179
  3. package/dist/index.js +18017 -11231
  4. package/dist/index.js.map +1 -1
  5. package/dist/monaco.d.ts +1 -0
  6. package/dist/monaco.js +4 -0
  7. package/dist/monaco.js.map +1 -0
  8. package/dist/styles/index.css +1471 -245
  9. package/package.json +9 -5
  10. package/src/BlockPropertiesPopover.tsx +30 -15
  11. package/src/DocumentSettingsDialog.tsx +13 -21
  12. package/src/EditorContext.tsx +199 -42
  13. package/src/EditorShell.tsx +348 -260
  14. package/src/ImageEditor.tsx +22 -1
  15. package/src/InlinePreviewGutter.tsx +16 -8
  16. package/src/MediaBin.tsx +107 -14
  17. package/src/OutlinePanel.tsx +202 -7
  18. package/src/PlainHtmlPreview.tsx +40 -1
  19. package/src/PreviewControls.tsx +309 -69
  20. package/src/PreviewPanel.tsx +312 -78
  21. package/src/RawEditor.tsx +13 -2
  22. package/src/StatusBar.tsx +5 -2
  23. package/src/TemplateAnnotation.ts +12 -3
  24. package/src/TemplatePicker.tsx +75 -21
  25. package/src/ThemeCustomizerPanel.tsx +8 -0
  26. package/src/ThemePicker.tsx +10 -5
  27. package/src/TimelineBlockPreview.tsx +1 -1
  28. package/src/Toolbar.tsx +202 -41
  29. package/src/TransitionPicker.tsx +36 -4
  30. package/src/ViewMenuPanel.tsx +12 -14
  31. package/src/WysiwygEditor.tsx +124 -91
  32. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
  33. package/src/__tests__/blockTagActivity.test.ts +183 -0
  34. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
  35. package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
  36. package/src/__tests__/buildPreviewDocTransition.test.ts +21 -1
  37. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
  38. package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
  39. package/src/__tests__/documentSettingsDialog.test.tsx +29 -1
  40. package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
  41. package/src/__tests__/editorScrollSync.test.ts +65 -0
  42. package/src/__tests__/editorShellProps.test.tsx +123 -1
  43. package/src/__tests__/emojiPicker.test.tsx +2 -42
  44. package/src/__tests__/headingTransition.test.ts +0 -12
  45. package/src/__tests__/imageEditAffordance.test.tsx +5 -1
  46. package/src/__tests__/imageEditModalContract.test.tsx +119 -0
  47. package/src/__tests__/imageEditorShell.test.tsx +15 -1
  48. package/src/__tests__/jsonEditor.test.tsx +56 -1
  49. package/src/__tests__/layersPanel.test.tsx +75 -5
  50. package/src/__tests__/majorApiTypes.test.ts +26 -0
  51. package/src/__tests__/mediaBinDrop.test.tsx +90 -0
  52. package/src/__tests__/narrationSave.test.ts +148 -0
  53. package/src/__tests__/outlinePanel.test.tsx +93 -3
  54. package/src/__tests__/outlineSource.test.ts +217 -0
  55. package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
  56. package/src/__tests__/presentationMode.test.tsx +290 -0
  57. package/src/__tests__/previewControls.test.tsx +218 -5
  58. package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
  59. package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
  60. package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
  61. package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
  62. package/src/__tests__/resolveBlockVisual.test.ts +43 -0
  63. package/src/__tests__/statusBar.test.tsx +27 -0
  64. package/src/__tests__/teleprompter.test.tsx +190 -0
  65. package/src/__tests__/templateAnnotationRoundTrip.test.ts +26 -0
  66. package/src/__tests__/templatePickerPortal.test.tsx +62 -0
  67. package/src/__tests__/tiptapBridge.test.ts +23 -0
  68. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
  69. package/src/__tests__/transformStyleId.test.ts +13 -0
  70. package/src/__tests__/useImageEditor.test.tsx +67 -0
  71. package/src/__tests__/useMediaRecorder.test.ts +75 -0
  72. package/src/__tests__/viewMenuPanel.test.tsx +130 -0
  73. package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
  74. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
  75. package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
  76. package/src/asciiDiagram/AsciiDiagramWidget.tsx +224 -0
  77. package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
  78. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
  79. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
  80. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +227 -0
  81. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
  82. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
  83. package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
  84. package/src/asciiDiagram/asciiDiagramData.ts +116 -0
  85. package/src/asciiDiagram/asciiDiagramOps.ts +219 -0
  86. package/src/asciiDiagram/asciiPaste.ts +19 -0
  87. package/src/blockTagActivity.ts +233 -0
  88. package/src/buildDocumentPreviewMarkdown.ts +168 -0
  89. package/src/buildPreviewDoc.ts +19 -12
  90. package/src/customTemplates/CustomLayoutManager.tsx +3 -2
  91. package/src/customTemplates/CustomTemplateContext.tsx +6 -0
  92. package/src/customTemplates/TemplateDesigner.tsx +7 -4
  93. package/src/customTemplates/__tests__/library.test.ts +8 -0
  94. package/src/customTemplates/designer.css +158 -99
  95. package/src/customTemplates/library.ts +16 -3
  96. package/src/customThemes/CustomThemeContext.tsx +6 -0
  97. package/src/customThemes/CustomThemeDialog.tsx +35 -5
  98. package/src/customThemes/ImportThemeSection.tsx +178 -0
  99. package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
  100. package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
  101. package/src/customThemes/customThemeLibrary.ts +7 -4
  102. package/src/customThemes/index.ts +7 -1
  103. package/src/customThemes/themeDraft.ts +23 -7
  104. package/src/diagram/DiagramCanvas.tsx +9 -4
  105. package/src/diagram/types.ts +35 -0
  106. package/src/editorScrollSync.ts +73 -0
  107. package/src/emojiData.ts +3 -23
  108. package/src/headingTransition.ts +0 -15
  109. package/src/imageEditor/CanvasSurface.tsx +29 -23
  110. package/src/imageEditor/LayersPanel.tsx +78 -3
  111. package/src/imageEditor/Toolbar.tsx +5 -1
  112. package/src/imageEditor/icons.tsx +4 -0
  113. package/src/imageEditor/image-editor.css +75 -0
  114. package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
  115. package/src/imageEditor/useImageEditor.ts +71 -14
  116. package/src/index.ts +191 -19
  117. package/src/jsonEditor/JsonEditorContext.tsx +12 -6
  118. package/src/jsonEditor/RenderNode.tsx +24 -3
  119. package/src/jsonEditor/editors.tsx +86 -19
  120. package/src/mediaEntries.ts +12 -0
  121. package/src/monaco.ts +35 -0
  122. package/src/monacoWorkers.ts +89 -0
  123. package/src/outlineSource.ts +171 -0
  124. package/src/presentation/PresentationMode.tsx +596 -0
  125. package/src/rawEditorIsolation.ts +18 -0
  126. package/src/recorder/hooks/useMediaRecorder.ts +97 -53
  127. package/src/resolveBlockVisual.ts +10 -4
  128. package/src/scene/Scene.tsx +48 -18
  129. package/src/scene/SceneBlockExtension.ts +17 -10
  130. package/src/scene/SceneBlockWidget.tsx +5 -1
  131. package/src/scene/SceneViewport.tsx +7 -3
  132. package/src/scene/ShapePalette.tsx +17 -2
  133. package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
  134. package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
  135. package/src/scene/__tests__/sceneIsolation.test.tsx +93 -0
  136. package/src/scene/adapters/DiagramAdapter.ts +12 -101
  137. package/src/scene/commands/SceneCommand.ts +4 -1
  138. package/src/scene/index.ts +1 -6
  139. package/src/scene/layers/DiagramEdges.tsx +21 -7
  140. package/src/scene/layers/edgeGeometry.ts +9 -1
  141. package/src/scene/layers/nodeCard.tsx +27 -8
  142. package/src/scene/scene.css +5 -2
  143. package/src/scene/text/SceneTextOverlay.tsx +5 -5
  144. package/src/scene/text/sceneTextChannel.ts +26 -20
  145. package/src/scene/text/sceneTextConfig.ts +4 -0
  146. package/src/scene/tools/ConnectTool.ts +13 -4
  147. package/src/scene/tools/SceneTool.ts +8 -0
  148. package/src/scene/tools/SelectTool.ts +34 -18
  149. package/src/scene/tools/ShapeTool.ts +2 -3
  150. package/src/styles/ascii-diagram.css +79 -0
  151. package/src/styles/ascii-timeline.css +499 -0
  152. package/src/styles/editor.css +706 -120
  153. package/src/styles/index.css +3 -0
  154. package/src/styles/tree-view.css +139 -0
  155. package/src/teleprompter/TeleprompterControls.tsx +218 -0
  156. package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
  157. package/src/teleprompter/TeleprompterSurface.tsx +267 -0
  158. package/src/teleprompter/TeleprompterView.tsx +338 -0
  159. package/src/teleprompter/canvasRenderer.ts +161 -0
  160. package/src/teleprompter/floatingWindow.ts +352 -0
  161. package/src/teleprompter/index.ts +61 -0
  162. package/src/teleprompter/pcmWorklet.ts +62 -0
  163. package/src/teleprompter/recording/insertPreamble.ts +80 -0
  164. package/src/teleprompter/recording/narrationSave.ts +134 -0
  165. package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
  166. package/src/teleprompter/scrollModel.ts +85 -0
  167. package/src/teleprompter/teleprompterTheme.ts +303 -0
  168. package/src/teleprompter/types.ts +38 -0
  169. package/src/teleprompter/useFloatingWindow.ts +74 -0
  170. package/src/teleprompter/useMicAnalysis.ts +211 -0
  171. package/src/teleprompter/useTeleprompter.ts +421 -0
  172. package/src/templateContentPreviewResolver.ts +12 -5
  173. package/src/timeline/TimelineEditorWidget.tsx +702 -0
  174. package/src/timeline/TimelineViewExtension.ts +252 -0
  175. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
  176. package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
  177. package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
  178. package/src/timeline/__tests__/timelineOps.test.ts +222 -0
  179. package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
  180. package/src/timeline/timelineCommands.ts +367 -0
  181. package/src/timeline/timelineData.ts +54 -0
  182. package/src/timeline/timelineOps.ts +277 -0
  183. package/src/timeline/timelinePaste.ts +8 -0
  184. package/src/tiptapBridge.ts +24 -16
  185. package/src/transformStyleId.ts +17 -0
  186. package/src/treeview/TreeOutlineWidget.tsx +240 -0
  187. package/src/treeview/TreeViewExtension.ts +250 -0
  188. package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
  189. package/src/treeview/__tests__/treeOps.test.ts +125 -0
  190. package/src/treeview/__tests__/treePaste.test.ts +40 -0
  191. package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
  192. package/src/treeview/treeOps.ts +172 -0
  193. package/src/treeview/treePaste.ts +13 -0
  194. package/src/treeview/treeViewCommands.ts +94 -0
  195. package/src/treeview/treeViewData.ts +41 -0
  196. package/src/useMonacoLoader.ts +11 -34
  197. package/src/wysiwygImageUpload.ts +113 -0
  198. package/src/diagram/DiagramExtension.ts +0 -209
  199. package/src/diagram/DiagramWidget.tsx +0 -272
  200. package/src/diagram/useDiagramData.ts +0 -126
package/src/index.ts CHANGED
@@ -45,6 +45,7 @@ export type {
45
45
  DocumentLinkProvider,
46
46
  ViewPreferences,
47
47
  ThemeInheritance,
48
+ BlockTagVisibility,
48
49
  LayoutMode,
49
50
  } from './EditorContext.js';
50
51
 
@@ -99,11 +100,12 @@ export { DocumentSettingsDialog } from './DocumentSettingsDialog.js';
99
100
  export type { DocumentSettingsDialogProps } from './DocumentSettingsDialog.js';
100
101
  export { ThemePicker } from './ThemePicker.js';
101
102
  export type { ThemePickerProps } from './ThemePicker.js';
102
- export { EMOJI_CATEGORIES, ALL_EMOJIS, searchEmojis } from './emojiData.js';
103
- export type { EmojiEntry, EmojiCategory } from './emojiData.js';
103
+ export { PICKER_CATEGORIES, ALL_PICKER_ENTRIES, searchPickerEntries } from './emojiData.js';
104
+ export type { PickerEntry, PickerCategory } from './emojiData.js';
104
105
  export {
105
106
  PreviewSettingsProvider,
106
107
  PreviewToolbarControls,
108
+ PreviewModeMenu,
107
109
  PreviewModeSwitch,
108
110
  PreviewFormatSwitch,
109
111
  usePreviewSettings,
@@ -142,7 +144,6 @@ export {
142
144
  setHeadingLineTransition,
143
145
  readBlockAttrsTransition,
144
146
  setHeadingAttrsTransition,
145
- setBlockAttrsTransition,
146
147
  EMPTY_TRANSITION,
147
148
  } from './headingTransition.js';
148
149
  export type { HeadingTransitionAttrs, TransitionFields } from './headingTransition.js';
@@ -190,12 +191,26 @@ export {
190
191
  export { useMonacoLoader } from './useMonacoLoader.js';
191
192
  export type { UseMonacoLoaderResult } from './useMonacoLoader.js';
192
193
 
194
+ // Monaco language-service worker wiring — the host supplies the `?worker`
195
+ // constructors, this owns the label→worker mapping. See monacoWorkers.ts.
196
+ export { configureMonacoWorkers } from './monacoWorkers.js';
197
+ export type { MonacoWorkerConstructor, MonacoWorkerConstructors } from './monacoWorkers.js';
198
+
193
199
  // Custom themes — provider stack (doc frontmatter + browser-local library)
194
- export { CustomThemeProvider, useCustomThemes, useDocCustomThemes } from './customThemes/index.js';
200
+ export {
201
+ CustomThemeProvider,
202
+ useCustomThemes,
203
+ useDocCustomThemes,
204
+ ImportThemeSection,
205
+ draftPatchFromImportedTheme,
206
+ } from './customThemes/index.js';
195
207
  export type {
196
208
  CustomThemeContextValue,
197
209
  CustomThemeProviderProps,
198
210
  DocCustomThemes,
211
+ ImportThemeSectionProps,
212
+ ImportedThemeResult,
213
+ ThemeSaveExtras,
199
214
  } from './customThemes/index.js';
200
215
 
201
216
  // Custom templates — provider stack (doc frontmatter + browser-local library)
@@ -219,24 +234,128 @@ export { buildPreviewDoc } from './buildPreviewDoc.js';
219
234
  // Tiptap extension: Heading with template annotation support
220
235
  export { HeadingWithTemplate } from './TemplateAnnotation.js';
221
236
 
222
- // Diagram editor — Tiptap extension that mounts a React-Flow canvas
223
- // below any `### Title {[diagram]}` heading and hides the section's
224
- // child headings (they're rendered as nodes in the canvas).
225
- export { DiagramExtension } from './diagram/DiagramExtension.js';
237
+ // Diagram editor — ASCII fences are the authored diagram format. The
238
+ // AsciiDiagramExtension turns code fences containing box-and-line art
239
+ // into interactive canvases; edits re-render the art (the fence stays the
240
+ // source of truth). Legacy `{[diagram]}` heading sections still RENDER in
241
+ // preview/player via core, but heading-based canvas editing was removed
242
+ // (BREAKING: DiagramExtension / DiagramWidget / useDiagramData / the
243
+ // heading command helpers are gone).
226
244
  export { DiagramCanvas } from './diagram/DiagramCanvas.js';
227
245
  export type { DiagramCommand } from './diagram/DiagramCanvas.js';
228
- export { DiagramWidget } from './diagram/DiagramWidget.js';
229
- export { useDiagramData } from './diagram/useDiagramData.js';
230
- export type { DiagramData, DiagramRFNode, DiagramRFEdge } from './diagram/useDiagramData.js';
246
+ export type { DiagramData, DiagramNode, DiagramEdge } from './diagram/types.js';
247
+ export {
248
+ AsciiDiagramExtension,
249
+ findAsciiDiagramBlockPos,
250
+ isAsciiSourceVisible,
251
+ toggleAsciiSource,
252
+ } from './asciiDiagram/AsciiDiagramExtension.js';
253
+ export type {
254
+ AsciiDiagramBlockEntry,
255
+ AsciiDiagramExtensionOptions,
256
+ AsciiDiagramPluginState,
257
+ } from './asciiDiagram/AsciiDiagramExtension.js';
258
+ export { AsciiDiagramWidget } from './asciiDiagram/AsciiDiagramWidget.js';
259
+ export { useAsciiDiagramData, asciiDiagramToCanvas } from './asciiDiagram/asciiDiagramData.js';
260
+ export type { AsciiDiagramView } from './asciiDiagram/asciiDiagramData.js';
261
+ export {
262
+ applyAsciiDiagramCommand,
263
+ applyRepairCommand,
264
+ replaceAsciiFenceText,
265
+ } from './asciiDiagram/asciiDiagramCommands.js';
266
+ // RepairableDiagramExtension mounts an inline "Repair as diagram" button on
267
+ // code fences holding BROKEN box art — art too misaligned for clean detection
268
+ // (so it renders as a faithful code block). One click reconstructs it into
269
+ // clean `diagram`-tagged art that the AsciiDiagramExtension turns into a
270
+ // canvas. Peer to the diagram/tree editors; claims the fences neither does.
271
+ export {
272
+ RepairableDiagramExtension,
273
+ findRepairableBlockPos,
274
+ isRepairableFence,
275
+ REPAIRABLE_KEY,
276
+ } from './asciiDiagram/RepairableDiagramExtension.js';
277
+ export type {
278
+ RepairableBlockEntry,
279
+ RepairableDiagramExtensionOptions,
280
+ RepairablePluginState,
281
+ } from './asciiDiagram/RepairableDiagramExtension.js';
282
+ export {
283
+ addEdgeOp,
284
+ addNodeOp,
285
+ moveNodeOp,
286
+ removeEdgeOp,
287
+ removeNodeOp,
288
+ renameNodeOp,
289
+ resizeNodeOp,
290
+ sanitizeAsciiLabel,
291
+ } from './asciiDiagram/asciiDiagramOps.js';
292
+ export { shouldPasteAsAsciiFence } from './asciiDiagram/asciiPaste.js';
293
+
294
+ // Treeview editor — ASCII file-tree / outline fences get an interactive
295
+ // outline widget; edits re-render the tree art (the fence stays the source
296
+ // of truth). Peer to the ASCII diagram editor; mutually exclusive with it
297
+ // (a fence with boxes is a diagram, not a tree).
298
+ export { TreeViewExtension, findTreeBlockPos } from './treeview/TreeViewExtension.js';
299
+ export type {
300
+ TreeBlockEntry,
301
+ TreeViewExtensionOptions,
302
+ TreeViewPluginState,
303
+ } from './treeview/TreeViewExtension.js';
304
+ export { TreeOutlineWidget } from './treeview/TreeOutlineWidget.js';
305
+ export { useTreeViewData } from './treeview/treeViewData.js';
306
+ export type { TreeViewData } from './treeview/treeViewData.js';
307
+ export { applyTreeCommand, replaceTreeFenceText } from './treeview/treeViewCommands.js';
308
+ export type { TreeCommand } from './treeview/treeViewCommands.js';
309
+ export {
310
+ addItemOp,
311
+ indentItemOp,
312
+ moveItemDownOp,
313
+ moveItemUpOp,
314
+ outdentItemOp,
315
+ removeItemOp,
316
+ renameItemOp,
317
+ sanitizeTreeLabel,
318
+ toggleDirOp,
319
+ } from './treeview/treeOps.js';
320
+ export { shouldPasteAsTreeFence } from './treeview/treePaste.js';
321
+
322
+ // Timeline editor — authored marker-rail fences become an accessible canvas
323
+ // with add-point affordances and a selected-point inspector. Semantic edits
324
+ // re-render the canonical `timeline` fence in one undoable transaction.
325
+ export {
326
+ TimelineViewExtension,
327
+ TIMELINE_VIEW_KEY,
328
+ findTimelineBlockPos,
329
+ getTimelineForNode,
330
+ parseTimelineForNode,
331
+ } from './timeline/TimelineViewExtension.js';
332
+ export type {
333
+ TimelineBlockEntry,
334
+ TimelineViewExtensionOptions,
335
+ TimelineViewPluginState,
336
+ } from './timeline/TimelineViewExtension.js';
337
+ export { TimelineEditorWidget } from './timeline/TimelineEditorWidget.js';
338
+ export type { TimelineEditorWidgetProps } from './timeline/TimelineEditorWidget.js';
339
+ export { useTimelineData } from './timeline/timelineData.js';
340
+ export type { TimelineViewData } from './timeline/timelineData.js';
231
341
  export {
232
- moveNode,
233
- addConnection,
234
- removeConnection,
235
- renameNode,
236
- addNode,
237
- removeNode,
238
- listDiagramChildren,
239
- } from './diagram/diagramCommands.js';
342
+ applyTimelineCommand,
343
+ isTimelineSourceSafeForSemanticEdit,
344
+ } from './timeline/timelineCommands.js';
345
+ export type { TimelineCommand, TimelineCommandResult } from './timeline/timelineCommands.js';
346
+ export {
347
+ addTimelineEventOp,
348
+ nextTimelineEventId,
349
+ removeTimelineEventOp,
350
+ sanitizeTimelineText,
351
+ updateTimelineEventOp,
352
+ } from './timeline/timelineOps.js';
353
+ export type {
354
+ AddTimelineEventOptions,
355
+ AddTimelineEventResult,
356
+ TimelineEventPatch,
357
+ } from './timeline/timelineOps.js';
358
+ export { shouldPasteAsTimelineFence } from './timeline/timelinePaste.js';
240
359
 
241
360
  // JSON Form — editable component
242
361
  export { JsonEditor } from './jsonEditor/index.js';
@@ -277,6 +396,59 @@ export type { CaptureKind, ResolvedFormat } from './recorder/formats.js';
277
396
  export { buildTimingJson, encodeTimingJson, timingPathFor } from './recorder/timingJson.js';
278
397
  export type { TimingJson, RecordedBookmark } from './recorder/timingJson.js';
279
398
 
399
+ // Teleprompter — the Narrate display mode under the Use tab: voice-paced
400
+ // prompter surface, floating-window tiers, and the mic analysis pipeline.
401
+ export {
402
+ TeleprompterView,
403
+ TeleprompterSurface,
404
+ TeleprompterControls,
405
+ useTeleprompter,
406
+ useNarrationRecorder,
407
+ buildNarrationSavePlan,
408
+ executeNarrationSave,
409
+ insertNarrationPreamble,
410
+ narrationAnnotationLine,
411
+ cameraVideoLine,
412
+ vadConfigForSensitivity,
413
+ useMicAnalysis,
414
+ PCM_WORKLET_SOURCE,
415
+ PCM_WORKLET_NAME,
416
+ registerPcmWorklet,
417
+ createFloatingWindowManager,
418
+ detectFloatTiers,
419
+ useFloatingWindow,
420
+ measureTokenLines,
421
+ targetOffsetFor,
422
+ stepScroll,
423
+ EYE_LINE_FRACTION,
424
+ TELEPROMPTER_CSS,
425
+ ensureTeleprompterStyles,
426
+ prompterVarsFromTheme,
427
+ DEFAULT_TELEPROMPTER_PREFS,
428
+ } from './teleprompter/index.js';
429
+ export type {
430
+ TeleprompterViewProps,
431
+ TeleprompterSurfaceProps,
432
+ TeleprompterControlsProps,
433
+ TeleprompterController,
434
+ TeleprompterRecordingDeps,
435
+ NarrationRecorderController,
436
+ NarrationRecorderState,
437
+ NarrationTake,
438
+ NarrationSavePlan,
439
+ NarrationSaveResult,
440
+ MicAnalysisHandle,
441
+ MicAnalysisStatus,
442
+ FloatingWindowManager,
443
+ FloatOpenOptions,
444
+ CanvasSink,
445
+ FloatingWindowHandle,
446
+ TokenLineMap,
447
+ TeleprompterPrefs,
448
+ PrompterTransport,
449
+ FloatTier,
450
+ } from './teleprompter/index.js';
451
+
280
452
  // Image editor — layered, sidecar-persisted raster authoring surface.
281
453
  // Pairs with `ImageViewer` and the `<basename>_files/` sidecar convention.
282
454
  export { ImageEditor } from './ImageEditor.js';
@@ -5,7 +5,7 @@
5
5
  * to the host's `onChange`.
6
6
  */
7
7
 
8
- import { createContext, useContext, useMemo, type ReactNode } from 'react';
8
+ import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';
9
9
  import type {
10
10
  JsonFormValidationError,
11
11
  JsonFormValidator,
@@ -38,18 +38,24 @@ export interface JsonEditorProviderProps {
38
38
  export function JsonEditorProvider(props: JsonEditorProviderProps) {
39
39
  const { rootSchema, rootData, onRootChange, density, validate, onValidate, children } = props;
40
40
 
41
+ const validationErrors = useMemo(
42
+ () => (validate ? validate(rootData, rootSchema) : []),
43
+ [validate, rootData, rootSchema],
44
+ );
41
45
  const errors = useMemo(() => {
42
- if (!validate) return new Map<string, JsonFormValidationError[]>();
43
- const list = validate(rootData, rootSchema);
44
- if (onValidate) onValidate(list);
45
46
  const grouped = new Map<string, JsonFormValidationError[]>();
46
- for (const e of list) {
47
+ for (const e of validationErrors) {
47
48
  const arr = grouped.get(e.path) ?? [];
48
49
  arr.push(e);
49
50
  grouped.set(e.path, arr);
50
51
  }
51
52
  return grouped;
52
- }, [validate, rootData, rootSchema, onValidate]);
53
+ }, [validationErrors]);
54
+
55
+ useEffect(() => {
56
+ if (!onValidate) return;
57
+ onValidate(validationErrors);
58
+ }, [validationErrors, onValidate]);
53
59
 
54
60
  const setAtPath = useMemo(
55
61
  () => (pointer: string, value: unknown) => {
@@ -11,6 +11,7 @@ import {
11
11
  resolveRef,
12
12
  type SquisqAnnotatedSchema,
13
13
  } from '@bendyline/squisq/jsonForm';
14
+ import { useId } from 'react';
14
15
  import { useJsonEditor } from './JsonEditorContext';
15
16
  import { EDITORS, isCompositeControl } from './editors';
16
17
 
@@ -23,6 +24,7 @@ export interface RenderNodeProps {
23
24
  }
24
25
 
25
26
  export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
27
+ const generatedId = useId().replace(/:/g, '');
26
28
  const { rootSchema, rootData, errors } = useJsonEditor();
27
29
  const resolved = resolveRef(props.schema, rootSchema) ?? props.schema;
28
30
 
@@ -35,6 +37,11 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
35
37
  const label = resolved.squisq?.label ?? resolved.title;
36
38
  const help = resolved.squisq?.help ?? resolved.description;
37
39
  const fieldErrors = errors.get(props.pointer) ?? [];
40
+ const controlId = `squisq-json-control-${generatedId}`;
41
+ const labelId = label ? `${controlId}-label` : undefined;
42
+ const helpId = help ? `${controlId}-help` : undefined;
43
+ const errorId = fieldErrors.length > 0 ? `${controlId}-error` : undefined;
44
+ const describedBy = [helpId, errorId].filter(Boolean).join(' ') || undefined;
38
45
 
39
46
  const editorEl = (
40
47
  <Editor
@@ -42,6 +49,10 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
42
49
  schema={resolved}
43
50
  pointer={props.pointer}
44
51
  disabled={disabled}
52
+ controlId={controlId}
53
+ labelledBy={labelId}
54
+ describedBy={describedBy}
55
+ invalid={fieldErrors.length > 0}
45
56
  // GroupEditor honors this — other editors ignore the extra prop.
46
57
  // @ts-expect-error optional renderer-specific prop
47
58
  suppressTitle={props.suppressTopGroupTitle}
@@ -55,11 +66,21 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
55
66
 
56
67
  return (
57
68
  <div className={`squisq-jf-field${disabled ? ' squisq-jf-field--disabled' : ''}`}>
58
- {label ? <label className="squisq-jf-field__label">{label}</label> : null}
69
+ {label ? (
70
+ <label id={labelId} htmlFor={controlId} className="squisq-jf-field__label">
71
+ {label}
72
+ </label>
73
+ ) : null}
59
74
  {editorEl}
60
- {help ? <p className="squisq-jf-field__help">{help}</p> : null}
75
+ {help ? (
76
+ <p id={helpId} className="squisq-jf-field__help">
77
+ {help}
78
+ </p>
79
+ ) : null}
61
80
  {fieldErrors.length > 0 ? (
62
- <p className="squisq-jf-field__error">{fieldErrors[0].message}</p>
81
+ <p id={errorId} className="squisq-jf-field__error" role="alert">
82
+ {fieldErrors[0].message}
83
+ </p>
63
84
  ) : null}
64
85
  </div>
65
86
  );
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { useEffect, useId, useRef, useState, type ChangeEvent } from 'react';
10
10
  import {
11
+ appendPointer,
11
12
  arrayItemKind,
12
13
  type ControlKind,
13
14
  type SquisqAnnotatedSchema,
@@ -21,6 +22,19 @@ export interface EditorProps {
21
22
  schema: SquisqAnnotatedSchema;
22
23
  pointer: string;
23
24
  disabled: boolean;
25
+ controlId?: string;
26
+ labelledBy?: string;
27
+ describedBy?: string;
28
+ invalid?: boolean;
29
+ }
30
+
31
+ function accessibilityProps(props: EditorProps) {
32
+ return {
33
+ id: props.controlId,
34
+ 'aria-labelledby': props.labelledBy,
35
+ 'aria-describedby': props.describedBy,
36
+ 'aria-invalid': props.invalid || undefined,
37
+ };
24
38
  }
25
39
 
26
40
  // ── Helpers ───────────────────────────────────────────────────────
@@ -38,10 +52,12 @@ function asNumber(v: unknown): number | '' {
38
52
 
39
53
  // ── Primitive editors ────────────────────────────────────────────
40
54
 
41
- export function TextEditor({ value, schema, pointer, disabled }: EditorProps) {
55
+ export function TextEditor(props: EditorProps) {
56
+ const { value, schema, pointer, disabled } = props;
42
57
  const { setAtPath } = useJsonEditor();
43
58
  return (
44
59
  <input
60
+ {...accessibilityProps(props)}
45
61
  type="text"
46
62
  className="squisq-jf-input"
47
63
  value={asString(value)}
@@ -52,7 +68,8 @@ export function TextEditor({ value, schema, pointer, disabled }: EditorProps) {
52
68
  );
53
69
  }
54
70
 
55
- export function MultilineEditor({ value, schema, pointer, disabled }: EditorProps) {
71
+ export function MultilineEditor(props: EditorProps) {
72
+ const { value, schema, pointer, disabled } = props;
56
73
  const { setAtPath } = useJsonEditor();
57
74
  const ref = useRef<HTMLTextAreaElement>(null);
58
75
  // Auto-grow vertically.
@@ -64,6 +81,7 @@ export function MultilineEditor({ value, schema, pointer, disabled }: EditorProp
64
81
  }, [value]);
65
82
  return (
66
83
  <textarea
84
+ {...accessibilityProps(props)}
67
85
  ref={ref}
68
86
  className="squisq-jf-textarea"
69
87
  value={asString(value)}
@@ -86,7 +104,8 @@ export function RichTextEditor({ value, schema, pointer, disabled }: EditorProps
86
104
  );
87
105
  }
88
106
 
89
- export function NumberStepperEditor({ value, schema, pointer, disabled }: EditorProps) {
107
+ export function NumberStepperEditor(props: EditorProps) {
108
+ const { value, schema, pointer, disabled } = props;
90
109
  const { setAtPath } = useJsonEditor();
91
110
  const isInt = schema.type === 'integer';
92
111
  const step = schema.squisq?.step ?? schema.multipleOf ?? (isInt ? 1 : 0.1);
@@ -111,6 +130,7 @@ export function NumberStepperEditor({ value, schema, pointer, disabled }: Editor
111
130
 
112
131
  </button>
113
132
  <input
133
+ {...accessibilityProps(props)}
114
134
  className="squisq-jf-stepper__input"
115
135
  type="number"
116
136
  value={num === '' ? '' : num}
@@ -138,7 +158,8 @@ export function NumberStepperEditor({ value, schema, pointer, disabled }: Editor
138
158
  );
139
159
  }
140
160
 
141
- export function SliderEditor({ value, schema, pointer, disabled }: EditorProps) {
161
+ export function SliderEditor(props: EditorProps) {
162
+ const { value, schema, pointer, disabled } = props;
142
163
  const { setAtPath } = useJsonEditor();
143
164
  const min = (schema.minimum ?? schema.exclusiveMinimum ?? 0) as number;
144
165
  const max = (schema.maximum ?? schema.exclusiveMaximum ?? 100) as number;
@@ -148,6 +169,7 @@ export function SliderEditor({ value, schema, pointer, disabled }: EditorProps)
148
169
  return (
149
170
  <span className="squisq-jf-slider-row">
150
171
  <input
172
+ {...accessibilityProps(props)}
151
173
  type="range"
152
174
  className="squisq-jf-slider"
153
175
  min={min}
@@ -162,11 +184,13 @@ export function SliderEditor({ value, schema, pointer, disabled }: EditorProps)
162
184
  );
163
185
  }
164
186
 
165
- export function ToggleEditor({ value, schema, pointer, disabled }: EditorProps) {
187
+ export function ToggleEditor(props: EditorProps) {
188
+ const { value, schema, pointer, disabled } = props;
166
189
  const { setAtPath } = useJsonEditor();
167
190
  const on = Boolean(value);
168
191
  return (
169
192
  <button
193
+ {...accessibilityProps(props)}
170
194
  type="button"
171
195
  className={`squisq-jf-toggle${on ? ' squisq-jf-toggle--on' : ''}`}
172
196
  disabled={disabled}
@@ -189,12 +213,17 @@ export function ToggleEditor({ value, schema, pointer, disabled }: EditorProps)
189
213
  );
190
214
  }
191
215
 
192
- export function CheckboxEditor({ value, schema, pointer, disabled }: EditorProps) {
216
+ export function CheckboxEditor(props: EditorProps) {
217
+ const { value, schema, pointer, disabled } = props;
193
218
  const { setAtPath } = useJsonEditor();
194
- const id = useId();
219
+ const generatedId = useId();
220
+ const id = props.controlId ?? generatedId;
195
221
  return (
196
222
  <label htmlFor={id} className="squisq-jf-toggle">
197
223
  <input
224
+ aria-labelledby={props.labelledBy}
225
+ aria-describedby={props.describedBy}
226
+ aria-invalid={props.invalid || undefined}
198
227
  id={id}
199
228
  type="checkbox"
200
229
  checked={Boolean(value)}
@@ -214,11 +243,12 @@ function enumOptions(schema: SquisqAnnotatedSchema): { value: unknown; label: st
214
243
  }));
215
244
  }
216
245
 
217
- export function SegmentedEditor({ value, schema, pointer, disabled }: EditorProps) {
246
+ export function SegmentedEditor(props: EditorProps) {
247
+ const { value, schema, pointer, disabled } = props;
218
248
  const { setAtPath } = useJsonEditor();
219
249
  const options = enumOptions(schema);
220
250
  return (
221
- <span className="squisq-jf-segmented">
251
+ <span {...accessibilityProps(props)} className="squisq-jf-segmented" role="group">
222
252
  {options.map((opt, i) => {
223
253
  const active = value === opt.value;
224
254
  return (
@@ -228,6 +258,7 @@ export function SegmentedEditor({ value, schema, pointer, disabled }: EditorProp
228
258
  className={`squisq-jf-segmented__btn${active ? ' squisq-jf-segmented__btn--active' : ''}`}
229
259
  disabled={disabled}
230
260
  onClick={() => setAtPath(pointer, opt.value)}
261
+ aria-pressed={active}
231
262
  >
232
263
  {opt.label}
233
264
  </button>
@@ -237,12 +268,13 @@ export function SegmentedEditor({ value, schema, pointer, disabled }: EditorProp
237
268
  );
238
269
  }
239
270
 
240
- export function RadioEditor({ value, schema, pointer, disabled }: EditorProps) {
271
+ export function RadioEditor(props: EditorProps) {
272
+ const { value, schema, pointer, disabled } = props;
241
273
  const { setAtPath } = useJsonEditor();
242
274
  const options = enumOptions(schema);
243
275
  const name = useId();
244
276
  return (
245
- <div className="squisq-jf-radio">
277
+ <div {...accessibilityProps(props)} className="squisq-jf-radio" role="radiogroup">
246
278
  {options.map((opt, i) => {
247
279
  const active = value === opt.value;
248
280
  return (
@@ -262,11 +294,13 @@ export function RadioEditor({ value, schema, pointer, disabled }: EditorProps) {
262
294
  );
263
295
  }
264
296
 
265
- export function ComboboxEditor({ value, schema, pointer, disabled }: EditorProps) {
297
+ export function ComboboxEditor(props: EditorProps) {
298
+ const { value, schema, pointer, disabled } = props;
266
299
  const { setAtPath } = useJsonEditor();
267
300
  const options = enumOptions(schema);
268
301
  return (
269
302
  <select
303
+ {...accessibilityProps(props)}
270
304
  className="squisq-jf-select"
271
305
  value={asString(value)}
272
306
  disabled={disabled}
@@ -285,12 +319,14 @@ export function ComboboxEditor({ value, schema, pointer, disabled }: EditorProps
285
319
  );
286
320
  }
287
321
 
288
- export function ColorEditor({ value, pointer, disabled }: EditorProps) {
322
+ export function ColorEditor(props: EditorProps) {
323
+ const { value, pointer, disabled } = props;
289
324
  const { setAtPath } = useJsonEditor();
290
325
  const hex = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000';
291
326
  return (
292
327
  <span className="squisq-jf-color">
293
328
  <input
329
+ {...accessibilityProps(props)}
294
330
  type="color"
295
331
  className="squisq-jf-color__input"
296
332
  value={hex}
@@ -308,7 +344,8 @@ export function ColorEditor({ value, pointer, disabled }: EditorProps) {
308
344
  );
309
345
  }
310
346
 
311
- export function DateEditor({ value, schema, pointer, disabled }: EditorProps) {
347
+ export function DateEditor(props: EditorProps) {
348
+ const { value, schema, pointer, disabled } = props;
312
349
  const { setAtPath } = useJsonEditor();
313
350
  const fmt = schema.format;
314
351
  const inputType = fmt === 'time' ? 'time' : fmt === 'date' ? 'date' : 'datetime-local';
@@ -321,6 +358,7 @@ export function DateEditor({ value, schema, pointer, disabled }: EditorProps) {
321
358
  : asString(value);
322
359
  return (
323
360
  <input
361
+ {...accessibilityProps(props)}
324
362
  type={inputType}
325
363
  className="squisq-jf-input"
326
364
  value={display}
@@ -348,7 +386,7 @@ export function GroupEditor(props: EditorProps & { suppressTitle?: boolean }) {
348
386
  key={key}
349
387
  value={obj[key]}
350
388
  schema={propSchema}
351
- pointer={`${pointer}/${key}`}
389
+ pointer={appendPointer(pointer, key)}
352
390
  parentDisabled={disabled}
353
391
  />
354
392
  ))}
@@ -581,7 +619,9 @@ function defaultForSchema(schema: SquisqAnnotatedSchema): unknown {
581
619
  }
582
620
  }
583
621
 
584
- export function TabsEditor({ value, schema, pointer, disabled }: EditorProps) {
622
+ export function TabsEditor(props: EditorProps) {
623
+ const { value, schema, pointer, disabled } = props;
624
+ const tabsId = useId().replace(/:/g, '');
585
625
  const branches = (schema.oneOf ?? schema.anyOf ?? []) as readonly SquisqAnnotatedSchema[];
586
626
  const initial = pickMatchingBranch(branches, value);
587
627
  const [active, setActive] = useState(initial);
@@ -593,9 +633,14 @@ export function TabsEditor({ value, schema, pointer, disabled }: EditorProps) {
593
633
 
594
634
  const branch = branches[active];
595
635
  if (!branch) return null;
636
+ const activateTab = (index: number) => {
637
+ const next = (index + branches.length) % branches.length;
638
+ setActive(next);
639
+ requestAnimationFrame(() => document.getElementById(`${tabsId}-tab-${next}`)?.focus());
640
+ };
596
641
  return (
597
- <div>
598
- <div className="squisq-jf-tabs__strip">
642
+ <div {...accessibilityProps(props)}>
643
+ <div className="squisq-jf-tabs__strip" role="tablist">
599
644
  {branches.map((b, i) => (
600
645
  <button
601
646
  key={i}
@@ -603,12 +648,34 @@ export function TabsEditor({ value, schema, pointer, disabled }: EditorProps) {
603
648
  className={`squisq-jf-tabs__tab${i === active ? ' squisq-jf-tabs__tab--active' : ''}`}
604
649
  disabled={disabled}
605
650
  onClick={() => setActive(i)}
651
+ onKeyDown={(event) => {
652
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
653
+ event.preventDefault();
654
+ activateTab(i + 1);
655
+ } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
656
+ event.preventDefault();
657
+ activateTab(i - 1);
658
+ } else if (event.key === 'Home') {
659
+ event.preventDefault();
660
+ activateTab(0);
661
+ } else if (event.key === 'End') {
662
+ event.preventDefault();
663
+ activateTab(branches.length - 1);
664
+ }
665
+ }}
666
+ role="tab"
667
+ id={`${tabsId}-tab-${i}`}
668
+ aria-selected={i === active}
669
+ aria-controls={`${tabsId}-panel`}
670
+ tabIndex={i === active ? 0 : -1}
606
671
  >
607
672
  {b.squisq?.label ?? b.title ?? `Option ${i + 1}`}
608
673
  </button>
609
674
  ))}
610
675
  </div>
611
- <RenderNode value={value} schema={branch} pointer={pointer} parentDisabled={disabled} />
676
+ <div id={`${tabsId}-panel`} role="tabpanel" aria-labelledby={`${tabsId}-tab-${active}`}>
677
+ <RenderNode value={value} schema={branch} pointer={pointer} parentDisabled={disabled} />
678
+ </div>
612
679
  </div>
613
680
  );
614
681
  }
@@ -0,0 +1,12 @@
1
+ import type { MediaEntry } from '@bendyline/squisq/schemas';
2
+
3
+ /** Files managed by the container but intentionally omitted from the Files panel. */
4
+ export function isVisibleMediaEntry(entry: MediaEntry): boolean {
5
+ const basename = entry.name.slice(entry.name.lastIndexOf('/') + 1);
6
+ return basename.toLowerCase() !== '.gitignore';
7
+ }
8
+
9
+ /** Return the entries that should participate in the Files panel and its count. */
10
+ export function filterVisibleMediaEntries(entries: readonly MediaEntry[]): MediaEntry[] {
11
+ return entries.filter(isVisibleMediaEntry);
12
+ }