@bendyline/squisq-editor-react 1.4.0 → 1.5.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 (446) hide show
  1. package/dist/DocumentSettingsDialog.d.ts +26 -0
  2. package/dist/DocumentSettingsDialog.d.ts.map +1 -0
  3. package/dist/DocumentSettingsDialog.js +115 -0
  4. package/dist/DocumentSettingsDialog.js.map +1 -0
  5. package/dist/EditorContext.d.ts +248 -4
  6. package/dist/EditorContext.d.ts.map +1 -1
  7. package/dist/EditorContext.js +248 -10
  8. package/dist/EditorContext.js.map +1 -1
  9. package/dist/EditorShell.d.ts +173 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +110 -10
  12. package/dist/EditorShell.js.map +1 -1
  13. package/dist/EmojiPicker.d.ts +50 -0
  14. package/dist/EmojiPicker.d.ts.map +1 -0
  15. package/dist/EmojiPicker.js +182 -0
  16. package/dist/EmojiPicker.js.map +1 -0
  17. package/dist/ImageEditor.d.ts +68 -0
  18. package/dist/ImageEditor.d.ts.map +1 -0
  19. package/dist/ImageEditor.js +166 -0
  20. package/dist/ImageEditor.js.map +1 -0
  21. package/dist/ImageNodeView.d.ts +13 -1
  22. package/dist/ImageNodeView.d.ts.map +1 -1
  23. package/dist/ImageNodeView.js +172 -19
  24. package/dist/ImageNodeView.js.map +1 -1
  25. package/dist/ImageViewer.d.ts +26 -0
  26. package/dist/ImageViewer.d.ts.map +1 -0
  27. package/dist/ImageViewer.js +119 -0
  28. package/dist/ImageViewer.js.map +1 -0
  29. package/dist/InlineIcon.d.ts +17 -0
  30. package/dist/InlineIcon.d.ts.map +1 -0
  31. package/dist/InlineIcon.js +72 -0
  32. package/dist/InlineIcon.js.map +1 -0
  33. package/dist/InlinePreviewGutter.d.ts +52 -0
  34. package/dist/InlinePreviewGutter.d.ts.map +1 -0
  35. package/dist/InlinePreviewGutter.js +397 -0
  36. package/dist/InlinePreviewGutter.js.map +1 -0
  37. package/dist/LinkDialog.d.ts +43 -0
  38. package/dist/LinkDialog.d.ts.map +1 -0
  39. package/dist/LinkDialog.js +102 -0
  40. package/dist/LinkDialog.js.map +1 -0
  41. package/dist/MentionExtension.js +10 -7
  42. package/dist/MentionExtension.js.map +1 -1
  43. package/dist/OutlinePanel.d.ts +17 -0
  44. package/dist/OutlinePanel.d.ts.map +1 -0
  45. package/dist/OutlinePanel.js +167 -0
  46. package/dist/OutlinePanel.js.map +1 -0
  47. package/dist/PlainHtmlPreview.d.ts +50 -0
  48. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  49. package/dist/PlainHtmlPreview.js +155 -0
  50. package/dist/PlainHtmlPreview.js.map +1 -0
  51. package/dist/PreviewControls.d.ts +15 -1
  52. package/dist/PreviewControls.d.ts.map +1 -1
  53. package/dist/PreviewControls.js +75 -18
  54. package/dist/PreviewControls.js.map +1 -1
  55. package/dist/PreviewPanel.d.ts +11 -10
  56. package/dist/PreviewPanel.d.ts.map +1 -1
  57. package/dist/PreviewPanel.js +20 -17
  58. package/dist/PreviewPanel.js.map +1 -1
  59. package/dist/RawEditor.d.ts.map +1 -1
  60. package/dist/RawEditor.js +198 -4
  61. package/dist/RawEditor.js.map +1 -1
  62. package/dist/RecorderEntry.d.ts +24 -0
  63. package/dist/RecorderEntry.d.ts.map +1 -0
  64. package/dist/RecorderEntry.js +139 -0
  65. package/dist/RecorderEntry.js.map +1 -0
  66. package/dist/TemplateAnnotation.d.ts.map +1 -1
  67. package/dist/TemplateAnnotation.js +32 -6
  68. package/dist/TemplateAnnotation.js.map +1 -1
  69. package/dist/TemplatePicker.d.ts +53 -0
  70. package/dist/TemplatePicker.d.ts.map +1 -0
  71. package/dist/TemplatePicker.js +388 -0
  72. package/dist/TemplatePicker.js.map +1 -0
  73. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  74. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  75. package/dist/ThemeCustomizerPanel.js +256 -0
  76. package/dist/ThemeCustomizerPanel.js.map +1 -0
  77. package/dist/ThemePicker.d.ts +33 -0
  78. package/dist/ThemePicker.d.ts.map +1 -0
  79. package/dist/ThemePicker.js +148 -0
  80. package/dist/ThemePicker.js.map +1 -0
  81. package/dist/Toolbar.d.ts.map +1 -1
  82. package/dist/Toolbar.js +508 -33
  83. package/dist/Toolbar.js.map +1 -1
  84. package/dist/VersionHistoryPanel.d.ts +14 -0
  85. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  86. package/dist/VersionHistoryPanel.js +147 -0
  87. package/dist/VersionHistoryPanel.js.map +1 -0
  88. package/dist/ViewMenuPanel.d.ts +13 -0
  89. package/dist/ViewMenuPanel.d.ts.map +1 -0
  90. package/dist/ViewMenuPanel.js +58 -0
  91. package/dist/ViewMenuPanel.js.map +1 -0
  92. package/dist/WysiwygEditor.d.ts.map +1 -1
  93. package/dist/WysiwygEditor.js +198 -9
  94. package/dist/WysiwygEditor.js.map +1 -1
  95. package/dist/__tests__/detectMarkdown.test.js +0 -14
  96. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  97. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  98. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  99. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  100. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  101. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  102. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  103. package/dist/__tests__/emojiPicker.test.js +111 -0
  104. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  105. package/dist/__tests__/fileKind.test.js +13 -0
  106. package/dist/__tests__/fileKind.test.js.map +1 -1
  107. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  108. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  109. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  110. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  111. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditorShell.test.js +52 -0
  114. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  116. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorState.test.js +148 -0
  118. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  119. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  120. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  121. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  122. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  126. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  127. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  128. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  129. package/dist/__tests__/jsonEditor.test.js +134 -0
  130. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  131. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  132. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  133. package/dist/__tests__/layersPanel.test.js +84 -0
  134. package/dist/__tests__/layersPanel.test.js.map +1 -0
  135. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  136. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  137. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  138. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  139. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  140. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  141. package/dist/__tests__/outlinePanel.test.js +68 -0
  142. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  143. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  144. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  145. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  146. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  147. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  148. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/propertiesPanel.test.js +64 -0
  150. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  151. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  152. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  153. package/dist/__tests__/recorderFormats.test.js +121 -0
  154. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  155. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  156. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  157. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  158. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  159. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  160. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  161. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  162. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  163. package/dist/__tests__/tiptapBridge.test.js +13 -0
  164. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  165. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  166. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  167. package/dist/__tests__/useImageEditor.test.js +131 -0
  168. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  169. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  170. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  171. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  172. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  173. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  174. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  175. package/dist/__tests__/versionHistory.test.js +124 -0
  176. package/dist/__tests__/versionHistory.test.js.map +1 -0
  177. package/dist/blockSlice.d.ts +24 -0
  178. package/dist/blockSlice.d.ts.map +1 -0
  179. package/dist/blockSlice.js +63 -0
  180. package/dist/blockSlice.js.map +1 -0
  181. package/dist/buildPreviewDoc.d.ts.map +1 -1
  182. package/dist/buildPreviewDoc.js +52 -2
  183. package/dist/buildPreviewDoc.js.map +1 -1
  184. package/dist/emojiData.d.ts +81 -0
  185. package/dist/emojiData.d.ts.map +1 -0
  186. package/dist/emojiData.js +1283 -0
  187. package/dist/emojiData.js.map +1 -0
  188. package/dist/fileKind.d.ts +6 -2
  189. package/dist/fileKind.d.ts.map +1 -1
  190. package/dist/fileKind.js +25 -4
  191. package/dist/fileKind.js.map +1 -1
  192. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  193. package/dist/hooks/useFileDrop.js +40 -4
  194. package/dist/hooks/useFileDrop.js.map +1 -1
  195. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  196. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  197. package/dist/imageEditor/CanvasSurface.js +264 -0
  198. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  199. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  200. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  201. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  202. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  203. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  204. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  205. package/dist/imageEditor/LayersPanel.js +43 -0
  206. package/dist/imageEditor/LayersPanel.js.map +1 -0
  207. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  208. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  209. package/dist/imageEditor/PropertiesPanel.js +97 -0
  210. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  211. package/dist/imageEditor/Toolbar.d.ts +30 -0
  212. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  213. package/dist/imageEditor/Toolbar.js +108 -0
  214. package/dist/imageEditor/Toolbar.js.map +1 -0
  215. package/dist/imageEditor/icons.d.ts +24 -0
  216. package/dist/imageEditor/icons.d.ts.map +1 -0
  217. package/dist/imageEditor/icons.js +45 -0
  218. package/dist/imageEditor/icons.js.map +1 -0
  219. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  220. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  221. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  222. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  223. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  224. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  225. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  226. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  227. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  228. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  229. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  230. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  231. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  232. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  234. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  235. package/dist/imageEditor/state.d.ts +76 -0
  236. package/dist/imageEditor/state.d.ts.map +1 -0
  237. package/dist/imageEditor/state.js +87 -0
  238. package/dist/imageEditor/state.js.map +1 -0
  239. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  240. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  241. package/dist/imageEditor/useImageEditor.js +244 -0
  242. package/dist/imageEditor/useImageEditor.js.map +1 -0
  243. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  244. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  245. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  246. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  247. package/dist/index.d.ts +48 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +36 -0
  250. package/dist/index.js.map +1 -1
  251. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  252. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  253. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  254. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  255. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  256. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  257. package/dist/jsonEditor/JsonEditor.js +15 -0
  258. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  259. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  260. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  261. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  262. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  263. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  264. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  265. package/dist/jsonEditor/RenderNode.js +32 -0
  266. package/dist/jsonEditor/RenderNode.js.map +1 -0
  267. package/dist/jsonEditor/editors.d.ts +36 -0
  268. package/dist/jsonEditor/editors.d.ts.map +1 -0
  269. package/dist/jsonEditor/editors.js +347 -0
  270. package/dist/jsonEditor/editors.js.map +1 -0
  271. package/dist/jsonEditor/index.d.ts +3 -0
  272. package/dist/jsonEditor/index.d.ts.map +1 -0
  273. package/dist/jsonEditor/index.js +2 -0
  274. package/dist/jsonEditor/index.js.map +1 -0
  275. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  276. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  277. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  278. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  279. package/dist/recorder/RecorderButton.d.ts +31 -0
  280. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  281. package/dist/recorder/RecorderButton.js +24 -0
  282. package/dist/recorder/RecorderButton.js.map +1 -0
  283. package/dist/recorder/RecorderModal.d.ts +59 -0
  284. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  285. package/dist/recorder/RecorderModal.js +333 -0
  286. package/dist/recorder/RecorderModal.js.map +1 -0
  287. package/dist/recorder/RecorderPanel.d.ts +25 -0
  288. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  289. package/dist/recorder/RecorderPanel.js +30 -0
  290. package/dist/recorder/RecorderPanel.js.map +1 -0
  291. package/dist/recorder/formats.d.ts +51 -0
  292. package/dist/recorder/formats.d.ts.map +1 -0
  293. package/dist/recorder/formats.js +144 -0
  294. package/dist/recorder/formats.js.map +1 -0
  295. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  296. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  297. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  298. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  299. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  300. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  301. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  302. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  303. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  304. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  305. package/dist/recorder/sources/cameraStream.js +24 -0
  306. package/dist/recorder/sources/cameraStream.js.map +1 -0
  307. package/dist/recorder/sources/micStream.d.ts +15 -0
  308. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  309. package/dist/recorder/sources/micStream.js +24 -0
  310. package/dist/recorder/sources/micStream.js.map +1 -0
  311. package/dist/recorder/sources/screenStream.d.ts +53 -0
  312. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  313. package/dist/recorder/sources/screenStream.js +114 -0
  314. package/dist/recorder/sources/screenStream.js.map +1 -0
  315. package/dist/recorder/timingJson.d.ts +51 -0
  316. package/dist/recorder/timingJson.d.ts.map +1 -0
  317. package/dist/recorder/timingJson.js +42 -0
  318. package/dist/recorder/timingJson.js.map +1 -0
  319. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  320. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  321. package/dist/tiptap/TiptapAudio.js +58 -0
  322. package/dist/tiptap/TiptapAudio.js.map +1 -0
  323. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  324. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  325. package/dist/tiptap/TiptapVideo.js +66 -0
  326. package/dist/tiptap/TiptapVideo.js.map +1 -0
  327. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  328. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  329. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  330. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  331. package/dist/tiptapBridge.d.ts.map +1 -1
  332. package/dist/tiptapBridge.js +171 -14
  333. package/dist/tiptapBridge.js.map +1 -1
  334. package/dist/useHeadingLayout.d.ts +54 -0
  335. package/dist/useHeadingLayout.d.ts.map +1 -0
  336. package/dist/useHeadingLayout.js +260 -0
  337. package/dist/useHeadingLayout.js.map +1 -0
  338. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  339. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  340. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  341. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  342. package/dist/utils/dropUtils.d.ts +21 -2
  343. package/dist/utils/dropUtils.d.ts.map +1 -1
  344. package/dist/utils/dropUtils.js +43 -4
  345. package/dist/utils/dropUtils.js.map +1 -1
  346. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  347. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  348. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  349. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  350. package/package.json +8 -5
  351. package/src/DocumentSettingsDialog.tsx +266 -0
  352. package/src/EditorContext.tsx +534 -10
  353. package/src/EditorShell.tsx +571 -55
  354. package/src/EmojiPicker.tsx +332 -0
  355. package/src/ImageEditor.tsx +327 -0
  356. package/src/ImageNodeView.tsx +222 -21
  357. package/src/ImageViewer.tsx +221 -0
  358. package/src/InlineIcon.ts +84 -0
  359. package/src/InlinePreviewGutter.tsx +582 -0
  360. package/src/LinkDialog.tsx +276 -0
  361. package/src/MentionExtension.tsx +10 -7
  362. package/src/OutlinePanel.tsx +295 -0
  363. package/src/PlainHtmlPreview.tsx +211 -0
  364. package/src/PreviewControls.tsx +130 -24
  365. package/src/PreviewPanel.tsx +38 -21
  366. package/src/RawEditor.tsx +215 -4
  367. package/src/RecorderEntry.tsx +164 -0
  368. package/src/TemplateAnnotation.ts +32 -6
  369. package/src/TemplatePicker.tsx +818 -0
  370. package/src/ThemeCustomizerPanel.tsx +595 -0
  371. package/src/ThemePicker.tsx +319 -0
  372. package/src/Toolbar.tsx +708 -111
  373. package/src/VersionHistoryPanel.tsx +329 -0
  374. package/src/ViewMenuPanel.tsx +188 -0
  375. package/src/WysiwygEditor.tsx +229 -9
  376. package/src/__tests__/detectMarkdown.test.ts +0 -15
  377. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  378. package/src/__tests__/emojiPicker.test.tsx +133 -0
  379. package/src/__tests__/fileKind.test.ts +16 -0
  380. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  381. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  382. package/src/__tests__/imageEditorState.test.ts +171 -0
  383. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  384. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  385. package/src/__tests__/jsonEditor.test.tsx +168 -0
  386. package/src/__tests__/layersPanel.test.tsx +97 -0
  387. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  388. package/src/__tests__/outlinePanel.test.tsx +79 -0
  389. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  390. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  391. package/src/__tests__/recorderFormats.test.ts +146 -0
  392. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  393. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  394. package/src/__tests__/tiptapBridge.test.ts +15 -0
  395. package/src/__tests__/useImageEditor.test.tsx +159 -0
  396. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  397. package/src/__tests__/versionHistory.test.tsx +197 -0
  398. package/src/blockSlice.ts +75 -0
  399. package/src/buildPreviewDoc.ts +61 -6
  400. package/src/emojiData.ts +1337 -0
  401. package/src/fileKind.ts +30 -6
  402. package/src/hooks/useFileDrop.ts +40 -4
  403. package/src/imageEditor/CanvasSurface.tsx +402 -0
  404. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  405. package/src/imageEditor/LayersPanel.tsx +143 -0
  406. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  407. package/src/imageEditor/Toolbar.tsx +242 -0
  408. package/src/imageEditor/icons.tsx +144 -0
  409. package/src/imageEditor/image-editor.css +450 -0
  410. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  411. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  412. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  413. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  414. package/src/imageEditor/state.ts +153 -0
  415. package/src/imageEditor/useImageEditor.ts +328 -0
  416. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  417. package/src/index.ts +82 -0
  418. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  419. package/src/jsonEditor/JsonEditor.tsx +81 -0
  420. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  421. package/src/jsonEditor/RenderNode.tsx +66 -0
  422. package/src/jsonEditor/editors.tsx +678 -0
  423. package/src/jsonEditor/index.ts +2 -0
  424. package/src/jsonEditor/json-editor.css +463 -0
  425. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  426. package/src/recorder/RecorderButton.tsx +72 -0
  427. package/src/recorder/RecorderModal.tsx +596 -0
  428. package/src/recorder/RecorderPanel.tsx +93 -0
  429. package/src/recorder/formats.ts +159 -0
  430. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  431. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  432. package/src/recorder/sources/cameraStream.ts +32 -0
  433. package/src/recorder/sources/micStream.ts +25 -0
  434. package/src/recorder/sources/screenStream.ts +162 -0
  435. package/src/recorder/timingJson.ts +66 -0
  436. package/src/styles/editor.css +2490 -51
  437. package/src/styles/image-edit-affordance.css +201 -0
  438. package/src/styles/index.css +10 -0
  439. package/src/tiptap/TiptapAudio.tsx +86 -0
  440. package/src/tiptap/TiptapVideo.tsx +119 -0
  441. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  442. package/src/tiptapBridge.ts +188 -20
  443. package/src/useHeadingLayout.ts +294 -0
  444. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  445. package/src/utils/dropUtils.ts +54 -6
  446. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
package/src/fileKind.ts CHANGED
@@ -12,8 +12,12 @@
12
12
  */
13
13
 
14
14
  export interface FileKind {
15
- /** 'markdown' keeps the full editor (WYSIWYG + Preview tabs); 'code' is Monaco-only. */
16
- mode: 'markdown' | 'code';
15
+ /**
16
+ * 'markdown' keeps the full editor (WYSIWYG + Preview tabs); 'code' is
17
+ * Monaco-only; 'image' renders a dedicated image viewer with no text
18
+ * editing surface.
19
+ */
20
+ mode: 'markdown' | 'code' | 'image';
17
21
  /** Monaco language ID — passed to `<Editor defaultLanguage={...} />`. */
18
22
  language: string;
19
23
  }
@@ -67,14 +71,29 @@ const EXT_TO_LANGUAGE: Record<string, string> = {
67
71
  kt: 'kotlin',
68
72
  kts: 'kotlin',
69
73
  dockerfile: 'dockerfile',
74
+ png: 'image',
75
+ jpg: 'image',
76
+ jpeg: 'image',
77
+ gif: 'image',
78
+ webp: 'image',
79
+ bmp: 'image',
80
+ ico: 'image',
81
+ avif: 'image',
70
82
  };
71
83
 
72
84
  /**
73
85
  * Languages that keep the full markdown shell (WYSIWYG + Preview). Anything
74
- * outside this set is treated as code.
86
+ * outside this set is treated as code, except for languages in
87
+ * `IMAGE_MODE_LANGUAGES`.
75
88
  */
76
89
  const MARKDOWN_MODE_LANGUAGES = new Set(['markdown', 'plaintext']);
77
90
 
91
+ /**
92
+ * Languages that switch the shell into image-viewer mode — no text editing
93
+ * surface, no markdown chrome.
94
+ */
95
+ const IMAGE_MODE_LANGUAGES = new Set(['image']);
96
+
78
97
  /**
79
98
  * Pull the lowercase extension (no leading dot) from a file name or bare
80
99
  * extension string. Returns null when none is discernible.
@@ -127,8 +146,13 @@ export function resolveFileKind(fileName?: string, language?: string): FileKind
127
146
  return { mode: 'markdown', language: 'markdown' };
128
147
  }
129
148
 
130
- const mode: FileKind['mode'] = MARKDOWN_MODE_LANGUAGES.has(resolvedLanguage)
131
- ? 'markdown'
132
- : 'code';
149
+ let mode: FileKind['mode'];
150
+ if (IMAGE_MODE_LANGUAGES.has(resolvedLanguage)) {
151
+ mode = 'image';
152
+ } else if (MARKDOWN_MODE_LANGUAGES.has(resolvedLanguage)) {
153
+ mode = 'markdown';
154
+ } else {
155
+ mode = 'code';
156
+ }
133
157
  return { mode, language: resolvedLanguage };
134
158
  }
@@ -7,7 +7,7 @@
7
7
  * drop events to callers.
8
8
  */
9
9
 
10
- import { useCallback, useRef, useState } from 'react';
10
+ import { useCallback, useEffect, useRef, useState } from 'react';
11
11
 
12
12
  // ─── File classification ────────────────────────────────
13
13
 
@@ -134,13 +134,49 @@ export function useFileDrop({ onDrop, enabled = true }: UseFileDropOptions): Use
134
134
  // so we track a count rather than a boolean.
135
135
  const dragCounterRef = useRef(0);
136
136
 
137
+ // True while a drag that originated *inside* this page is in flight.
138
+ // Browsers expose draggable `<img>` elements as virtual files in
139
+ // `dataTransfer.types` (so they can be dropped onto the desktop), so
140
+ // checking for `'Files'` alone can't tell an OS file drop apart from
141
+ // an in-app image reorder. We watch the document-level `dragstart`
142
+ // event — only OS drags from outside the page lack a `dragstart` —
143
+ // and bail out of the overlay logic when this flag is set.
144
+ const inPageDragRef = useRef(false);
145
+ useEffect(() => {
146
+ if (!enabled) return;
147
+ const onStart = () => {
148
+ inPageDragRef.current = true;
149
+ };
150
+ const onEnd = () => {
151
+ inPageDragRef.current = false;
152
+ };
153
+ document.addEventListener('dragstart', onStart, true);
154
+ document.addEventListener('dragend', onEnd, true);
155
+ document.addEventListener('drop', onEnd, true);
156
+ return () => {
157
+ document.removeEventListener('dragstart', onStart, true);
158
+ document.removeEventListener('dragend', onEnd, true);
159
+ document.removeEventListener('drop', onEnd, true);
160
+ };
161
+ }, [enabled]);
162
+
137
163
  const handleDragEnter = useCallback(
138
164
  (e: React.DragEvent) => {
139
165
  if (!enabled) return;
140
166
 
141
- // Only react to OS file drags. In-app drags (e.g. dragging a thumbnail
142
- // out of the MediaBin) don't carry file-kind items and must pass
143
- // through to the editors without showing the drop overlay.
167
+ // Drag started inside the page (e.g. repositioning an `<img>`
168
+ // already in the WYSIWYG document, or dragging a thumbnail out of
169
+ // the MediaBin). Do NOT show the file-drop overlay — let
170
+ // ProseMirror handle the in-document move with its dropcursor.
171
+ if (inPageDragRef.current) return;
172
+
173
+ // For OS-level drags, the browser sets the special `'Files'`
174
+ // type. Bail out for anything else (text drags between apps,
175
+ // etc.) so we don't flash the overlay on irrelevant gestures.
176
+ const types = e.dataTransfer.types;
177
+ const hasFiles = types && Array.from(types).includes('Files');
178
+ if (!hasFiles) return;
179
+
144
180
  const classification = e.dataTransfer.items
145
181
  ? classifyDataTransferItems(e.dataTransfer.items)
146
182
  : 'mixed';
@@ -0,0 +1,402 @@
1
+ /**
2
+ * CanvasSurface — the SVG editing surface for `<ImageEditor>`.
3
+ *
4
+ * Renders the doc's canvas (with background and per-layer rendering) in
5
+ * an `<svg>` whose `viewBox` matches the canvas dimensions. Pointer
6
+ * events are normalized to canvas coordinates and dispatched as either
7
+ * selection / drag / resize gestures (select tool) or as a crop-rect
8
+ * gesture (crop tool).
9
+ *
10
+ * Layer rendering uses small purpose-built renderers in `./layers/`,
11
+ * not the heavier `@bendyline/squisq-react` layer components — the
12
+ * editor doesn't need media-context lookup, animation, or
13
+ * blockTime-driven reflow.
14
+ */
15
+
16
+ import { useCallback, useEffect, useRef, useState } from 'react';
17
+ import type { ImageEditDoc, ImageEditLayer } from '@bendyline/squisq/schemas';
18
+ import type { CanvasRect, ImageEditorAction, ImageEditorTool } from './state.js';
19
+ import { EditorImageLayer } from './layers/EditorImageLayer.js';
20
+ import { EditorTextLayer } from './layers/EditorTextLayer.js';
21
+ import { EditorShapeLayer } from './layers/EditorShapeLayer.js';
22
+ import { SelectionHandles, type Handle } from './layers/SelectionHandles.js';
23
+
24
+ export interface CanvasSurfaceProps {
25
+ doc: ImageEditDoc;
26
+ selectedLayerId: string | null;
27
+ tool: ImageEditorTool;
28
+ resolveAssetUrl: (path: string) => Promise<string>;
29
+ dispatch: (action: ImageEditorAction) => void;
30
+ /** When the text tool drops a new layer at a given canvas point. */
31
+ onCreateTextAt?: (x: number, y: number) => void;
32
+ /** When the shape tool drops a new layer at a given canvas point. */
33
+ onCreateShapeAt?: (x: number, y: number) => void;
34
+ /** Background fill behind the canvas (the editor "paper"). */
35
+ workspaceBackground?: string;
36
+ }
37
+
38
+ interface DragState {
39
+ layerId: string;
40
+ startCanvasX: number;
41
+ startCanvasY: number;
42
+ startBox: CanvasRect;
43
+ handle: Handle | 'move';
44
+ }
45
+
46
+ interface CropDragState {
47
+ startCanvasX: number;
48
+ startCanvasY: number;
49
+ currentX: number;
50
+ currentY: number;
51
+ }
52
+
53
+ export function CanvasSurface({
54
+ doc,
55
+ selectedLayerId,
56
+ tool,
57
+ resolveAssetUrl,
58
+ dispatch,
59
+ onCreateTextAt,
60
+ onCreateShapeAt,
61
+ workspaceBackground,
62
+ }: CanvasSurfaceProps) {
63
+ const svgRef = useRef<SVGSVGElement | null>(null);
64
+ const dragRef = useRef<DragState | null>(null);
65
+ const [, forceRender] = useState(0);
66
+ const [cropDrag, setCropDrag] = useState<CropDragState | null>(null);
67
+
68
+ /** Convert client-space coordinates to canvas coordinates. */
69
+ const toCanvas = useCallback(
70
+ (clientX: number, clientY: number): { x: number; y: number } => {
71
+ const svg = svgRef.current;
72
+ if (!svg) return { x: 0, y: 0 };
73
+ const rect = svg.getBoundingClientRect();
74
+ const x = ((clientX - rect.left) / rect.width) * doc.canvas.width;
75
+ const y = ((clientY - rect.top) / rect.height) * doc.canvas.height;
76
+ return { x, y };
77
+ },
78
+ [doc.canvas.width, doc.canvas.height],
79
+ );
80
+
81
+ // ── Pointer handlers ───────────────────────────────────────────────────
82
+ const onPointerDownLayer = useCallback(
83
+ (e: React.PointerEvent<SVGGElement>, layer: ImageEditLayer) => {
84
+ if (tool !== 'select') return;
85
+ if (layer.locked) return;
86
+ e.stopPropagation();
87
+ const pt = toCanvas(e.clientX, e.clientY);
88
+ dispatch({ type: 'select', layerId: layer.id });
89
+ const box = layerBox(layer, doc);
90
+ dragRef.current = {
91
+ layerId: layer.id,
92
+ startCanvasX: pt.x,
93
+ startCanvasY: pt.y,
94
+ startBox: box,
95
+ handle: 'move',
96
+ };
97
+ (e.target as Element).setPointerCapture?.(e.pointerId);
98
+ },
99
+ [tool, dispatch, toCanvas, doc],
100
+ );
101
+
102
+ const onPointerDownHandle = useCallback(
103
+ (e: React.PointerEvent<SVGRectElement>, handle: Handle) => {
104
+ if (!selectedLayerId) return;
105
+ const layer = doc.layers.find((l) => l.id === selectedLayerId);
106
+ if (!layer || layer.locked) return;
107
+ e.stopPropagation();
108
+ const pt = toCanvas(e.clientX, e.clientY);
109
+ const box = layerBox(layer, doc);
110
+ dragRef.current = {
111
+ layerId: layer.id,
112
+ startCanvasX: pt.x,
113
+ startCanvasY: pt.y,
114
+ startBox: box,
115
+ handle,
116
+ };
117
+ (e.target as Element).setPointerCapture?.(e.pointerId);
118
+ },
119
+ [selectedLayerId, doc, toCanvas],
120
+ );
121
+
122
+ const onPointerDownEmpty = useCallback(
123
+ (e: React.PointerEvent<SVGSVGElement>) => {
124
+ const pt = toCanvas(e.clientX, e.clientY);
125
+ if (tool === 'select') {
126
+ dispatch({ type: 'select', layerId: null });
127
+ return;
128
+ }
129
+ if (tool === 'crop') {
130
+ e.preventDefault();
131
+ setCropDrag({ startCanvasX: pt.x, startCanvasY: pt.y, currentX: pt.x, currentY: pt.y });
132
+ (e.target as Element).setPointerCapture?.(e.pointerId);
133
+ return;
134
+ }
135
+ if (tool === 'text') {
136
+ onCreateTextAt?.(pt.x, pt.y);
137
+ return;
138
+ }
139
+ if (tool === 'shape') {
140
+ onCreateShapeAt?.(pt.x, pt.y);
141
+ }
142
+ },
143
+ [tool, dispatch, toCanvas, onCreateTextAt, onCreateShapeAt],
144
+ );
145
+
146
+ useEffect(() => {
147
+ function onMove(e: PointerEvent) {
148
+ const drag = dragRef.current;
149
+ if (drag) {
150
+ const pt = toCanvas(e.clientX, e.clientY);
151
+ const dx = pt.x - drag.startCanvasX;
152
+ const dy = pt.y - drag.startCanvasY;
153
+ const next = applyHandle(drag.startBox, drag.handle, dx, dy);
154
+ dispatch({
155
+ type: 'update-layer',
156
+ layerId: drag.layerId,
157
+ patch: {
158
+ position: {
159
+ x: Math.round(next.x),
160
+ y: Math.round(next.y),
161
+ width: Math.round(next.width),
162
+ height: Math.round(next.height),
163
+ },
164
+ },
165
+ });
166
+ return;
167
+ }
168
+ if (cropDrag) {
169
+ const pt = toCanvas(e.clientX, e.clientY);
170
+ setCropDrag((prev) => (prev ? { ...prev, currentX: pt.x, currentY: pt.y } : prev));
171
+ }
172
+ }
173
+ function onUp() {
174
+ if (dragRef.current) {
175
+ dragRef.current = null;
176
+ forceRender((n) => n + 1);
177
+ }
178
+ if (cropDrag) {
179
+ const rect = normalizeCropRect(cropDrag);
180
+ if (rect.width >= 8 && rect.height >= 8) {
181
+ dispatch({ type: 'crop', rect });
182
+ dispatch({ type: 'set-tool', tool: 'select' });
183
+ }
184
+ setCropDrag(null);
185
+ }
186
+ }
187
+ window.addEventListener('pointermove', onMove);
188
+ window.addEventListener('pointerup', onUp);
189
+ return () => {
190
+ window.removeEventListener('pointermove', onMove);
191
+ window.removeEventListener('pointerup', onUp);
192
+ };
193
+ }, [toCanvas, dispatch, cropDrag]);
194
+
195
+ // ── Render ─────────────────────────────────────────────────────────────
196
+ const selectedLayer = selectedLayerId
197
+ ? (doc.layers.find((l) => l.id === selectedLayerId) ?? null)
198
+ : null;
199
+ const selectedBox = selectedLayer ? layerBox(selectedLayer, doc) : null;
200
+ const selectionBox =
201
+ selectedLayer && selectedLayer.type === 'text'
202
+ ? measureTextLayerBox(selectedLayer, selectedBox!)
203
+ : selectedBox;
204
+
205
+ return (
206
+ <div
207
+ className="squisq-image-editor-surface"
208
+ style={{ background: workspaceBackground ?? '#1f1f24' }}
209
+ >
210
+ <svg
211
+ ref={svgRef}
212
+ viewBox={`0 0 ${doc.canvas.width} ${doc.canvas.height}`}
213
+ preserveAspectRatio="xMidYMid meet"
214
+ className={`squisq-image-editor-canvas squisq-image-editor-canvas--tool-${tool}`}
215
+ onPointerDown={onPointerDownEmpty}
216
+ >
217
+ {/* Canvas background */}
218
+ <rect
219
+ x={0}
220
+ y={0}
221
+ width={doc.canvas.width}
222
+ height={doc.canvas.height}
223
+ fill={
224
+ doc.canvas.background && doc.canvas.background !== 'transparent'
225
+ ? doc.canvas.background
226
+ : 'url(#squisq-image-editor-checker)'
227
+ }
228
+ />
229
+ <defs>
230
+ <pattern
231
+ id="squisq-image-editor-checker"
232
+ width="16"
233
+ height="16"
234
+ patternUnits="userSpaceOnUse"
235
+ >
236
+ <rect width="16" height="16" fill="#f0f0f0" />
237
+ <rect width="8" height="8" fill="#d0d0d0" />
238
+ <rect x="8" y="8" width="8" height="8" fill="#d0d0d0" />
239
+ </pattern>
240
+ </defs>
241
+
242
+ {/* Layers, back-to-front */}
243
+ {doc.layers.map((layer) => {
244
+ if (layer.visible === false) return null;
245
+ const onPointerDown = (e: React.PointerEvent<SVGGElement>) =>
246
+ onPointerDownLayer(e, layer);
247
+ const opacity = layer.opacity ?? 1;
248
+ return (
249
+ <g
250
+ key={layer.id}
251
+ data-layer-id={layer.id}
252
+ opacity={opacity}
253
+ style={{ cursor: tool === 'select' && !layer.locked ? 'move' : undefined }}
254
+ onPointerDown={onPointerDown}
255
+ >
256
+ {layer.type === 'image' && (
257
+ <EditorImageLayer
258
+ layer={layer}
259
+ canvas={doc.canvas}
260
+ resolveAssetUrl={resolveAssetUrl}
261
+ />
262
+ )}
263
+ {layer.type === 'text' && <EditorTextLayer layer={layer} canvas={doc.canvas} />}
264
+ {layer.type === 'shape' && <EditorShapeLayer layer={layer} canvas={doc.canvas} />}
265
+ </g>
266
+ );
267
+ })}
268
+
269
+ {/* Selection handles */}
270
+ {selectedLayer && selectionBox && tool === 'select' && !selectedLayer.locked && (
271
+ <SelectionHandles box={selectionBox} onHandlePointerDown={onPointerDownHandle} />
272
+ )}
273
+
274
+ {/* Crop rectangle preview */}
275
+ {cropDrag &&
276
+ (() => {
277
+ const r = normalizeCropRect(cropDrag);
278
+ return (
279
+ <g pointerEvents="none">
280
+ <rect
281
+ x={r.x}
282
+ y={r.y}
283
+ width={r.width}
284
+ height={r.height}
285
+ fill="rgba(255,255,255,0.05)"
286
+ stroke="#39f"
287
+ strokeWidth={2}
288
+ strokeDasharray="6 4"
289
+ />
290
+ </g>
291
+ );
292
+ })()}
293
+ </svg>
294
+ </div>
295
+ );
296
+ }
297
+
298
+ // ============================================
299
+ // Helpers
300
+ // ============================================
301
+
302
+ /** Resolve a layer's pixel box, ignoring anchor/percentage strings (the editor authors numeric coords). */
303
+ function layerBox(layer: ImageEditLayer, doc: ImageEditDoc): CanvasRect {
304
+ const p = layer.position;
305
+ const x = typeof p.x === 'number' ? p.x : 0;
306
+ const y = typeof p.y === 'number' ? p.y : 0;
307
+ const width = typeof p.width === 'number' ? p.width : doc.canvas.width;
308
+ const height = typeof p.height === 'number' ? p.height : doc.canvas.height;
309
+ return { x, y, width, height };
310
+ }
311
+
312
+ const MIN_DIM = 4;
313
+
314
+ function applyHandle(box: CanvasRect, handle: Handle | 'move', dx: number, dy: number): CanvasRect {
315
+ if (handle === 'move') return { ...box, x: box.x + dx, y: box.y + dy };
316
+ let { x, y, width, height } = box;
317
+ // Each handle adjusts a subset of (x, y, width, height).
318
+ if (handle.includes('w')) {
319
+ const newWidth = Math.max(MIN_DIM, width - dx);
320
+ x = x + (width - newWidth);
321
+ width = newWidth;
322
+ } else if (handle.includes('e')) {
323
+ width = Math.max(MIN_DIM, width + dx);
324
+ }
325
+ if (handle.includes('n')) {
326
+ const newHeight = Math.max(MIN_DIM, height - dy);
327
+ y = y + (height - newHeight);
328
+ height = newHeight;
329
+ } else if (handle.includes('s')) {
330
+ height = Math.max(MIN_DIM, height + dy);
331
+ }
332
+ return { x, y, width, height };
333
+ }
334
+
335
+ function normalizeCropRect(d: CropDragState): CanvasRect {
336
+ const x = Math.min(d.startCanvasX, d.currentX);
337
+ const y = Math.min(d.startCanvasY, d.currentY);
338
+ const width = Math.abs(d.currentX - d.startCanvasX);
339
+ const height = Math.abs(d.currentY - d.startCanvasY);
340
+ return { x, y, width, height };
341
+ }
342
+
343
+ /**
344
+ * Measure the visual bounding box of a text layer using a 2D canvas.
345
+ * Falls back to the layer's authored width/height if measurement isn't
346
+ * available (SSR, headless test envs without canvas). The returned box
347
+ * tightly wraps the rendered glyphs so the selection rectangle hugs the
348
+ * actual text rather than an arbitrary author-supplied frame.
349
+ */
350
+ let measureCtx: CanvasRenderingContext2D | null | undefined;
351
+ function getMeasureCtx(): CanvasRenderingContext2D | null {
352
+ if (measureCtx !== undefined) return measureCtx;
353
+ if (typeof document === 'undefined') {
354
+ measureCtx = null;
355
+ return null;
356
+ }
357
+ try {
358
+ const c = document.createElement('canvas');
359
+ measureCtx = c.getContext('2d');
360
+ } catch {
361
+ measureCtx = null;
362
+ }
363
+ return measureCtx ?? null;
364
+ }
365
+
366
+ function measureTextLayerBox(
367
+ layer: ImageEditLayer & { type: 'text' },
368
+ fallback: CanvasRect,
369
+ ): CanvasRect {
370
+ const ctx = getMeasureCtx();
371
+ if (!ctx) return fallback;
372
+ const { text, style } = layer.content;
373
+ const fontSize = style.fontSize;
374
+ const fontWeight = style.fontWeight ?? 'normal';
375
+ const fontFamily = style.fontFamily ?? 'sans-serif';
376
+ ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
377
+ const lines = (text ?? '').split('\n');
378
+ let maxWidth = 0;
379
+ for (const line of lines) {
380
+ const w = ctx.measureText(line || ' ').width;
381
+ if (w > maxWidth) maxWidth = w;
382
+ }
383
+ const lineHeight = style.lineHeight ?? 1.4;
384
+ const lineHeightPx = fontSize * lineHeight;
385
+ // First line spans fontSize tall; subsequent lines add lineHeightPx each.
386
+ const totalHeight = fontSize + Math.max(0, lines.length - 1) * lineHeightPx;
387
+ // Mirror the textAnchor logic in EditorTextLayer.
388
+ const anchor =
389
+ style.textAlign === 'center' ? 'middle' : style.textAlign === 'right' ? 'end' : 'start';
390
+ const x =
391
+ anchor === 'middle'
392
+ ? fallback.x - maxWidth / 2
393
+ : anchor === 'end'
394
+ ? fallback.x - maxWidth
395
+ : fallback.x;
396
+ return {
397
+ x,
398
+ y: fallback.y,
399
+ width: Math.max(MIN_DIM, Math.ceil(maxWidth)),
400
+ height: Math.max(MIN_DIM, Math.ceil(totalHeight)),
401
+ };
402
+ }