@bendyline/squisq-editor-react 1.4.0 → 1.5.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 (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
@@ -0,0 +1,595 @@
1
+ /**
2
+ * ThemeCustomizerPanel
3
+ *
4
+ * Drop-in toolbar button + popover that lets the user author a custom
5
+ * Theme by picking seed colors, fonts, and a few thematic presets.
6
+ *
7
+ * Design:
8
+ * - **Controlled component** — host owns the `value` and forwards
9
+ * `onChange` whenever the user edits anything. This avoids coupling
10
+ * the panel to any specific preview wiring; the host decides whether
11
+ * to register the theme, set it as the preview theme, persist it, etc.
12
+ * - **Subset of the Theme schema** — the panel exposes seed colors,
13
+ * curated/free-text fonts, and a handful of preset groups. Everything
14
+ * else (templateHints, layoutOverrides, persistentLayers, individual
15
+ * colorSchemes, animation defaults) inherits from the compiler's
16
+ * STARTER_THEME and can only be edited by hand-modifying the JSON.
17
+ * - **Industry-standard mental model** — primary / secondary / accent +
18
+ * derived lighter/darker variants, like Material UI / Tailwind / Radix.
19
+ */
20
+
21
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
+ import type { Theme, FontFamily, ThemeSeedColors } from '@bendyline/squisq/schemas';
23
+ import {
24
+ AVAILABLE_FONT_STACKS,
25
+ compileTheme,
26
+ deriveScale,
27
+ isHex,
28
+ serializeTheme,
29
+ } from '@bendyline/squisq/schemas';
30
+
31
+ // ── Preset → schema-value tables ────────────────────────────────────
32
+
33
+ const BORDER_RADIUS_PRESETS = {
34
+ sharp: 0,
35
+ soft: 6,
36
+ rounded: 16,
37
+ } as const;
38
+ type BorderRadiusPreset = keyof typeof BORDER_RADIUS_PRESETS;
39
+
40
+ const ANIMATION_SPEED_PRESETS = {
41
+ static: 0,
42
+ subtle: 1.4,
43
+ normal: 1.0,
44
+ expressive: 0.7,
45
+ } as const;
46
+ type AnimationSpeedPreset = keyof typeof ANIMATION_SPEED_PRESETS;
47
+
48
+ const TEXT_SHADOW_PRESETS = {
49
+ off: false,
50
+ on: true,
51
+ } as const;
52
+ type TextShadowPreset = keyof typeof TEXT_SHADOW_PRESETS;
53
+
54
+ const CONTRAST_PRESETS = ['subtle', 'balanced', 'high'] as const;
55
+ type ContrastPreset = (typeof CONTRAST_PRESETS)[number];
56
+
57
+ const FALLBACK_OPTIONS = ['sans-serif', 'serif', 'monospace', 'system-ui'] as const;
58
+ type FallbackOption = (typeof FALLBACK_OPTIONS)[number];
59
+
60
+ // ── Draft state — reflects the editable subset of the schema ────────
61
+
62
+ interface CustomFontInput {
63
+ kind: 'curated' | 'custom';
64
+ stackId?: string;
65
+ customName?: string;
66
+ customFallback?: FallbackOption;
67
+ }
68
+
69
+ interface Draft {
70
+ name: string;
71
+ seeds: ThemeSeedColors;
72
+ titleFont: CustomFontInput;
73
+ bodyFont: CustomFontInput;
74
+ borderRadius: BorderRadiusPreset;
75
+ animationSpeed: AnimationSpeedPreset;
76
+ textShadow: TextShadowPreset;
77
+ contrast: ContrastPreset;
78
+ }
79
+
80
+ const DEFAULT_DRAFT: Draft = {
81
+ name: 'My Theme',
82
+ seeds: {
83
+ primary: '#3182ce',
84
+ secondary: '#4a5568',
85
+ accent: '#63b3ed',
86
+ background: '#1a202c',
87
+ text: '#f7fafc',
88
+ },
89
+ titleFont: { kind: 'curated', stackId: 'system-serif' },
90
+ bodyFont: { kind: 'curated', stackId: 'system-sans' },
91
+ borderRadius: 'soft',
92
+ animationSpeed: 'normal',
93
+ textShadow: 'on',
94
+ contrast: 'balanced',
95
+ };
96
+
97
+ function findRadiusPreset(value: number | undefined): BorderRadiusPreset {
98
+ if (value === undefined) return 'soft';
99
+ let best: BorderRadiusPreset = 'soft';
100
+ let bestDist = Infinity;
101
+ (Object.entries(BORDER_RADIUS_PRESETS) as [BorderRadiusPreset, number][]).forEach(([k, v]) => {
102
+ const d = Math.abs(v - value);
103
+ if (d < bestDist) {
104
+ best = k;
105
+ bestDist = d;
106
+ }
107
+ });
108
+ return best;
109
+ }
110
+
111
+ function findAnimationPreset(value: number | undefined): AnimationSpeedPreset {
112
+ if (value === undefined || value === 0) return value === 0 ? 'static' : 'normal';
113
+ let best: AnimationSpeedPreset = 'normal';
114
+ let bestDist = Infinity;
115
+ (Object.entries(ANIMATION_SPEED_PRESETS) as [AnimationSpeedPreset, number][]).forEach(
116
+ ([k, v]) => {
117
+ if (v === 0) return; // 'static' handled above
118
+ const d = Math.abs(v - value);
119
+ if (d < bestDist) {
120
+ best = k;
121
+ bestDist = d;
122
+ }
123
+ },
124
+ );
125
+ return best;
126
+ }
127
+
128
+ function fontFamilyToInput(f: FontFamily | undefined, fallbackStackId: string): CustomFontInput {
129
+ if (!f) return { kind: 'curated', stackId: fallbackStackId };
130
+ if ('stackId' in f) return { kind: 'curated', stackId: f.stackId };
131
+ if ('custom' in f)
132
+ return {
133
+ kind: 'custom',
134
+ customName: f.custom.name,
135
+ customFallback: f.custom.fallback,
136
+ };
137
+ return { kind: 'curated', stackId: fallbackStackId };
138
+ }
139
+
140
+ function inputToFontFamily(input: CustomFontInput): FontFamily {
141
+ if (input.kind === 'curated') {
142
+ return { stackId: input.stackId ?? 'system-sans' };
143
+ }
144
+ return {
145
+ custom: {
146
+ name: input.customName ?? 'Sans',
147
+ fallback: input.customFallback ?? 'sans-serif',
148
+ },
149
+ };
150
+ }
151
+
152
+ function themeToDraft(theme: Theme | null): Draft {
153
+ if (!theme) return { ...DEFAULT_DRAFT };
154
+ const seeds: ThemeSeedColors = theme.seedColors ?? {
155
+ primary: theme.colors.primary,
156
+ secondary: theme.colors.secondary,
157
+ accent: theme.colors.highlight,
158
+ background: theme.colors.background,
159
+ text: theme.colors.text,
160
+ };
161
+ return {
162
+ name: theme.name,
163
+ seeds: {
164
+ primary: seeds.primary,
165
+ secondary: seeds.secondary,
166
+ accent: seeds.accent,
167
+ background: seeds.background,
168
+ text: seeds.text,
169
+ },
170
+ titleFont: fontFamilyToInput(theme.typography.titleFont, 'system-serif'),
171
+ bodyFont: fontFamilyToInput(theme.typography.bodyFont, 'system-sans'),
172
+ borderRadius: findRadiusPreset(theme.style.borderRadius),
173
+ animationSpeed: findAnimationPreset(theme.style.animationSpeed),
174
+ textShadow: theme.style.textShadow === false ? 'off' : 'on',
175
+ contrast: 'balanced',
176
+ };
177
+ }
178
+
179
+ function slugify(s: string): string {
180
+ return (
181
+ s
182
+ .toLowerCase()
183
+ .replace(/[^a-z0-9]+/g, '-')
184
+ .replace(/^-+|-+$/g, '') || 'custom'
185
+ );
186
+ }
187
+
188
+ function compileDraft(draft: Draft, baseId?: string): Theme {
189
+ const id = baseId && baseId.startsWith('custom-') ? baseId : `custom-${slugify(draft.name)}`;
190
+ return compileTheme(
191
+ {
192
+ id,
193
+ name: draft.name,
194
+ seedColors: draft.seeds,
195
+ typography: {
196
+ titleFont: inputToFontFamily(draft.titleFont),
197
+ bodyFont: inputToFontFamily(draft.bodyFont),
198
+ },
199
+ style: {
200
+ borderRadius: BORDER_RADIUS_PRESETS[draft.borderRadius],
201
+ animationSpeed: ANIMATION_SPEED_PRESETS[draft.animationSpeed],
202
+ textShadow: TEXT_SHADOW_PRESETS[draft.textShadow],
203
+ },
204
+ },
205
+ { contrast: draft.contrast },
206
+ );
207
+ }
208
+
209
+ // ── Component ───────────────────────────────────────────────────────
210
+
211
+ export interface ThemeCustomizerPanelProps {
212
+ /** Current custom theme (or null to start from defaults). */
213
+ value: Theme | null;
214
+ /** Fired on every edit. Host typically registers the theme + previews it. */
215
+ onChange: (theme: Theme) => void;
216
+ /** Fired when the user clicks Save. Host typically persists the theme JSON. */
217
+ onSave?: (theme: Theme, json: string) => void;
218
+ /** Fired when the user clicks Reset. Host typically clears its persistent storage. */
219
+ onReset?: () => void;
220
+ }
221
+
222
+ export function ThemeCustomizerPanel({
223
+ value,
224
+ onChange,
225
+ onSave,
226
+ onReset,
227
+ }: ThemeCustomizerPanelProps) {
228
+ const [open, setOpen] = useState(false);
229
+ const [draft, setDraft] = useState<Draft>(() => themeToDraft(value));
230
+ const containerRef = useRef<HTMLDivElement>(null);
231
+
232
+ // Whenever an external value lands (e.g., page load with persisted theme),
233
+ // sync the draft. Internal edits update both draft and value via onChange.
234
+ const externalIdRef = useRef<string | null>(value?.id ?? null);
235
+ useEffect(() => {
236
+ const incomingId = value?.id ?? null;
237
+ if (incomingId !== externalIdRef.current) {
238
+ externalIdRef.current = incomingId;
239
+ setDraft(themeToDraft(value));
240
+ }
241
+ }, [value]);
242
+
243
+ // Click-outside to close.
244
+ useEffect(() => {
245
+ if (!open) return;
246
+ const handler = (e: MouseEvent) => {
247
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
248
+ setOpen(false);
249
+ }
250
+ };
251
+ document.addEventListener('mousedown', handler);
252
+ return () => document.removeEventListener('mousedown', handler);
253
+ }, [open]);
254
+
255
+ const updateDraft = useCallback(
256
+ (patch: Partial<Draft> | ((d: Draft) => Draft)) => {
257
+ setDraft((prev) => {
258
+ const next = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch };
259
+ try {
260
+ const compiled = compileDraft(next, value?.id);
261
+ onChange(compiled);
262
+ } catch {
263
+ // Invalid intermediate state (e.g., bad hex while typing) — skip emit.
264
+ }
265
+ return next;
266
+ });
267
+ },
268
+ [onChange, value?.id],
269
+ );
270
+
271
+ const updateSeed = useCallback(
272
+ (key: keyof ThemeSeedColors, hex: string) => {
273
+ updateDraft((d) => ({ ...d, seeds: { ...d.seeds, [key]: hex } }));
274
+ },
275
+ [updateDraft],
276
+ );
277
+
278
+ const handleSave = useCallback(() => {
279
+ try {
280
+ const compiled = compileDraft(draft, value?.id);
281
+ onSave?.(compiled, serializeTheme(compiled));
282
+ } catch {
283
+ // Validation should already have surfaced via the disabled state.
284
+ }
285
+ }, [draft, onSave, value?.id]);
286
+
287
+ const handleReset = useCallback(() => {
288
+ setDraft({ ...DEFAULT_DRAFT });
289
+ try {
290
+ onChange(compileDraft(DEFAULT_DRAFT));
291
+ } catch {
292
+ // ignore
293
+ }
294
+ onReset?.();
295
+ }, [onChange, onReset]);
296
+
297
+ const previewSwatches = useMemo(() => {
298
+ const seed = draft.seeds.primary;
299
+ if (!isHex(seed)) return null;
300
+ return deriveScale(
301
+ seed,
302
+ draft.contrast === 'high' ? 0.22 : draft.contrast === 'subtle' ? 0.08 : 0.15,
303
+ );
304
+ }, [draft.seeds.primary, draft.contrast]);
305
+
306
+ return (
307
+ <div className="squisq-theme-customizer" ref={containerRef}>
308
+ <button
309
+ type="button"
310
+ className={`squisq-toolbar-button squisq-theme-customizer-trigger${
311
+ open ? ' squisq-toolbar-button--active' : ''
312
+ }`}
313
+ data-tooltip="Customize theme"
314
+ aria-label="Customize theme"
315
+ aria-expanded={open}
316
+ onClick={() => setOpen((v) => !v)}
317
+ >
318
+ <svg
319
+ width="16"
320
+ height="16"
321
+ viewBox="0 0 16 16"
322
+ fill="none"
323
+ stroke="currentColor"
324
+ strokeWidth="1.5"
325
+ strokeLinecap="round"
326
+ strokeLinejoin="round"
327
+ >
328
+ <circle cx="8" cy="8" r="6" />
329
+ <path d="M8 2 A6 6 0 0 1 8 14 Z" fill="currentColor" stroke="none" />
330
+ </svg>
331
+ </button>
332
+ {open && (
333
+ <div className="squisq-theme-customizer-popover" role="dialog" aria-label="Customize theme">
334
+ <div className="squisq-theme-customizer-header">
335
+ <span className="squisq-theme-customizer-title">Customize theme</span>
336
+ </div>
337
+
338
+ <div className="squisq-theme-customizer-body">
339
+ <Section title="Name">
340
+ <input
341
+ type="text"
342
+ className="squisq-theme-customizer-input"
343
+ value={draft.name}
344
+ onChange={(e) => updateDraft({ name: e.target.value })}
345
+ aria-label="Theme name"
346
+ />
347
+ </Section>
348
+
349
+ <Section title="Colors" hint="Pick seed colors. The rest is derived.">
350
+ <SeedColorRow
351
+ label="Primary"
352
+ value={draft.seeds.primary}
353
+ onChange={(v) => updateSeed('primary', v)}
354
+ />
355
+ <SeedColorRow
356
+ label="Secondary"
357
+ value={draft.seeds.secondary ?? ''}
358
+ onChange={(v) => updateSeed('secondary', v)}
359
+ />
360
+ <SeedColorRow
361
+ label="Accent"
362
+ value={draft.seeds.accent ?? ''}
363
+ onChange={(v) => updateSeed('accent', v)}
364
+ />
365
+ <SeedColorRow
366
+ label="Background"
367
+ value={draft.seeds.background ?? ''}
368
+ onChange={(v) => updateSeed('background', v)}
369
+ />
370
+ <SeedColorRow
371
+ label="Text"
372
+ value={draft.seeds.text ?? ''}
373
+ onChange={(v) => updateSeed('text', v)}
374
+ />
375
+ {previewSwatches && (
376
+ <div className="squisq-theme-customizer-scale" aria-label="Derived primary scale">
377
+ {(['lighter2', 'lighter1', 'base', 'darker1', 'darker2'] as const).map((k) => (
378
+ <span
379
+ key={k}
380
+ className="squisq-theme-customizer-swatch"
381
+ style={{ background: previewSwatches[k] }}
382
+ title={`${k}: ${previewSwatches[k]}`}
383
+ />
384
+ ))}
385
+ </div>
386
+ )}
387
+ </Section>
388
+
389
+ <Section title="Typography">
390
+ <FontPicker
391
+ label="Heading"
392
+ value={draft.titleFont}
393
+ onChange={(next) => updateDraft({ titleFont: next })}
394
+ />
395
+ <FontPicker
396
+ label="Body"
397
+ value={draft.bodyFont}
398
+ onChange={(next) => updateDraft({ bodyFont: next })}
399
+ />
400
+ </Section>
401
+
402
+ <Section title="Style">
403
+ <PresetRow
404
+ label="Border radius"
405
+ value={draft.borderRadius}
406
+ options={Object.keys(BORDER_RADIUS_PRESETS) as BorderRadiusPreset[]}
407
+ onChange={(v) => updateDraft({ borderRadius: v })}
408
+ />
409
+ <PresetRow
410
+ label="Animation"
411
+ value={draft.animationSpeed}
412
+ options={Object.keys(ANIMATION_SPEED_PRESETS) as AnimationSpeedPreset[]}
413
+ onChange={(v) => updateDraft({ animationSpeed: v })}
414
+ />
415
+ <PresetRow
416
+ label="Text shadow"
417
+ value={draft.textShadow}
418
+ options={Object.keys(TEXT_SHADOW_PRESETS) as TextShadowPreset[]}
419
+ onChange={(v) => updateDraft({ textShadow: v })}
420
+ />
421
+ <PresetRow
422
+ label="Contrast"
423
+ value={draft.contrast}
424
+ options={CONTRAST_PRESETS as readonly ContrastPreset[]}
425
+ onChange={(v) => updateDraft({ contrast: v })}
426
+ />
427
+ </Section>
428
+ </div>
429
+
430
+ <div className="squisq-theme-customizer-footer">
431
+ <button type="button" className="squisq-theme-customizer-button" onClick={handleReset}>
432
+ Reset
433
+ </button>
434
+ {onSave && (
435
+ <button
436
+ type="button"
437
+ className="squisq-theme-customizer-button squisq-theme-customizer-button--primary"
438
+ onClick={handleSave}
439
+ >
440
+ Save
441
+ </button>
442
+ )}
443
+ </div>
444
+ </div>
445
+ )}
446
+ </div>
447
+ );
448
+ }
449
+
450
+ // ── Subcomponents ───────────────────────────────────────────────────
451
+
452
+ function Section({
453
+ title,
454
+ hint,
455
+ children,
456
+ }: {
457
+ title: string;
458
+ hint?: string;
459
+ children: React.ReactNode;
460
+ }) {
461
+ return (
462
+ <div className="squisq-theme-customizer-section">
463
+ <div className="squisq-theme-customizer-section-title">{title}</div>
464
+ {hint && <div className="squisq-theme-customizer-section-hint">{hint}</div>}
465
+ <div className="squisq-theme-customizer-section-body">{children}</div>
466
+ </div>
467
+ );
468
+ }
469
+
470
+ function SeedColorRow({
471
+ label,
472
+ value,
473
+ onChange,
474
+ }: {
475
+ label: string;
476
+ value: string;
477
+ onChange: (hex: string) => void;
478
+ }) {
479
+ const safeValue = isHex(value) ? value : '#000000';
480
+ return (
481
+ <label className="squisq-theme-customizer-row">
482
+ <span className="squisq-theme-customizer-row-label">{label}</span>
483
+ <input
484
+ type="color"
485
+ className="squisq-theme-customizer-color"
486
+ value={safeValue}
487
+ onChange={(e) => onChange(e.target.value)}
488
+ />
489
+ <input
490
+ type="text"
491
+ className="squisq-theme-customizer-input squisq-theme-customizer-input--hex"
492
+ value={value}
493
+ onChange={(e) => onChange(e.target.value)}
494
+ spellCheck={false}
495
+ aria-label={`${label} hex value`}
496
+ />
497
+ </label>
498
+ );
499
+ }
500
+
501
+ function FontPicker({
502
+ label,
503
+ value,
504
+ onChange,
505
+ }: {
506
+ label: string;
507
+ value: CustomFontInput;
508
+ onChange: (next: CustomFontInput) => void;
509
+ }) {
510
+ return (
511
+ <div className="squisq-theme-customizer-row squisq-theme-customizer-row--font">
512
+ <span className="squisq-theme-customizer-row-label">{label}</span>
513
+ <select
514
+ className="squisq-theme-customizer-input"
515
+ value={value.kind === 'custom' ? '__custom__' : (value.stackId ?? '')}
516
+ onChange={(e) => {
517
+ const v = e.target.value;
518
+ if (v === '__custom__') {
519
+ onChange({
520
+ kind: 'custom',
521
+ customName: value.customName ?? '',
522
+ customFallback: value.customFallback ?? 'sans-serif',
523
+ });
524
+ } else {
525
+ onChange({ kind: 'curated', stackId: v });
526
+ }
527
+ }}
528
+ aria-label={`${label} font`}
529
+ >
530
+ {AVAILABLE_FONT_STACKS.map((stack) => (
531
+ <option key={stack.id} value={stack.id}>
532
+ {stack.label}
533
+ </option>
534
+ ))}
535
+ <option value="__custom__">Custom…</option>
536
+ </select>
537
+ {value.kind === 'custom' && (
538
+ <>
539
+ <input
540
+ type="text"
541
+ className="squisq-theme-customizer-input"
542
+ placeholder="Font name"
543
+ value={value.customName ?? ''}
544
+ onChange={(e) => onChange({ ...value, customName: e.target.value })}
545
+ aria-label={`${label} custom font name`}
546
+ />
547
+ <select
548
+ className="squisq-theme-customizer-input"
549
+ value={value.customFallback ?? 'sans-serif'}
550
+ onChange={(e) =>
551
+ onChange({ ...value, customFallback: e.target.value as FallbackOption })
552
+ }
553
+ aria-label={`${label} custom font fallback`}
554
+ >
555
+ {FALLBACK_OPTIONS.map((opt) => (
556
+ <option key={opt} value={opt}>
557
+ {opt}
558
+ </option>
559
+ ))}
560
+ </select>
561
+ </>
562
+ )}
563
+ </div>
564
+ );
565
+ }
566
+
567
+ function PresetRow<T extends string>({
568
+ label,
569
+ value,
570
+ options,
571
+ onChange,
572
+ }: {
573
+ label: string;
574
+ value: T;
575
+ options: readonly T[];
576
+ onChange: (v: T) => void;
577
+ }) {
578
+ return (
579
+ <label className="squisq-theme-customizer-row">
580
+ <span className="squisq-theme-customizer-row-label">{label}</span>
581
+ <select
582
+ className="squisq-theme-customizer-input"
583
+ value={value}
584
+ onChange={(e) => onChange(e.target.value as T)}
585
+ aria-label={label}
586
+ >
587
+ {options.map((o) => (
588
+ <option key={o} value={o}>
589
+ {o}
590
+ </option>
591
+ ))}
592
+ </select>
593
+ </label>
594
+ );
595
+ }