@bendyline/squisq-editor-react 2.1.0 → 2.3.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 (415) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +40 -0
  3. package/dist/chunk-54UGTQBO.js +862 -0
  4. package/dist/chunk-5JMHFAVW.js +2408 -0
  5. package/dist/chunk-5Q4JN4I5.js +132 -0
  6. package/dist/chunk-6VDYKI3L.js +49 -0
  7. package/dist/chunk-GS7QWYFT.js +9 -0
  8. package/dist/chunk-MJJK7YQB.js +949 -0
  9. package/dist/chunk-NITZVAXL.js +986 -0
  10. package/dist/chunk-TRCKHRBS.js +35234 -0
  11. package/dist/chunk-V44VP242.js +3256 -0
  12. package/dist/image-editor/index.d.ts +223 -0
  13. package/dist/image-editor/index.js +15 -0
  14. package/dist/index.d.ts +2060 -4040
  15. package/dist/index.js +496 -40014
  16. package/dist/json-editor/index.d.ts +27 -0
  17. package/dist/json-editor/index.js +7 -0
  18. package/dist/monaco.d.ts +1 -1
  19. package/dist/monaco.js +1 -2
  20. package/dist/recorder/index.d.ts +407 -0
  21. package/dist/recorder/index.js +43 -0
  22. package/dist/shell/index.d.ts +9 -0
  23. package/dist/shell/index.js +22 -0
  24. package/dist/shell-BxSCBm4H.d.ts +1064 -0
  25. package/dist/styles/index.css +598 -161
  26. package/dist/teleprompter/index.d.ts +497 -0
  27. package/dist/teleprompter/index.js +57 -0
  28. package/package.json +34 -8
  29. package/dist/index.js.map +0 -1
  30. package/dist/monaco.js.map +0 -1
  31. package/src/BlockCardView.tsx +0 -121
  32. package/src/BlockPreviewPanel.tsx +0 -69
  33. package/src/BlockPropertiesPopover.tsx +0 -222
  34. package/src/DocumentSettingsDialog.tsx +0 -272
  35. package/src/DropZoneOverlay.tsx +0 -137
  36. package/src/EditorContext.tsx +0 -1274
  37. package/src/EditorShell.tsx +0 -1358
  38. package/src/EmojiPicker.tsx +0 -332
  39. package/src/FolderView.tsx +0 -131
  40. package/src/Icon.tsx +0 -26
  41. package/src/ImageEditor.tsx +0 -395
  42. package/src/ImageNodeView.tsx +0 -284
  43. package/src/ImageViewer.tsx +0 -221
  44. package/src/InlineIcon.ts +0 -84
  45. package/src/InlinePreviewGutter.tsx +0 -590
  46. package/src/LinkDialog.tsx +0 -276
  47. package/src/MediaBin.tsx +0 -500
  48. package/src/MentionExtension.tsx +0 -261
  49. package/src/OutlinePanel.tsx +0 -547
  50. package/src/PlainHtmlPreview.tsx +0 -277
  51. package/src/PreviewControls.tsx +0 -1265
  52. package/src/PreviewPanel.tsx +0 -426
  53. package/src/RawEditor.tsx +0 -743
  54. package/src/RecorderEntry.tsx +0 -162
  55. package/src/StatusBar.tsx +0 -51
  56. package/src/TemplateAnnotation.ts +0 -167
  57. package/src/TemplateContentPreview.tsx +0 -56
  58. package/src/TemplatePicker.tsx +0 -1314
  59. package/src/ThemeCustomizerPanel.tsx +0 -304
  60. package/src/ThemePicker.tsx +0 -433
  61. package/src/TimelineBlockPreview.tsx +0 -37
  62. package/src/TimelineTrack.tsx +0 -671
  63. package/src/Toolbar.tsx +0 -2885
  64. package/src/Tooltip.tsx +0 -142
  65. package/src/TransitionPicker.tsx +0 -390
  66. package/src/VersionHistoryPanel.tsx +0 -359
  67. package/src/ViewMenuPanel.tsx +0 -189
  68. package/src/ViewSwitcher.tsx +0 -56
  69. package/src/WysiwygEditor.tsx +0 -806
  70. package/src/__tests__/blockProperties.test.ts +0 -92
  71. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
  72. package/src/__tests__/blockRange.test.ts +0 -105
  73. package/src/__tests__/blockTagActivity.test.ts +0 -183
  74. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
  75. package/src/__tests__/buildPreviewDocContent.test.ts +0 -65
  76. package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
  77. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
  78. package/src/__tests__/codeContextSectionView.test.tsx +0 -97
  79. package/src/__tests__/codeContextZoneManager.test.ts +0 -127
  80. package/src/__tests__/createShapeLayer.test.ts +0 -46
  81. package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
  82. package/src/__tests__/detectMarkdown.test.ts +0 -73
  83. package/src/__tests__/diffContextSections.test.ts +0 -39
  84. package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
  85. package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
  86. package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
  87. package/src/__tests__/editorScrollSync.test.ts +0 -65
  88. package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
  89. package/src/__tests__/editorShellProps.test.tsx +0 -550
  90. package/src/__tests__/embeddedMedia.test.ts +0 -48
  91. package/src/__tests__/emojiPicker.test.tsx +0 -93
  92. package/src/__tests__/fileKind.test.ts +0 -112
  93. package/src/__tests__/findMode.test.tsx +0 -104
  94. package/src/__tests__/findModel.test.ts +0 -55
  95. package/src/__tests__/headingTransition.test.ts +0 -176
  96. package/src/__tests__/imageEditAffordance.test.tsx +0 -272
  97. package/src/__tests__/imageEditModalContract.test.tsx +0 -119
  98. package/src/__tests__/imageEditorShell.test.tsx +0 -94
  99. package/src/__tests__/imageEditorState.test.ts +0 -171
  100. package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
  101. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
  102. package/src/__tests__/jsonEditor.test.tsx +0 -223
  103. package/src/__tests__/layersPanel.test.tsx +0 -167
  104. package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
  105. package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
  106. package/src/__tests__/majorApiTypes.test.ts +0 -26
  107. package/src/__tests__/markdownCodeFence.test.ts +0 -45
  108. package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
  109. package/src/__tests__/mediaBinDrop.test.tsx +0 -90
  110. package/src/__tests__/mediaReferences.test.ts +0 -97
  111. package/src/__tests__/narrationSave.test.ts +0 -148
  112. package/src/__tests__/outlinePanel.test.tsx +0 -169
  113. package/src/__tests__/outlineSource.test.ts +0 -217
  114. package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
  115. package/src/__tests__/presentationMode.test.tsx +0 -290
  116. package/src/__tests__/previewControls.test.tsx +0 -571
  117. package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
  118. package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
  119. package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
  120. package/src/__tests__/propertiesPanel.test.tsx +0 -69
  121. package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
  122. package/src/__tests__/recorderFormats.test.ts +0 -146
  123. package/src/__tests__/recorderMediaInsert.test.ts +0 -86
  124. package/src/__tests__/recorderTheme.test.tsx +0 -42
  125. package/src/__tests__/recorderTimingJson.test.ts +0 -41
  126. package/src/__tests__/resolveBlockVisual.test.ts +0 -43
  127. package/src/__tests__/selectionConversions.test.ts +0 -80
  128. package/src/__tests__/statusBar.test.tsx +0 -27
  129. package/src/__tests__/teleprompter.test.tsx +0 -190
  130. package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
  131. package/src/__tests__/templateContentPreview.test.ts +0 -101
  132. package/src/__tests__/templatePickerMetadata.test.ts +0 -32
  133. package/src/__tests__/templatePickerPortal.test.tsx +0 -62
  134. package/src/__tests__/timelineSource.test.ts +0 -134
  135. package/src/__tests__/tiptapBridge.test.ts +0 -575
  136. package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
  137. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
  138. package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
  139. package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -190
  140. package/src/__tests__/tooltip.test.tsx +0 -72
  141. package/src/__tests__/transformStyleId.test.ts +0 -13
  142. package/src/__tests__/transitionCatalog.test.ts +0 -64
  143. package/src/__tests__/useBlockNavigator.test.tsx +0 -67
  144. package/src/__tests__/useImageEditor.test.tsx +0 -226
  145. package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
  146. package/src/__tests__/useMediaRecorder.test.ts +0 -302
  147. package/src/__tests__/useTimelineClock.test.ts +0 -21
  148. package/src/__tests__/versionHistory.test.tsx +0 -197
  149. package/src/__tests__/viewMenuPanel.test.tsx +0 -130
  150. package/src/__tests__/writeCanvasSettings.test.ts +0 -15
  151. package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
  152. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
  153. package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
  154. package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
  155. package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
  156. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -164
  157. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
  158. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
  159. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
  160. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
  161. package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
  162. package/src/asciiDiagram/asciiDiagramData.ts +0 -149
  163. package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
  164. package/src/asciiDiagram/asciiPaste.ts +0 -19
  165. package/src/blockProperties.ts +0 -88
  166. package/src/blockRange.ts +0 -132
  167. package/src/blockSlice.ts +0 -75
  168. package/src/blockTagActivity.ts +0 -233
  169. package/src/buildDocumentPreviewMarkdown.ts +0 -168
  170. package/src/buildPreviewDoc.ts +0 -407
  171. package/src/codeContext/CodeContextSectionView.tsx +0 -124
  172. package/src/codeContext/CodeContextZoneManager.ts +0 -149
  173. package/src/codeContext/CodeContextZones.tsx +0 -121
  174. package/src/codeContext/diffContextSections.ts +0 -38
  175. package/src/codeContext/types.ts +0 -75
  176. package/src/codeSnippet/CodeSnippetExtension.ts +0 -205
  177. package/src/codeSnippet/CodeSnippetWidget.tsx +0 -109
  178. package/src/codeSnippet/__tests__/CodeSnippetExtension.test.ts +0 -95
  179. package/src/codeSnippet/__tests__/codeSnippetLanguages.test.ts +0 -50
  180. package/src/codeSnippet/codeSnippetCommands.ts +0 -21
  181. package/src/codeSnippet/codeSnippetData.ts +0 -43
  182. package/src/codeSnippet/codeSnippetLanguages.ts +0 -216
  183. package/src/customTemplates/AddBin.tsx +0 -126
  184. package/src/customTemplates/CustomLayoutManager.tsx +0 -234
  185. package/src/customTemplates/CustomTemplateContext.tsx +0 -188
  186. package/src/customTemplates/LayerToolbar.tsx +0 -580
  187. package/src/customTemplates/ShapeGlyph.tsx +0 -47
  188. package/src/customTemplates/TemplateDesigner.tsx +0 -433
  189. package/src/customTemplates/__tests__/library.test.ts +0 -96
  190. package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
  191. package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
  192. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
  193. package/src/customTemplates/designer.css +0 -732
  194. package/src/customTemplates/index.ts +0 -31
  195. package/src/customTemplates/library.ts +0 -110
  196. package/src/customTemplates/normalizePositions.ts +0 -75
  197. package/src/customTemplates/shapeDefs.ts +0 -131
  198. package/src/customTemplates/thumbnail.tsx +0 -63
  199. package/src/customTemplates/tokenDefs.ts +0 -60
  200. package/src/customTemplates/useDocCustomTemplates.ts +0 -52
  201. package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
  202. package/src/customThemes/CustomThemeContext.tsx +0 -185
  203. package/src/customThemes/CustomThemeDialog.tsx +0 -316
  204. package/src/customThemes/ImportThemeSection.tsx +0 -178
  205. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
  206. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
  207. package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
  208. package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
  209. package/src/customThemes/customThemeLibrary.ts +0 -100
  210. package/src/customThemes/index.ts +0 -37
  211. package/src/customThemes/themeControls.tsx +0 -229
  212. package/src/customThemes/themeDraft.ts +0 -288
  213. package/src/customThemes/useDocCustomThemes.ts +0 -49
  214. package/src/detectMarkdown.ts +0 -62
  215. package/src/diagram/DiagramCanvas.tsx +0 -260
  216. package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
  217. package/src/diagram/diagramCommands.ts +0 -604
  218. package/src/diagram/diagramConstants.ts +0 -17
  219. package/src/diagram/types.ts +0 -35
  220. package/src/editorScrollSync.ts +0 -73
  221. package/src/embeddedMedia.ts +0 -78
  222. package/src/emojiData.ts +0 -1317
  223. package/src/fileKind.ts +0 -158
  224. package/src/find/FindHighlightExtension.ts +0 -81
  225. package/src/find/FindToolbar.tsx +0 -314
  226. package/src/find/findModel.ts +0 -77
  227. package/src/frontmatter.ts +0 -29
  228. package/src/frontmatterSettings.ts +0 -23
  229. package/src/headingTransition.ts +0 -291
  230. package/src/hooks/useFileDrop.ts +0 -266
  231. package/src/imageEditor/CanvasSurface.tsx +0 -703
  232. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
  233. package/src/imageEditor/LayersPanel.tsx +0 -218
  234. package/src/imageEditor/PropertiesPanel.tsx +0 -474
  235. package/src/imageEditor/Toolbar.tsx +0 -459
  236. package/src/imageEditor/createShapeLayer.ts +0 -280
  237. package/src/imageEditor/icons.tsx +0 -68
  238. package/src/imageEditor/image-editor.css +0 -574
  239. package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
  240. package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
  241. package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
  242. package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
  243. package/src/imageEditor/state.ts +0 -173
  244. package/src/imageEditor/useImageEditor.ts +0 -385
  245. package/src/imageEditor/useImageEditorTokens.ts +0 -70
  246. package/src/index.ts +0 -559
  247. package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
  248. package/src/jsonEditor/JsonEditor.tsx +0 -81
  249. package/src/jsonEditor/JsonEditorContext.tsx +0 -81
  250. package/src/jsonEditor/RenderNode.tsx +0 -87
  251. package/src/jsonEditor/editors.tsx +0 -745
  252. package/src/jsonEditor/index.ts +0 -2
  253. package/src/jsonEditor/json-editor.css +0 -463
  254. package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
  255. package/src/markdownCodeFence.ts +0 -72
  256. package/src/mediaDragMime.ts +0 -32
  257. package/src/mediaEntries.ts +0 -12
  258. package/src/mediaReferences.ts +0 -301
  259. package/src/mermaid/MermaidDiagramCanvas.tsx +0 -693
  260. package/src/mermaid/MermaidDiagramExtension.ts +0 -243
  261. package/src/mermaid/MermaidDiagramTypeThumbnail.tsx +0 -184
  262. package/src/mermaid/MermaidDiagramWidget.tsx +0 -653
  263. package/src/mermaid/MermaidShapePalette.tsx +0 -245
  264. package/src/mermaid/__tests__/MermaidDiagramExtension.test.ts +0 -361
  265. package/src/mermaid/__tests__/mermaidDiagramTypes.test.ts +0 -35
  266. package/src/mermaid/__tests__/mermaidRenderer.test.ts +0 -49
  267. package/src/mermaid/__tests__/mermaidSourceOps.test.ts +0 -213
  268. package/src/mermaid/__tests__/mermaidSyntax.test.ts +0 -71
  269. package/src/mermaid/mermaidCommands.ts +0 -34
  270. package/src/mermaid/mermaidData.ts +0 -31
  271. package/src/mermaid/mermaidDiagramTypes.ts +0 -325
  272. package/src/mermaid/mermaidModel.ts +0 -31
  273. package/src/mermaid/mermaidRenderer.ts +0 -181
  274. package/src/mermaid/mermaidShapes.ts +0 -113
  275. package/src/mermaid/mermaidSourceOps.ts +0 -454
  276. package/src/monaco.ts +0 -35
  277. package/src/monacoWorkers.ts +0 -89
  278. package/src/outlineSource.ts +0 -171
  279. package/src/presentation/PresentationMode.tsx +0 -596
  280. package/src/rawEditorIsolation.ts +0 -18
  281. package/src/recorder/RecorderButton.tsx +0 -80
  282. package/src/recorder/RecorderModal.tsx +0 -706
  283. package/src/recorder/RecorderPanel.tsx +0 -77
  284. package/src/recorder/formats.ts +0 -159
  285. package/src/recorder/hooks/useMediaRecorder.ts +0 -436
  286. package/src/recorder/hooks/useStreamPreview.ts +0 -47
  287. package/src/recorder/insertMediaBlock.ts +0 -30
  288. package/src/recorder/sources/cameraStream.ts +0 -32
  289. package/src/recorder/sources/micStream.ts +0 -25
  290. package/src/recorder/sources/screenStream.ts +0 -162
  291. package/src/recorder/timingJson.ts +0 -66
  292. package/src/resolveBlockVisual.ts +0 -39
  293. package/src/scene/Scene.tsx +0 -647
  294. package/src/scene/SceneBlockExtension.ts +0 -205
  295. package/src/scene/SceneBlockToolbar.tsx +0 -216
  296. package/src/scene/SceneBlockWidget.tsx +0 -441
  297. package/src/scene/ScenePropsBar.tsx +0 -85
  298. package/src/scene/SceneSelection.tsx +0 -111
  299. package/src/scene/SceneSideToolbar.tsx +0 -89
  300. package/src/scene/SceneViewControls.tsx +0 -43
  301. package/src/scene/SceneViewport.tsx +0 -106
  302. package/src/scene/ShapePalette.tsx +0 -196
  303. package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
  304. package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
  305. package/src/scene/__tests__/bezierEdit.test.ts +0 -85
  306. package/src/scene/__tests__/blockLayers.test.ts +0 -57
  307. package/src/scene/__tests__/fitScale.test.ts +0 -19
  308. package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
  309. package/src/scene/__tests__/shapeLayers.test.ts +0 -106
  310. package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
  311. package/src/scene/__tests__/useScenePanZoom.test.ts +0 -130
  312. package/src/scene/adapters/DiagramAdapter.ts +0 -79
  313. package/src/scene/adapters/DrawingAdapter.ts +0 -420
  314. package/src/scene/adapters/LayoutAdapter.ts +0 -313
  315. package/src/scene/adapters/blockLayers.ts +0 -159
  316. package/src/scene/commands/SceneCommand.ts +0 -84
  317. package/src/scene/commands/drawingCommands.ts +0 -318
  318. package/src/scene/commands/layoutCommands.ts +0 -301
  319. package/src/scene/fitScale.ts +0 -17
  320. package/src/scene/hooks/useSceneHitTest.ts +0 -105
  321. package/src/scene/hooks/useScenePanZoom.ts +0 -154
  322. package/src/scene/hooks/useSceneSelection.ts +0 -62
  323. package/src/scene/index.ts +0 -90
  324. package/src/scene/layers/DiagramEdges.tsx +0 -142
  325. package/src/scene/layers/edgeGeometry.ts +0 -104
  326. package/src/scene/layers/nodeCard.tsx +0 -164
  327. package/src/scene/layers/renderLayer.tsx +0 -70
  328. package/src/scene/layers/shapeLayers.ts +0 -201
  329. package/src/scene/paths/bezierEdit.ts +0 -208
  330. package/src/scene/scene.css +0 -874
  331. package/src/scene/text/SceneTextOverlay.tsx +0 -161
  332. package/src/scene/text/sceneTextChannel.ts +0 -46
  333. package/src/scene/text/sceneTextConfig.ts +0 -31
  334. package/src/scene/text/sceneTiptap.ts +0 -36
  335. package/src/scene/text/useSceneTextEditing.ts +0 -39
  336. package/src/scene/tools/ConnectTool.ts +0 -209
  337. package/src/scene/tools/DrawingConnectTool.ts +0 -231
  338. package/src/scene/tools/PathTool.ts +0 -158
  339. package/src/scene/tools/PlaceTool.ts +0 -47
  340. package/src/scene/tools/SceneTool.ts +0 -85
  341. package/src/scene/tools/SelectTool.ts +0 -300
  342. package/src/scene/tools/ShapeTool.ts +0 -143
  343. package/src/scene/tools/TextTool.ts +0 -72
  344. package/src/scene/tools/TokenTool.ts +0 -95
  345. package/src/scene/tools/createDrawShapeTool.ts +0 -82
  346. package/src/selectionConversions.ts +0 -155
  347. package/src/styles/ascii-diagram.css +0 -79
  348. package/src/styles/ascii-timeline.css +0 -596
  349. package/src/styles/code-context.css +0 -155
  350. package/src/styles/code-snippet.css +0 -76
  351. package/src/styles/diagram.css +0 -183
  352. package/src/styles/editor.css +0 -7131
  353. package/src/styles/folder-view.css +0 -210
  354. package/src/styles/image-edit-affordance.css +0 -201
  355. package/src/styles/index.css +0 -20
  356. package/src/styles/mermaid-diagram.css +0 -487
  357. package/src/styles/tree-view.css +0 -189
  358. package/src/teleprompter/TeleprompterControls.tsx +0 -218
  359. package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
  360. package/src/teleprompter/TeleprompterSurface.tsx +0 -267
  361. package/src/teleprompter/TeleprompterView.tsx +0 -338
  362. package/src/teleprompter/canvasRenderer.ts +0 -161
  363. package/src/teleprompter/floatingWindow.ts +0 -352
  364. package/src/teleprompter/index.ts +0 -61
  365. package/src/teleprompter/pcmWorklet.ts +0 -62
  366. package/src/teleprompter/recording/insertPreamble.ts +0 -80
  367. package/src/teleprompter/recording/narrationSave.ts +0 -134
  368. package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
  369. package/src/teleprompter/scrollModel.ts +0 -85
  370. package/src/teleprompter/teleprompterTheme.ts +0 -303
  371. package/src/teleprompter/types.ts +0 -38
  372. package/src/teleprompter/useFloatingWindow.ts +0 -74
  373. package/src/teleprompter/useMicAnalysis.ts +0 -211
  374. package/src/teleprompter/useTeleprompter.ts +0 -421
  375. package/src/templateContentPreviewResolver.ts +0 -360
  376. package/src/timeline/TimelineEditorWidget.tsx +0 -861
  377. package/src/timeline/TimelineViewExtension.ts +0 -252
  378. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
  379. package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
  380. package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
  381. package/src/timeline/__tests__/timelineOps.test.ts +0 -277
  382. package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
  383. package/src/timeline/timelineCommands.ts +0 -421
  384. package/src/timeline/timelineData.ts +0 -54
  385. package/src/timeline/timelineOps.ts +0 -381
  386. package/src/timeline/timelinePaste.ts +0 -8
  387. package/src/timelineSource.ts +0 -244
  388. package/src/tiptap/TiptapAudio.tsx +0 -86
  389. package/src/tiptap/TiptapVideo.tsx +0 -119
  390. package/src/tiptap/useResolvedMediaSrc.ts +0 -47
  391. package/src/tiptapBridge.ts +0 -986
  392. package/src/tooltipPlacement.ts +0 -13
  393. package/src/transformStyleId.ts +0 -17
  394. package/src/transitionCatalog.ts +0 -159
  395. package/src/treeview/TreeOutlineWidget.tsx +0 -390
  396. package/src/treeview/TreeViewExtension.ts +0 -250
  397. package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
  398. package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
  399. package/src/treeview/__tests__/treeOps.test.ts +0 -177
  400. package/src/treeview/__tests__/treePaste.test.ts +0 -40
  401. package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
  402. package/src/treeview/treeOps.ts +0 -231
  403. package/src/treeview/treePaste.ts +0 -13
  404. package/src/treeview/treeViewCommands.ts +0 -99
  405. package/src/treeview/treeViewData.ts +0 -41
  406. package/src/types/monaco-shims.d.ts +0 -10
  407. package/src/useBlockNavigator.ts +0 -153
  408. package/src/useHeadingLayout.ts +0 -294
  409. package/src/useMonacoLoader.ts +0 -82
  410. package/src/useTimelineClock.ts +0 -76
  411. package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
  412. package/src/utils/dropUtils.ts +0 -136
  413. package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
  414. package/src/writeCanvasSettings.ts +0 -30
  415. package/src/wysiwygImageUpload.ts +0 -113
@@ -1,77 +0,0 @@
1
- /**
2
- * RecorderPanel — toolbar-anchored trigger that opens the
3
- * {@link RecorderModal} in a portal. Shaped to slot into an editor
4
- * toolbar alongside other panels (e.g. `VersionHistoryPanel`); ships a
5
- * compact mic/record icon and no label by default.
6
- *
7
- * For a button that owns its own visual label, use {@link RecorderButton}
8
- * instead.
9
- */
10
-
11
- import { useCallback, useState } from 'react';
12
- import { createPortal } from 'react-dom';
13
- import type { MediaProvider } from '@bendyline/squisq/schemas';
14
- import type { ContentContainer } from '@bendyline/squisq/storage';
15
- import {
16
- RecorderModal,
17
- type RecorderColorScheme,
18
- type RecorderSaveResult,
19
- } from './RecorderModal.js';
20
- import { Icon } from '../Icon';
21
- import type { RecorderSource } from './hooks/useMediaRecorder.js';
22
-
23
- export interface RecorderPanelProps {
24
- mediaProvider: MediaProvider;
25
- container?: ContentContainer | null;
26
- initialMode?: RecorderSource;
27
- /** Light/dark chrome scheme copied onto the portaled modal. */
28
- colorScheme?: RecorderColorScheme;
29
- onSave?: (result: RecorderSaveResult) => void;
30
- /** ARIA / tooltip label. Defaults to `'Record media'`. */
31
- tooltip?: string;
32
- /** Optional className for the trigger button. */
33
- className?: string;
34
- }
35
-
36
- export function RecorderPanel({
37
- mediaProvider,
38
- container = null,
39
- initialMode = 'mic',
40
- colorScheme = 'light',
41
- onSave,
42
- tooltip = 'Record media',
43
- className,
44
- }: RecorderPanelProps) {
45
- const [open, setOpen] = useState(false);
46
- const handleClose = useCallback(() => setOpen(false), []);
47
-
48
- return (
49
- <>
50
- <button
51
- type="button"
52
- className={className}
53
- data-tooltip={tooltip}
54
- aria-label={tooltip}
55
- aria-expanded={open}
56
- onClick={() => setOpen((v) => !v)}
57
- >
58
- <Icon icon="fa-solid fa-microphone" />
59
- </button>
60
- {open &&
61
- typeof document !== 'undefined' &&
62
- createPortal(
63
- <RecorderModal
64
- mediaProvider={mediaProvider}
65
- container={container}
66
- initialMode={initialMode}
67
- colorScheme={colorScheme}
68
- onClose={handleClose}
69
- onSave={(result) => {
70
- onSave?.(result);
71
- }}
72
- />,
73
- document.body,
74
- )}
75
- </>
76
- );
77
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Format probing for MediaRecorder.
3
- *
4
- * Different browsers expose different container/codec combinations. Chrome
5
- * and Firefox produce WebM (VP8/VP9 + Opus); Safari produces MP4 (H.264 +
6
- * AAC). We probe at runtime via `MediaRecorder.isTypeSupported()` and pick
7
- * the best supported option, falling back to whatever the browser hands
8
- * back when no probe succeeds.
9
- */
10
-
11
- /** What the recorded stream is intended to capture. */
12
- export type CaptureKind = 'audio' | 'video';
13
-
14
- /** A probed format choice — what to pass to `MediaRecorder` and where to write it. */
15
- export interface ResolvedFormat {
16
- /** MIME type to pass to `new MediaRecorder(stream, { mimeType })`. Empty string means "let the browser pick". */
17
- mimeType: string;
18
- /** File extension to use when writing to the container, including the leading dot. */
19
- extension: string;
20
- /** Container directory inside the `ContentContainer` (no trailing slash). */
21
- directory: 'audio' | 'video';
22
- }
23
-
24
- /**
25
- * Preferred MIME types for audio-only recording, in priority order. The
26
- * first one `MediaRecorder.isTypeSupported()` accepts wins.
27
- *
28
- * Opus in a WebM container is the modern default (Chrome, Firefox, Edge).
29
- * MP4/AAC covers Safari. Bare strings are kept as a final fallback for
30
- * older browsers that don't accept codec hints.
31
- */
32
- const AUDIO_CANDIDATES = [
33
- 'audio/webm;codecs=opus',
34
- 'audio/webm',
35
- 'audio/mp4;codecs=mp4a.40.2',
36
- 'audio/mp4',
37
- 'audio/ogg;codecs=opus',
38
- ];
39
-
40
- /**
41
- * Preferred MIME types for video recording. VP9/Opus on top because it
42
- * yields good quality at modest bitrate in Chrome/Firefox. VP8 follows for
43
- * older Chromium. MP4/H.264 covers Safari.
44
- */
45
- const VIDEO_CANDIDATES = [
46
- 'video/webm;codecs=vp9,opus',
47
- 'video/webm;codecs=vp8,opus',
48
- 'video/webm',
49
- 'video/mp4;codecs=avc1.42E01E,mp4a.40.2',
50
- 'video/mp4',
51
- ];
52
-
53
- /**
54
- * Map a chosen MIME type to a file extension. Best-effort — if we can't
55
- * tell, default to `.bin` so the file is at least retrievable.
56
- */
57
- function extensionForMime(mimeType: string): string {
58
- const m = mimeType.toLowerCase();
59
- if (m.startsWith('audio/webm')) return '.webm';
60
- if (m.startsWith('audio/ogg')) return '.ogg';
61
- if (m.startsWith('audio/mp4')) return '.m4a';
62
- if (m.startsWith('audio/mpeg')) return '.mp3';
63
- if (m.startsWith('audio/wav')) return '.wav';
64
- if (m.startsWith('video/webm')) return '.webm';
65
- if (m.startsWith('video/mp4')) return '.mp4';
66
- return '.bin';
67
- }
68
-
69
- /**
70
- * Probe `MediaRecorder.isTypeSupported()` and return the first supported
71
- * MIME type from the candidate list. Returns `null` when the
72
- * MediaRecorder API itself is unavailable or none of the candidates pass.
73
- */
74
- function probeMimeType(candidates: readonly string[]): string | null {
75
- if (typeof MediaRecorder === 'undefined') return null;
76
- for (const candidate of candidates) {
77
- try {
78
- if (MediaRecorder.isTypeSupported(candidate)) return candidate;
79
- } catch {
80
- // isTypeSupported isn't supposed to throw, but Safari has historically
81
- // misbehaved on unknown codec strings. Keep probing.
82
- }
83
- }
84
- return null;
85
- }
86
-
87
- /**
88
- * Resolve the format the recorder will use for a given capture kind. If a
89
- * `preferred` MIME type is supported, it wins; otherwise we fall through
90
- * the priority list. When nothing matches (extremely old browser), we
91
- * return an empty `mimeType` — `MediaRecorder` will pick a default and we
92
- * tag the file with `.webm` as a best guess.
93
- */
94
- export function resolveFormat(kind: CaptureKind, preferred?: string): ResolvedFormat {
95
- const candidates = kind === 'audio' ? AUDIO_CANDIDATES : VIDEO_CANDIDATES;
96
- const probed = (preferred && probeMimeType([preferred])) ?? probeMimeType(candidates) ?? '';
97
- const directory = kind === 'audio' ? 'audio' : 'video';
98
- const extension = probed ? extensionForMime(probed) : '.webm';
99
- return { mimeType: probed, extension, directory };
100
- }
101
-
102
- /**
103
- * `MediaRecorder` support probe. Returns false when running in a
104
- * non-browser environment (e.g. SSR) or on a browser that doesn't
105
- * implement the API at all.
106
- */
107
- export function supportsMediaRecorder(): boolean {
108
- return typeof MediaRecorder !== 'undefined';
109
- }
110
-
111
- /**
112
- * `getUserMedia` support probe (for mic / camera capture).
113
- */
114
- export function supportsUserMedia(): boolean {
115
- return (
116
- typeof navigator !== 'undefined' &&
117
- typeof navigator.mediaDevices !== 'undefined' &&
118
- typeof navigator.mediaDevices.getUserMedia === 'function'
119
- );
120
- }
121
-
122
- /**
123
- * `getDisplayMedia` support probe (for screen capture). Browsers may
124
- * implement `mediaDevices` without `getDisplayMedia` (Firefox on Android
125
- * being the long-standing example), so this is its own probe.
126
- */
127
- export function supportsDisplayMedia(): boolean {
128
- return (
129
- typeof navigator !== 'undefined' &&
130
- typeof navigator.mediaDevices !== 'undefined' &&
131
- typeof navigator.mediaDevices.getDisplayMedia === 'function'
132
- );
133
- }
134
-
135
- /**
136
- * Build a default filename for a recording. `basename` is a hint
137
- * (e.g. user-typed name); when omitted, a sortable timestamp is used so
138
- * concurrent recordings don't collide.
139
- */
140
- export function buildFilename(kind: CaptureKind, extension: string, basename?: string): string {
141
- const safe = basename
142
- ? basename
143
- .trim()
144
- .replace(/[\\/:*?"<>|]+/g, '-')
145
- .replace(/\s+/g, '-')
146
- : '';
147
- if (safe) return `${safe}${extension}`;
148
- const now = new Date();
149
- const stamp =
150
- now.getFullYear().toString().padStart(4, '0') +
151
- (now.getMonth() + 1).toString().padStart(2, '0') +
152
- now.getDate().toString().padStart(2, '0') +
153
- '-' +
154
- now.getHours().toString().padStart(2, '0') +
155
- now.getMinutes().toString().padStart(2, '0') +
156
- now.getSeconds().toString().padStart(2, '0');
157
- const prefix = kind === 'audio' ? 'narration' : 'recording';
158
- return `${prefix}-${stamp}${extension}`;
159
- }
@@ -1,436 +0,0 @@
1
- /**
2
- * useMediaRecorder
3
- *
4
- * React wrapper around `MediaRecorder` that handles stream acquisition,
5
- * the recorder lifecycle, and produces a single `Blob` on stop. Selects
6
- * a browser-supported MIME type via {@link resolveFormat}.
7
- *
8
- * Mirrors the shape of `useVideoExport` in `@bendyline/squisq-video-react`
9
- * (request → start → stop → blob), inverted for capture rather than
10
- * export.
11
- */
12
-
13
- import { useCallback, useEffect, useRef, useState } from 'react';
14
- import {
15
- resolveFormat,
16
- supportsMediaRecorder,
17
- type CaptureKind,
18
- type ResolvedFormat,
19
- } from '../formats.js';
20
- import { requestMicStream } from '../sources/micStream.js';
21
- import { requestCameraStream } from '../sources/cameraStream.js';
22
- import { requestScreenStream, type ScreenStreamHandle } from '../sources/screenStream.js';
23
-
24
- /** Which capture source to use. `screen+mic` mixes the microphone into the screen stream. */
25
- export type RecorderSource = 'mic' | 'camera' | 'screen' | 'screen+mic';
26
-
27
- /** Discriminated state describing what the recorder is currently doing. */
28
- export type RecorderState =
29
- | 'idle'
30
- | 'requesting'
31
- | 'ready'
32
- | 'recording'
33
- | 'stopping'
34
- | 'stopped'
35
- | 'error';
36
-
37
- export interface UseMediaRecorderOptions {
38
- /** Which capture pipeline to use (default: `'mic'`). */
39
- source?: RecorderSource;
40
- /**
41
- * Preferred MIME type override. When the browser supports it, this
42
- * wins over the default candidate list. When unset (or unsupported),
43
- * the hook probes a built-in priority list.
44
- */
45
- mimeType?: string;
46
- /** Video track constraints for camera / screen sources. */
47
- videoConstraints?: MediaTrackConstraints | boolean;
48
- /** Audio track constraints for mic / camera / screen+mic sources. */
49
- audioConstraints?: MediaTrackConstraints | boolean;
50
- /**
51
- * Bits-per-second hint passed to `MediaRecorder`. Most browsers cap to
52
- * reasonable defaults internally; leaving this undefined is usually
53
- * fine.
54
- */
55
- bitsPerSecond?: number;
56
- /**
57
- * Whether to attempt to capture system audio when `source === 'screen'`
58
- * or `'screen+mic'`. Browser support is limited (desktop Chromium
59
- * only); when unsupported the resulting stream simply omits it.
60
- */
61
- systemAudio?: boolean;
62
- /**
63
- * For `source === 'camera'`, whether to include the microphone track.
64
- * Defaults to `true` (camera + mic). Set `false` to capture silent
65
- * video. Ignored for other sources, whose mic handling is encoded in
66
- * the source itself (`'mic'`, `'screen+mic'`).
67
- */
68
- includeMicrophone?: boolean;
69
- }
70
-
71
- export interface UseMediaRecorderResult {
72
- /** Current recorder state. */
73
- state: RecorderState;
74
- /** Live `MediaStream` after `request()` succeeds; useful for preview. */
75
- stream: MediaStream | null;
76
- /** Final `Blob` after `stop()` resolves, or `null` while recording. */
77
- blob: Blob | null;
78
- /** MIME type the recorder actually used (after `request()`). */
79
- mimeType: string | null;
80
- /** File extension matching `mimeType` (e.g. `.webm`). */
81
- extension: string | null;
82
- /** Suggested container directory (`'audio'` for mic, `'video'` for camera/screen). */
83
- directory: 'audio' | 'video' | null;
84
- /** Milliseconds elapsed since `start()` was called. Updates ~10× per second while recording. */
85
- durationMs: number;
86
- /** Most recent error, if any. */
87
- error: Error | null;
88
- /**
89
- * Acquire the stream and prepare a `MediaRecorder`. After this resolves
90
- * the hook is in `'ready'` state and a `<video>`/`<audio>` element can
91
- * preview `stream`. Call `start()` to begin recording.
92
- */
93
- request: () => Promise<void>;
94
- /** Start recording. Must be called from `'ready'`. */
95
- start: () => void;
96
- /**
97
- * Stop recording and resolve with the resulting `Blob`. Safe to call
98
- * from `'recording'`; a no-op from any other state (resolves with the
99
- * existing `blob`, or `null`).
100
- */
101
- stop: () => Promise<Blob | null>;
102
- /**
103
- * Tear everything down — stops the recorder if running, releases all
104
- * tracks, disposes the AudioContext mixer (if any), and returns to
105
- * `'idle'`. Always safe to call.
106
- */
107
- cancel: () => void;
108
- /** Reset state without releasing the stream. Useful for re-recording. */
109
- reset: () => void;
110
- }
111
-
112
- /**
113
- * Acquire the right stream for the chosen source. Returns the stream
114
- * plus an optional `dispose` callback for sources that own auxiliary
115
- * resources (e.g. the screen+mic AudioContext mixer).
116
- */
117
- async function acquireStream(
118
- source: RecorderSource,
119
- opts: UseMediaRecorderOptions,
120
- ): Promise<{ stream: MediaStream; dispose: () => void }> {
121
- switch (source) {
122
- case 'mic': {
123
- const audio = typeof opts.audioConstraints === 'object' ? opts.audioConstraints : undefined;
124
- const stream = await requestMicStream(audio);
125
- return { stream, dispose: () => {} };
126
- }
127
- case 'camera': {
128
- const stream = await requestCameraStream({
129
- video: opts.videoConstraints ?? true,
130
- audio: opts.includeMicrophone === false ? false : (opts.audioConstraints ?? true),
131
- });
132
- return { stream, dispose: () => {} };
133
- }
134
- case 'screen':
135
- case 'screen+mic': {
136
- const handle: ScreenStreamHandle = await requestScreenStream({
137
- video: opts.videoConstraints ?? true,
138
- systemAudio: opts.systemAudio ?? false,
139
- includeMicrophone: source === 'screen+mic',
140
- microphoneConstraints:
141
- typeof opts.audioConstraints === 'object' ? opts.audioConstraints : undefined,
142
- });
143
- return { stream: handle.stream, dispose: handle.dispose };
144
- }
145
- }
146
- }
147
-
148
- /** Whether the chosen source records video (vs. audio-only). */
149
- function captureKindFor(source: RecorderSource): CaptureKind {
150
- return source === 'mic' ? 'audio' : 'video';
151
- }
152
-
153
- /**
154
- * Returns the kind of capture that the given source produces. Exposed
155
- * separately from {@link useMediaRecorder} so non-React callers
156
- * (e.g. headless tests) can resolve a format up front.
157
- */
158
- export function getCaptureKind(source: RecorderSource): CaptureKind {
159
- return captureKindFor(source);
160
- }
161
-
162
- export function useMediaRecorder(options: UseMediaRecorderOptions = {}): UseMediaRecorderResult {
163
- const [state, setState] = useState<RecorderState>('idle');
164
- const [stream, setStream] = useState<MediaStream | null>(null);
165
- const [blob, setBlob] = useState<Blob | null>(null);
166
- const [format, setFormat] = useState<ResolvedFormat | null>(null);
167
- const [durationMs, setDurationMs] = useState(0);
168
- const [error, setError] = useState<Error | null>(null);
169
-
170
- const recorderRef = useRef<MediaRecorder | null>(null);
171
- const chunksRef = useRef<Blob[]>([]);
172
- const disposeStreamRef = useRef<(() => void) | null>(null);
173
- const startTimestampRef = useRef<number | null>(null);
174
- const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null);
175
- const stopResolversRef = useRef<Array<(blob: Blob | null) => void>>([]);
176
- const stopPromiseRef = useRef<Promise<Blob | null> | null>(null);
177
- const requestPromiseRef = useRef<Promise<void> | null>(null);
178
- const lifecycleRef = useRef(0);
179
-
180
- // Stable copy of options for callbacks that read them late. Re-evaluated
181
- // each render, but each callback closes over the ref so we don't have
182
- // to recreate them on every options change.
183
- const optionsRef = useRef(options);
184
- optionsRef.current = options;
185
-
186
- const clearTicker = useCallback(() => {
187
- if (tickerRef.current !== null) {
188
- clearInterval(tickerRef.current);
189
- tickerRef.current = null;
190
- }
191
- }, []);
192
-
193
- const releaseStream = useCallback(() => {
194
- const s = recorderRef.current?.stream;
195
- if (s) {
196
- s.getTracks().forEach((t) => t.stop());
197
- }
198
- // Also stop whatever we last handed to setStream — it may differ
199
- // from recorderRef.current.stream when stream/recorder lifecycles
200
- // diverged (e.g. cancel before start).
201
- setStream((current) => {
202
- current?.getTracks().forEach((t) => t.stop());
203
- return null;
204
- });
205
- disposeStreamRef.current?.();
206
- disposeStreamRef.current = null;
207
- }, []);
208
-
209
- const reset = useCallback(() => {
210
- setBlob(null);
211
- setDurationMs(0);
212
- setError(null);
213
- chunksRef.current = [];
214
- startTimestampRef.current = null;
215
- clearTicker();
216
- // If a stream is still live from a prior `request()`, hop back to
217
- // `'ready'` so the UI can offer "record again" without the caller
218
- // having to re-acquire permissions. Otherwise drop to `'idle'`.
219
- const rec = recorderRef.current;
220
- if (rec && rec.state === 'inactive' && rec.stream.active) {
221
- setState('ready');
222
- } else {
223
- setState('idle');
224
- }
225
- }, [clearTicker]);
226
-
227
- const cancel = useCallback(() => {
228
- lifecycleRef.current += 1;
229
- const rec = recorderRef.current;
230
- if (rec && rec.state !== 'inactive') {
231
- try {
232
- rec.ondataavailable = null;
233
- rec.onstop = null;
234
- rec.onerror = null;
235
- rec.stop();
236
- } catch {
237
- // Ignore — we're tearing down anyway.
238
- }
239
- }
240
- recorderRef.current = null;
241
- releaseStream();
242
- clearTicker();
243
- chunksRef.current = [];
244
- startTimestampRef.current = null;
245
- // Any in-flight stop() promises won't get a blob.
246
- stopResolversRef.current.splice(0).forEach((resolve) => resolve(null));
247
- stopPromiseRef.current = null;
248
- requestPromiseRef.current = null;
249
- setBlob(null);
250
- setDurationMs(0);
251
- setError(null);
252
- setState('idle');
253
- }, [clearTicker, releaseStream]);
254
-
255
- const request = useCallback(async () => {
256
- if (requestPromiseRef.current) return requestPromiseRef.current;
257
- if (recorderRef.current?.stream.active) return;
258
- if (!supportsMediaRecorder()) {
259
- const err = new Error('MediaRecorder is not supported in this environment.');
260
- setError(err);
261
- setState('error');
262
- throw err;
263
- }
264
- const lifecycle = ++lifecycleRef.current;
265
- const requestPromise = (async () => {
266
- setError(null);
267
- setState('requesting');
268
- let acquired: { stream: MediaStream; dispose: () => void } | null = null;
269
- try {
270
- const source = optionsRef.current.source ?? 'mic';
271
- acquired = await acquireStream(source, optionsRef.current);
272
- const { stream: nextStream, dispose } = acquired;
273
- if (lifecycle !== lifecycleRef.current) {
274
- nextStream.getTracks().forEach((track) => track.stop());
275
- dispose();
276
- return;
277
- }
278
- const resolved = resolveFormat(captureKindFor(source), optionsRef.current.mimeType);
279
- const recorderOptions: MediaRecorderOptions = {};
280
- if (resolved.mimeType) recorderOptions.mimeType = resolved.mimeType;
281
- if (optionsRef.current.bitsPerSecond) {
282
- recorderOptions.bitsPerSecond = optionsRef.current.bitsPerSecond;
283
- }
284
- const recorder = new MediaRecorder(nextStream, recorderOptions);
285
-
286
- recorder.ondataavailable = (e) => {
287
- if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
288
- };
289
- recorder.onstop = () => {
290
- if (recorderRef.current !== recorder || lifecycle !== lifecycleRef.current) return;
291
- // The recorded MIME type is authoritative once data is in hand —
292
- // some browsers down-negotiate the format (e.g. drop codec hint).
293
- const recordedType = recorder.mimeType || resolved.mimeType || 'application/octet-stream';
294
- const finalBlob = new Blob(chunksRef.current, { type: recordedType });
295
- chunksRef.current = [];
296
- setBlob(finalBlob);
297
- setState('stopped');
298
- clearTicker();
299
- stopResolversRef.current.splice(0).forEach((resolve) => resolve(finalBlob));
300
- stopPromiseRef.current = null;
301
- };
302
- recorder.onerror = (event) => {
303
- if (recorderRef.current !== recorder || lifecycle !== lifecycleRef.current) return;
304
- const detail = (event as unknown as { error?: DOMException }).error;
305
- const err = detail instanceof Error ? detail : new Error('Recorder error');
306
- setError(err);
307
- setState('error');
308
- clearTicker();
309
- stopResolversRef.current.splice(0).forEach((resolve) => resolve(null));
310
- stopPromiseRef.current = null;
311
- };
312
-
313
- recorderRef.current = recorder;
314
- disposeStreamRef.current = dispose;
315
- setStream(nextStream);
316
- setFormat(resolved);
317
- setBlob(null);
318
- setDurationMs(0);
319
- setState('ready');
320
- acquired = null;
321
- } catch (err: unknown) {
322
- if (acquired) {
323
- acquired.stream.getTracks().forEach((track) => track.stop());
324
- acquired.dispose();
325
- }
326
- const normalized = err instanceof Error ? err : new Error('Stream acquisition failed');
327
- if (lifecycle === lifecycleRef.current) {
328
- setError(normalized);
329
- setState('error');
330
- }
331
- throw normalized;
332
- } finally {
333
- if (lifecycle === lifecycleRef.current) requestPromiseRef.current = null;
334
- }
335
- })();
336
- requestPromiseRef.current = requestPromise;
337
- return requestPromise;
338
- }, [clearTicker]);
339
-
340
- const start = useCallback(() => {
341
- const rec = recorderRef.current;
342
- if (!rec) {
343
- const err = new Error('Recorder is not ready. Call request() first.');
344
- setError(err);
345
- setState('error');
346
- return;
347
- }
348
- if (rec.state === 'recording') return;
349
- chunksRef.current = [];
350
- setBlob(null);
351
- setDurationMs(0);
352
- startTimestampRef.current = Date.now();
353
- rec.start(1000);
354
- setState('recording');
355
- clearTicker();
356
- tickerRef.current = setInterval(() => {
357
- if (startTimestampRef.current !== null) {
358
- setDurationMs(Date.now() - startTimestampRef.current);
359
- }
360
- }, 100);
361
- }, [clearTicker]);
362
-
363
- const stop = useCallback((): Promise<Blob | null> => {
364
- if (stopPromiseRef.current) return stopPromiseRef.current;
365
- const rec = recorderRef.current;
366
- if (!rec || rec.state === 'inactive') {
367
- return Promise.resolve(blob);
368
- }
369
- setState('stopping');
370
- const stopPromise = new Promise<Blob | null>((resolve) => {
371
- stopResolversRef.current.push(resolve);
372
- try {
373
- rec.stop();
374
- } catch (err: unknown) {
375
- const normalized = err instanceof Error ? err : new Error('Failed to stop recorder');
376
- setError(normalized);
377
- setState('error');
378
- clearTicker();
379
- stopResolversRef.current.splice(0).forEach((r) => r(null));
380
- }
381
- });
382
- stopPromiseRef.current = stopPromise;
383
- void stopPromise.finally(() => {
384
- if (stopPromiseRef.current === stopPromise) stopPromiseRef.current = null;
385
- });
386
- return stopPromise;
387
- }, [blob, clearTicker]);
388
-
389
- // Final unmount cleanup — make sure we don't leak the camera light /
390
- // screen-capture indicator if the component disappears mid-recording.
391
- //
392
- // `stopResolversRef.current` is captured at effect-run time into a
393
- // local. That ref is initialized once at construction and never
394
- // reassigned, so the local handle stays a live view onto the same
395
- // mutable array — entries pushed by later `stop()` calls still
396
- // appear here on cleanup. Capturing keeps `react-hooks/exhaustive-deps`
397
- // satisfied without changing the runtime behavior.
398
- useEffect(() => {
399
- const pendingResolvers = stopResolversRef.current;
400
- return () => {
401
- lifecycleRef.current += 1;
402
- requestPromiseRef.current = null;
403
- const rec = recorderRef.current;
404
- if (rec && rec.state !== 'inactive') {
405
- try {
406
- rec.ondataavailable = null;
407
- rec.onstop = null;
408
- rec.onerror = null;
409
- rec.stop();
410
- } catch {
411
- // ignore
412
- }
413
- }
414
- releaseStream();
415
- clearTicker();
416
- pendingResolvers.splice(0).forEach((resolve) => resolve(null));
417
- stopPromiseRef.current = null;
418
- };
419
- }, [releaseStream, clearTicker]);
420
-
421
- return {
422
- state,
423
- stream,
424
- blob,
425
- mimeType: format?.mimeType ?? null,
426
- extension: format?.extension ?? null,
427
- directory: format?.directory ?? null,
428
- durationMs,
429
- error,
430
- request,
431
- start,
432
- stop,
433
- cancel,
434
- reset,
435
- };
436
- }