@bendyline/squisq-editor-react 1.3.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.
- package/dist/DocumentSettingsDialog.d.ts +26 -0
- package/dist/DocumentSettingsDialog.d.ts.map +1 -0
- package/dist/DocumentSettingsDialog.js +115 -0
- package/dist/DocumentSettingsDialog.js.map +1 -0
- package/dist/EditorContext.d.ts +248 -4
- package/dist/EditorContext.d.ts.map +1 -1
- package/dist/EditorContext.js +248 -10
- package/dist/EditorContext.js.map +1 -1
- package/dist/EditorShell.d.ts +184 -4
- package/dist/EditorShell.d.ts.map +1 -1
- package/dist/EditorShell.js +184 -12
- package/dist/EditorShell.js.map +1 -1
- package/dist/EmojiPicker.d.ts +50 -0
- package/dist/EmojiPicker.d.ts.map +1 -0
- package/dist/EmojiPicker.js +182 -0
- package/dist/EmojiPicker.js.map +1 -0
- package/dist/ImageEditor.d.ts +68 -0
- package/dist/ImageEditor.d.ts.map +1 -0
- package/dist/ImageEditor.js +166 -0
- package/dist/ImageEditor.js.map +1 -0
- package/dist/ImageNodeView.d.ts +13 -1
- package/dist/ImageNodeView.d.ts.map +1 -1
- package/dist/ImageNodeView.js +172 -19
- package/dist/ImageNodeView.js.map +1 -1
- package/dist/ImageViewer.d.ts +26 -0
- package/dist/ImageViewer.d.ts.map +1 -0
- package/dist/ImageViewer.js +119 -0
- package/dist/ImageViewer.js.map +1 -0
- package/dist/InlineIcon.d.ts +17 -0
- package/dist/InlineIcon.d.ts.map +1 -0
- package/dist/InlineIcon.js +72 -0
- package/dist/InlineIcon.js.map +1 -0
- package/dist/InlinePreviewGutter.d.ts +52 -0
- package/dist/InlinePreviewGutter.d.ts.map +1 -0
- package/dist/InlinePreviewGutter.js +397 -0
- package/dist/InlinePreviewGutter.js.map +1 -0
- package/dist/LinkDialog.d.ts +43 -0
- package/dist/LinkDialog.d.ts.map +1 -0
- package/dist/LinkDialog.js +102 -0
- package/dist/LinkDialog.js.map +1 -0
- package/dist/MediaBin.d.ts +12 -1
- package/dist/MediaBin.d.ts.map +1 -1
- package/dist/MediaBin.js +13 -3
- package/dist/MediaBin.js.map +1 -1
- package/dist/MentionExtension.js +10 -7
- package/dist/MentionExtension.js.map +1 -1
- package/dist/OutlinePanel.d.ts +17 -0
- package/dist/OutlinePanel.d.ts.map +1 -0
- package/dist/OutlinePanel.js +167 -0
- package/dist/OutlinePanel.js.map +1 -0
- package/dist/PlainHtmlPreview.d.ts +50 -0
- package/dist/PlainHtmlPreview.d.ts.map +1 -0
- package/dist/PlainHtmlPreview.js +155 -0
- package/dist/PlainHtmlPreview.js.map +1 -0
- package/dist/PreviewControls.d.ts +15 -1
- package/dist/PreviewControls.d.ts.map +1 -1
- package/dist/PreviewControls.js +75 -18
- package/dist/PreviewControls.js.map +1 -1
- package/dist/PreviewPanel.d.ts +11 -10
- package/dist/PreviewPanel.d.ts.map +1 -1
- package/dist/PreviewPanel.js +20 -17
- package/dist/PreviewPanel.js.map +1 -1
- package/dist/RawEditor.d.ts.map +1 -1
- package/dist/RawEditor.js +198 -4
- package/dist/RawEditor.js.map +1 -1
- package/dist/RecorderEntry.d.ts +24 -0
- package/dist/RecorderEntry.d.ts.map +1 -0
- package/dist/RecorderEntry.js +139 -0
- package/dist/RecorderEntry.js.map +1 -0
- package/dist/TemplateAnnotation.d.ts.map +1 -1
- package/dist/TemplateAnnotation.js +32 -6
- package/dist/TemplateAnnotation.js.map +1 -1
- package/dist/TemplatePicker.d.ts +53 -0
- package/dist/TemplatePicker.d.ts.map +1 -0
- package/dist/TemplatePicker.js +388 -0
- package/dist/TemplatePicker.js.map +1 -0
- package/dist/ThemeCustomizerPanel.d.ts +32 -0
- package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
- package/dist/ThemeCustomizerPanel.js +256 -0
- package/dist/ThemeCustomizerPanel.js.map +1 -0
- package/dist/ThemePicker.d.ts +33 -0
- package/dist/ThemePicker.d.ts.map +1 -0
- package/dist/ThemePicker.js +148 -0
- package/dist/ThemePicker.js.map +1 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar.js +508 -33
- package/dist/Toolbar.js.map +1 -1
- package/dist/VersionHistoryPanel.d.ts +14 -0
- package/dist/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/VersionHistoryPanel.js +147 -0
- package/dist/VersionHistoryPanel.js.map +1 -0
- package/dist/ViewMenuPanel.d.ts +13 -0
- package/dist/ViewMenuPanel.d.ts.map +1 -0
- package/dist/ViewMenuPanel.js +58 -0
- package/dist/ViewMenuPanel.js.map +1 -0
- package/dist/WysiwygEditor.d.ts.map +1 -1
- package/dist/WysiwygEditor.js +198 -9
- package/dist/WysiwygEditor.js.map +1 -1
- package/dist/__tests__/detectMarkdown.test.js +0 -14
- package/dist/__tests__/detectMarkdown.test.js.map +1 -1
- package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
- package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
- package/dist/__tests__/documentSettingsDialog.test.js +132 -0
- package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
- package/dist/__tests__/emojiPicker.test.d.ts +2 -0
- package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
- package/dist/__tests__/emojiPicker.test.js +111 -0
- package/dist/__tests__/emojiPicker.test.js.map +1 -0
- package/dist/__tests__/fileKind.test.js +13 -0
- package/dist/__tests__/fileKind.test.js.map +1 -1
- package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
- package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditAffordance.test.js +188 -0
- package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
- package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
- package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditorShell.test.js +52 -0
- package/dist/__tests__/imageEditorShell.test.js.map +1 -0
- package/dist/__tests__/imageEditorState.test.d.ts +3 -0
- package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditorState.test.js +148 -0
- package/dist/__tests__/imageEditorState.test.js.map +1 -0
- package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
- package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
- package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
- package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
- package/dist/__tests__/jsonEditor.test.d.ts +2 -0
- package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
- package/dist/__tests__/jsonEditor.test.js +134 -0
- package/dist/__tests__/jsonEditor.test.js.map +1 -0
- package/dist/__tests__/layersPanel.test.d.ts +2 -0
- package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
- package/dist/__tests__/layersPanel.test.js +84 -0
- package/dist/__tests__/layersPanel.test.js.map +1 -0
- package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
- package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
- package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
- package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
- package/dist/__tests__/mediaAttachmentFlow.test.d.ts +2 -0
- package/dist/__tests__/mediaAttachmentFlow.test.d.ts.map +1 -0
- package/dist/__tests__/mediaAttachmentFlow.test.js +99 -0
- package/dist/__tests__/mediaAttachmentFlow.test.js.map +1 -0
- package/dist/__tests__/outlinePanel.test.d.ts +2 -0
- package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
- package/dist/__tests__/outlinePanel.test.js +68 -0
- package/dist/__tests__/outlinePanel.test.js.map +1 -0
- package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
- package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
- package/dist/__tests__/plainHtmlPreview.test.js +87 -0
- package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
- package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
- package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
- package/dist/__tests__/propertiesPanel.test.js +64 -0
- package/dist/__tests__/propertiesPanel.test.js.map +1 -0
- package/dist/__tests__/recorderFormats.test.d.ts +2 -0
- package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
- package/dist/__tests__/recorderFormats.test.js +121 -0
- package/dist/__tests__/recorderFormats.test.js.map +1 -0
- package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
- package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
- package/dist/__tests__/recorderTimingJson.test.js +37 -0
- package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
- package/dist/__tests__/tiptapBridge.test.js +26 -0
- package/dist/__tests__/tiptapBridge.test.js.map +1 -1
- package/dist/__tests__/tiptapImageRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/tiptapImageRoundTrip.test.d.ts.map +1 -0
- package/dist/__tests__/tiptapImageRoundTrip.test.js +68 -0
- package/dist/__tests__/tiptapImageRoundTrip.test.js.map +1 -0
- package/dist/__tests__/useImageEditor.test.d.ts +2 -0
- package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
- package/dist/__tests__/useImageEditor.test.js +131 -0
- package/dist/__tests__/useImageEditor.test.js.map +1 -0
- package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
- package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
- package/dist/__tests__/useMediaRecorder.test.js +153 -0
- package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
- package/dist/__tests__/versionHistory.test.d.ts +2 -0
- package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
- package/dist/__tests__/versionHistory.test.js +124 -0
- package/dist/__tests__/versionHistory.test.js.map +1 -0
- package/dist/blockSlice.d.ts +24 -0
- package/dist/blockSlice.d.ts.map +1 -0
- package/dist/blockSlice.js +63 -0
- package/dist/blockSlice.js.map +1 -0
- package/dist/buildPreviewDoc.d.ts.map +1 -1
- package/dist/buildPreviewDoc.js +52 -2
- package/dist/buildPreviewDoc.js.map +1 -1
- package/dist/emojiData.d.ts +81 -0
- package/dist/emojiData.d.ts.map +1 -0
- package/dist/emojiData.js +1283 -0
- package/dist/emojiData.js.map +1 -0
- package/dist/fileKind.d.ts +6 -2
- package/dist/fileKind.d.ts.map +1 -1
- package/dist/fileKind.js +25 -4
- package/dist/fileKind.js.map +1 -1
- package/dist/hooks/useFileDrop.d.ts.map +1 -1
- package/dist/hooks/useFileDrop.js +40 -4
- package/dist/hooks/useFileDrop.js.map +1 -1
- package/dist/imageEditor/CanvasSurface.d.ts +31 -0
- package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
- package/dist/imageEditor/CanvasSurface.js +264 -0
- package/dist/imageEditor/CanvasSurface.js.map +1 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
- package/dist/imageEditor/LayersPanel.d.ts +14 -0
- package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
- package/dist/imageEditor/LayersPanel.js +43 -0
- package/dist/imageEditor/LayersPanel.js.map +1 -0
- package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
- package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
- package/dist/imageEditor/PropertiesPanel.js +97 -0
- package/dist/imageEditor/PropertiesPanel.js.map +1 -0
- package/dist/imageEditor/Toolbar.d.ts +30 -0
- package/dist/imageEditor/Toolbar.d.ts.map +1 -0
- package/dist/imageEditor/Toolbar.js +108 -0
- package/dist/imageEditor/Toolbar.js.map +1 -0
- package/dist/imageEditor/icons.d.ts +24 -0
- package/dist/imageEditor/icons.d.ts.map +1 -0
- package/dist/imageEditor/icons.js +45 -0
- package/dist/imageEditor/icons.js.map +1 -0
- package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
- package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
- package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
- package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
- package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
- package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
- package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
- package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
- package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
- package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
- package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
- package/dist/imageEditor/layers/SelectionHandles.js +19 -0
- package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
- package/dist/imageEditor/state.d.ts +76 -0
- package/dist/imageEditor/state.d.ts.map +1 -0
- package/dist/imageEditor/state.js +87 -0
- package/dist/imageEditor/state.js.map +1 -0
- package/dist/imageEditor/useImageEditor.d.ts +53 -0
- package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
- package/dist/imageEditor/useImageEditor.js +244 -0
- package/dist/imageEditor/useImageEditor.js.map +1 -0
- package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
- package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
- package/dist/imageEditor/useImageEditorTokens.js +45 -0
- package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
- package/dist/index.d.ts +48 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
- package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
- package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
- package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
- package/dist/jsonEditor/JsonEditor.d.ts +36 -0
- package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
- package/dist/jsonEditor/JsonEditor.js +15 -0
- package/dist/jsonEditor/JsonEditor.js.map +1 -0
- package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
- package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
- package/dist/jsonEditor/JsonEditorContext.js +41 -0
- package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
- package/dist/jsonEditor/RenderNode.d.ts +16 -0
- package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
- package/dist/jsonEditor/RenderNode.js +32 -0
- package/dist/jsonEditor/RenderNode.js.map +1 -0
- package/dist/jsonEditor/editors.d.ts +36 -0
- package/dist/jsonEditor/editors.d.ts.map +1 -0
- package/dist/jsonEditor/editors.js +347 -0
- package/dist/jsonEditor/editors.js.map +1 -0
- package/dist/jsonEditor/index.d.ts +3 -0
- package/dist/jsonEditor/index.d.ts.map +1 -0
- package/dist/jsonEditor/index.js +2 -0
- package/dist/jsonEditor/index.js.map +1 -0
- package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
- package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
- package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
- package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
- package/dist/recorder/RecorderButton.d.ts +31 -0
- package/dist/recorder/RecorderButton.d.ts.map +1 -0
- package/dist/recorder/RecorderButton.js +24 -0
- package/dist/recorder/RecorderButton.js.map +1 -0
- package/dist/recorder/RecorderModal.d.ts +59 -0
- package/dist/recorder/RecorderModal.d.ts.map +1 -0
- package/dist/recorder/RecorderModal.js +333 -0
- package/dist/recorder/RecorderModal.js.map +1 -0
- package/dist/recorder/RecorderPanel.d.ts +25 -0
- package/dist/recorder/RecorderPanel.d.ts.map +1 -0
- package/dist/recorder/RecorderPanel.js +30 -0
- package/dist/recorder/RecorderPanel.js.map +1 -0
- package/dist/recorder/formats.d.ts +51 -0
- package/dist/recorder/formats.d.ts.map +1 -0
- package/dist/recorder/formats.js +144 -0
- package/dist/recorder/formats.js.map +1 -0
- package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
- package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
- package/dist/recorder/hooks/useMediaRecorder.js +277 -0
- package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
- package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
- package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
- package/dist/recorder/hooks/useStreamPreview.js +44 -0
- package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
- package/dist/recorder/sources/cameraStream.d.ts +22 -0
- package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
- package/dist/recorder/sources/cameraStream.js +24 -0
- package/dist/recorder/sources/cameraStream.js.map +1 -0
- package/dist/recorder/sources/micStream.d.ts +15 -0
- package/dist/recorder/sources/micStream.d.ts.map +1 -0
- package/dist/recorder/sources/micStream.js +24 -0
- package/dist/recorder/sources/micStream.js.map +1 -0
- package/dist/recorder/sources/screenStream.d.ts +53 -0
- package/dist/recorder/sources/screenStream.d.ts.map +1 -0
- package/dist/recorder/sources/screenStream.js +114 -0
- package/dist/recorder/sources/screenStream.js.map +1 -0
- package/dist/recorder/timingJson.d.ts +51 -0
- package/dist/recorder/timingJson.d.ts.map +1 -0
- package/dist/recorder/timingJson.js +42 -0
- package/dist/recorder/timingJson.js.map +1 -0
- package/dist/tiptap/TiptapAudio.d.ts +26 -0
- package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
- package/dist/tiptap/TiptapAudio.js +58 -0
- package/dist/tiptap/TiptapAudio.js.map +1 -0
- package/dist/tiptap/TiptapVideo.d.ts +30 -0
- package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
- package/dist/tiptap/TiptapVideo.js +66 -0
- package/dist/tiptap/TiptapVideo.js.map +1 -0
- package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
- package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
- package/dist/tiptap/useResolvedMediaSrc.js +42 -0
- package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
- package/dist/tiptapBridge.d.ts.map +1 -1
- package/dist/tiptapBridge.js +210 -16
- package/dist/tiptapBridge.js.map +1 -1
- package/dist/useHeadingLayout.d.ts +54 -0
- package/dist/useHeadingLayout.d.ts.map +1 -0
- package/dist/useHeadingLayout.js +260 -0
- package/dist/useHeadingLayout.js.map +1 -0
- package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
- package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
- package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
- package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
- package/dist/utils/dropUtils.d.ts +21 -2
- package/dist/utils/dropUtils.d.ts.map +1 -1
- package/dist/utils/dropUtils.js +43 -4
- package/dist/utils/dropUtils.js.map +1 -1
- package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
- package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
- package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
- package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
- package/package.json +8 -5
- package/src/DocumentSettingsDialog.tsx +266 -0
- package/src/EditorContext.tsx +534 -10
- package/src/EditorShell.tsx +691 -63
- package/src/EmojiPicker.tsx +332 -0
- package/src/ImageEditor.tsx +327 -0
- package/src/ImageNodeView.tsx +222 -21
- package/src/ImageViewer.tsx +221 -0
- package/src/InlineIcon.ts +84 -0
- package/src/InlinePreviewGutter.tsx +582 -0
- package/src/LinkDialog.tsx +276 -0
- package/src/MediaBin.tsx +22 -3
- package/src/MentionExtension.tsx +10 -7
- package/src/OutlinePanel.tsx +295 -0
- package/src/PlainHtmlPreview.tsx +211 -0
- package/src/PreviewControls.tsx +130 -24
- package/src/PreviewPanel.tsx +38 -21
- package/src/RawEditor.tsx +215 -4
- package/src/RecorderEntry.tsx +164 -0
- package/src/TemplateAnnotation.ts +32 -6
- package/src/TemplatePicker.tsx +818 -0
- package/src/ThemeCustomizerPanel.tsx +595 -0
- package/src/ThemePicker.tsx +319 -0
- package/src/Toolbar.tsx +708 -111
- package/src/VersionHistoryPanel.tsx +329 -0
- package/src/ViewMenuPanel.tsx +188 -0
- package/src/WysiwygEditor.tsx +229 -9
- package/src/__tests__/detectMarkdown.test.ts +0 -15
- package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
- package/src/__tests__/emojiPicker.test.tsx +133 -0
- package/src/__tests__/fileKind.test.ts +16 -0
- package/src/__tests__/imageEditAffordance.test.tsx +268 -0
- package/src/__tests__/imageEditorShell.test.tsx +57 -0
- package/src/__tests__/imageEditorState.test.ts +171 -0
- package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
- package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
- package/src/__tests__/jsonEditor.test.tsx +168 -0
- package/src/__tests__/layersPanel.test.tsx +97 -0
- package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
- package/src/__tests__/mediaAttachmentFlow.test.ts +110 -0
- package/src/__tests__/outlinePanel.test.tsx +79 -0
- package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
- package/src/__tests__/propertiesPanel.test.tsx +69 -0
- package/src/__tests__/recorderFormats.test.ts +146 -0
- package/src/__tests__/recorderTimingJson.test.ts +41 -0
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
- package/src/__tests__/tiptapBridge.test.ts +29 -0
- package/src/__tests__/tiptapImageRoundTrip.test.ts +73 -0
- package/src/__tests__/useImageEditor.test.tsx +159 -0
- package/src/__tests__/useMediaRecorder.test.ts +186 -0
- package/src/__tests__/versionHistory.test.tsx +197 -0
- package/src/blockSlice.ts +75 -0
- package/src/buildPreviewDoc.ts +61 -6
- package/src/emojiData.ts +1337 -0
- package/src/fileKind.ts +30 -6
- package/src/hooks/useFileDrop.ts +40 -4
- package/src/imageEditor/CanvasSurface.tsx +402 -0
- package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
- package/src/imageEditor/LayersPanel.tsx +143 -0
- package/src/imageEditor/PropertiesPanel.tsx +428 -0
- package/src/imageEditor/Toolbar.tsx +242 -0
- package/src/imageEditor/icons.tsx +144 -0
- package/src/imageEditor/image-editor.css +450 -0
- package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
- package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
- package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
- package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
- package/src/imageEditor/state.ts +153 -0
- package/src/imageEditor/useImageEditor.ts +328 -0
- package/src/imageEditor/useImageEditorTokens.ts +70 -0
- package/src/index.ts +82 -0
- package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
- package/src/jsonEditor/JsonEditor.tsx +81 -0
- package/src/jsonEditor/JsonEditorContext.tsx +75 -0
- package/src/jsonEditor/RenderNode.tsx +66 -0
- package/src/jsonEditor/editors.tsx +678 -0
- package/src/jsonEditor/index.ts +2 -0
- package/src/jsonEditor/json-editor.css +463 -0
- package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
- package/src/recorder/RecorderButton.tsx +72 -0
- package/src/recorder/RecorderModal.tsx +596 -0
- package/src/recorder/RecorderPanel.tsx +93 -0
- package/src/recorder/formats.ts +159 -0
- package/src/recorder/hooks/useMediaRecorder.ts +378 -0
- package/src/recorder/hooks/useStreamPreview.ts +47 -0
- package/src/recorder/sources/cameraStream.ts +32 -0
- package/src/recorder/sources/micStream.ts +25 -0
- package/src/recorder/sources/screenStream.ts +162 -0
- package/src/recorder/timingJson.ts +66 -0
- package/src/styles/editor.css +2490 -51
- package/src/styles/image-edit-affordance.css +201 -0
- package/src/styles/index.css +10 -0
- package/src/tiptap/TiptapAudio.tsx +86 -0
- package/src/tiptap/TiptapVideo.tsx +119 -0
- package/src/tiptap/useResolvedMediaSrc.ts +47 -0
- package/src/tiptapBridge.ts +227 -22
- package/src/useHeadingLayout.ts +294 -0
- package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
- package/src/utils/dropUtils.ts +54 -6
- package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TemplatePicker
|
|
3
|
+
*
|
|
4
|
+
* A custom popover that replaces the plain <select> for block templates.
|
|
5
|
+
* Each template entry shows a mini wireframe SVG, a human-readable label,
|
|
6
|
+
* and a one-sentence description so authors can quickly find the right layout.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect, useRef, useState } from 'react';
|
|
10
|
+
import { createPortal } from 'react-dom';
|
|
11
|
+
|
|
12
|
+
// ── Template metadata ─────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
interface TemplateEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description: string;
|
|
18
|
+
icon: JSX.Element;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const W = 56;
|
|
22
|
+
const H = 40;
|
|
23
|
+
|
|
24
|
+
/** Neutral fill for structural shapes */
|
|
25
|
+
const F1 = '#d1d5db';
|
|
26
|
+
/** Slightly darker fill for important / featured elements */
|
|
27
|
+
const F2 = '#9ca3af';
|
|
28
|
+
/** Accent fill (stat number, image, play button) */
|
|
29
|
+
const FA = '#818cf8';
|
|
30
|
+
|
|
31
|
+
function TemplateIcon({ children }: { children: React.ReactNode }) {
|
|
32
|
+
return (
|
|
33
|
+
<svg
|
|
34
|
+
width={W}
|
|
35
|
+
height={H}
|
|
36
|
+
viewBox={`0 0 ${W} ${H}`}
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
className="squisq-template-picker-icon"
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const NONE_ENTRY: TemplateEntry = {
|
|
46
|
+
name: '',
|
|
47
|
+
label: '— none —',
|
|
48
|
+
description: 'Plain heading block with no visual template.',
|
|
49
|
+
icon: (
|
|
50
|
+
<TemplateIcon>
|
|
51
|
+
<rect
|
|
52
|
+
x={4}
|
|
53
|
+
y={4}
|
|
54
|
+
width={48}
|
|
55
|
+
height={32}
|
|
56
|
+
rx={2}
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke={F1}
|
|
59
|
+
strokeWidth={1.5}
|
|
60
|
+
strokeDasharray="3 2"
|
|
61
|
+
/>
|
|
62
|
+
<rect x={12} y={15} width={32} height={4} rx={1} fill={F1} />
|
|
63
|
+
<rect x={16} y={22} width={24} height={3} rx={1} fill={F1} opacity={0.6} />
|
|
64
|
+
</TemplateIcon>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const TEMPLATE_ENTRIES: TemplateEntry[] = [
|
|
69
|
+
{
|
|
70
|
+
name: 'title',
|
|
71
|
+
label: 'Title',
|
|
72
|
+
description: 'A bold opening slide with large title text, perfect for covers and chapters.',
|
|
73
|
+
icon: (
|
|
74
|
+
<TemplateIcon>
|
|
75
|
+
<rect x={4} y={4} width={48} height={32} rx={2} fill={F1} opacity={0.3} />
|
|
76
|
+
<rect x={8} y={11} width={40} height={8} rx={1} fill={FA} />
|
|
77
|
+
<rect x={14} y={23} width={28} height={3} rx={1} fill={F2} />
|
|
78
|
+
<rect x={20} y={29} width={16} height={2} rx={1} fill={F1} />
|
|
79
|
+
</TemplateIcon>
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'sectionHeader',
|
|
84
|
+
label: 'Section Header',
|
|
85
|
+
description: 'A clean section break with a prominent title and optional subtitle.',
|
|
86
|
+
icon: (
|
|
87
|
+
<TemplateIcon>
|
|
88
|
+
<rect x={4} y={4} width={3} height={32} rx={1} fill={FA} />
|
|
89
|
+
<rect x={11} y={8} width={36} height={6} rx={1} fill={F2} />
|
|
90
|
+
<rect x={11} y={18} width={28} height={3} rx={1} fill={F1} />
|
|
91
|
+
<rect x={11} y={24} width={20} height={2.5} rx={1} fill={F1} opacity={0.7} />
|
|
92
|
+
</TemplateIcon>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'statHighlight',
|
|
97
|
+
label: 'Stat Highlight',
|
|
98
|
+
description: 'Showcases a single key number or metric with supporting context.',
|
|
99
|
+
icon: (
|
|
100
|
+
<TemplateIcon>
|
|
101
|
+
<rect x={14} y={4} width={28} height={16} rx={2} fill={FA} />
|
|
102
|
+
<rect x={10} y={24} width={36} height={3.5} rx={1} fill={F2} />
|
|
103
|
+
<rect x={16} y={30} width={24} height={2.5} rx={1} fill={F1} />
|
|
104
|
+
</TemplateIcon>
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'quote',
|
|
109
|
+
label: 'Quote',
|
|
110
|
+
description: 'Displays a stylized pull quote with decorative marks and attribution.',
|
|
111
|
+
icon: (
|
|
112
|
+
<TemplateIcon>
|
|
113
|
+
<text
|
|
114
|
+
x={5}
|
|
115
|
+
y={18}
|
|
116
|
+
fontSize={18}
|
|
117
|
+
fill={FA}
|
|
118
|
+
fontFamily="serif"
|
|
119
|
+
fontWeight="bold"
|
|
120
|
+
opacity={0.7}
|
|
121
|
+
>
|
|
122
|
+
"
|
|
123
|
+
</text>
|
|
124
|
+
<rect x={16} y={8} width={36} height={4} rx={1} fill={F2} />
|
|
125
|
+
<rect x={16} y={15} width={32} height={4} rx={1} fill={F2} />
|
|
126
|
+
<rect x={16} y={22} width={24} height={4} rx={1} fill={F2} />
|
|
127
|
+
<rect x={16} y={31} width={18} height={2.5} rx={1} fill={F1} />
|
|
128
|
+
</TemplateIcon>
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'factCard',
|
|
133
|
+
label: 'Fact Card',
|
|
134
|
+
description: 'Presents a focused fact or insight with a labeled header and body text.',
|
|
135
|
+
icon: (
|
|
136
|
+
<TemplateIcon>
|
|
137
|
+
<rect x={4} y={4} width={20} height={6} rx={3} fill={FA} opacity={0.8} />
|
|
138
|
+
<rect x={4} y={14} width={48} height={5} rx={1} fill={F2} />
|
|
139
|
+
<rect x={4} y={22} width={44} height={3.5} rx={1} fill={F1} />
|
|
140
|
+
<rect x={4} y={28} width={36} height={3} rx={1} fill={F1} opacity={0.7} />
|
|
141
|
+
</TemplateIcon>
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'twoColumn',
|
|
146
|
+
label: 'Two Column',
|
|
147
|
+
description: 'Divides the slide into two equal side-by-side content columns.',
|
|
148
|
+
icon: (
|
|
149
|
+
<TemplateIcon>
|
|
150
|
+
<rect x={3} y={4} width={23} height={32} rx={2} fill={F1} opacity={0.4} />
|
|
151
|
+
<rect x={3} y={4} width={23} height={7} rx={2} fill={F2} opacity={0.6} />
|
|
152
|
+
<rect x={6} y={15} width={17} height={3} rx={1} fill={F1} />
|
|
153
|
+
<rect x={6} y={21} width={14} height={2.5} rx={1} fill={F1} opacity={0.7} />
|
|
154
|
+
<rect x={30} y={4} width={23} height={32} rx={2} fill={F1} opacity={0.4} />
|
|
155
|
+
<rect x={30} y={4} width={23} height={7} rx={2} fill={F2} opacity={0.6} />
|
|
156
|
+
<rect x={33} y={15} width={17} height={3} rx={1} fill={F1} />
|
|
157
|
+
<rect x={33} y={21} width={14} height={2.5} rx={1} fill={F1} opacity={0.7} />
|
|
158
|
+
</TemplateIcon>
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'dateEvent',
|
|
163
|
+
label: 'Date Event',
|
|
164
|
+
description: 'Highlights a date-based event or milestone with a prominent date display.',
|
|
165
|
+
icon: (
|
|
166
|
+
<TemplateIcon>
|
|
167
|
+
<rect x={3} y={4} width={18} height={18} rx={3} fill={FA} opacity={0.85} />
|
|
168
|
+
<rect x={6} y={8} width={12} height={2} rx={1} fill="white" opacity={0.7} />
|
|
169
|
+
<rect x={6} y={13} width={12} height={6} rx={1} fill="white" opacity={0.5} />
|
|
170
|
+
<rect x={25} y={6} width={27} height={5} rx={1} fill={F2} />
|
|
171
|
+
<rect x={25} y={15} width={22} height={3} rx={1} fill={F1} />
|
|
172
|
+
<rect x={25} y={21} width={16} height={2.5} rx={1} fill={F1} opacity={0.7} />
|
|
173
|
+
<rect x={3} y={27} width={50} height={2.5} rx={1} fill={F1} opacity={0.5} />
|
|
174
|
+
<rect x={3} y={32} width={40} height={2} rx={1} fill={F1} opacity={0.4} />
|
|
175
|
+
</TemplateIcon>
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'imageWithCaption',
|
|
180
|
+
label: 'Image with Caption',
|
|
181
|
+
description: 'Displays a full-bleed image with an optional caption below.',
|
|
182
|
+
icon: (
|
|
183
|
+
<TemplateIcon>
|
|
184
|
+
<rect x={3} y={3} width={50} height={28} rx={2} fill={F2} opacity={0.5} />
|
|
185
|
+
<line x1={3} y1={14} x2={53} y2={14} stroke="white" strokeWidth={0.5} opacity={0.3} />
|
|
186
|
+
<polygon points="22,10 22,20 32,15" fill="white" opacity={0.4} />
|
|
187
|
+
<rect x={10} y={35} width={36} height={2.5} rx={1} fill={F1} />
|
|
188
|
+
</TemplateIcon>
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'leftFeature',
|
|
193
|
+
label: 'Left Feature',
|
|
194
|
+
description: 'Image on the left, title and body text stacked on the right.',
|
|
195
|
+
icon: (
|
|
196
|
+
<TemplateIcon>
|
|
197
|
+
<rect x={3} y={4} width={24} height={32} rx={2} fill={F2} opacity={0.55} />
|
|
198
|
+
<polygon points="11,16 11,24 19,20" fill="white" opacity={0.5} />
|
|
199
|
+
<rect x={31} y={9} width={20} height={4} rx={1} fill={FA} />
|
|
200
|
+
<rect x={31} y={17} width={22} height={2.5} rx={1} fill={F1} />
|
|
201
|
+
<rect x={31} y={22} width={22} height={2.5} rx={1} fill={F1} opacity={0.75} />
|
|
202
|
+
<rect x={31} y={27} width={16} height={2.5} rx={1} fill={F1} opacity={0.6} />
|
|
203
|
+
</TemplateIcon>
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'rightFeature',
|
|
208
|
+
label: 'Right Feature',
|
|
209
|
+
description: 'Image on the right, title and body text stacked on the left.',
|
|
210
|
+
icon: (
|
|
211
|
+
<TemplateIcon>
|
|
212
|
+
<rect x={29} y={4} width={24} height={32} rx={2} fill={F2} opacity={0.55} />
|
|
213
|
+
<polygon points="37,16 37,24 45,20" fill="white" opacity={0.5} />
|
|
214
|
+
<rect x={3} y={9} width={20} height={4} rx={1} fill={FA} />
|
|
215
|
+
<rect x={3} y={17} width={22} height={2.5} rx={1} fill={F1} />
|
|
216
|
+
<rect x={3} y={22} width={22} height={2.5} rx={1} fill={F1} opacity={0.75} />
|
|
217
|
+
<rect x={3} y={27} width={16} height={2.5} rx={1} fill={F1} opacity={0.6} />
|
|
218
|
+
</TemplateIcon>
|
|
219
|
+
),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'map',
|
|
223
|
+
label: 'Map',
|
|
224
|
+
description: 'Embeds an interactive map centered on a geographic location.',
|
|
225
|
+
icon: (
|
|
226
|
+
<TemplateIcon>
|
|
227
|
+
<rect x={3} y={3} width={50} height={34} rx={2} fill={F1} opacity={0.3} />
|
|
228
|
+
<line x1={3} y1={13} x2={53} y2={13} stroke={F1} strokeWidth={1} />
|
|
229
|
+
<line x1={3} y1={23} x2={53} y2={23} stroke={F1} strokeWidth={1} />
|
|
230
|
+
<line x1={18} y1={3} x2={18} y2={37} stroke={F1} strokeWidth={1} />
|
|
231
|
+
<line x1={38} y1={3} x2={38} y2={37} stroke={F1} strokeWidth={1} />
|
|
232
|
+
<circle cx={28} cy={18} r={4} fill={FA} opacity={0.8} />
|
|
233
|
+
<line x1={28} y1={22} x2={28} y2={26} stroke={FA} strokeWidth={1.5} opacity={0.6} />
|
|
234
|
+
</TemplateIcon>
|
|
235
|
+
),
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'fullBleedQuote',
|
|
239
|
+
label: 'Full Bleed Quote',
|
|
240
|
+
description: 'A full-width quote that spans the entire slide for maximum impact.',
|
|
241
|
+
icon: (
|
|
242
|
+
<TemplateIcon>
|
|
243
|
+
<rect x={4} y={4} width={48} height={32} rx={2} fill={F2} opacity={0.2} />
|
|
244
|
+
<text
|
|
245
|
+
x={5}
|
|
246
|
+
y={15}
|
|
247
|
+
fontSize={16}
|
|
248
|
+
fill={FA}
|
|
249
|
+
fontFamily="serif"
|
|
250
|
+
fontWeight="bold"
|
|
251
|
+
opacity={0.8}
|
|
252
|
+
>
|
|
253
|
+
"
|
|
254
|
+
</text>
|
|
255
|
+
<rect x={4} y={14} width={48} height={5} rx={1} fill={F2} />
|
|
256
|
+
<rect x={4} y={22} width={44} height={5} rx={1} fill={F2} />
|
|
257
|
+
<rect x={4} y={30} width={30} height={4} rx={1} fill={F2} opacity={0.7} />
|
|
258
|
+
</TemplateIcon>
|
|
259
|
+
),
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'list',
|
|
263
|
+
label: 'List',
|
|
264
|
+
description: 'Renders a bulleted or numbered list in a clean, card-style layout.',
|
|
265
|
+
icon: (
|
|
266
|
+
<TemplateIcon>
|
|
267
|
+
<rect x={4} y={5} width={6} height={5} rx={3} fill={FA} opacity={0.8} />
|
|
268
|
+
<rect x={14} y={6} width={38} height={4} rx={1} fill={F2} />
|
|
269
|
+
<rect x={4} y={17} width={6} height={5} rx={3} fill={FA} opacity={0.8} />
|
|
270
|
+
<rect x={14} y={18} width={34} height={4} rx={1} fill={F2} />
|
|
271
|
+
<rect x={4} y={29} width={6} height={5} rx={3} fill={FA} opacity={0.8} />
|
|
272
|
+
<rect x={14} y={30} width={36} height={4} rx={1} fill={F2} />
|
|
273
|
+
</TemplateIcon>
|
|
274
|
+
),
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'photoGrid',
|
|
278
|
+
label: 'Photo Grid',
|
|
279
|
+
description: 'Arranges multiple photos in a 2×2 or 3×3 mosaic grid.',
|
|
280
|
+
icon: (
|
|
281
|
+
<TemplateIcon>
|
|
282
|
+
<rect x={3} y={3} width={23} height={16} rx={1.5} fill={F2} opacity={0.55} />
|
|
283
|
+
<rect x={30} y={3} width={23} height={16} rx={1.5} fill={F2} opacity={0.7} />
|
|
284
|
+
<rect x={3} y={22} width={23} height={16} rx={1.5} fill={F2} opacity={0.8} />
|
|
285
|
+
<rect x={30} y={22} width={23} height={16} rx={1.5} fill={F2} opacity={0.5} />
|
|
286
|
+
</TemplateIcon>
|
|
287
|
+
),
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: 'definitionCard',
|
|
291
|
+
label: 'Definition Card',
|
|
292
|
+
description: 'Shows a term and its definition in a structured, dictionary-style card.',
|
|
293
|
+
icon: (
|
|
294
|
+
<TemplateIcon>
|
|
295
|
+
<rect x={4} y={6} width={32} height={6} rx={1} fill={FA} opacity={0.8} />
|
|
296
|
+
<rect x={4} y={17} width={48} height={3.5} rx={1} fill={F1} />
|
|
297
|
+
<rect x={4} y={23} width={44} height={3} rx={1} fill={F1} opacity={0.8} />
|
|
298
|
+
<rect x={4} y={29} width={36} height={3} rx={1} fill={F1} opacity={0.6} />
|
|
299
|
+
</TemplateIcon>
|
|
300
|
+
),
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'comparisonBar',
|
|
304
|
+
label: 'Comparison Bar',
|
|
305
|
+
description: 'Visualizes two or more values side-by-side with labeled horizontal bars.',
|
|
306
|
+
icon: (
|
|
307
|
+
<TemplateIcon>
|
|
308
|
+
<rect x={4} y={5} width={14} height={3} rx={1} fill={F1} />
|
|
309
|
+
<rect x={20} y={4} width={32} height={5} rx={1} fill={FA} opacity={0.85} />
|
|
310
|
+
<rect x={4} y={16} width={14} height={3} rx={1} fill={F1} />
|
|
311
|
+
<rect x={20} y={15} width={22} height={5} rx={1} fill={F2} opacity={0.7} />
|
|
312
|
+
<rect x={4} y={27} width={14} height={3} rx={1} fill={F1} />
|
|
313
|
+
<rect x={20} y={26} width={28} height={5} rx={1} fill={F2} opacity={0.5} />
|
|
314
|
+
</TemplateIcon>
|
|
315
|
+
),
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'pullQuote',
|
|
319
|
+
label: 'Pull Quote',
|
|
320
|
+
description: 'A stylized pull quote with large decorative marks and centered text.',
|
|
321
|
+
icon: (
|
|
322
|
+
<TemplateIcon>
|
|
323
|
+
<text x={3} y={20} fontSize={26} fill={FA} fontFamily="serif" opacity={0.5}>
|
|
324
|
+
"
|
|
325
|
+
</text>
|
|
326
|
+
<rect x={16} y={7} width={36} height={4.5} rx={1} fill={F2} />
|
|
327
|
+
<rect x={16} y={15} width={34} height={4.5} rx={1} fill={F2} />
|
|
328
|
+
<rect x={16} y={23} width={28} height={4.5} rx={1} fill={F2} />
|
|
329
|
+
<rect x={32} y={31} width={20} height={3} rx={1} fill={F1} />
|
|
330
|
+
<text x={44} y={42} fontSize={26} fill={FA} fontFamily="serif" opacity={0.3}>
|
|
331
|
+
"
|
|
332
|
+
</text>
|
|
333
|
+
</TemplateIcon>
|
|
334
|
+
),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'videoWithCaption',
|
|
338
|
+
label: 'Video with Caption',
|
|
339
|
+
description: 'Embeds a video player with an optional caption below.',
|
|
340
|
+
icon: (
|
|
341
|
+
<TemplateIcon>
|
|
342
|
+
<rect x={3} y={3} width={50} height={28} rx={2} fill={F1} opacity={0.4} />
|
|
343
|
+
<rect x={3} y={3} width={50} height={28} rx={2} fill={F2} opacity={0.25} />
|
|
344
|
+
<circle cx={28} cy={17} r={8} fill={FA} opacity={0.7} />
|
|
345
|
+
<polygon points="25,13 25,21 33,17" fill="white" />
|
|
346
|
+
<rect x={10} y={35} width={36} height={2.5} rx={1} fill={F1} />
|
|
347
|
+
</TemplateIcon>
|
|
348
|
+
),
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'videoPullQuote',
|
|
352
|
+
label: 'Video Pull Quote',
|
|
353
|
+
description: 'Combines a video panel with a highlighted pull quote side-by-side.',
|
|
354
|
+
icon: (
|
|
355
|
+
<TemplateIcon>
|
|
356
|
+
<rect x={3} y={4} width={23} height={32} rx={2} fill={F2} opacity={0.35} />
|
|
357
|
+
<circle cx={14.5} cy={20} r={6} fill={FA} opacity={0.65} />
|
|
358
|
+
<polygon points="12,17 12,23 18,20" fill="white" />
|
|
359
|
+
<rect x={30} y={4} width={23} height={32} rx={2} fill={F1} opacity={0.3} />
|
|
360
|
+
<text x={32} y={16} fontSize={12} fill={FA} fontFamily="serif" opacity={0.6}>
|
|
361
|
+
"
|
|
362
|
+
</text>
|
|
363
|
+
<rect x={30} y={17} width={20} height={3.5} rx={1} fill={F2} />
|
|
364
|
+
<rect x={30} y={23} width={18} height={3} rx={1} fill={F1} />
|
|
365
|
+
<rect x={30} y={29} width={14} height={2.5} rx={1} fill={F1} opacity={0.7} />
|
|
366
|
+
</TemplateIcon>
|
|
367
|
+
),
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'dataTable',
|
|
371
|
+
label: 'Data Table',
|
|
372
|
+
description: 'Renders tabular data in a clean, styled table with a header row.',
|
|
373
|
+
icon: (
|
|
374
|
+
<TemplateIcon>
|
|
375
|
+
<rect x={3} y={3} width={50} height={8} rx={1.5} fill={FA} opacity={0.7} />
|
|
376
|
+
<rect x={3} y={13} width={50} height={6} rx={1} fill={F1} opacity={0.5} />
|
|
377
|
+
<rect x={3} y={21} width={50} height={6} rx={1} fill={F1} opacity={0.35} />
|
|
378
|
+
<rect x={3} y={29} width={50} height={6} rx={1} fill={F1} opacity={0.5} />
|
|
379
|
+
<line x1={20} y1={3} x2={20} y2={35} stroke="white" strokeWidth={1} opacity={0.4} />
|
|
380
|
+
<line x1={37} y1={3} x2={37} y2={35} stroke="white" strokeWidth={1} opacity={0.4} />
|
|
381
|
+
</TemplateIcon>
|
|
382
|
+
),
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Canonical template names known to the picker, in the order they
|
|
388
|
+
* appear in the gallery. Exported so callers can hand the same list to
|
|
389
|
+
* `recommendTemplatesForBlock()` and stay in sync with the visual
|
|
390
|
+
* order.
|
|
391
|
+
*/
|
|
392
|
+
export const TEMPLATE_NAMES: readonly string[] = TEMPLATE_ENTRIES.map((e) => e.name);
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Convert a camelCase template id to a human-readable label. Accepts both
|
|
396
|
+
* the canonical short ids (`title`, `quote`, `map`, `list`) and the
|
|
397
|
+
* legacy long ones (`titleBlock`, `quoteBlock`, `mapBlock`, `listBlock`)
|
|
398
|
+
* so existing documents keep showing a friendly label without first
|
|
399
|
+
* normalizing their annotations.
|
|
400
|
+
*/
|
|
401
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
402
|
+
export function templateLabel(name: string): string {
|
|
403
|
+
if (!name) return '— none —';
|
|
404
|
+
const resolved = TEMPLATE_NAME_ALIASES[name] ?? name;
|
|
405
|
+
const entry = TEMPLATE_ENTRIES.find((e) => e.name === resolved);
|
|
406
|
+
if (entry) return entry.label;
|
|
407
|
+
// Fallback: split camelCase
|
|
408
|
+
return resolved.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase());
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Legacy template id → canonical short id. Kept inline to avoid pulling
|
|
413
|
+
* the core package's runtime registry into the picker's bundle — the
|
|
414
|
+
* picker only needs this for label resolution.
|
|
415
|
+
*/
|
|
416
|
+
const TEMPLATE_NAME_ALIASES: Readonly<Record<string, string>> = {
|
|
417
|
+
titleBlock: 'title',
|
|
418
|
+
quoteBlock: 'quote',
|
|
419
|
+
mapBlock: 'map',
|
|
420
|
+
listBlock: 'list',
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// ── Component ─────────────────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
export interface TemplatePickerProps {
|
|
426
|
+
value: string;
|
|
427
|
+
onChange: (name: string) => void;
|
|
428
|
+
/** When true, shows only the trigger button (no popover) — used in the overflow menu. */
|
|
429
|
+
compact?: boolean;
|
|
430
|
+
/**
|
|
431
|
+
* Template names to surface in a "Recommended for this block" section
|
|
432
|
+
* above the full list. When omitted or empty, the gallery renders as a
|
|
433
|
+
* single ungrouped grid (legacy behavior).
|
|
434
|
+
*/
|
|
435
|
+
recommended?: readonly string[];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function TemplatePicker({ value, onChange, compact, recommended }: TemplatePickerProps) {
|
|
439
|
+
const [open, setOpen] = useState(false);
|
|
440
|
+
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
441
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
442
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
443
|
+
|
|
444
|
+
// Position the portal popover below the trigger button. Clamp the
|
|
445
|
+
// left edge so the gallery (up to 780px wide) doesn't overflow the
|
|
446
|
+
// viewport when the toolbar trigger sits near the right edge of the
|
|
447
|
+
// window — previously the popover was left-aligned to the trigger,
|
|
448
|
+
// which pushed half the cards off-screen at narrow widths.
|
|
449
|
+
const updatePosition = () => {
|
|
450
|
+
if (!triggerRef.current) return;
|
|
451
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
452
|
+
const popoverEl = document.getElementById('squisq-template-gallery-portal');
|
|
453
|
+
// Use the actual rendered width if the popover is already mounted;
|
|
454
|
+
// otherwise fall back to the CSS-defined max so the first paint
|
|
455
|
+
// doesn't overflow either. Measure the portal element directly —
|
|
456
|
+
// its `firstElementChild` is the (small) "(none)" option, not the
|
|
457
|
+
// gallery itself.
|
|
458
|
+
const popoverWidth = popoverEl?.getBoundingClientRect().width ?? 780;
|
|
459
|
+
const margin = 8;
|
|
460
|
+
const maxLeft = Math.max(margin, window.innerWidth - popoverWidth - margin);
|
|
461
|
+
const left = Math.min(Math.max(margin, rect.left), maxLeft);
|
|
462
|
+
setPopoverStyle({
|
|
463
|
+
position: 'fixed',
|
|
464
|
+
top: rect.bottom + 6,
|
|
465
|
+
left,
|
|
466
|
+
zIndex: 9999,
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const handleOpen = () => {
|
|
471
|
+
updatePosition();
|
|
472
|
+
setOpen((v) => !v);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Close on outside click
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
if (!open) return;
|
|
478
|
+
const handler = (e: MouseEvent) => {
|
|
479
|
+
const target = e.target as Node;
|
|
480
|
+
const inTrigger = triggerRef.current?.contains(target);
|
|
481
|
+
const inPopover = document.getElementById('squisq-template-gallery-portal')?.contains(target);
|
|
482
|
+
if (!inTrigger && !inPopover) {
|
|
483
|
+
setOpen(false);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
document.addEventListener('mousedown', handler);
|
|
487
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
488
|
+
}, [open]);
|
|
489
|
+
|
|
490
|
+
// Close on Escape
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (!open) return;
|
|
493
|
+
const handler = (e: KeyboardEvent) => {
|
|
494
|
+
if (e.key === 'Escape') setOpen(false);
|
|
495
|
+
};
|
|
496
|
+
document.addEventListener('keydown', handler);
|
|
497
|
+
return () => document.removeEventListener('keydown', handler);
|
|
498
|
+
}, [open]);
|
|
499
|
+
|
|
500
|
+
// Reposition on scroll/resize while open
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
if (!open) return;
|
|
503
|
+
// Reposition once on the next frame so the clamp uses the actual
|
|
504
|
+
// rendered popover width (the initial open() runs before mount).
|
|
505
|
+
requestAnimationFrame(updatePosition);
|
|
506
|
+
const handler = () => updatePosition();
|
|
507
|
+
window.addEventListener('scroll', handler, true);
|
|
508
|
+
window.addEventListener('resize', handler);
|
|
509
|
+
return () => {
|
|
510
|
+
window.removeEventListener('scroll', handler, true);
|
|
511
|
+
window.removeEventListener('resize', handler);
|
|
512
|
+
};
|
|
513
|
+
}, [open]);
|
|
514
|
+
|
|
515
|
+
const handleSelect = (name: string) => {
|
|
516
|
+
onChange(name);
|
|
517
|
+
setOpen(false);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const currentLabel = templateLabel(value);
|
|
521
|
+
const currentEntry: TemplateEntry =
|
|
522
|
+
(value && TEMPLATE_ENTRIES.find((e) => e.name === value)) || NONE_ENTRY;
|
|
523
|
+
|
|
524
|
+
if (compact) {
|
|
525
|
+
// In overflow menu, use a simple select for space efficiency
|
|
526
|
+
const all: TemplateEntry[] = [NONE_ENTRY, ...TEMPLATE_ENTRIES];
|
|
527
|
+
return (
|
|
528
|
+
<select
|
|
529
|
+
className="squisq-template-picker-select"
|
|
530
|
+
value={value}
|
|
531
|
+
onChange={(e) => onChange(e.target.value)}
|
|
532
|
+
>
|
|
533
|
+
{all.map((e) => (
|
|
534
|
+
<option key={e.name} value={e.name}>
|
|
535
|
+
{e.label}
|
|
536
|
+
</option>
|
|
537
|
+
))}
|
|
538
|
+
</select>
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const gallery = open
|
|
543
|
+
? createPortal(
|
|
544
|
+
<TemplateGalleryBody
|
|
545
|
+
value={value}
|
|
546
|
+
onSelect={handleSelect}
|
|
547
|
+
style={popoverStyle}
|
|
548
|
+
recommended={recommended}
|
|
549
|
+
/>,
|
|
550
|
+
document.body,
|
|
551
|
+
)
|
|
552
|
+
: null;
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<div className="squisq-template-picker-popover-host" ref={containerRef}>
|
|
556
|
+
<button
|
|
557
|
+
ref={triggerRef}
|
|
558
|
+
type="button"
|
|
559
|
+
className={`squisq-template-picker-trigger${open ? ' squisq-template-picker-trigger--open' : ''}`}
|
|
560
|
+
onClick={handleOpen}
|
|
561
|
+
aria-haspopup="listbox"
|
|
562
|
+
aria-expanded={open}
|
|
563
|
+
title="Choose block template"
|
|
564
|
+
>
|
|
565
|
+
<span className="squisq-template-picker-trigger-label">Block:</span>
|
|
566
|
+
<span className="squisq-template-picker-trigger-thumb" aria-hidden="true">
|
|
567
|
+
{currentEntry.icon}
|
|
568
|
+
</span>
|
|
569
|
+
<span className="squisq-template-picker-trigger-value">
|
|
570
|
+
{value ? currentLabel : '(No visual)'}
|
|
571
|
+
</span>
|
|
572
|
+
<svg
|
|
573
|
+
className="squisq-template-picker-trigger-caret"
|
|
574
|
+
width="10"
|
|
575
|
+
height="10"
|
|
576
|
+
viewBox="0 0 10 10"
|
|
577
|
+
fill="currentColor"
|
|
578
|
+
aria-hidden="true"
|
|
579
|
+
>
|
|
580
|
+
<path
|
|
581
|
+
d="M2 3.5l3 3 3-3"
|
|
582
|
+
stroke="currentColor"
|
|
583
|
+
strokeWidth="1.5"
|
|
584
|
+
strokeLinecap="round"
|
|
585
|
+
fill="none"
|
|
586
|
+
/>
|
|
587
|
+
</svg>
|
|
588
|
+
</button>
|
|
589
|
+
{gallery}
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ── Reusable gallery body ──────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* The popover/grid markup shared by the toolbar `TemplatePicker` and the
|
|
598
|
+
* inline `TemplateBadgeMenu`. Renders all template cards plus the
|
|
599
|
+
* "(none)" option; no positioning logic — callers supply `style` (typically
|
|
600
|
+
* a `position: fixed` rect from `getBoundingClientRect()`).
|
|
601
|
+
*/
|
|
602
|
+
function TemplateCard({
|
|
603
|
+
entry,
|
|
604
|
+
value,
|
|
605
|
+
onSelect,
|
|
606
|
+
}: {
|
|
607
|
+
entry: TemplateEntry;
|
|
608
|
+
value: string;
|
|
609
|
+
onSelect: (name: string) => void;
|
|
610
|
+
}) {
|
|
611
|
+
return (
|
|
612
|
+
<button
|
|
613
|
+
type="button"
|
|
614
|
+
role="option"
|
|
615
|
+
aria-selected={value === entry.name}
|
|
616
|
+
className={`squisq-template-gallery-card${value === entry.name ? ' squisq-template-gallery-card--selected' : ''}`}
|
|
617
|
+
onClick={() => onSelect(entry.name)}
|
|
618
|
+
title={entry.description}
|
|
619
|
+
>
|
|
620
|
+
<div className="squisq-template-gallery-card-icon">{entry.icon}</div>
|
|
621
|
+
<div className="squisq-template-gallery-card-body">
|
|
622
|
+
<span className="squisq-template-gallery-card-name">{entry.label}</span>
|
|
623
|
+
<span className="squisq-template-gallery-card-desc">{entry.description}</span>
|
|
624
|
+
</div>
|
|
625
|
+
</button>
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function TemplateGalleryBody({
|
|
630
|
+
value,
|
|
631
|
+
onSelect,
|
|
632
|
+
style,
|
|
633
|
+
recommended,
|
|
634
|
+
}: {
|
|
635
|
+
value: string;
|
|
636
|
+
onSelect: (name: string) => void;
|
|
637
|
+
style: React.CSSProperties;
|
|
638
|
+
recommended?: readonly string[];
|
|
639
|
+
}) {
|
|
640
|
+
const recommendedSet = recommended && recommended.length > 0 ? new Set(recommended) : null;
|
|
641
|
+
const recommendedEntries = recommendedSet
|
|
642
|
+
? TEMPLATE_ENTRIES.filter((e) => recommendedSet.has(e.name))
|
|
643
|
+
: [];
|
|
644
|
+
const restEntries = recommendedSet
|
|
645
|
+
? TEMPLATE_ENTRIES.filter((e) => !recommendedSet.has(e.name))
|
|
646
|
+
: TEMPLATE_ENTRIES;
|
|
647
|
+
const segmented = recommendedEntries.length > 0;
|
|
648
|
+
|
|
649
|
+
return (
|
|
650
|
+
<div
|
|
651
|
+
id="squisq-template-gallery-portal"
|
|
652
|
+
className={`squisq-template-gallery${segmented ? ' squisq-template-gallery--segmented' : ''}`}
|
|
653
|
+
role="listbox"
|
|
654
|
+
aria-label="Block templates"
|
|
655
|
+
style={style}
|
|
656
|
+
>
|
|
657
|
+
<button
|
|
658
|
+
type="button"
|
|
659
|
+
role="option"
|
|
660
|
+
aria-selected={value === ''}
|
|
661
|
+
className={`squisq-template-gallery-none${value === '' ? ' squisq-template-gallery-card--selected' : ''}`}
|
|
662
|
+
onClick={() => onSelect('')}
|
|
663
|
+
>
|
|
664
|
+
{NONE_ENTRY.icon}
|
|
665
|
+
<span className="squisq-template-gallery-none-label">{NONE_ENTRY.label}</span>
|
|
666
|
+
<span className="squisq-template-gallery-none-desc">{NONE_ENTRY.description}</span>
|
|
667
|
+
</button>
|
|
668
|
+
|
|
669
|
+
{segmented && (
|
|
670
|
+
<div className="squisq-template-gallery-section">
|
|
671
|
+
<h3 className="squisq-template-gallery-section-title">Recommended for this block</h3>
|
|
672
|
+
<div className="squisq-template-gallery-grid">
|
|
673
|
+
{recommendedEntries.map((entry) => (
|
|
674
|
+
<TemplateCard key={entry.name} entry={entry} value={value} onSelect={onSelect} />
|
|
675
|
+
))}
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
)}
|
|
679
|
+
|
|
680
|
+
{segmented ? (
|
|
681
|
+
<div className="squisq-template-gallery-section">
|
|
682
|
+
<h3 className="squisq-template-gallery-section-title">All templates</h3>
|
|
683
|
+
<div className="squisq-template-gallery-grid">
|
|
684
|
+
{restEntries.map((entry) => (
|
|
685
|
+
<TemplateCard key={entry.name} entry={entry} value={value} onSelect={onSelect} />
|
|
686
|
+
))}
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
) : (
|
|
690
|
+
<div className="squisq-template-gallery-grid">
|
|
691
|
+
{restEntries.map((entry) => (
|
|
692
|
+
<TemplateCard key={entry.name} entry={entry} value={value} onSelect={onSelect} />
|
|
693
|
+
))}
|
|
694
|
+
</div>
|
|
695
|
+
)}
|
|
696
|
+
</div>
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ── Inline badge popover (anchored at a heading badge) ─────────────
|
|
701
|
+
|
|
702
|
+
export interface TemplateBadgePopoverProps {
|
|
703
|
+
/** DOMRect of the badge that triggered the popover (in viewport coords). */
|
|
704
|
+
anchorRect: DOMRect;
|
|
705
|
+
/** Currently active template name (empty string for none). */
|
|
706
|
+
value: string;
|
|
707
|
+
onChange: (name: string) => void;
|
|
708
|
+
onClose: () => void;
|
|
709
|
+
/** Optional list of template names to surface as "Recommended for this block". */
|
|
710
|
+
recommended?: readonly string[];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Standalone popover that mirrors the toolbar `TemplatePicker`'s gallery,
|
|
715
|
+
* but is anchored to a caller-supplied DOM rect (typically a clicked
|
|
716
|
+
* `.squisq-template-badge` span). Handles its own positioning, outside
|
|
717
|
+
* clicks, Escape, and viewport-edge clamping.
|
|
718
|
+
*/
|
|
719
|
+
export function TemplateBadgePopover({
|
|
720
|
+
anchorRect,
|
|
721
|
+
value,
|
|
722
|
+
onChange,
|
|
723
|
+
onClose,
|
|
724
|
+
recommended,
|
|
725
|
+
}: TemplateBadgePopoverProps) {
|
|
726
|
+
const [style, setStyle] = useState<React.CSSProperties>(() => computePopoverStyle(anchorRect));
|
|
727
|
+
|
|
728
|
+
// Reposition once after mount using the actual rendered popover width.
|
|
729
|
+
useEffect(() => {
|
|
730
|
+
requestAnimationFrame(() => setStyle(computePopoverStyle(anchorRect)));
|
|
731
|
+
}, [anchorRect]);
|
|
732
|
+
|
|
733
|
+
// Outside click + Escape close
|
|
734
|
+
useEffect(() => {
|
|
735
|
+
const onKey = (e: KeyboardEvent) => {
|
|
736
|
+
if (e.key === 'Escape') onClose();
|
|
737
|
+
};
|
|
738
|
+
const onMouse = (e: MouseEvent) => {
|
|
739
|
+
const target = e.target as Node;
|
|
740
|
+
const inPopover = document.getElementById('squisq-template-gallery-portal')?.contains(target);
|
|
741
|
+
if (!inPopover) onClose();
|
|
742
|
+
};
|
|
743
|
+
// Defer the mousedown listener by one frame so the click that opened
|
|
744
|
+
// us doesn't immediately close us.
|
|
745
|
+
const id = requestAnimationFrame(() => {
|
|
746
|
+
document.addEventListener('mousedown', onMouse);
|
|
747
|
+
});
|
|
748
|
+
document.addEventListener('keydown', onKey);
|
|
749
|
+
return () => {
|
|
750
|
+
cancelAnimationFrame(id);
|
|
751
|
+
document.removeEventListener('mousedown', onMouse);
|
|
752
|
+
document.removeEventListener('keydown', onKey);
|
|
753
|
+
};
|
|
754
|
+
}, [onClose]);
|
|
755
|
+
|
|
756
|
+
const handleSelect = (name: string) => {
|
|
757
|
+
onChange(name);
|
|
758
|
+
onClose();
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
return createPortal(
|
|
762
|
+
<TemplateGalleryBody
|
|
763
|
+
value={value}
|
|
764
|
+
onSelect={handleSelect}
|
|
765
|
+
style={style}
|
|
766
|
+
recommended={recommended}
|
|
767
|
+
/>,
|
|
768
|
+
document.body,
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function computePopoverStyle(rect: DOMRect): React.CSSProperties {
|
|
773
|
+
// The portal *is* the gallery (`#squisq-template-gallery-portal` is the
|
|
774
|
+
// outer `.squisq-template-gallery` div). Measure it directly — using
|
|
775
|
+
// `firstElementChild` returns the "(none)" option, which is ~50px tall
|
|
776
|
+
// and made the "fits below" check incorrectly pass on tall galleries.
|
|
777
|
+
const popoverEl = document.getElementById('squisq-template-gallery-portal');
|
|
778
|
+
const popoverRect = popoverEl?.getBoundingClientRect();
|
|
779
|
+
const popoverWidth = popoverRect?.width ?? 780;
|
|
780
|
+
const popoverHeight = popoverRect?.height ?? 520;
|
|
781
|
+
const margin = 8;
|
|
782
|
+
const gap = 6;
|
|
783
|
+
const vw = window.innerWidth;
|
|
784
|
+
const vh = window.innerHeight;
|
|
785
|
+
|
|
786
|
+
// Vertical placement: prefer below the badge, fall back to above.
|
|
787
|
+
// If neither fits, center the popover in the viewport (dialog-style)
|
|
788
|
+
// so the gallery is always fully visible no matter where the chip
|
|
789
|
+
// sits in the editor (top, middle, bottom).
|
|
790
|
+
const spaceBelow = vh - rect.bottom - margin;
|
|
791
|
+
const spaceAbove = rect.top - margin;
|
|
792
|
+
let top: number;
|
|
793
|
+
let left: number;
|
|
794
|
+
if (popoverHeight + gap <= spaceBelow) {
|
|
795
|
+
top = rect.bottom + gap;
|
|
796
|
+
const maxLeft = Math.max(margin, vw - popoverWidth - margin);
|
|
797
|
+
left = Math.min(Math.max(margin, rect.left), maxLeft);
|
|
798
|
+
} else if (popoverHeight + gap <= spaceAbove) {
|
|
799
|
+
top = rect.top - popoverHeight - gap;
|
|
800
|
+
const maxLeft = Math.max(margin, vw - popoverWidth - margin);
|
|
801
|
+
left = Math.min(Math.max(margin, rect.left), maxLeft);
|
|
802
|
+
} else {
|
|
803
|
+
// Center it.
|
|
804
|
+
top = Math.max(margin, Math.floor((vh - popoverHeight) / 2));
|
|
805
|
+
left = Math.max(margin, Math.floor((vw - popoverWidth) / 2));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return {
|
|
809
|
+
position: 'fixed',
|
|
810
|
+
top,
|
|
811
|
+
left,
|
|
812
|
+
// Cap height so an oversized gallery still fits and scrolls
|
|
813
|
+
// gracefully instead of pushing past the viewport.
|
|
814
|
+
maxHeight: `${vh - 2 * margin}px`,
|
|
815
|
+
overflowY: 'auto',
|
|
816
|
+
zIndex: 9999,
|
|
817
|
+
};
|
|
818
|
+
}
|