@bendyline/squisq-editor-react 2.0.1 → 2.2.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/LICENSE +21 -0
- package/NOTICE.md +40 -0
- package/README.md +10 -2
- package/dist/index.d.ts +683 -60
- package/dist/index.js +9648 -3750
- package/dist/monaco.js +0 -1
- package/dist/styles/index.css +1160 -241
- package/package.json +10 -6
- package/dist/index.js.map +0 -1
- package/dist/monaco.js.map +0 -1
- package/src/BlockCardView.tsx +0 -121
- package/src/BlockPreviewPanel.tsx +0 -69
- package/src/BlockPropertiesPopover.tsx +0 -222
- package/src/DocumentSettingsDialog.tsx +0 -272
- package/src/DropZoneOverlay.tsx +0 -137
- package/src/EditorContext.tsx +0 -1247
- package/src/EditorShell.tsx +0 -1334
- package/src/EmojiPicker.tsx +0 -332
- package/src/FolderView.tsx +0 -131
- package/src/Icon.tsx +0 -26
- package/src/ImageEditor.tsx +0 -395
- package/src/ImageNodeView.tsx +0 -284
- package/src/ImageViewer.tsx +0 -221
- package/src/InlineIcon.ts +0 -84
- package/src/InlinePreviewGutter.tsx +0 -590
- package/src/LinkDialog.tsx +0 -276
- package/src/MediaBin.tsx +0 -500
- package/src/MentionExtension.tsx +0 -261
- package/src/OutlinePanel.tsx +0 -547
- package/src/PlainHtmlPreview.tsx +0 -277
- package/src/PreviewControls.tsx +0 -1250
- package/src/PreviewPanel.tsx +0 -422
- package/src/RawEditor.tsx +0 -731
- package/src/RecorderEntry.tsx +0 -159
- package/src/StatusBar.tsx +0 -51
- package/src/TemplateAnnotation.ts +0 -167
- package/src/TemplateContentPreview.tsx +0 -56
- package/src/TemplatePicker.tsx +0 -1314
- package/src/ThemeCustomizerPanel.tsx +0 -304
- package/src/ThemePicker.tsx +0 -433
- package/src/TimelineBlockPreview.tsx +0 -37
- package/src/TimelineTrack.tsx +0 -671
- package/src/Toolbar.tsx +0 -2587
- package/src/Tooltip.tsx +0 -142
- package/src/TransitionPicker.tsx +0 -390
- package/src/VersionHistoryPanel.tsx +0 -359
- package/src/ViewMenuPanel.tsx +0 -189
- package/src/ViewSwitcher.tsx +0 -56
- package/src/WysiwygEditor.tsx +0 -792
- package/src/__tests__/blockProperties.test.ts +0 -92
- package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
- package/src/__tests__/blockRange.test.ts +0 -105
- package/src/__tests__/blockTagActivity.test.ts +0 -183
- package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
- package/src/__tests__/buildPreviewDocContent.test.ts +0 -48
- package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
- package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
- package/src/__tests__/codeContextSectionView.test.tsx +0 -97
- package/src/__tests__/codeContextZoneManager.test.ts +0 -127
- package/src/__tests__/createShapeLayer.test.ts +0 -46
- package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
- package/src/__tests__/detectMarkdown.test.ts +0 -73
- package/src/__tests__/diffContextSections.test.ts +0 -39
- package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
- package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
- package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
- package/src/__tests__/editorScrollSync.test.ts +0 -65
- package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
- package/src/__tests__/editorShellProps.test.tsx +0 -485
- package/src/__tests__/embeddedMedia.test.ts +0 -48
- package/src/__tests__/emojiPicker.test.tsx +0 -93
- package/src/__tests__/fileKind.test.ts +0 -112
- package/src/__tests__/headingTransition.test.ts +0 -176
- package/src/__tests__/imageEditAffordance.test.tsx +0 -272
- package/src/__tests__/imageEditModalContract.test.tsx +0 -119
- package/src/__tests__/imageEditorShell.test.tsx +0 -94
- package/src/__tests__/imageEditorState.test.ts +0 -171
- package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
- package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
- package/src/__tests__/jsonEditor.test.tsx +0 -223
- package/src/__tests__/layersPanel.test.tsx +0 -167
- package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
- package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
- package/src/__tests__/majorApiTypes.test.ts +0 -26
- package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
- package/src/__tests__/mediaBinDrop.test.tsx +0 -90
- package/src/__tests__/mediaReferences.test.ts +0 -82
- package/src/__tests__/narrationSave.test.ts +0 -148
- package/src/__tests__/outlinePanel.test.tsx +0 -169
- package/src/__tests__/outlineSource.test.ts +0 -217
- package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
- package/src/__tests__/presentationMode.test.tsx +0 -290
- package/src/__tests__/previewControls.test.tsx +0 -540
- package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
- package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
- package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
- package/src/__tests__/propertiesPanel.test.tsx +0 -69
- package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
- package/src/__tests__/recorderFormats.test.ts +0 -146
- package/src/__tests__/recorderMediaInsert.test.ts +0 -86
- package/src/__tests__/recorderTheme.test.tsx +0 -42
- package/src/__tests__/recorderTimingJson.test.ts +0 -41
- package/src/__tests__/resolveBlockVisual.test.ts +0 -43
- package/src/__tests__/selectionConversions.test.ts +0 -80
- package/src/__tests__/statusBar.test.tsx +0 -27
- package/src/__tests__/teleprompter.test.tsx +0 -190
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
- package/src/__tests__/templateContentPreview.test.ts +0 -101
- package/src/__tests__/templatePickerMetadata.test.ts +0 -32
- package/src/__tests__/templatePickerPortal.test.tsx +0 -62
- package/src/__tests__/timelineSource.test.ts +0 -134
- package/src/__tests__/tiptapBridge.test.ts +0 -566
- package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
- package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
- package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -164
- package/src/__tests__/tooltip.test.tsx +0 -72
- package/src/__tests__/transformStyleId.test.ts +0 -13
- package/src/__tests__/transitionCatalog.test.ts +0 -64
- package/src/__tests__/useBlockNavigator.test.tsx +0 -67
- package/src/__tests__/useImageEditor.test.tsx +0 -226
- package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
- package/src/__tests__/useMediaRecorder.test.ts +0 -302
- package/src/__tests__/useTimelineClock.test.ts +0 -21
- package/src/__tests__/versionHistory.test.tsx +0 -197
- package/src/__tests__/viewMenuPanel.test.tsx +0 -130
- package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
- package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
- package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
- package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
- package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
- package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -145
- package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
- package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
- package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
- package/src/asciiDiagram/asciiDiagramData.ts +0 -149
- package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
- package/src/asciiDiagram/asciiPaste.ts +0 -19
- package/src/blockProperties.ts +0 -88
- package/src/blockRange.ts +0 -132
- package/src/blockSlice.ts +0 -75
- package/src/blockTagActivity.ts +0 -233
- package/src/buildDocumentPreviewMarkdown.ts +0 -168
- package/src/buildPreviewDoc.ts +0 -405
- package/src/codeContext/CodeContextSectionView.tsx +0 -124
- package/src/codeContext/CodeContextZoneManager.ts +0 -149
- package/src/codeContext/CodeContextZones.tsx +0 -121
- package/src/codeContext/diffContextSections.ts +0 -38
- package/src/codeContext/types.ts +0 -75
- package/src/customTemplates/AddBin.tsx +0 -126
- package/src/customTemplates/CustomLayoutManager.tsx +0 -234
- package/src/customTemplates/CustomTemplateContext.tsx +0 -188
- package/src/customTemplates/LayerToolbar.tsx +0 -580
- package/src/customTemplates/ShapeGlyph.tsx +0 -47
- package/src/customTemplates/TemplateDesigner.tsx +0 -433
- package/src/customTemplates/__tests__/library.test.ts +0 -96
- package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
- package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
- package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
- package/src/customTemplates/designer.css +0 -732
- package/src/customTemplates/index.ts +0 -31
- package/src/customTemplates/library.ts +0 -110
- package/src/customTemplates/normalizePositions.ts +0 -75
- package/src/customTemplates/shapeDefs.ts +0 -131
- package/src/customTemplates/thumbnail.tsx +0 -63
- package/src/customTemplates/tokenDefs.ts +0 -60
- package/src/customTemplates/useDocCustomTemplates.ts +0 -52
- package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
- package/src/customThemes/CustomThemeContext.tsx +0 -185
- package/src/customThemes/CustomThemeDialog.tsx +0 -316
- package/src/customThemes/ImportThemeSection.tsx +0 -178
- package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
- package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
- package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
- package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
- package/src/customThemes/customThemeLibrary.ts +0 -100
- package/src/customThemes/index.ts +0 -37
- package/src/customThemes/themeControls.tsx +0 -229
- package/src/customThemes/themeDraft.ts +0 -288
- package/src/customThemes/useDocCustomThemes.ts +0 -49
- package/src/detectMarkdown.ts +0 -62
- package/src/diagram/DiagramCanvas.tsx +0 -259
- package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
- package/src/diagram/diagramCommands.ts +0 -604
- package/src/diagram/diagramConstants.ts +0 -17
- package/src/diagram/types.ts +0 -35
- package/src/editorScrollSync.ts +0 -73
- package/src/embeddedMedia.ts +0 -78
- package/src/emojiData.ts +0 -1317
- package/src/fileKind.ts +0 -158
- package/src/frontmatter.ts +0 -29
- package/src/frontmatterSettings.ts +0 -23
- package/src/headingTransition.ts +0 -291
- package/src/hooks/useFileDrop.ts +0 -266
- package/src/imageEditor/CanvasSurface.tsx +0 -703
- package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
- package/src/imageEditor/LayersPanel.tsx +0 -218
- package/src/imageEditor/PropertiesPanel.tsx +0 -474
- package/src/imageEditor/Toolbar.tsx +0 -459
- package/src/imageEditor/createShapeLayer.ts +0 -280
- package/src/imageEditor/icons.tsx +0 -68
- package/src/imageEditor/image-editor.css +0 -574
- package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
- package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
- package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
- package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
- package/src/imageEditor/state.ts +0 -173
- package/src/imageEditor/useImageEditor.ts +0 -385
- package/src/imageEditor/useImageEditorTokens.ts +0 -70
- package/src/index.ts +0 -470
- package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
- package/src/jsonEditor/JsonEditor.tsx +0 -81
- package/src/jsonEditor/JsonEditorContext.tsx +0 -81
- package/src/jsonEditor/RenderNode.tsx +0 -87
- package/src/jsonEditor/editors.tsx +0 -745
- package/src/jsonEditor/index.ts +0 -2
- package/src/jsonEditor/json-editor.css +0 -463
- package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
- package/src/mediaDragMime.ts +0 -32
- package/src/mediaEntries.ts +0 -12
- package/src/mediaReferences.ts +0 -299
- package/src/monaco.ts +0 -35
- package/src/monacoWorkers.ts +0 -89
- package/src/outlineSource.ts +0 -171
- package/src/presentation/PresentationMode.tsx +0 -596
- package/src/rawEditorIsolation.ts +0 -18
- package/src/recorder/RecorderButton.tsx +0 -80
- package/src/recorder/RecorderModal.tsx +0 -706
- package/src/recorder/RecorderPanel.tsx +0 -77
- package/src/recorder/formats.ts +0 -159
- package/src/recorder/hooks/useMediaRecorder.ts +0 -436
- package/src/recorder/hooks/useStreamPreview.ts +0 -47
- package/src/recorder/insertMediaBlock.ts +0 -30
- package/src/recorder/sources/cameraStream.ts +0 -32
- package/src/recorder/sources/micStream.ts +0 -25
- package/src/recorder/sources/screenStream.ts +0 -162
- package/src/recorder/timingJson.ts +0 -66
- package/src/resolveBlockVisual.ts +0 -39
- package/src/scene/Scene.tsx +0 -616
- package/src/scene/SceneBlockExtension.ts +0 -205
- package/src/scene/SceneBlockToolbar.tsx +0 -201
- package/src/scene/SceneBlockWidget.tsx +0 -441
- package/src/scene/ScenePropsBar.tsx +0 -85
- package/src/scene/SceneSelection.tsx +0 -111
- package/src/scene/SceneSideToolbar.tsx +0 -89
- package/src/scene/SceneViewport.tsx +0 -106
- package/src/scene/ShapePalette.tsx +0 -196
- package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
- package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
- package/src/scene/__tests__/bezierEdit.test.ts +0 -85
- package/src/scene/__tests__/blockLayers.test.ts +0 -57
- package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
- package/src/scene/__tests__/shapeLayers.test.ts +0 -106
- package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
- package/src/scene/__tests__/useScenePanZoom.test.ts +0 -103
- package/src/scene/adapters/DiagramAdapter.ts +0 -79
- package/src/scene/adapters/DrawingAdapter.ts +0 -420
- package/src/scene/adapters/LayoutAdapter.ts +0 -313
- package/src/scene/adapters/blockLayers.ts +0 -159
- package/src/scene/commands/SceneCommand.ts +0 -84
- package/src/scene/commands/drawingCommands.ts +0 -318
- package/src/scene/commands/layoutCommands.ts +0 -301
- package/src/scene/hooks/useSceneHitTest.ts +0 -105
- package/src/scene/hooks/useScenePanZoom.ts +0 -147
- package/src/scene/hooks/useSceneSelection.ts +0 -62
- package/src/scene/index.ts +0 -90
- package/src/scene/layers/DiagramEdges.tsx +0 -142
- package/src/scene/layers/edgeGeometry.ts +0 -104
- package/src/scene/layers/nodeCard.tsx +0 -164
- package/src/scene/layers/renderLayer.tsx +0 -70
- package/src/scene/layers/shapeLayers.ts +0 -201
- package/src/scene/paths/bezierEdit.ts +0 -208
- package/src/scene/scene.css +0 -792
- package/src/scene/text/SceneTextOverlay.tsx +0 -161
- package/src/scene/text/sceneTextChannel.ts +0 -46
- package/src/scene/text/sceneTextConfig.ts +0 -31
- package/src/scene/text/sceneTiptap.ts +0 -36
- package/src/scene/text/useSceneTextEditing.ts +0 -39
- package/src/scene/tools/ConnectTool.ts +0 -209
- package/src/scene/tools/DrawingConnectTool.ts +0 -231
- package/src/scene/tools/PathTool.ts +0 -158
- package/src/scene/tools/PlaceTool.ts +0 -47
- package/src/scene/tools/SceneTool.ts +0 -85
- package/src/scene/tools/SelectTool.ts +0 -300
- package/src/scene/tools/ShapeTool.ts +0 -143
- package/src/scene/tools/TextTool.ts +0 -72
- package/src/scene/tools/TokenTool.ts +0 -95
- package/src/scene/tools/createDrawShapeTool.ts +0 -82
- package/src/selectionConversions.ts +0 -155
- package/src/styles/ascii-diagram.css +0 -79
- package/src/styles/ascii-timeline.css +0 -596
- package/src/styles/code-context.css +0 -155
- package/src/styles/diagram.css +0 -183
- package/src/styles/editor.css +0 -6811
- package/src/styles/folder-view.css +0 -210
- package/src/styles/image-edit-affordance.css +0 -201
- package/src/styles/index.css +0 -18
- package/src/styles/tree-view.css +0 -189
- package/src/teleprompter/TeleprompterControls.tsx +0 -218
- package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
- package/src/teleprompter/TeleprompterSurface.tsx +0 -267
- package/src/teleprompter/TeleprompterView.tsx +0 -338
- package/src/teleprompter/canvasRenderer.ts +0 -161
- package/src/teleprompter/floatingWindow.ts +0 -352
- package/src/teleprompter/index.ts +0 -61
- package/src/teleprompter/pcmWorklet.ts +0 -62
- package/src/teleprompter/recording/insertPreamble.ts +0 -80
- package/src/teleprompter/recording/narrationSave.ts +0 -134
- package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
- package/src/teleprompter/scrollModel.ts +0 -85
- package/src/teleprompter/teleprompterTheme.ts +0 -303
- package/src/teleprompter/types.ts +0 -38
- package/src/teleprompter/useFloatingWindow.ts +0 -74
- package/src/teleprompter/useMicAnalysis.ts +0 -211
- package/src/teleprompter/useTeleprompter.ts +0 -421
- package/src/templateContentPreviewResolver.ts +0 -360
- package/src/timeline/TimelineEditorWidget.tsx +0 -861
- package/src/timeline/TimelineViewExtension.ts +0 -252
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
- package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
- package/src/timeline/__tests__/timelineOps.test.ts +0 -277
- package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
- package/src/timeline/timelineCommands.ts +0 -421
- package/src/timeline/timelineData.ts +0 -54
- package/src/timeline/timelineOps.ts +0 -381
- package/src/timeline/timelinePaste.ts +0 -8
- package/src/timelineSource.ts +0 -244
- package/src/tiptap/TiptapAudio.tsx +0 -86
- package/src/tiptap/TiptapVideo.tsx +0 -119
- package/src/tiptap/useResolvedMediaSrc.ts +0 -47
- package/src/tiptapBridge.ts +0 -986
- package/src/tooltipPlacement.ts +0 -13
- package/src/transformStyleId.ts +0 -17
- package/src/transitionCatalog.ts +0 -159
- package/src/treeview/TreeOutlineWidget.tsx +0 -390
- package/src/treeview/TreeViewExtension.ts +0 -250
- package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
- package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
- package/src/treeview/__tests__/treeOps.test.ts +0 -177
- package/src/treeview/__tests__/treePaste.test.ts +0 -40
- package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
- package/src/treeview/treeOps.ts +0 -231
- package/src/treeview/treePaste.ts +0 -13
- package/src/treeview/treeViewCommands.ts +0 -99
- package/src/treeview/treeViewData.ts +0 -41
- package/src/types/monaco-shims.d.ts +0 -10
- package/src/useBlockNavigator.ts +0 -153
- package/src/useHeadingLayout.ts +0 -294
- package/src/useMonacoLoader.ts +0 -82
- package/src/useTimelineClock.ts +0 -76
- package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
- package/src/utils/dropUtils.ts +0 -136
- package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
- package/src/wysiwygImageUpload.ts +0 -113
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SceneTextOverlay — the shared inline rich-text editor for canvas
|
|
3
|
-
* textboxes. An absolutely-positioned HTML Tiptap instance laid over the
|
|
4
|
-
* edited layer's on-screen box (so it tracks pan/zoom), rendered inside
|
|
5
|
-
* `.squisq-scene-root`. Real HTML over the SVG (not a transformed
|
|
6
|
-
* foreignObject) to avoid SVG focus/IME quirks.
|
|
7
|
-
*
|
|
8
|
-
* Lifecycle: focuses on mount, publishes its editor to `sceneTextChannel`
|
|
9
|
-
* (so the top toolbar can format it), commits on blur/Enter(inline)/
|
|
10
|
-
* Cmd+Enter and cancels on Escape. Clears the channel on unmount.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
14
|
-
import { useEditor, EditorContent } from '@tiptap/react';
|
|
15
|
-
import type { Layer } from '@bendyline/squisq/schemas';
|
|
16
|
-
import { DEFAULT_DOC_FONT } from '@bendyline/squisq/schemas';
|
|
17
|
-
import { plainTextFromInlineHtml } from '@bendyline/squisq/markdown';
|
|
18
|
-
import { layerBounds } from '../hooks/useSceneHitTest';
|
|
19
|
-
import type { SceneTransform } from '../hooks/useScenePanZoom';
|
|
20
|
-
import type { SceneTextEditConfig } from './sceneTextConfig';
|
|
21
|
-
import { buildSceneTextExtensions } from './sceneTiptap';
|
|
22
|
-
|
|
23
|
-
interface SceneTextOverlayProps {
|
|
24
|
-
config: SceneTextEditConfig;
|
|
25
|
-
/** The layer being edited (for box geometry + base text style). */
|
|
26
|
-
layer: Layer;
|
|
27
|
-
editableId: string;
|
|
28
|
-
viewport: { width: number; height: number };
|
|
29
|
-
transform: SceneTransform;
|
|
30
|
-
viewportToScreen: (vx: number, vy: number) => { x: number; y: number };
|
|
31
|
-
onClose: () => void;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function SceneTextOverlay({
|
|
35
|
-
config,
|
|
36
|
-
layer,
|
|
37
|
-
editableId,
|
|
38
|
-
viewport,
|
|
39
|
-
transform,
|
|
40
|
-
viewportToScreen,
|
|
41
|
-
onClose,
|
|
42
|
-
}: SceneTextOverlayProps) {
|
|
43
|
-
const initialHtml = useMemo(() => config.getHtml(editableId), [config, editableId]);
|
|
44
|
-
const style = layer.type === 'text' ? layer.content.style : undefined;
|
|
45
|
-
|
|
46
|
-
// Stable commit/cancel via refs so the editor's keydown closure (bound
|
|
47
|
-
// once at creation) always calls the latest handlers.
|
|
48
|
-
const committedRef = useRef(false);
|
|
49
|
-
const commitRef = useRef<() => void>(() => {});
|
|
50
|
-
const cancelRef = useRef<() => void>(() => {});
|
|
51
|
-
|
|
52
|
-
const editor = useEditor({
|
|
53
|
-
extensions: buildSceneTextExtensions(config.level),
|
|
54
|
-
content: initialHtml,
|
|
55
|
-
autofocus: 'end',
|
|
56
|
-
editorProps: {
|
|
57
|
-
handleKeyDown: (_view, event) => {
|
|
58
|
-
if (event.key === 'Escape') {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
cancelRef.current();
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
// Inline labels: Enter commits (Shift+Enter = hard break). Rich
|
|
64
|
-
// textboxes: Enter is a paragraph; commit on Cmd/Ctrl+Enter.
|
|
65
|
-
const enterCommits =
|
|
66
|
-
config.level === 'inline' ? !event.shiftKey : event.metaKey || event.ctrlKey;
|
|
67
|
-
if (event.key === 'Enter' && enterCommits) {
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
commitRef.current();
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
return false;
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
commitRef.current = useCallback(() => {
|
|
78
|
-
if (committedRef.current || !editor) return;
|
|
79
|
-
committedRef.current = true;
|
|
80
|
-
const html = editor.getHTML();
|
|
81
|
-
config.commit(editableId, { html, text: plainTextFromInlineHtml(html) });
|
|
82
|
-
onClose();
|
|
83
|
-
}, [editor, config, editableId, onClose]) as () => void;
|
|
84
|
-
|
|
85
|
-
cancelRef.current = useCallback(() => {
|
|
86
|
-
committedRef.current = true; // suppress the blur-commit that follows
|
|
87
|
-
onClose();
|
|
88
|
-
}, [onClose]) as () => void;
|
|
89
|
-
|
|
90
|
-
// Publish to the toolbar channel while mounted; clear on unmount.
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (!editor || !config.channel) return;
|
|
93
|
-
const channel = config.channel;
|
|
94
|
-
channel.set({ editor, level: config.level });
|
|
95
|
-
return () => {
|
|
96
|
-
if (channel.get()?.editor === editor) channel.set(null);
|
|
97
|
-
};
|
|
98
|
-
}, [editor, config.level, config.channel]);
|
|
99
|
-
|
|
100
|
-
// Commit when focus leaves the editor — unless it went to the toolbar
|
|
101
|
-
// (the user clicking Bold) or stayed within the overlay.
|
|
102
|
-
const onBlur = useCallback((e: React.FocusEvent) => {
|
|
103
|
-
const next = e.relatedTarget as HTMLElement | null;
|
|
104
|
-
if (next && next.closest('.squisq-toolbar, .squisq-scene-text-overlay')) return;
|
|
105
|
-
commitRef.current();
|
|
106
|
-
}, []);
|
|
107
|
-
|
|
108
|
-
const bounds = layerBounds(layer, viewport);
|
|
109
|
-
if (!bounds) return null;
|
|
110
|
-
const tl = viewportToScreen(bounds.x, bounds.y);
|
|
111
|
-
const scale = transform.scale;
|
|
112
|
-
const fontSize = (style?.fontSize ?? 18) * scale;
|
|
113
|
-
const pad = (style?.padding ?? 0) * scale;
|
|
114
|
-
// Mirror the layer's vertical alignment so changing it (via the toolbar)
|
|
115
|
-
// updates the editor live, matching what the committed box will look like.
|
|
116
|
-
const justifyContent =
|
|
117
|
-
style?.verticalAlign === 'middle'
|
|
118
|
-
? 'center'
|
|
119
|
-
: style?.verticalAlign === 'bottom'
|
|
120
|
-
? 'flex-end'
|
|
121
|
-
: 'flex-start';
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div
|
|
125
|
-
className="squisq-scene-text-overlay"
|
|
126
|
-
onBlur={onBlur}
|
|
127
|
-
// Keep clicks inside the editor from reaching the SVG (which would
|
|
128
|
-
// focus the canvas and blur us mid-edit).
|
|
129
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
130
|
-
onMouseDown={(e) => e.stopPropagation()}
|
|
131
|
-
onDoubleClick={(e) => e.stopPropagation()}
|
|
132
|
-
style={{
|
|
133
|
-
position: 'absolute',
|
|
134
|
-
left: tl.x,
|
|
135
|
-
top: tl.y,
|
|
136
|
-
width: bounds.width * scale,
|
|
137
|
-
height: bounds.height * scale,
|
|
138
|
-
zIndex: 20,
|
|
139
|
-
boxSizing: 'border-box',
|
|
140
|
-
display: 'flex',
|
|
141
|
-
flexDirection: 'column',
|
|
142
|
-
justifyContent,
|
|
143
|
-
padding: pad,
|
|
144
|
-
fontSize,
|
|
145
|
-
fontFamily: style?.fontFamily || DEFAULT_DOC_FONT,
|
|
146
|
-
color: style?.color ?? '#1e293b',
|
|
147
|
-
lineHeight: style?.lineHeight || 1.4,
|
|
148
|
-
textAlign: style?.textAlign ?? 'left',
|
|
149
|
-
background: '#ffffff',
|
|
150
|
-
border: '2px solid #6366f1',
|
|
151
|
-
borderRadius: 4,
|
|
152
|
-
overflow: 'auto',
|
|
153
|
-
cursor: 'text',
|
|
154
|
-
}}
|
|
155
|
-
>
|
|
156
|
-
<EditorContent editor={editor} className="squisq-scene-text-editor" />
|
|
157
|
-
</div>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export default SceneTextOverlay;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sceneTextChannel — an editor-owned channel that bridges the canvas's inline
|
|
3
|
-
* text editor (which renders in a **detached React root** created by the
|
|
4
|
-
* Diagram/SceneBlock ProseMirror extensions, outside `<EditorProvider>`)
|
|
5
|
-
* to the provider, so the top formatting toolbar can target it.
|
|
6
|
-
*
|
|
7
|
-
* The active textbox's `SceneTextOverlay` publishes its Tiptap editor here
|
|
8
|
-
* on focus and clears it on blur/unmount; `EditorProvider` subscribes and
|
|
9
|
-
* mirrors the handle into `activeSceneText`. Each EditorProvider creates its
|
|
10
|
-
* own channel, preventing focus in one editor from mutating another editor.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { Editor as TiptapEditor } from '@tiptap/core';
|
|
14
|
-
import type { SceneTextLevel } from './sceneTextConfig';
|
|
15
|
-
|
|
16
|
-
export interface SceneTextHandle {
|
|
17
|
-
editor: TiptapEditor;
|
|
18
|
-
level: SceneTextLevel;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type Listener = (handle: SceneTextHandle | null) => void;
|
|
22
|
-
|
|
23
|
-
export interface SceneTextChannel {
|
|
24
|
-
set(handle: SceneTextHandle | null): void;
|
|
25
|
-
get(): SceneTextHandle | null;
|
|
26
|
-
subscribe(listener: Listener): () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Create a channel owned by one EditorProvider; no cross-editor singleton. */
|
|
30
|
-
export function createSceneTextChannel(): SceneTextChannel {
|
|
31
|
-
let current: SceneTextHandle | null = null;
|
|
32
|
-
const listeners = new Set<Listener>();
|
|
33
|
-
return {
|
|
34
|
-
set(handle) {
|
|
35
|
-
current = handle;
|
|
36
|
-
for (const listener of listeners) listener(handle);
|
|
37
|
-
},
|
|
38
|
-
get() {
|
|
39
|
-
return current;
|
|
40
|
-
},
|
|
41
|
-
subscribe(listener) {
|
|
42
|
-
listeners.add(listener);
|
|
43
|
-
return () => listeners.delete(listener);
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration a Scene host (diagram / drawing / layout widget) supplies
|
|
3
|
-
* so the shared inline text editor knows how to read and write the text of
|
|
4
|
-
* a given layer. The Scene owns the editing UI (overlay + positioning);
|
|
5
|
-
* the host owns persistence (markdown heading vs. layout JSON blob).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { SceneTextChannel } from './sceneTextChannel';
|
|
9
|
-
|
|
10
|
-
export type SceneTextLevel = 'inline' | 'block' | 'rich';
|
|
11
|
-
|
|
12
|
-
export interface SceneTextEditConfig {
|
|
13
|
-
/** Per-editor bridge to the formatting toolbar. */
|
|
14
|
-
channel?: SceneTextChannel;
|
|
15
|
-
/**
|
|
16
|
-
* Map a hit layer id (what the pointer landed on) to the layer id whose
|
|
17
|
-
* on-screen box the editor should cover, or `null` when that layer isn't
|
|
18
|
-
* text-editable. e.g. a diagram card hit (`node-card-x`) maps to itself;
|
|
19
|
-
* a non-text drawing shape maps to `null`.
|
|
20
|
-
*/
|
|
21
|
-
resolveEditableId: (layerId: string) => string | null;
|
|
22
|
-
/** Seed HTML for the editor (e.g. `content.html` or `markdownToTiptap(label)`). */
|
|
23
|
-
getHtml: (editableId: string) => string;
|
|
24
|
-
/** Persist the edited content. The host serializes per its storage model. */
|
|
25
|
-
commit: (editableId: string, content: { html: string; text: string }) => void;
|
|
26
|
-
/**
|
|
27
|
-
* Toolbar capability gate: `inline` = marks only; `block` = marks + lists +
|
|
28
|
-
* blockquote (no headings, for layout textboxes); `rich` = also headings.
|
|
29
|
-
*/
|
|
30
|
-
level: SceneTextLevel;
|
|
31
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slim Tiptap presets for the canvas inline text editor.
|
|
3
|
-
*
|
|
4
|
-
* Deliberately NOT the document editor's extension set: a textbox must not
|
|
5
|
-
* recursively mount canvases (no `DiagramExtension`/`SceneBlockExtension`)
|
|
6
|
-
* or carry block-template annotations (no `HeadingWithTemplate`).
|
|
7
|
-
*
|
|
8
|
-
* - `inline` → paragraph + marks (bold/italic/strike/code) + link. Used for
|
|
9
|
-
* single-label text (diagram nodes, drawing shapes).
|
|
10
|
-
* - `block` → also lists, blockquote, code block — but NO headings. Used for
|
|
11
|
-
* layout textboxes, whose content is stored as the child sub-block's
|
|
12
|
-
* markdown body (a heading there would re-parse as a sibling sub-block).
|
|
13
|
-
* - `rich` → also headings. (Currently unused by shipping surfaces.)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { Extensions } from '@tiptap/core';
|
|
17
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
18
|
-
import Link from '@tiptap/extension-link';
|
|
19
|
-
import type { SceneTextLevel } from './sceneTextConfig';
|
|
20
|
-
|
|
21
|
-
export function buildSceneTextExtensions(level: SceneTextLevel): Extensions {
|
|
22
|
-
const starter =
|
|
23
|
-
level === 'inline'
|
|
24
|
-
? StarterKit.configure({
|
|
25
|
-
heading: false,
|
|
26
|
-
bulletList: false,
|
|
27
|
-
orderedList: false,
|
|
28
|
-
blockquote: false,
|
|
29
|
-
codeBlock: false,
|
|
30
|
-
horizontalRule: false,
|
|
31
|
-
})
|
|
32
|
-
: level === 'block'
|
|
33
|
-
? StarterKit.configure({ heading: false, horizontalRule: false })
|
|
34
|
-
: StarterKit.configure({ horizontalRule: false });
|
|
35
|
-
return [starter, Link.configure({ openOnClick: false })];
|
|
36
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSceneTextEditing — Scene-level state for "which layer is being text
|
|
3
|
-
* edited". The Scene wires `begin` into the tool context (`beginTextEdit`)
|
|
4
|
-
* so SelectTool's double-click can open the editor, and renders
|
|
5
|
-
* `<SceneTextOverlay>` while `editingId` is set.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useCallback, useRef, useState } from 'react';
|
|
9
|
-
import type { SceneTextEditConfig } from './sceneTextConfig';
|
|
10
|
-
|
|
11
|
-
export interface SceneTextEditing {
|
|
12
|
-
/** The editable layer id currently being edited, or null. */
|
|
13
|
-
editingId: string | null;
|
|
14
|
-
/** Open the editor for the layer the pointer hit (resolved via config). */
|
|
15
|
-
begin: (hitLayerId: string) => void;
|
|
16
|
-
/** Close the editor (after commit/cancel). */
|
|
17
|
-
close: () => void;
|
|
18
|
-
/** Ref mirror of whether an edit is active — for non-React event handlers. */
|
|
19
|
-
activeRef: React.MutableRefObject<boolean>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function useSceneTextEditing(config?: SceneTextEditConfig): SceneTextEditing {
|
|
23
|
-
const [editingId, setEditingId] = useState<string | null>(null);
|
|
24
|
-
const activeRef = useRef(false);
|
|
25
|
-
activeRef.current = editingId != null;
|
|
26
|
-
|
|
27
|
-
const begin = useCallback(
|
|
28
|
-
(hitLayerId: string) => {
|
|
29
|
-
if (!config) return;
|
|
30
|
-
const id = config.resolveEditableId(hitLayerId);
|
|
31
|
-
if (id) setEditingId(id);
|
|
32
|
-
},
|
|
33
|
-
[config],
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const close = useCallback(() => setEditingId(null), []);
|
|
37
|
-
|
|
38
|
-
return { editingId, begin, close, activeRef };
|
|
39
|
-
}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConnectTool — drag from a diagram node to another to create an edge.
|
|
3
|
-
*
|
|
4
|
-
* Press on a node-card layer to anchor the connection start. While
|
|
5
|
-
* dragging, render a dashed preview path from the source node's center
|
|
6
|
-
* to the pointer. On release over a different node-card layer, dispatch
|
|
7
|
-
* an `addEdge` command. Release elsewhere cancels.
|
|
8
|
-
*
|
|
9
|
-
* The tool reads the node descriptors from `ctx.edges`/`ctx.layers` —
|
|
10
|
-
* not from a parallel data store — so it stays in sync with whatever
|
|
11
|
-
* the DiagramAdapter just produced.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { createElement, type JSX } from 'react';
|
|
15
|
-
import type { SceneTool, SceneToolContext } from './SceneTool';
|
|
16
|
-
import { nodeIdFromCardLayerId } from '../layers/nodeCard';
|
|
17
|
-
import {
|
|
18
|
-
curvedPath,
|
|
19
|
-
edgeEndpoints,
|
|
20
|
-
snapPointToward,
|
|
21
|
-
type EdgeNodeBox,
|
|
22
|
-
} from '../layers/edgeGeometry';
|
|
23
|
-
|
|
24
|
-
interface ConnectState {
|
|
25
|
-
sourceNodeId: string;
|
|
26
|
-
/** Source node/card box in viewport units (cached at drag start). */
|
|
27
|
-
sourceBox: EdgeNodeBox;
|
|
28
|
-
/** Preview start/end in viewport units. */
|
|
29
|
-
start: { x: number; y: number };
|
|
30
|
-
end: { x: number; y: number };
|
|
31
|
-
/** Currently-hovered target node id, if any (for highlight + drop). */
|
|
32
|
-
hoveredTargetId: string | null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getState(ctx: SceneToolContext): ConnectState | null {
|
|
36
|
-
return (ctx.interaction.connect as ConnectState | undefined) ?? null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function setState(ctx: SceneToolContext, state: ConnectState | null): void {
|
|
40
|
-
ctx.interaction.connect = state ?? undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function pointerFromEvent(e: React.PointerEvent): { sx: number; sy: number } {
|
|
44
|
-
const rect = (e.currentTarget as Element).getBoundingClientRect();
|
|
45
|
-
return { sx: e.clientX - rect.left, sy: e.clientY - rect.top };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function nodeBoxFromCard(ctx: SceneToolContext, nodeId: string): EdgeNodeBox | null {
|
|
49
|
-
const card = ctx.hitItems.find((it) => it.id === `node-card-${nodeId}`);
|
|
50
|
-
if (!card) return null;
|
|
51
|
-
return {
|
|
52
|
-
id: nodeId,
|
|
53
|
-
x: card.bounds.x,
|
|
54
|
-
y: card.bounds.y,
|
|
55
|
-
width: card.bounds.width,
|
|
56
|
-
height: card.bounds.height,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function nodeBoxes(ctx: SceneToolContext): EdgeNodeBox[] {
|
|
61
|
-
const boxes: EdgeNodeBox[] = [];
|
|
62
|
-
for (const it of ctx.hitItems) {
|
|
63
|
-
if (!it.id.startsWith('node-card-')) continue;
|
|
64
|
-
const nodeId = nodeIdFromCardLayerId(it.id);
|
|
65
|
-
if (!nodeId) continue;
|
|
66
|
-
boxes.push({
|
|
67
|
-
id: nodeId,
|
|
68
|
-
x: it.bounds.x,
|
|
69
|
-
y: it.bounds.y,
|
|
70
|
-
width: it.bounds.width,
|
|
71
|
-
height: it.bounds.height,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return boxes;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function connectionPoints(box: EdgeNodeBox): Array<{ x: number; y: number }> {
|
|
78
|
-
const width = box.width ?? 0;
|
|
79
|
-
const height = box.height ?? 0;
|
|
80
|
-
const left = box.x;
|
|
81
|
-
const right = box.x + width;
|
|
82
|
-
const top = box.y;
|
|
83
|
-
const bottom = box.y + height;
|
|
84
|
-
const centerX = box.x + width / 2;
|
|
85
|
-
const centerY = box.y + height / 2;
|
|
86
|
-
return [
|
|
87
|
-
{ x: centerX, y: top },
|
|
88
|
-
{ x: right, y: centerY },
|
|
89
|
-
{ x: centerX, y: bottom },
|
|
90
|
-
{ x: left, y: centerY },
|
|
91
|
-
{ x: left, y: top },
|
|
92
|
-
{ x: right, y: top },
|
|
93
|
-
{ x: right, y: bottom },
|
|
94
|
-
{ x: left, y: bottom },
|
|
95
|
-
];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function updatePreview(
|
|
99
|
-
sourceBox: EdgeNodeBox,
|
|
100
|
-
current: { x: number; y: number },
|
|
101
|
-
targetBox: EdgeNodeBox | null,
|
|
102
|
-
): { start: { x: number; y: number }; end: { x: number; y: number } } {
|
|
103
|
-
if (targetBox) {
|
|
104
|
-
return (
|
|
105
|
-
edgeEndpoints([sourceBox, targetBox], sourceBox.id, targetBox.id) ?? {
|
|
106
|
-
start: current,
|
|
107
|
-
end: current,
|
|
108
|
-
}
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
start: snapPointToward([sourceBox], sourceBox.id, current) ?? current,
|
|
113
|
-
end: current,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export const ConnectTool: SceneTool = {
|
|
118
|
-
id: 'connect',
|
|
119
|
-
label: 'Connect',
|
|
120
|
-
cursor: 'crosshair',
|
|
121
|
-
shortcut: 'c',
|
|
122
|
-
hideSelectionHandles: true,
|
|
123
|
-
|
|
124
|
-
onPointerDown(e, ctx) {
|
|
125
|
-
if (e.button !== 0) return;
|
|
126
|
-
const { sx, sy } = pointerFromEvent(e);
|
|
127
|
-
const v = ctx.screenToViewport(sx, sy);
|
|
128
|
-
const hitId = ctx.hit(v);
|
|
129
|
-
if (!hitId) return;
|
|
130
|
-
const nodeId = nodeIdFromCardLayerId(hitId);
|
|
131
|
-
if (!nodeId) return;
|
|
132
|
-
const sourceBox = nodeBoxFromCard(ctx, nodeId);
|
|
133
|
-
if (!sourceBox) return;
|
|
134
|
-
const preview = updatePreview(sourceBox, v, null);
|
|
135
|
-
setState(ctx, {
|
|
136
|
-
sourceNodeId: nodeId,
|
|
137
|
-
sourceBox,
|
|
138
|
-
start: preview.start,
|
|
139
|
-
end: preview.end,
|
|
140
|
-
hoveredTargetId: null,
|
|
141
|
-
});
|
|
142
|
-
(e.currentTarget as Element).setPointerCapture?.(e.pointerId);
|
|
143
|
-
e.stopPropagation();
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
onPointerMove(e, ctx) {
|
|
147
|
-
const state = getState(ctx);
|
|
148
|
-
if (!state) return;
|
|
149
|
-
const { sx, sy } = pointerFromEvent(e);
|
|
150
|
-
const v = ctx.screenToViewport(sx, sy);
|
|
151
|
-
const hitId = ctx.hit(v);
|
|
152
|
-
const nodeId = hitId ? nodeIdFromCardLayerId(hitId) : null;
|
|
153
|
-
const targetId = nodeId && nodeId !== state.sourceNodeId ? nodeId : null;
|
|
154
|
-
const targetBox = targetId ? nodeBoxFromCard(ctx, targetId) : null;
|
|
155
|
-
const preview = updatePreview(state.sourceBox, v, targetBox);
|
|
156
|
-
state.start = preview.start;
|
|
157
|
-
state.end = preview.end;
|
|
158
|
-
state.hoveredTargetId = targetBox?.id ?? null;
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
onPointerUp(e, ctx) {
|
|
162
|
-
const state = getState(ctx);
|
|
163
|
-
if (!state) return;
|
|
164
|
-
const target = state.hoveredTargetId;
|
|
165
|
-
if (target) {
|
|
166
|
-
ctx.dispatch({ kind: 'addEdge', source: state.sourceNodeId, target });
|
|
167
|
-
}
|
|
168
|
-
(e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
|
|
169
|
-
setState(ctx, null);
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
renderOverlay(ctx): JSX.Element | null {
|
|
173
|
-
const state = getState(ctx);
|
|
174
|
-
const points = nodeBoxes(ctx).flatMap((box) =>
|
|
175
|
-
connectionPoints(box).map((point, index) =>
|
|
176
|
-
createElement('circle', {
|
|
177
|
-
key: `${box.id}-${index}`,
|
|
178
|
-
cx: point.x,
|
|
179
|
-
cy: point.y,
|
|
180
|
-
r: 4,
|
|
181
|
-
className:
|
|
182
|
-
state?.hoveredTargetId === box.id
|
|
183
|
-
? 'squisq-scene-connection-point squisq-scene-connection-point--active'
|
|
184
|
-
: 'squisq-scene-connection-point',
|
|
185
|
-
}),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (!state) {
|
|
190
|
-
return points.length ? createElement('g', { key: 'connection-points' }, ...points) : null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const { start: a, end: b } = state;
|
|
194
|
-
const d = curvedPath(a, b);
|
|
195
|
-
return createElement(
|
|
196
|
-
'g',
|
|
197
|
-
{ key: 'connect-preview' },
|
|
198
|
-
...points,
|
|
199
|
-
createElement('path', { d, className: 'squisq-scene-connect-preview' }),
|
|
200
|
-
// Optional: a dot at the endpoint so the user sees the snap target.
|
|
201
|
-
createElement('circle', {
|
|
202
|
-
cx: b.x,
|
|
203
|
-
cy: b.y,
|
|
204
|
-
r: 4,
|
|
205
|
-
className: 'squisq-scene-connect-preview',
|
|
206
|
-
}),
|
|
207
|
-
);
|
|
208
|
-
},
|
|
209
|
-
};
|