@r2digisolutions/components 0.1.7 → 0.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/dist/components/atoms/IconButton/IconButton.svelte +1 -1
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.stories.js +8 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte +122 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte.d.ts +11 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte +12 -0
- package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte.d.ts +18 -0
- package/dist/components/atoms/PanKnob/PanKnob.stories.d.ts +9 -0
- package/dist/components/atoms/PanKnob/PanKnob.stories.js +8 -0
- package/dist/components/atoms/PanKnob/PanKnob.svelte +218 -0
- package/dist/components/atoms/PanKnob/PanKnob.svelte.d.ts +19 -0
- package/dist/components/atoms/PanKnob/PanKnobStory.svelte +15 -0
- package/dist/components/atoms/PanKnob/PanKnobStory.svelte.d.ts +3 -0
- package/dist/components/atoms/Playhead/Playhead.stories.js +8 -0
- package/dist/components/atoms/Playhead/Playhead.svelte +59 -0
- package/dist/components/atoms/Playhead/Playhead.svelte.d.ts +13 -0
- package/dist/components/atoms/Playhead/PlayheadStory.svelte +9 -0
- package/dist/components/atoms/Playhead/PlayheadStory.svelte.d.ts +18 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.stories.js +8 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.svelte +58 -0
- package/dist/components/atoms/TimeRuler/TimeRuler.svelte.d.ts +10 -0
- package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte +7 -0
- package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte.d.ts +18 -0
- package/dist/components/atoms/Tooltip/Tooltip.svelte +1 -1
- package/dist/components/atoms/TrimHandle/TrimHandle.stories.js +8 -0
- package/dist/components/atoms/TrimHandle/TrimHandle.svelte +63 -0
- package/dist/components/atoms/TrimHandle/TrimHandle.svelte.d.ts +12 -0
- package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte +11 -0
- package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte.d.ts +18 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.d.ts +9 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.js +8 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte +102 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte.d.ts +20 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte +34 -0
- package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte.d.ts +3 -0
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +97 -61
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte.d.ts +2 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.stories.js +8 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte +117 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte.d.ts +23 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte +18 -0
- package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.stories.js +8 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte +858 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte.d.ts +43 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte +7 -0
- package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.d.ts +9 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.js +8 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte +1140 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte.d.ts +28 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte +20 -0
- package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte.d.ts +3 -0
- package/dist/components/molecules/ColorPicker/ColorPicker.svelte +1 -1
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.d.ts +9 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.js +8 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte +108 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte.d.ts +18 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte +15 -0
- package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte.d.ts +3 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.stories.js +8 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte +73 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte.d.ts +16 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte +11 -0
- package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte.d.ts +18 -0
- package/dist/components/molecules/ContextMenu/ContextMenu.svelte +204 -60
- package/dist/components/molecules/ContextMenu/ContextMenu.svelte.d.ts +9 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.d.ts +9 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.js +8 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte +56 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte.d.ts +12 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte +12 -0
- package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte.d.ts +3 -0
- package/dist/components/molecules/FilterList/FilterList.stories.d.ts +9 -0
- package/dist/components/molecules/FilterList/FilterList.stories.js +8 -0
- package/dist/components/molecules/FilterList/FilterList.svelte +101 -0
- package/dist/components/molecules/FilterList/FilterList.svelte.d.ts +18 -0
- package/dist/components/molecules/FilterList/FilterListStory.svelte +31 -0
- package/dist/components/molecules/FilterList/FilterListStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.d.ts +9 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.js +8 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte +94 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte.d.ts +15 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte +18 -0
- package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.stories.js +8 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte +50 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte.d.ts +11 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte +13 -0
- package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte.d.ts +18 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.stories.js +8 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte +380 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte.d.ts +33 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte +14 -0
- package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte.d.ts +18 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.d.ts +9 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.js +8 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte +399 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte.d.ts +37 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte +24 -0
- package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaStage/MediaStage.stories.d.ts +9 -0
- package/dist/components/molecules/MediaStage/MediaStage.stories.js +8 -0
- package/dist/components/molecules/MediaStage/MediaStage.svelte +1498 -0
- package/dist/components/molecules/MediaStage/MediaStage.svelte.d.ts +45 -0
- package/dist/components/molecules/MediaStage/MediaStageStory.svelte +35 -0
- package/dist/components/molecules/MediaStage/MediaStageStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.d.ts +9 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.js +8 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte +131 -0
- package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte.d.ts +19 -0
- package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte +20 -0
- package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte.d.ts +3 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.stories.d.ts +9 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.stories.js +8 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.svelte +92 -0
- package/dist/components/molecules/MixerChannel/MixerChannel.svelte.d.ts +11 -0
- package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte +13 -0
- package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte.d.ts +3 -0
- package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte +454 -0
- package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte.d.ts +8 -0
- package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte +26 -0
- package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte.d.ts +22 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.stories.d.ts +12 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.stories.js +9 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.svelte +1409 -0
- package/dist/components/molecules/NodeGraph/NodeGraph.svelte.d.ts +33 -0
- package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte +90 -0
- package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte.d.ts +3 -0
- package/dist/components/molecules/NodePalette/NodePalette.stories.js +8 -0
- package/dist/components/molecules/NodePalette/NodePalette.svelte +86 -0
- package/dist/components/molecules/NodePalette/NodePalette.svelte.d.ts +11 -0
- package/dist/components/molecules/NodePalette/NodePaletteStory.svelte +11 -0
- package/dist/components/molecules/NodePalette/NodePaletteStory.svelte.d.ts +18 -0
- package/dist/components/molecules/Poll/Poll.stories.d.ts +1 -1
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.stories.js +8 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte +72 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte.d.ts +12 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte +16 -0
- package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte.d.ts +18 -0
- package/dist/components/molecules/PropertyField/PropertyField.svelte +57 -3
- package/dist/components/molecules/PropertyField/PropertyField.svelte.d.ts +10 -0
- package/dist/components/molecules/PropertyField/PropertyFieldStory.svelte +21 -0
- package/dist/components/molecules/PropertyGroup/PropertyGroup.svelte +1 -1
- package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.d.ts +9 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.js +8 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte +52 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte.d.ts +18 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte +62 -0
- package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.stories.d.ts +9 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.stories.js +8 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.svelte +180 -0
- package/dist/components/molecules/RelationPicker/RelationPicker.svelte.d.ts +24 -0
- package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte +16 -0
- package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.d.ts +9 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.js +8 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte +154 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte.d.ts +12 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte +16 -0
- package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SecretField/SecretField.stories.d.ts +9 -0
- package/dist/components/molecules/SecretField/SecretField.stories.js +8 -0
- package/dist/components/molecules/SecretField/SecretField.svelte +58 -0
- package/dist/components/molecules/SecretField/SecretField.svelte.d.ts +15 -0
- package/dist/components/molecules/SecretField/SecretFieldStory.svelte +8 -0
- package/dist/components/molecules/SecretField/SecretFieldStory.svelte.d.ts +3 -0
- package/dist/components/molecules/SplitPane/SplitPane.svelte +96 -37
- package/dist/components/molecules/SplitPane/SplitPane.svelte.d.ts +10 -2
- package/dist/components/molecules/TableToolbar/TableToolbar.svelte.d.ts +1 -1
- package/dist/components/molecules/Terminal/Terminal.stories.js +20 -0
- package/dist/components/molecules/Terminal/Terminal.svelte +238 -0
- package/dist/components/molecules/Terminal/Terminal.svelte.d.ts +19 -0
- package/dist/components/molecules/Terminal/TerminalStory.svelte +47 -0
- package/dist/components/molecules/Terminal/TerminalStory.svelte.d.ts +6 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.stories.js +8 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.svelte +116 -0
- package/dist/components/molecules/TimelineClip/TimelineClip.svelte.d.ts +15 -0
- package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte +18 -0
- package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.stories.js +8 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte +56 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte.d.ts +16 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte +35 -0
- package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.stories.js +8 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte +158 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte.d.ts +18 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte +55 -0
- package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte.d.ts +18 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.stories.d.ts +9 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.stories.js +8 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.svelte +54 -0
- package/dist/components/molecules/TrackHeader/TrackHeader.svelte.d.ts +9 -0
- package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte +10 -0
- package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/TransportControls/TransportControls.stories.d.ts +9 -0
- package/dist/components/molecules/TransportControls/TransportControls.stories.js +8 -0
- package/dist/components/molecules/TransportControls/TransportControls.svelte +53 -0
- package/dist/components/molecules/TransportControls/TransportControls.svelte.d.ts +12 -0
- package/dist/components/molecules/TransportControls/TransportControlsStory.svelte +18 -0
- package/dist/components/molecules/TransportControls/TransportControlsStory.svelte.d.ts +3 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.stories.js +8 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte +117 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte.d.ts +20 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte +36 -0
- package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte.d.ts +18 -0
- package/dist/components/molecules/VideoPlayer/VideoPlayer.stories.d.ts +1 -1
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.d.ts +9 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.js +8 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte +59 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte.d.ts +9 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte +10 -0
- package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte.d.ts +3 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.stories.js +8 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte +82 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte.d.ts +13 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte +17 -0
- package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte.d.ts +18 -0
- package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte +13 -4
- package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte.d.ts +3 -1
- package/dist/components/molecules/WidgetCanvas/widgetCanvasContext.d.ts +2 -0
- package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte +206 -37
- package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte.d.ts +31 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.stories.d.ts +12 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.stories.js +9 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.svelte +273 -0
- package/dist/components/organisms/AudioEditor/AudioEditor.svelte.d.ts +13 -0
- package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte +10 -0
- package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.stories.d.ts +12 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.stories.js +9 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.svelte +1208 -0
- package/dist/components/organisms/BlogEditor/BlogEditor.svelte.d.ts +16 -0
- package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +144 -0
- package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte +28 -0
- package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte.d.ts +10 -0
- package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte +34 -0
- package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte.d.ts +11 -0
- package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte +13 -0
- package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte.d.ts +8 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.stories.js +8 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte +469 -0
- package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte.d.ts +14 -0
- package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte +20 -0
- package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte.d.ts +18 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.d.ts +12 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.js +9 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte +1012 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte.d.ts +16 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte +76 -0
- package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.d.ts +12 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.js +9 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte +1680 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte.d.ts +18 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte +329 -0
- package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte.d.ts +3 -0
- package/dist/components/organisms/DashboardBuilderPage/DashboardBuilderPage.svelte.d.ts +1 -1
- package/dist/components/organisms/DesktopShell/DesktopShell.svelte.d.ts +1 -1
- package/dist/components/organisms/EditorShell/EditorShell.svelte +123 -28
- package/dist/components/organisms/EditorShell/EditorShell.svelte.d.ts +21 -1
- package/dist/components/organisms/FileUploader/FileUploader.svelte +77 -15
- package/dist/components/organisms/FileUploader/FileUploader.svelte.d.ts +11 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.stories.js +8 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.svelte +209 -0
- package/dist/components/organisms/ImportWizard/ImportWizard.svelte.d.ts +22 -0
- package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte +16 -0
- package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte.d.ts +18 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.stories.d.ts +12 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.stories.js +9 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.svelte +308 -0
- package/dist/components/organisms/VideoEditor/VideoEditor.svelte.d.ts +13 -0
- package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte +10 -0
- package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte.d.ts +3 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.js +9 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte +22 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte.d.ts +13 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte +8 -0
- package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.js +9 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte +32 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte.d.ts +17 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte +21 -0
- package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.d.ts +12 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.js +9 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte +22 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte.d.ts +13 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte +8 -0
- package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte.d.ts +3 -0
- package/dist/index.d.ts +83 -1
- package/dist/index.js +60 -0
- package/dist/types/xyflow-svelte.d.ts +62 -0
- package/dist/utils/audioProject.d.ts +19 -0
- package/dist/utils/audioProject.js +70 -0
- package/dist/utils/blogDocument.d.ts +175 -0
- package/dist/utils/blogDocument.js +220 -0
- package/dist/utils/blueprintRuntime.d.ts +34 -0
- package/dist/utils/blueprintRuntime.js +289 -0
- package/dist/utils/canvasDocument.d.ts +239 -0
- package/dist/utils/canvasDocument.js +443 -0
- package/dist/utils/canvasExport.d.ts +10 -0
- package/dist/utils/canvasExport.js +469 -0
- package/dist/utils/canvasHierarchy.d.ts +189 -0
- package/dist/utils/canvasHierarchy.js +674 -0
- package/dist/utils/canvasHistory.d.ts +19 -0
- package/dist/utils/canvasHistory.js +48 -0
- package/dist/utils/canvasWidget.d.ts +81 -0
- package/dist/utils/canvasWidget.js +597 -0
- package/dist/utils/mediaTime.d.ts +16 -0
- package/dist/utils/mediaTime.js +57 -0
- package/dist/utils/mediaTracks.d.ts +61 -0
- package/dist/utils/mediaTracks.js +102 -0
- package/dist/utils/nodeGraph.d.ts +354 -0
- package/dist/utils/nodeGraph.js +1453 -0
- package/dist/utils/queryBridge.d.ts +18 -0
- package/dist/utils/queryBridge.js +118 -0
- package/dist/utils/videoSequence.d.ts +13 -0
- package/dist/utils/videoSequence.js +63 -0
- package/package.json +5 -2
|
@@ -0,0 +1,1498 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, setContext } from 'svelte';
|
|
3
|
+
import MediaLayerItem from '../MediaLayerItem/MediaLayerItem.svelte';
|
|
4
|
+
import MarqueeRect from '../../atoms/MarqueeRect/MarqueeRect.svelte';
|
|
5
|
+
import {
|
|
6
|
+
WIDGET_CANVAS_CONTEXT,
|
|
7
|
+
snapToGrid,
|
|
8
|
+
snapWidgetRect,
|
|
9
|
+
clampWidgetRect,
|
|
10
|
+
type WidgetCanvasContext,
|
|
11
|
+
type WidgetRect
|
|
12
|
+
} from '../WidgetCanvas/widgetCanvasContext.js';
|
|
13
|
+
import {
|
|
14
|
+
attachMarqueeSelect,
|
|
15
|
+
resolveMarqueeSelection,
|
|
16
|
+
type MarqueeRect as MarqueeBox
|
|
17
|
+
} from '../../../utils/marqueeSelect.js';
|
|
18
|
+
import {
|
|
19
|
+
createCanvasGuide,
|
|
20
|
+
pathPointsToDoc,
|
|
21
|
+
rebakePathLayer,
|
|
22
|
+
snapLayerRect,
|
|
23
|
+
snapRectToGuides,
|
|
24
|
+
type CanvasDocument,
|
|
25
|
+
type CanvasGuide,
|
|
26
|
+
type CanvasGuideOrientation,
|
|
27
|
+
type CanvasLayer
|
|
28
|
+
} from '../../../utils/canvasDocument.js';
|
|
29
|
+
import {
|
|
30
|
+
applyScrollBoxOffsets,
|
|
31
|
+
computeAbsoluteRects,
|
|
32
|
+
contentPadding,
|
|
33
|
+
LAYOUT_BOX_KINDS,
|
|
34
|
+
isEffectivelyVisible,
|
|
35
|
+
isMarqueePassThroughKind,
|
|
36
|
+
paintTransformForLayer,
|
|
37
|
+
scrollBoxOverflow,
|
|
38
|
+
scrollBarMetrics,
|
|
39
|
+
slotFromLocalRect,
|
|
40
|
+
clipPathForLayer
|
|
41
|
+
} from '../../../utils/canvasHierarchy.js';
|
|
42
|
+
import { flattenLayersWithWidgets } from '../../../utils/canvasWidget.js';
|
|
43
|
+
import { backgroundAlpha } from '../../../utils/canvasExport.js';
|
|
44
|
+
import {
|
|
45
|
+
CANVAS_ELEMENT_MIME,
|
|
46
|
+
type CanvasElementDef
|
|
47
|
+
} from '../CanvasElementsPanel/CanvasElementsPanel.svelte';
|
|
48
|
+
|
|
49
|
+
interface MediaStageProps {
|
|
50
|
+
document: CanvasDocument;
|
|
51
|
+
selectedIds?: string[];
|
|
52
|
+
/** Multiplier on fit-to-view scale (1 = fit). */
|
|
53
|
+
zoom?: number;
|
|
54
|
+
showGrid?: boolean;
|
|
55
|
+
showGuides?: boolean;
|
|
56
|
+
snap?: boolean;
|
|
57
|
+
cellSize?: number;
|
|
58
|
+
/** Pen tool: click to place points, dblclick / Enter to finish, click first point to close. */
|
|
59
|
+
drawMode?: boolean;
|
|
60
|
+
/** Expand widget instances into resolved children for preview. */
|
|
61
|
+
expandWidgets?: boolean;
|
|
62
|
+
class?: string;
|
|
63
|
+
onselect?: (ids: string[]) => void;
|
|
64
|
+
onzoom?: (zoom: number) => void;
|
|
65
|
+
onlayerchange?: (layer: CanvasLayer) => void;
|
|
66
|
+
ondocumentchange?: (doc: CanvasDocument) => void;
|
|
67
|
+
oncontextlayer?: (payload: { id: string; x: number; y: number }) => void;
|
|
68
|
+
/** Drop an element from the Elements panel onto the artboard. */
|
|
69
|
+
ondropelement?: (payload: { def: CanvasElementDef; x: number; y: number }) => void;
|
|
70
|
+
ondrawcomplete?: (payload: { points: { x: number; y: number }[]; closed: boolean }) => void;
|
|
71
|
+
ondrawcancel?: () => void;
|
|
72
|
+
/** Double-click a widget instance or container. */
|
|
73
|
+
onenterlayer?: (id: string) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let {
|
|
77
|
+
document: doc,
|
|
78
|
+
selectedIds = [],
|
|
79
|
+
zoom = 1,
|
|
80
|
+
showGrid = false,
|
|
81
|
+
showGuides = true,
|
|
82
|
+
snap = true,
|
|
83
|
+
cellSize = 8,
|
|
84
|
+
drawMode = false,
|
|
85
|
+
expandWidgets = true,
|
|
86
|
+
class: className = '',
|
|
87
|
+
onselect,
|
|
88
|
+
onzoom,
|
|
89
|
+
onlayerchange,
|
|
90
|
+
ondocumentchange,
|
|
91
|
+
oncontextlayer,
|
|
92
|
+
ondropelement,
|
|
93
|
+
ondrawcomplete,
|
|
94
|
+
ondrawcancel,
|
|
95
|
+
onenterlayer
|
|
96
|
+
}: MediaStageProps = $props();
|
|
97
|
+
|
|
98
|
+
let viewportEl = $state<HTMLDivElement | null>(null);
|
|
99
|
+
let artboardEl = $state<HTMLDivElement | null>(null);
|
|
100
|
+
let stageEl = $state<HTMLDivElement | null>(null);
|
|
101
|
+
let vw = $state(0);
|
|
102
|
+
let vh = $state(0);
|
|
103
|
+
/** Artboard top-left relative to the visible scroll viewport (updates on scroll/zoom). */
|
|
104
|
+
let boardScreenX = $state(0);
|
|
105
|
+
let boardScreenY = $state(0);
|
|
106
|
+
let marquee = $state<MarqueeBox | null>(null);
|
|
107
|
+
let skipClearClick = false;
|
|
108
|
+
let draggingGuideId = $state<string | null>(null);
|
|
109
|
+
/** Full-stage guide line while dragging (screen coords relative to stage). */
|
|
110
|
+
let guidePreview = $state<{
|
|
111
|
+
orientation: CanvasGuideOrientation;
|
|
112
|
+
x: number;
|
|
113
|
+
y: number;
|
|
114
|
+
} | null>(null);
|
|
115
|
+
let draftPoints = $state<{ x: number; y: number }[]>([]);
|
|
116
|
+
let draftCursor = $state<{ x: number; y: number } | null>(null);
|
|
117
|
+
let activePathPoint = $state<number | null>(null);
|
|
118
|
+
/** Local layer edits while dragging — commit to document on pointer up. */
|
|
119
|
+
let draftLayers = $state<CanvasLayer[] | null>(null);
|
|
120
|
+
let interactCount = $state(0);
|
|
121
|
+
|
|
122
|
+
const RULER_TOP = 20;
|
|
123
|
+
/** Wide enough for upright major labels (e.g. 1000) beside ticks. */
|
|
124
|
+
const RULER_LEFT = 36;
|
|
125
|
+
const PAD = 48;
|
|
126
|
+
const workingLayers = $derived(draftLayers ?? doc.layers);
|
|
127
|
+
const displayLayers = $derived(
|
|
128
|
+
expandWidgets
|
|
129
|
+
? flattenLayersWithWidgets(workingLayers, doc.widgets ?? [])
|
|
130
|
+
: workingLayers
|
|
131
|
+
);
|
|
132
|
+
const absMap = $derived(
|
|
133
|
+
computeAbsoluteRects(displayLayers, { width: doc.width, height: doc.height })
|
|
134
|
+
);
|
|
135
|
+
/** Ephemeral scroll offsets for scrollBox preview (not persisted). */
|
|
136
|
+
let scrollOffsets = $state<Map<string, { x: number; y: number }>>(new Map());
|
|
137
|
+
const paintAbsMap = $derived(
|
|
138
|
+
applyScrollBoxOffsets(displayLayers, absMap, scrollOffsets)
|
|
139
|
+
);
|
|
140
|
+
const sorted = $derived.by(() => {
|
|
141
|
+
const out: typeof displayLayers = [];
|
|
142
|
+
const walk = (parentId: string | null) => {
|
|
143
|
+
const kids = displayLayers
|
|
144
|
+
.filter((l) => (l.parentId ?? null) === parentId)
|
|
145
|
+
.sort((a, b) => a.zIndex - b.zIndex);
|
|
146
|
+
for (const k of kids) {
|
|
147
|
+
out.push(k);
|
|
148
|
+
walk(k.id);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
walk(null);
|
|
152
|
+
return out;
|
|
153
|
+
});
|
|
154
|
+
const layerById = $derived(new Map(displayLayers.map((l) => [l.id, l])));
|
|
155
|
+
const effectivelyVisible = $derived.by(() => {
|
|
156
|
+
const set = new Set<string>();
|
|
157
|
+
for (const l of displayLayers) {
|
|
158
|
+
if (isEffectivelyVisible(displayLayers, l.id, layerById)) set.add(l.id);
|
|
159
|
+
}
|
|
160
|
+
return set;
|
|
161
|
+
});
|
|
162
|
+
const selectedSet = $derived(new Set(selectedIds));
|
|
163
|
+
const sceneLayerIds = $derived(new Set(workingLayers.map((l) => l.id)));
|
|
164
|
+
|
|
165
|
+
function resolveSelectableId(id: string): string {
|
|
166
|
+
if (sceneLayerIds.has(id)) return id;
|
|
167
|
+
const host = id.split('::')[0];
|
|
168
|
+
return sceneLayerIds.has(host) ? host : id;
|
|
169
|
+
}
|
|
170
|
+
const guides = $derived(doc.guides ?? []);
|
|
171
|
+
const guidesLocked = $derived(!!doc.guidesLocked);
|
|
172
|
+
const artboardTransparent = $derived(backgroundAlpha(doc.background) < 0.999);
|
|
173
|
+
const selectedPath = $derived(
|
|
174
|
+
selectedIds.length === 1
|
|
175
|
+
? (workingLayers.find((l) => l.id === selectedIds[0] && l.kind === 'path') ?? null)
|
|
176
|
+
: null
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const fitScale = $derived.by(() => {
|
|
180
|
+
if (vw <= 0 || vh <= 0) return 1;
|
|
181
|
+
const sx = (vw - PAD * 2) / doc.width;
|
|
182
|
+
const sy = (vh - PAD * 2) / doc.height;
|
|
183
|
+
return Math.max(0.05, Math.min(sx, sy, 1));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const scale = $derived(Math.min(4, Math.max(0.1, fitScale * zoom)));
|
|
187
|
+
const boardW = $derived(doc.width * scale);
|
|
188
|
+
const boardH = $derived(doc.height * scale);
|
|
189
|
+
|
|
190
|
+
const guideSnapList = $derived(
|
|
191
|
+
guides.map((g) => ({ orientation: g.orientation, position: g.position }))
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
type RulerTick = { pos: number; level: 'major' | 'mid' | 'minor' };
|
|
195
|
+
|
|
196
|
+
/** Nice major step so labels sit ~56–80px apart on screen. */
|
|
197
|
+
function majorStepForScale(s: number) {
|
|
198
|
+
const targetDoc = 64 / Math.max(0.05, s);
|
|
199
|
+
const nice = [1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 5000];
|
|
200
|
+
let best = nice[nice.length - 1];
|
|
201
|
+
for (const n of nice) {
|
|
202
|
+
if (n >= targetDoc) {
|
|
203
|
+
best = n;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
best = n;
|
|
207
|
+
}
|
|
208
|
+
return best;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function buildTicks(length: number, s: number): RulerTick[] {
|
|
212
|
+
const major = majorStepForScale(s);
|
|
213
|
+
const mid = major / 2;
|
|
214
|
+
const minor = major / 10;
|
|
215
|
+
const ticks: RulerTick[] = [];
|
|
216
|
+
const eps = minor / 4;
|
|
217
|
+
for (let p = 0; p <= length + eps; p += minor) {
|
|
218
|
+
const pos = Math.round(p * 1000) / 1000;
|
|
219
|
+
if (pos > length) break;
|
|
220
|
+
const nearMajor = Math.abs(pos % major) < eps || Math.abs((pos % major) - major) < eps;
|
|
221
|
+
const nearMid = Math.abs(pos % mid) < eps || Math.abs((pos % mid) - mid) < eps;
|
|
222
|
+
ticks.push({
|
|
223
|
+
pos,
|
|
224
|
+
level: nearMajor ? 'major' : nearMid ? 'mid' : 'minor'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return ticks;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const rulerTicksX = $derived(buildTicks(doc.width, scale));
|
|
231
|
+
const rulerTicksY = $derived(buildTicks(doc.height, scale));
|
|
232
|
+
|
|
233
|
+
/** Tick position inside the top / left ruler strips (aligned with the scroll viewport). */
|
|
234
|
+
function tickX(docPos: number) {
|
|
235
|
+
return boardScreenX + docPos * scale;
|
|
236
|
+
}
|
|
237
|
+
function tickY(docPos: number) {
|
|
238
|
+
return boardScreenY + docPos * scale;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const api: WidgetCanvasContext = {
|
|
242
|
+
get showGrid() {
|
|
243
|
+
return showGrid;
|
|
244
|
+
},
|
|
245
|
+
get snap() {
|
|
246
|
+
return snap;
|
|
247
|
+
},
|
|
248
|
+
get cellSize() {
|
|
249
|
+
return cellSize;
|
|
250
|
+
},
|
|
251
|
+
get autoCells() {
|
|
252
|
+
return 8;
|
|
253
|
+
},
|
|
254
|
+
get cellSizeMode() {
|
|
255
|
+
return 'fixed' as const;
|
|
256
|
+
},
|
|
257
|
+
get scale() {
|
|
258
|
+
return scale;
|
|
259
|
+
},
|
|
260
|
+
getBounds() {
|
|
261
|
+
return { width: doc.width, height: doc.height };
|
|
262
|
+
},
|
|
263
|
+
snapValue(value: number) {
|
|
264
|
+
return snapToGrid(value, cellSize, snap);
|
|
265
|
+
},
|
|
266
|
+
snapRect(rect: WidgetRect, minW: number, minH: number) {
|
|
267
|
+
let r = snapWidgetRect(rect, cellSize, snap, minW, minH);
|
|
268
|
+
if (guideSnapList.length) {
|
|
269
|
+
const snapped = snapRectToGuides(r, guideSnapList, 8);
|
|
270
|
+
r = { ...r, x: snapped.x, y: snapped.y };
|
|
271
|
+
}
|
|
272
|
+
return r;
|
|
273
|
+
},
|
|
274
|
+
clampRect(rect: WidgetRect, minW: number, minH: number) {
|
|
275
|
+
return clampWidgetRect(rect, { width: doc.width, height: doc.height }, minW, minH);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
setContext(WIDGET_CANVAS_CONTEXT, api);
|
|
280
|
+
|
|
281
|
+
const gridOverlayStyle = $derived.by(() => {
|
|
282
|
+
const cell = Math.max(4, cellSize);
|
|
283
|
+
const major = cell * 5;
|
|
284
|
+
const fine = 'color-mix(in oklab, #0f172a 18%, transparent)';
|
|
285
|
+
const bold = 'color-mix(in oklab, #0f172a 32%, transparent)';
|
|
286
|
+
return [
|
|
287
|
+
`background-image:`,
|
|
288
|
+
`linear-gradient(to right, ${fine} 1px, transparent 1px),`,
|
|
289
|
+
`linear-gradient(to bottom, ${fine} 1px, transparent 1px),`,
|
|
290
|
+
`linear-gradient(to right, ${bold} 1px, transparent 1px),`,
|
|
291
|
+
`linear-gradient(to bottom, ${bold} 1px, transparent 1px);`,
|
|
292
|
+
`background-size:${cell}px ${cell}px, ${cell}px ${cell}px, ${major}px ${major}px, ${major}px ${major}px;`
|
|
293
|
+
].join('');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function syncBoardScreen() {
|
|
297
|
+
if (!viewportEl || !artboardEl) return;
|
|
298
|
+
const v = viewportEl.getBoundingClientRect();
|
|
299
|
+
const a = artboardEl.getBoundingClientRect();
|
|
300
|
+
boardScreenX = a.left - v.left;
|
|
301
|
+
boardScreenY = a.top - v.top;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function patchGuides(next: CanvasGuide[], locked = guidesLocked) {
|
|
305
|
+
ondocumentchange?.({ ...doc, guides: next, guidesLocked: locked });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function absToLocalUpdate(layer: CanvasLayer, absRect: WidgetRect): CanvasLayer {
|
|
309
|
+
const parentId = layer.parentId ?? null;
|
|
310
|
+
const parentAbs = parentId ? (absMap.get(parentId) ?? null) : null;
|
|
311
|
+
const parentLayer = parentId ? (workingLayers.find((l) => l.id === parentId) ?? null) : null;
|
|
312
|
+
const pad = parentLayer
|
|
313
|
+
? contentPadding(parentLayer)
|
|
314
|
+
: { left: 0, top: 0, right: 0, bottom: 0 };
|
|
315
|
+
const originX = (parentAbs?.x ?? 0) + pad.left;
|
|
316
|
+
const originY = (parentAbs?.y ?? 0) + pad.top;
|
|
317
|
+
const contentW = parentAbs
|
|
318
|
+
? Math.max(0, parentAbs.w - pad.left - pad.right)
|
|
319
|
+
: doc.width;
|
|
320
|
+
const contentH = parentAbs
|
|
321
|
+
? Math.max(0, parentAbs.h - pad.top - pad.bottom)
|
|
322
|
+
: doc.height;
|
|
323
|
+
const local = {
|
|
324
|
+
x: absRect.x - originX,
|
|
325
|
+
y: absRect.y - originY,
|
|
326
|
+
w: absRect.w,
|
|
327
|
+
h: absRect.h
|
|
328
|
+
};
|
|
329
|
+
const anchors = layer.slot?.anchors ?? { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
330
|
+
const slot = {
|
|
331
|
+
...slotFromLocalRect({ width: contentW, height: contentH }, local, anchors),
|
|
332
|
+
padding: layer.slot?.padding,
|
|
333
|
+
sizeRule: layer.slot?.sizeRule,
|
|
334
|
+
alignment: layer.slot?.alignment,
|
|
335
|
+
order: layer.slot?.order
|
|
336
|
+
};
|
|
337
|
+
return { ...layer, rect: local, slot };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function commitDraft() {
|
|
341
|
+
if (!draftLayers) return;
|
|
342
|
+
const layers = draftLayers;
|
|
343
|
+
draftLayers = null;
|
|
344
|
+
ondocumentchange?.({ ...doc, layers });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function handleLayerInteract(active: boolean) {
|
|
348
|
+
if (active) {
|
|
349
|
+
interactCount += 1;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
interactCount = Math.max(0, interactCount - 1);
|
|
353
|
+
if (interactCount === 0) commitDraft();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function cancelLayerDraft() {
|
|
357
|
+
draftLayers = null;
|
|
358
|
+
interactCount = 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isLayoutLocked(layer: CanvasLayer): boolean {
|
|
362
|
+
const parentId = layer.parentId ?? null;
|
|
363
|
+
if (!parentId) return false;
|
|
364
|
+
const parent = workingLayers.find((l) => l.id === parentId);
|
|
365
|
+
return !!parent && LAYOUT_BOX_KINDS.has(parent.kind);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function handleRect(layer: CanvasLayer, rect: WidgetRect) {
|
|
369
|
+
if (!sceneLayerIds.has(layer.id)) return;
|
|
370
|
+
if (isLayoutLocked(layer)) return;
|
|
371
|
+
const layers = draftLayers ?? doc.layers;
|
|
372
|
+
const source = layers.find((l) => l.id === layer.id);
|
|
373
|
+
if (!source) return;
|
|
374
|
+
|
|
375
|
+
const minW = source.kind === 'line' || source.kind === 'arrow' ? 24 : 40;
|
|
376
|
+
const minH = source.kind === 'line' ? 2 : 24;
|
|
377
|
+
const nextAbs = snapLayerRect(
|
|
378
|
+
{ x: rect.x, y: rect.y, w: rect.w, h: rect.h },
|
|
379
|
+
cellSize,
|
|
380
|
+
snap,
|
|
381
|
+
{ width: doc.width, height: doc.height },
|
|
382
|
+
minW,
|
|
383
|
+
minH,
|
|
384
|
+
guideSnapList
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const prevAbs = absMap.get(source.id) ?? source.rect;
|
|
388
|
+
const dx = nextAbs.x - prevAbs.x;
|
|
389
|
+
const dy = nextAbs.y - prevAbs.y;
|
|
390
|
+
const moving =
|
|
391
|
+
Math.abs(nextAbs.w - prevAbs.w) < 0.5 && Math.abs(nextAbs.h - prevAbs.h) < 0.5;
|
|
392
|
+
const groupMove =
|
|
393
|
+
moving &&
|
|
394
|
+
selectedIds.length > 1 &&
|
|
395
|
+
selectedSet.has(source.id) &&
|
|
396
|
+
(Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01);
|
|
397
|
+
|
|
398
|
+
if (groupMove) {
|
|
399
|
+
draftLayers = layers.map((l) => {
|
|
400
|
+
if (!selectedSet.has(l.id) || l.locked) return l;
|
|
401
|
+
const a = absMap.get(l.id) ?? l.rect;
|
|
402
|
+
const moved = {
|
|
403
|
+
x: a.x + dx,
|
|
404
|
+
y: a.y + dy,
|
|
405
|
+
w: a.w,
|
|
406
|
+
h: a.h
|
|
407
|
+
};
|
|
408
|
+
return absToLocalUpdate(l, moved);
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const updated = absToLocalUpdate(source, nextAbs);
|
|
414
|
+
draftLayers = layers.map((l) => (l.id === updated.id ? updated : l));
|
|
415
|
+
// If somehow interact end already fired without draft, commit immediately.
|
|
416
|
+
if (interactCount === 0) commitDraft();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function selectLayer(id: string, e: MouseEvent) {
|
|
420
|
+
e.stopPropagation();
|
|
421
|
+
const realId = resolveSelectableId(id);
|
|
422
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
|
423
|
+
const set = new Set(selectedIds);
|
|
424
|
+
if (set.has(realId)) set.delete(realId);
|
|
425
|
+
else set.add(realId);
|
|
426
|
+
onselect?.([...set]);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Keep multi-selection when clicking an already-selected item (group drag).
|
|
430
|
+
if (selectedSet.has(realId)) return;
|
|
431
|
+
onselect?.([realId]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function clearSelectionIfEmptyClick(e?: MouseEvent) {
|
|
435
|
+
if (skipClearClick) {
|
|
436
|
+
skipClearClick = false;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (e) {
|
|
440
|
+
const t = e.target as HTMLElement | null;
|
|
441
|
+
if (
|
|
442
|
+
t?.closest(
|
|
443
|
+
'[data-layer-item], [data-layer-frame], [data-resize-handle], [data-guide], [data-ruler]'
|
|
444
|
+
)
|
|
445
|
+
) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
onselect?.([]);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function hasElementDrag(dt: DataTransfer | null) {
|
|
453
|
+
if (!dt) return false;
|
|
454
|
+
return [...dt.types].includes(CANVAS_ELEMENT_MIME);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function handleElementDragOver(e: DragEvent) {
|
|
458
|
+
if (!hasElementDrag(e.dataTransfer)) return;
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function handleElementDrop(e: DragEvent) {
|
|
464
|
+
if (!hasElementDrag(e.dataTransfer)) return;
|
|
465
|
+
e.preventDefault();
|
|
466
|
+
e.stopPropagation();
|
|
467
|
+
const raw = e.dataTransfer?.getData(CANVAS_ELEMENT_MIME);
|
|
468
|
+
if (!raw || !ondropelement) return;
|
|
469
|
+
try {
|
|
470
|
+
const def = JSON.parse(raw) as CanvasElementDef;
|
|
471
|
+
const pt = clientToDoc(e.clientX, e.clientY);
|
|
472
|
+
ondropelement({ def, x: pt.x, y: pt.y });
|
|
473
|
+
} catch {
|
|
474
|
+
/* ignore bad payload */
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function layerHitBoxes(): { id: string; x: number; y: number; width: number; height: number }[] {
|
|
479
|
+
if (!artboardEl || !viewportEl) return [];
|
|
480
|
+
const vRect = viewportEl.getBoundingClientRect();
|
|
481
|
+
const aRect = artboardEl.getBoundingClientRect();
|
|
482
|
+
const originX = aRect.left - vRect.left + viewportEl.scrollLeft;
|
|
483
|
+
const originY = aRect.top - vRect.top + viewportEl.scrollTop;
|
|
484
|
+
const s = scale;
|
|
485
|
+
return sorted
|
|
486
|
+
.filter((l) => {
|
|
487
|
+
if (!effectivelyVisible.has(l.id) || l.locked) return false;
|
|
488
|
+
const realId = resolveSelectableId(l.id);
|
|
489
|
+
if (!sceneLayerIds.has(realId)) return false;
|
|
490
|
+
// Don't always rubber-band-select the full-bleed root panel.
|
|
491
|
+
const scene = workingLayers.find((x) => x.id === realId);
|
|
492
|
+
if (scene?.kind === 'canvasPanel') return false;
|
|
493
|
+
return true;
|
|
494
|
+
})
|
|
495
|
+
.map((l) => {
|
|
496
|
+
const r = paintAbsMap.get(l.id) ?? absMap.get(l.id) ?? l.rect;
|
|
497
|
+
return {
|
|
498
|
+
id: resolveSelectableId(l.id),
|
|
499
|
+
x: originX + r.x * s,
|
|
500
|
+
y: originY + r.y * s,
|
|
501
|
+
width: r.w * s,
|
|
502
|
+
height: r.h * s
|
|
503
|
+
};
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function clientToDoc(clientX: number, clientY: number) {
|
|
508
|
+
if (!artboardEl) return { x: 0, y: 0 };
|
|
509
|
+
const aRect = artboardEl.getBoundingClientRect();
|
|
510
|
+
return {
|
|
511
|
+
x: (clientX - aRect.left) / scale,
|
|
512
|
+
y: (clientY - aRect.top) / scale
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Topmost selectable layer at a document point (deepest in paint order). */
|
|
517
|
+
function pickLayerAtDoc(pt: { x: number; y: number }): string | null {
|
|
518
|
+
for (let i = sorted.length - 1; i >= 0; i--) {
|
|
519
|
+
const l = sorted[i];
|
|
520
|
+
if (!effectivelyVisible.has(l.id) || l.locked) continue;
|
|
521
|
+
const selectable = resolveSelectableId(l.id);
|
|
522
|
+
if (!sceneLayerIds.has(l.id) && !sceneLayerIds.has(selectable)) continue;
|
|
523
|
+
const r = paintAbsMap.get(l.id) ?? l.rect;
|
|
524
|
+
if (pt.x >= r.x && pt.x <= r.x + r.w && pt.y >= r.y && pt.y <= r.y + r.h) {
|
|
525
|
+
return selectable;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function findScrollBoxAtDoc(pt: { x: number; y: number }): string | null {
|
|
532
|
+
const byId = new Map(displayLayers.map((l) => [l.id, l]));
|
|
533
|
+
for (let i = sorted.length - 1; i >= 0; i--) {
|
|
534
|
+
const l = sorted[i];
|
|
535
|
+
if (!effectivelyVisible.has(l.id)) continue;
|
|
536
|
+
const r = paintAbsMap.get(l.id) ?? absMap.get(l.id);
|
|
537
|
+
if (!r) continue;
|
|
538
|
+
if (pt.x < r.x || pt.x > r.x + r.w || pt.y < r.y || pt.y > r.y + r.h) continue;
|
|
539
|
+
let cur: typeof l | undefined = l;
|
|
540
|
+
while (cur) {
|
|
541
|
+
if (cur.kind === 'scrollBox') return cur.id;
|
|
542
|
+
cur = cur.parentId ? byId.get(cur.parentId) : undefined;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function handleScrollWheel(e: WheelEvent) {
|
|
549
|
+
if (drawMode) return;
|
|
550
|
+
const pt = clientToDoc(e.clientX, e.clientY);
|
|
551
|
+
const boxId = findScrollBoxAtDoc(pt);
|
|
552
|
+
if (!boxId) return;
|
|
553
|
+
setScrollOffset(boxId, {
|
|
554
|
+
x: (scrollOffsets.get(boxId)?.x ?? 0) + e.deltaX,
|
|
555
|
+
y: (scrollOffsets.get(boxId)?.y ?? 0) + e.deltaY
|
|
556
|
+
}, e);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function setScrollOffset(
|
|
560
|
+
boxId: string,
|
|
561
|
+
raw: { x: number; y: number },
|
|
562
|
+
e?: WheelEvent
|
|
563
|
+
) {
|
|
564
|
+
const { maxX, maxY } = scrollBoxOverflow(boxId, displayLayers, absMap);
|
|
565
|
+
if (maxX <= 0 && maxY <= 0) return;
|
|
566
|
+
const cur = scrollOffsets.get(boxId) ?? { x: 0, y: 0 };
|
|
567
|
+
const next = {
|
|
568
|
+
x: Math.min(maxX, Math.max(0, raw.x)),
|
|
569
|
+
y: Math.min(maxY, Math.max(0, raw.y))
|
|
570
|
+
};
|
|
571
|
+
if (next.x === cur.x && next.y === cur.y) return;
|
|
572
|
+
e?.preventDefault();
|
|
573
|
+
e?.stopPropagation();
|
|
574
|
+
const map = new Map(scrollOffsets);
|
|
575
|
+
map.set(boxId, next);
|
|
576
|
+
scrollOffsets = map;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function beginScrollThumbDrag(
|
|
580
|
+
boxId: string,
|
|
581
|
+
axis: 'x' | 'y',
|
|
582
|
+
chrome: { x: number; y: number; maxX: number; maxY: number },
|
|
583
|
+
vMetrics: ReturnType<typeof scrollBarMetrics>,
|
|
584
|
+
hMetrics: ReturnType<typeof scrollBarMetrics>,
|
|
585
|
+
e: PointerEvent
|
|
586
|
+
) {
|
|
587
|
+
e.stopPropagation();
|
|
588
|
+
e.preventDefault();
|
|
589
|
+
const thumb = e.currentTarget as HTMLElement;
|
|
590
|
+
const track = thumb.parentElement;
|
|
591
|
+
if (!track) return;
|
|
592
|
+
thumb.setPointerCapture(e.pointerId);
|
|
593
|
+
|
|
594
|
+
const move = (ev: PointerEvent) => {
|
|
595
|
+
const rect = track.getBoundingClientRect();
|
|
596
|
+
if (axis === 'y' && vMetrics) {
|
|
597
|
+
const travel = Math.max(0, rect.height - vMetrics.thumb);
|
|
598
|
+
const y = ev.clientY - rect.top - vMetrics.thumb / 2;
|
|
599
|
+
const ratio = travel > 0 ? Math.min(1, Math.max(0, y / travel)) : 0;
|
|
600
|
+
setScrollOffset(boxId, { x: chrome.x, y: ratio * chrome.maxY });
|
|
601
|
+
} else if (axis === 'x' && hMetrics) {
|
|
602
|
+
const travel = Math.max(0, rect.width - hMetrics.thumb);
|
|
603
|
+
const x = ev.clientX - rect.left - hMetrics.thumb / 2;
|
|
604
|
+
const ratio = travel > 0 ? Math.min(1, Math.max(0, x / travel)) : 0;
|
|
605
|
+
setScrollOffset(boxId, { x: ratio * chrome.maxX, y: chrome.y });
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const up = (ev: PointerEvent) => {
|
|
609
|
+
thumb.releasePointerCapture(ev.pointerId);
|
|
610
|
+
thumb.removeEventListener('pointermove', move);
|
|
611
|
+
thumb.removeEventListener('pointerup', up);
|
|
612
|
+
thumb.removeEventListener('pointercancel', up);
|
|
613
|
+
};
|
|
614
|
+
thumb.addEventListener('pointermove', move);
|
|
615
|
+
thumb.addEventListener('pointerup', up);
|
|
616
|
+
thumb.addEventListener('pointercancel', up);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function jumpScrollTrack(
|
|
620
|
+
boxId: string,
|
|
621
|
+
axis: 'x' | 'y',
|
|
622
|
+
chrome: { x: number; y: number; maxX: number; maxY: number },
|
|
623
|
+
vMetrics: ReturnType<typeof scrollBarMetrics>,
|
|
624
|
+
hMetrics: ReturnType<typeof scrollBarMetrics>,
|
|
625
|
+
e: PointerEvent
|
|
626
|
+
) {
|
|
627
|
+
if ((e.target as HTMLElement).dataset.scrollThumb) return;
|
|
628
|
+
e.stopPropagation();
|
|
629
|
+
const track = e.currentTarget as HTMLElement;
|
|
630
|
+
const rect = track.getBoundingClientRect();
|
|
631
|
+
if (axis === 'y' && vMetrics) {
|
|
632
|
+
const travel = Math.max(0, rect.height - vMetrics.thumb);
|
|
633
|
+
const y = e.clientY - rect.top - vMetrics.thumb / 2;
|
|
634
|
+
const ratio = travel > 0 ? Math.min(1, Math.max(0, y / travel)) : 0;
|
|
635
|
+
setScrollOffset(boxId, { x: chrome.x, y: ratio * chrome.maxY });
|
|
636
|
+
} else if (axis === 'x' && hMetrics) {
|
|
637
|
+
const travel = Math.max(0, rect.width - hMetrics.thumb);
|
|
638
|
+
const x = e.clientX - rect.left - hMetrics.thumb / 2;
|
|
639
|
+
const ratio = travel > 0 ? Math.min(1, Math.max(0, x / travel)) : 0;
|
|
640
|
+
setScrollOffset(boxId, { x: ratio * chrome.maxX, y: chrome.y });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function snapDocPoint(pt: { x: number; y: number }) {
|
|
645
|
+
if (!snap) return pt;
|
|
646
|
+
return {
|
|
647
|
+
x: Math.round(pt.x / cellSize) * cellSize,
|
|
648
|
+
y: Math.round(pt.y / cellSize) * cellSize
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function finishDraft(closed: boolean) {
|
|
653
|
+
if (draftPoints.length < 2) {
|
|
654
|
+
draftPoints = [];
|
|
655
|
+
draftCursor = null;
|
|
656
|
+
ondrawcancel?.();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const points = [...draftPoints];
|
|
660
|
+
draftPoints = [];
|
|
661
|
+
draftCursor = null;
|
|
662
|
+
ondrawcomplete?.({ points, closed });
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function cancelDraft() {
|
|
666
|
+
draftPoints = [];
|
|
667
|
+
draftCursor = null;
|
|
668
|
+
ondrawcancel?.();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function handleDrawClick(e: MouseEvent) {
|
|
672
|
+
if (!drawMode) return;
|
|
673
|
+
e.preventDefault();
|
|
674
|
+
e.stopPropagation();
|
|
675
|
+
const raw = clientToDoc(e.clientX, e.clientY);
|
|
676
|
+
const pt = snapDocPoint(raw);
|
|
677
|
+
if (draftPoints.length >= 3) {
|
|
678
|
+
const first = draftPoints[0];
|
|
679
|
+
const dist = Math.hypot(pt.x - first.x, pt.y - first.y);
|
|
680
|
+
if (dist <= 12 / scale) {
|
|
681
|
+
finishDraft(true);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
draftPoints = [...draftPoints, pt];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function handleDrawMove(e: MouseEvent) {
|
|
689
|
+
if (!drawMode || !draftPoints.length) {
|
|
690
|
+
draftCursor = null;
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
draftCursor = snapDocPoint(clientToDoc(e.clientX, e.clientY));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function handleDrawDblClick(e: MouseEvent) {
|
|
697
|
+
if (!drawMode) return;
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
e.stopPropagation();
|
|
700
|
+
finishDraft(false);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function pathAbsRect(layer: CanvasLayer) {
|
|
704
|
+
return paintAbsMap.get(layer.id) ?? absMap.get(layer.id) ?? layer.rect;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function pathAbsPoints(layer: CanvasLayer) {
|
|
708
|
+
return pathPointsToDoc(layer, pathAbsRect(layer));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function commitPathDocPoints(layer: CanvasLayer, docPoints: { x: number; y: number }[]) {
|
|
712
|
+
const baked = rebakePathLayer(layer, docPoints);
|
|
713
|
+
// rebake yields an absolute rect; convert back to parent-local for the document.
|
|
714
|
+
const localized = absToLocalUpdate(
|
|
715
|
+
{ ...layer, points: baked.points, closed: baked.closed },
|
|
716
|
+
baked.rect
|
|
717
|
+
);
|
|
718
|
+
onlayerchange?.({
|
|
719
|
+
...localized,
|
|
720
|
+
points: baked.points,
|
|
721
|
+
closed: baked.closed
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function beginPathPointDrag(index: number, e: PointerEvent) {
|
|
726
|
+
if (!selectedPath || selectedPath.locked) return;
|
|
727
|
+
e.preventDefault();
|
|
728
|
+
e.stopPropagation();
|
|
729
|
+
activePathPoint = index;
|
|
730
|
+
const meta = selectedPath;
|
|
731
|
+
const working = pathAbsPoints(meta);
|
|
732
|
+
const target = e.currentTarget as HTMLElement;
|
|
733
|
+
target.setPointerCapture(e.pointerId);
|
|
734
|
+
const onMove = (ev: PointerEvent) => {
|
|
735
|
+
working[index] = snapDocPoint(clientToDoc(ev.clientX, ev.clientY));
|
|
736
|
+
commitPathDocPoints(
|
|
737
|
+
meta,
|
|
738
|
+
working.map((p) => ({ ...p }))
|
|
739
|
+
);
|
|
740
|
+
};
|
|
741
|
+
const onUp = () => {
|
|
742
|
+
try {
|
|
743
|
+
target.releasePointerCapture(e.pointerId);
|
|
744
|
+
} catch {
|
|
745
|
+
/* ignore */
|
|
746
|
+
}
|
|
747
|
+
window.removeEventListener('pointermove', onMove);
|
|
748
|
+
window.removeEventListener('pointerup', onUp);
|
|
749
|
+
};
|
|
750
|
+
window.addEventListener('pointermove', onMove);
|
|
751
|
+
window.addEventListener('pointerup', onUp);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function insertPathPoint(afterIndex: number, e: MouseEvent) {
|
|
755
|
+
if (!selectedPath || selectedPath.locked) return;
|
|
756
|
+
e.preventDefault();
|
|
757
|
+
e.stopPropagation();
|
|
758
|
+
const docs = pathAbsPoints(selectedPath);
|
|
759
|
+
const pt = snapDocPoint(clientToDoc(e.clientX, e.clientY));
|
|
760
|
+
docs.splice(afterIndex + 1, 0, pt);
|
|
761
|
+
commitPathDocPoints(selectedPath, docs);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function removeActivePathPoint() {
|
|
765
|
+
if (!selectedPath || selectedPath.locked || activePathPoint == null) return;
|
|
766
|
+
const docs = pathAbsPoints(selectedPath);
|
|
767
|
+
if (docs.length <= 2) return;
|
|
768
|
+
docs.splice(activePathPoint, 1);
|
|
769
|
+
activePathPoint = null;
|
|
770
|
+
commitPathDocPoints(selectedPath, docs);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
$effect(() => {
|
|
774
|
+
if (!drawMode) {
|
|
775
|
+
draftPoints = [];
|
|
776
|
+
draftCursor = null;
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
$effect(() => {
|
|
781
|
+
if (!selectedPath) activePathPoint = null;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
function clampGuidePos(orientation: CanvasGuideOrientation, pos: number) {
|
|
785
|
+
const max = orientation === 'vertical' ? doc.width : doc.height;
|
|
786
|
+
let p = Math.min(Math.max(0, pos), max);
|
|
787
|
+
if (snap) p = snapToGrid(p, cellSize, true);
|
|
788
|
+
return p;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function setGuidePreview(orientation: CanvasGuideOrientation, clientX: number, clientY: number) {
|
|
792
|
+
if (!stageEl) return;
|
|
793
|
+
const r = stageEl.getBoundingClientRect();
|
|
794
|
+
guidePreview = {
|
|
795
|
+
orientation,
|
|
796
|
+
x: clientX - r.left,
|
|
797
|
+
y: clientY - r.top
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function beginGuideDrag(guide: CanvasGuide, e: PointerEvent) {
|
|
802
|
+
if (guidesLocked || guide.locked) return;
|
|
803
|
+
e.preventDefault();
|
|
804
|
+
e.stopPropagation();
|
|
805
|
+
draggingGuideId = guide.id;
|
|
806
|
+
setGuidePreview(guide.orientation, e.clientX, e.clientY);
|
|
807
|
+
const target = e.currentTarget as HTMLElement;
|
|
808
|
+
target.setPointerCapture(e.pointerId);
|
|
809
|
+
|
|
810
|
+
const onMove = (ev: PointerEvent) => {
|
|
811
|
+
setGuidePreview(guide.orientation, ev.clientX, ev.clientY);
|
|
812
|
+
const pt = clientToDoc(ev.clientX, ev.clientY);
|
|
813
|
+
const pos = clampGuidePos(
|
|
814
|
+
guide.orientation,
|
|
815
|
+
guide.orientation === 'vertical' ? pt.x : pt.y
|
|
816
|
+
);
|
|
817
|
+
patchGuides(guides.map((g) => (g.id === guide.id ? { ...g, position: pos } : g)));
|
|
818
|
+
};
|
|
819
|
+
const onUp = (ev: PointerEvent) => {
|
|
820
|
+
draggingGuideId = null;
|
|
821
|
+
guidePreview = null;
|
|
822
|
+
try {
|
|
823
|
+
target.releasePointerCapture(ev.pointerId);
|
|
824
|
+
} catch {
|
|
825
|
+
/* ignore */
|
|
826
|
+
}
|
|
827
|
+
window.removeEventListener('pointermove', onMove);
|
|
828
|
+
window.removeEventListener('pointerup', onUp);
|
|
829
|
+
};
|
|
830
|
+
window.addEventListener('pointermove', onMove);
|
|
831
|
+
window.addEventListener('pointerup', onUp);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Drag from ruler: live full-stage preview, commit guide on release. */
|
|
835
|
+
function beginRulerDrag(orientation: CanvasGuideOrientation, e: PointerEvent) {
|
|
836
|
+
if (guidesLocked) return;
|
|
837
|
+
e.preventDefault();
|
|
838
|
+
e.stopPropagation();
|
|
839
|
+
setGuidePreview(orientation, e.clientX, e.clientY);
|
|
840
|
+
|
|
841
|
+
const onMove = (ev: PointerEvent) => {
|
|
842
|
+
setGuidePreview(orientation, ev.clientX, ev.clientY);
|
|
843
|
+
};
|
|
844
|
+
const onUp = (ev: PointerEvent) => {
|
|
845
|
+
const pt = clientToDoc(ev.clientX, ev.clientY);
|
|
846
|
+
const pos = clampGuidePos(orientation, orientation === 'vertical' ? pt.x : pt.y);
|
|
847
|
+
patchGuides([...guides, createCanvasGuide(orientation, pos)]);
|
|
848
|
+
guidePreview = null;
|
|
849
|
+
window.removeEventListener('pointermove', onMove);
|
|
850
|
+
window.removeEventListener('pointerup', onUp);
|
|
851
|
+
};
|
|
852
|
+
window.addEventListener('pointermove', onMove);
|
|
853
|
+
window.addEventListener('pointerup', onUp);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function removeGuide(id: string) {
|
|
857
|
+
if (guidesLocked) return;
|
|
858
|
+
patchGuides(guides.filter((g) => g.id !== id));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
onMount(() => {
|
|
862
|
+
if (!viewportEl) return;
|
|
863
|
+
const el = viewportEl;
|
|
864
|
+
const ro = new ResizeObserver(() => {
|
|
865
|
+
vw = el.clientWidth;
|
|
866
|
+
vh = el.clientHeight;
|
|
867
|
+
requestAnimationFrame(syncBoardScreen);
|
|
868
|
+
});
|
|
869
|
+
ro.observe(el);
|
|
870
|
+
vw = el.clientWidth;
|
|
871
|
+
vh = el.clientHeight;
|
|
872
|
+
requestAnimationFrame(syncBoardScreen);
|
|
873
|
+
|
|
874
|
+
const onScroll = () => syncBoardScreen();
|
|
875
|
+
el.addEventListener('scroll', onScroll, { passive: true });
|
|
876
|
+
|
|
877
|
+
const disposeMarquee = attachMarqueeSelect(el, {
|
|
878
|
+
getItems: () => layerHitBoxes(),
|
|
879
|
+
shouldIgnore: (target) => {
|
|
880
|
+
if (drawMode) return true;
|
|
881
|
+
if (!(target instanceof Element)) return false;
|
|
882
|
+
if (
|
|
883
|
+
target.closest(
|
|
884
|
+
'[data-resize-handle], [data-guide], [data-ruler], [data-path-point], button, input, textarea'
|
|
885
|
+
)
|
|
886
|
+
) {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
const frame = target.closest('[data-layer-frame]');
|
|
890
|
+
if (!frame) return false;
|
|
891
|
+
const id = frame.getAttribute('data-layer-frame');
|
|
892
|
+
if (!id) return true;
|
|
893
|
+
const realId = resolveSelectableId(id);
|
|
894
|
+
const scene = workingLayers.find((l) => l.id === realId);
|
|
895
|
+
// Full-bleed / layout shells: empty drag = marquee. Selected non-root
|
|
896
|
+
// shells still drag-move. Root canvasPanel always marquees.
|
|
897
|
+
if (scene && isMarqueePassThroughKind(scene.kind)) {
|
|
898
|
+
if (scene.kind === 'canvasPanel') return false;
|
|
899
|
+
return selectedSet.has(realId);
|
|
900
|
+
}
|
|
901
|
+
return true;
|
|
902
|
+
},
|
|
903
|
+
onRect: (r) => (marquee = r),
|
|
904
|
+
onDrag: ({ ids, modifier }) => {
|
|
905
|
+
onselect?.(resolveMarqueeSelection(ids, selectedIds, modifier));
|
|
906
|
+
},
|
|
907
|
+
onSelect: ({ ids, modifier }) => {
|
|
908
|
+
skipClearClick = true;
|
|
909
|
+
onselect?.(resolveMarqueeSelection(ids, selectedIds, modifier));
|
|
910
|
+
requestAnimationFrame(() => {
|
|
911
|
+
requestAnimationFrame(() => {
|
|
912
|
+
skipClearClick = false;
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
const onWheel = (e: WheelEvent) => {
|
|
919
|
+
if (e.ctrlKey || e.metaKey) {
|
|
920
|
+
e.preventDefault();
|
|
921
|
+
const intensity = Math.min(0.25, Math.abs(e.deltaY) / 400);
|
|
922
|
+
const step = (e.deltaY > 0 ? -1 : 1) * Math.max(0.05, intensity);
|
|
923
|
+
onzoom?.(Math.round((zoom + step) * 100) / 100);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
e.preventDefault();
|
|
927
|
+
el.scrollLeft += e.deltaX || e.deltaY;
|
|
928
|
+
el.scrollTop += e.deltaY;
|
|
929
|
+
};
|
|
930
|
+
el.addEventListener('wheel', onWheel, { passive: false });
|
|
931
|
+
|
|
932
|
+
let panX = 0;
|
|
933
|
+
let panY = 0;
|
|
934
|
+
let panning = false;
|
|
935
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
936
|
+
if (e.button !== 1) return;
|
|
937
|
+
e.preventDefault();
|
|
938
|
+
panning = true;
|
|
939
|
+
panX = e.clientX;
|
|
940
|
+
panY = e.clientY;
|
|
941
|
+
el.setPointerCapture(e.pointerId);
|
|
942
|
+
};
|
|
943
|
+
const onPointerMove = (e: PointerEvent) => {
|
|
944
|
+
if (!panning) return;
|
|
945
|
+
el.scrollLeft -= e.clientX - panX;
|
|
946
|
+
el.scrollTop -= e.clientY - panY;
|
|
947
|
+
panX = e.clientX;
|
|
948
|
+
panY = e.clientY;
|
|
949
|
+
};
|
|
950
|
+
const onPointerUp = (e: PointerEvent) => {
|
|
951
|
+
if (!panning) return;
|
|
952
|
+
panning = false;
|
|
953
|
+
try {
|
|
954
|
+
el.releasePointerCapture(e.pointerId);
|
|
955
|
+
} catch {
|
|
956
|
+
/* ignore */
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
el.addEventListener('pointerdown', onPointerDown);
|
|
960
|
+
el.addEventListener('pointermove', onPointerMove);
|
|
961
|
+
el.addEventListener('pointerup', onPointerUp);
|
|
962
|
+
el.addEventListener('auxclick', (e) => {
|
|
963
|
+
if (e.button === 1) e.preventDefault();
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
return () => {
|
|
967
|
+
ro.disconnect();
|
|
968
|
+
disposeMarquee();
|
|
969
|
+
el.removeEventListener('scroll', onScroll);
|
|
970
|
+
el.removeEventListener('wheel', onWheel);
|
|
971
|
+
el.removeEventListener('pointerdown', onPointerDown);
|
|
972
|
+
el.removeEventListener('pointermove', onPointerMove);
|
|
973
|
+
el.removeEventListener('pointerup', onPointerUp);
|
|
974
|
+
};
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
$effect(() => {
|
|
978
|
+
// Re-measure when zoom / board size changes
|
|
979
|
+
scale;
|
|
980
|
+
boardW;
|
|
981
|
+
boardH;
|
|
982
|
+
queueMicrotask(syncBoardScreen);
|
|
983
|
+
});
|
|
984
|
+
</script>
|
|
985
|
+
|
|
986
|
+
<svelte:window
|
|
987
|
+
onblur={() => {
|
|
988
|
+
if (draftLayers) cancelLayerDraft();
|
|
989
|
+
}}
|
|
990
|
+
onkeydown={(e) => {
|
|
991
|
+
const t = e.target as HTMLElement | null;
|
|
992
|
+
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
|
993
|
+
if (drawMode) {
|
|
994
|
+
if (e.key === 'Escape') {
|
|
995
|
+
e.preventDefault();
|
|
996
|
+
cancelDraft();
|
|
997
|
+
} else if (e.key === 'Enter') {
|
|
998
|
+
e.preventDefault();
|
|
999
|
+
finishDraft(false);
|
|
1000
|
+
}
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
if (e.key === 'Escape' && draftLayers) {
|
|
1004
|
+
e.preventDefault();
|
|
1005
|
+
cancelLayerDraft();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && e.altKey && activePathPoint != null) {
|
|
1009
|
+
e.preventDefault();
|
|
1010
|
+
removeActivePathPoint();
|
|
1011
|
+
}
|
|
1012
|
+
}}
|
|
1013
|
+
/>
|
|
1014
|
+
|
|
1015
|
+
<div bind:this={stageEl} class={['relative flex h-full min-h-0 w-full flex-col', className]}>
|
|
1016
|
+
<!-- Top ruler — slim, crisp ticks -->
|
|
1017
|
+
<div
|
|
1018
|
+
class="relative z-30 flex shrink-0 border-b border-black/10 bg-[#eceff3] dark:border-white/10 dark:bg-[#1e2329]"
|
|
1019
|
+
>
|
|
1020
|
+
<div
|
|
1021
|
+
class="shrink-0 border-r border-black/10 bg-[#e2e6eb] dark:border-white/10 dark:bg-[#181c21]"
|
|
1022
|
+
style:width={`${RULER_LEFT}px`}
|
|
1023
|
+
style:height={`${RULER_TOP}px`}
|
|
1024
|
+
></div>
|
|
1025
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1026
|
+
<div
|
|
1027
|
+
data-ruler="x"
|
|
1028
|
+
class="relative min-w-0 flex-1 cursor-ns-resize overflow-hidden"
|
|
1029
|
+
style:height={`${RULER_TOP}px`}
|
|
1030
|
+
title="Drag down to add a horizontal guide"
|
|
1031
|
+
onpointerdown={(e) => {
|
|
1032
|
+
if (e.button !== 0) return;
|
|
1033
|
+
beginRulerDrag('horizontal', e);
|
|
1034
|
+
}}
|
|
1035
|
+
>
|
|
1036
|
+
<svg class="absolute inset-0 h-full w-full" aria-hidden="true">
|
|
1037
|
+
{#each rulerTicksX as tick (tick.pos)}
|
|
1038
|
+
{@const left = tickX(tick.pos)}
|
|
1039
|
+
{#if left >= -8 && left <= (vw || 0) + 8}
|
|
1040
|
+
<line
|
|
1041
|
+
x1={left}
|
|
1042
|
+
x2={left}
|
|
1043
|
+
y1={tick.level === 'major' ? 5 : tick.level === 'mid' ? 9 : 12}
|
|
1044
|
+
y2={RULER_TOP}
|
|
1045
|
+
stroke="currentColor"
|
|
1046
|
+
class={tick.level === 'major'
|
|
1047
|
+
? 'text-slate-500 dark:text-slate-400'
|
|
1048
|
+
: tick.level === 'mid'
|
|
1049
|
+
? 'text-slate-400/80 dark:text-slate-500'
|
|
1050
|
+
: 'text-slate-300 dark:text-slate-600'}
|
|
1051
|
+
stroke-width="1"
|
|
1052
|
+
/>
|
|
1053
|
+
{#if tick.level === 'major'}
|
|
1054
|
+
<text
|
|
1055
|
+
x={left + 3}
|
|
1056
|
+
y="9"
|
|
1057
|
+
class="fill-slate-600 dark:fill-slate-300"
|
|
1058
|
+
font-size="9"
|
|
1059
|
+
font-family="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
1060
|
+
>
|
|
1061
|
+
{Math.round(tick.pos)}
|
|
1062
|
+
</text>
|
|
1063
|
+
{/if}
|
|
1064
|
+
{/if}
|
|
1065
|
+
{/each}
|
|
1066
|
+
</svg>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<div class="flex min-h-0 min-w-0 flex-1">
|
|
1071
|
+
<!-- Left ruler — upright labels (not rotated) so digits stay readable -->
|
|
1072
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1073
|
+
<div
|
|
1074
|
+
data-ruler="y"
|
|
1075
|
+
class="relative z-30 shrink-0 cursor-ew-resize overflow-hidden border-r border-black/10 bg-[#eceff3] dark:border-white/10 dark:bg-[#1e2329]"
|
|
1076
|
+
style:width={`${RULER_LEFT}px`}
|
|
1077
|
+
title="Drag right to add a vertical guide"
|
|
1078
|
+
onpointerdown={(e) => {
|
|
1079
|
+
if (e.button !== 0) return;
|
|
1080
|
+
beginRulerDrag('vertical', e);
|
|
1081
|
+
}}
|
|
1082
|
+
>
|
|
1083
|
+
<svg class="pointer-events-none absolute inset-0 h-full w-full" aria-hidden="true">
|
|
1084
|
+
{#each rulerTicksY as tick (tick.pos)}
|
|
1085
|
+
{@const top = tickY(tick.pos)}
|
|
1086
|
+
{#if top >= -8 && top <= (vh || 0) + 8}
|
|
1087
|
+
<line
|
|
1088
|
+
y1={top}
|
|
1089
|
+
y2={top}
|
|
1090
|
+
x1={tick.level === 'major' ? 22 : tick.level === 'mid' ? 28 : 31}
|
|
1091
|
+
x2={RULER_LEFT}
|
|
1092
|
+
stroke="currentColor"
|
|
1093
|
+
class={tick.level === 'major'
|
|
1094
|
+
? 'text-slate-500 dark:text-slate-400'
|
|
1095
|
+
: tick.level === 'mid'
|
|
1096
|
+
? 'text-slate-400/80 dark:text-slate-500'
|
|
1097
|
+
: 'text-slate-300 dark:text-slate-600'}
|
|
1098
|
+
stroke-width="1"
|
|
1099
|
+
/>
|
|
1100
|
+
{/if}
|
|
1101
|
+
{/each}
|
|
1102
|
+
</svg>
|
|
1103
|
+
{#each rulerTicksY as tick (tick.pos)}
|
|
1104
|
+
{#if tick.level === 'major'}
|
|
1105
|
+
{@const top = tickY(tick.pos)}
|
|
1106
|
+
{#if top >= 4 && top <= (vh || 0) - 2}
|
|
1107
|
+
<span
|
|
1108
|
+
class="pointer-events-none absolute left-0.5 w-[20px] text-right font-mono text-[9px] leading-none tabular-nums text-slate-700 dark:text-slate-200"
|
|
1109
|
+
style:top={`${top}px`}
|
|
1110
|
+
style:transform="translateY(-50%)"
|
|
1111
|
+
>
|
|
1112
|
+
{Math.round(tick.pos)}
|
|
1113
|
+
</span>
|
|
1114
|
+
{/if}
|
|
1115
|
+
{/if}
|
|
1116
|
+
{/each}
|
|
1117
|
+
</div>
|
|
1118
|
+
|
|
1119
|
+
<!-- Scrollable canvas viewport -->
|
|
1120
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1121
|
+
<div
|
|
1122
|
+
bind:this={viewportEl}
|
|
1123
|
+
class="relative min-h-0 min-w-0 flex-1 overflow-auto"
|
|
1124
|
+
style:background-color="#dfe3e8"
|
|
1125
|
+
style:background-image="linear-gradient(45deg,#cfd5dc 25%,transparent 25%),linear-gradient(-45deg,#cfd5dc 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#cfd5dc 75%),linear-gradient(-45deg,transparent 75%,#cfd5dc 75%)"
|
|
1126
|
+
style:background-size="16px 16px"
|
|
1127
|
+
style:background-position="0 0,0 8px,8px -8px,-8px 0"
|
|
1128
|
+
role="presentation"
|
|
1129
|
+
onclick={clearSelectionIfEmptyClick}
|
|
1130
|
+
ondragover={handleElementDragOver}
|
|
1131
|
+
ondrop={handleElementDrop}
|
|
1132
|
+
>
|
|
1133
|
+
<div
|
|
1134
|
+
class="flex items-center justify-center"
|
|
1135
|
+
style:min-width="100%"
|
|
1136
|
+
style:min-height="100%"
|
|
1137
|
+
style:width={`${Math.max(vw || 0, boardW + PAD * 2)}px`}
|
|
1138
|
+
style:height={`${Math.max(vh || 0, boardH + PAD * 2)}px`}
|
|
1139
|
+
>
|
|
1140
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1141
|
+
<div
|
|
1142
|
+
bind:this={artboardEl}
|
|
1143
|
+
onwheel={handleScrollWheel}
|
|
1144
|
+
onclick={(e) => {
|
|
1145
|
+
e.stopPropagation();
|
|
1146
|
+
if (drawMode) {
|
|
1147
|
+
handleDrawClick(e);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
clearSelectionIfEmptyClick(e);
|
|
1151
|
+
}}
|
|
1152
|
+
ondblclick={(e) => {
|
|
1153
|
+
if (drawMode) {
|
|
1154
|
+
handleDrawDblClick(e);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const pt = clientToDoc(e.clientX, e.clientY);
|
|
1158
|
+
const hit = pickLayerAtDoc(pt);
|
|
1159
|
+
if (hit) onenterlayer?.(hit);
|
|
1160
|
+
}}
|
|
1161
|
+
onmousemove={handleDrawMove}
|
|
1162
|
+
class={[
|
|
1163
|
+
'relative shrink-0 overflow-visible shadow-[0_8px_40px_rgba(15,23,42,0.18)] ring-1 ring-black/10',
|
|
1164
|
+
drawMode && 'cursor-crosshair'
|
|
1165
|
+
]}
|
|
1166
|
+
style:width={`${boardW}px`}
|
|
1167
|
+
style:height={`${boardH}px`}
|
|
1168
|
+
>
|
|
1169
|
+
<div
|
|
1170
|
+
class="absolute left-0 top-0 origin-top-left overflow-visible"
|
|
1171
|
+
style:width={`${doc.width}px`}
|
|
1172
|
+
style:height={`${doc.height}px`}
|
|
1173
|
+
style:transform={`scale(${scale})`}
|
|
1174
|
+
>
|
|
1175
|
+
<!-- Artboard surface (clipped). Layers sit above and may overhang like a compositor. -->
|
|
1176
|
+
<div class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
1177
|
+
{#if artboardTransparent}
|
|
1178
|
+
<div
|
|
1179
|
+
class="absolute inset-0"
|
|
1180
|
+
style:background-color="#e8ebef"
|
|
1181
|
+
style:background-image="linear-gradient(45deg,#cfd5dc 25%,transparent 25%),linear-gradient(-45deg,#cfd5dc 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#cfd5dc 75%),linear-gradient(-45deg,transparent 75%,#cfd5dc 75%)"
|
|
1182
|
+
style:background-size="16px 16px"
|
|
1183
|
+
style:background-position="0 0,0 8px,8px -8px,-8px 0"
|
|
1184
|
+
></div>
|
|
1185
|
+
{/if}
|
|
1186
|
+
<div class="absolute inset-0" style:background-color={doc.background}></div>
|
|
1187
|
+
</div>
|
|
1188
|
+
|
|
1189
|
+
{#if showGrid}
|
|
1190
|
+
<div
|
|
1191
|
+
class="pointer-events-none absolute inset-0 z-100 overflow-hidden"
|
|
1192
|
+
style={gridOverlayStyle}
|
|
1193
|
+
aria-hidden="true"
|
|
1194
|
+
></div>
|
|
1195
|
+
{/if}
|
|
1196
|
+
|
|
1197
|
+
<div class={['absolute inset-0 overflow-visible', drawMode && 'pointer-events-none']}>
|
|
1198
|
+
{#each sorted as layer, paintIndex (layer.id)}
|
|
1199
|
+
{#if effectivelyVisible.has(layer.id)}
|
|
1200
|
+
{@const realId = resolveSelectableId(layer.id)}
|
|
1201
|
+
{@const isSynthetic = !sceneLayerIds.has(layer.id)}
|
|
1202
|
+
{@const lockedPass = !!layer.locked || isSynthetic}
|
|
1203
|
+
{@const displayRect = paintAbsMap.get(layer.id) ?? layer.rect}
|
|
1204
|
+
{@const clipPath = clipPathForLayer(layer.id, displayLayers, paintAbsMap)}
|
|
1205
|
+
{@const sceneLayer = workingLayers.find((l) => l.id === realId)}
|
|
1206
|
+
{@const marqueeBg =
|
|
1207
|
+
!!sceneLayer && isMarqueePassThroughKind(sceneLayer.kind)}
|
|
1208
|
+
{@const isSelected = selectedSet.has(realId) && !isSynthetic}
|
|
1209
|
+
{@const dragOk =
|
|
1210
|
+
!layer.locked &&
|
|
1211
|
+
!isSynthetic &&
|
|
1212
|
+
!isLayoutLocked(layer) &&
|
|
1213
|
+
(!marqueeBg ||
|
|
1214
|
+
(isSelected && sceneLayer?.kind !== 'canvasPanel'))}
|
|
1215
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1216
|
+
<div
|
|
1217
|
+
data-layer-item
|
|
1218
|
+
data-marquee-id={realId}
|
|
1219
|
+
class="contents"
|
|
1220
|
+
oncontextmenu={(e) => {
|
|
1221
|
+
if (lockedPass && isSynthetic) {
|
|
1222
|
+
e.preventDefault();
|
|
1223
|
+
e.stopPropagation();
|
|
1224
|
+
onselect?.([realId]);
|
|
1225
|
+
oncontextlayer?.({ id: realId, x: e.clientX, y: e.clientY });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (layer.locked) return;
|
|
1229
|
+
e.preventDefault();
|
|
1230
|
+
e.stopPropagation();
|
|
1231
|
+
if (!selectedSet.has(realId)) onselect?.([realId]);
|
|
1232
|
+
oncontextlayer?.({ id: realId, x: e.clientX, y: e.clientY });
|
|
1233
|
+
}}
|
|
1234
|
+
>
|
|
1235
|
+
<MediaLayerItem
|
|
1236
|
+
{layer}
|
|
1237
|
+
{displayRect}
|
|
1238
|
+
{clipPath}
|
|
1239
|
+
stackIndex={paintIndex}
|
|
1240
|
+
paintTransform={paintTransformForLayer(
|
|
1241
|
+
displayLayers,
|
|
1242
|
+
layer.id,
|
|
1243
|
+
paintAbsMap,
|
|
1244
|
+
layerById
|
|
1245
|
+
)}
|
|
1246
|
+
selected={isSelected}
|
|
1247
|
+
passthrough={!!layer.locked}
|
|
1248
|
+
readOnly={isSynthetic}
|
|
1249
|
+
layoutLocked={isLayoutLocked(layer) || (marqueeBg && !dragOk)}
|
|
1250
|
+
onclick={(e) => selectLayer(layer.id, e)}
|
|
1251
|
+
ondblclick={(e) => {
|
|
1252
|
+
if (layer.locked) return;
|
|
1253
|
+
const pt = clientToDoc(e.clientX, e.clientY);
|
|
1254
|
+
const hit = pickLayerAtDoc(pt) ?? realId;
|
|
1255
|
+
onenterlayer?.(hit);
|
|
1256
|
+
}}
|
|
1257
|
+
onchange={(rect) => handleRect(layer, rect)}
|
|
1258
|
+
oninteract={handleLayerInteract}
|
|
1259
|
+
/>
|
|
1260
|
+
</div>
|
|
1261
|
+
{/if}
|
|
1262
|
+
{/each}
|
|
1263
|
+
|
|
1264
|
+
<!-- ScrollBox chrome above nested layer content -->
|
|
1265
|
+
{#each displayLayers.filter((l) => l.kind === 'scrollBox') as box (box.id + ':scroll')}
|
|
1266
|
+
{#if effectivelyVisible.has(box.id)}
|
|
1267
|
+
{@const boxRect = paintAbsMap.get(box.id) ?? box.rect}
|
|
1268
|
+
{@const scrollOver = scrollBoxOverflow(box.id, displayLayers, absMap)}
|
|
1269
|
+
{@const scrollOff = scrollOffsets.get(box.id) ?? { x: 0, y: 0 }}
|
|
1270
|
+
{@const chrome = {
|
|
1271
|
+
x: scrollOff.x,
|
|
1272
|
+
y: scrollOff.y,
|
|
1273
|
+
maxX: scrollOver.maxX,
|
|
1274
|
+
maxY: scrollOver.maxY
|
|
1275
|
+
}}
|
|
1276
|
+
{@const vScroll =
|
|
1277
|
+
chrome.maxY > 0
|
|
1278
|
+
? scrollBarMetrics(boxRect.h, chrome.maxY, chrome.y)
|
|
1279
|
+
: null}
|
|
1280
|
+
{@const hScroll =
|
|
1281
|
+
chrome.maxX > 0
|
|
1282
|
+
? scrollBarMetrics(boxRect.w, chrome.maxX, chrome.x)
|
|
1283
|
+
: null}
|
|
1284
|
+
{#if vScroll || hScroll}
|
|
1285
|
+
<div
|
|
1286
|
+
class="pointer-events-none absolute z-[999990]"
|
|
1287
|
+
style:left="{boxRect.x}px"
|
|
1288
|
+
style:top="{boxRect.y}px"
|
|
1289
|
+
style:width="{boxRect.w}px"
|
|
1290
|
+
style:height="{boxRect.h}px"
|
|
1291
|
+
>
|
|
1292
|
+
{#if vScroll}
|
|
1293
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1294
|
+
<div
|
|
1295
|
+
class="pointer-events-auto absolute bottom-0 right-0 top-0 w-2.5 rounded-r-md bg-black/[0.04] dark:bg-white/[0.06]"
|
|
1296
|
+
style:margin-bottom={hScroll ? '10px' : '0'}
|
|
1297
|
+
onpointerdown={(e) =>
|
|
1298
|
+
jumpScrollTrack(box.id, 'y', chrome, vScroll, hScroll, e)}
|
|
1299
|
+
>
|
|
1300
|
+
<div
|
|
1301
|
+
data-scroll-thumb="y"
|
|
1302
|
+
class="absolute left-0.5 right-0.5 cursor-grab rounded-full bg-slate-400/75 shadow-sm active:cursor-grabbing dark:bg-slate-500/85"
|
|
1303
|
+
style:top="{vScroll.offset}px"
|
|
1304
|
+
style:height="{vScroll.thumb}px"
|
|
1305
|
+
onpointerdown={(e) =>
|
|
1306
|
+
beginScrollThumbDrag(
|
|
1307
|
+
box.id,
|
|
1308
|
+
'y',
|
|
1309
|
+
chrome,
|
|
1310
|
+
vScroll,
|
|
1311
|
+
hScroll,
|
|
1312
|
+
e
|
|
1313
|
+
)}
|
|
1314
|
+
></div>
|
|
1315
|
+
</div>
|
|
1316
|
+
{/if}
|
|
1317
|
+
{#if hScroll}
|
|
1318
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1319
|
+
<div
|
|
1320
|
+
class="pointer-events-auto absolute bottom-0 left-0 h-2.5 rounded-b-md bg-black/[0.04] dark:bg-white/[0.06]"
|
|
1321
|
+
style:right={vScroll ? '10px' : '0'}
|
|
1322
|
+
onpointerdown={(e) =>
|
|
1323
|
+
jumpScrollTrack(box.id, 'x', chrome, vScroll, hScroll, e)}
|
|
1324
|
+
>
|
|
1325
|
+
<div
|
|
1326
|
+
data-scroll-thumb="x"
|
|
1327
|
+
class="absolute top-0.5 bottom-0.5 cursor-grab rounded-full bg-slate-400/75 shadow-sm active:cursor-grabbing dark:bg-slate-500/85"
|
|
1328
|
+
style:left="{hScroll.offset}px"
|
|
1329
|
+
style:width="{hScroll.thumb}px"
|
|
1330
|
+
onpointerdown={(e) =>
|
|
1331
|
+
beginScrollThumbDrag(
|
|
1332
|
+
box.id,
|
|
1333
|
+
'x',
|
|
1334
|
+
chrome,
|
|
1335
|
+
vScroll,
|
|
1336
|
+
hScroll,
|
|
1337
|
+
e
|
|
1338
|
+
)}
|
|
1339
|
+
></div>
|
|
1340
|
+
</div>
|
|
1341
|
+
{/if}
|
|
1342
|
+
{#if vScroll && hScroll}
|
|
1343
|
+
<div
|
|
1344
|
+
class="absolute bottom-0 right-0 h-2.5 w-2.5 rounded-br-md bg-black/[0.06] dark:bg-white/[0.08]"
|
|
1345
|
+
aria-hidden="true"
|
|
1346
|
+
></div>
|
|
1347
|
+
{/if}
|
|
1348
|
+
</div>
|
|
1349
|
+
{/if}
|
|
1350
|
+
{/if}
|
|
1351
|
+
{/each}
|
|
1352
|
+
</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
</div>
|
|
1355
|
+
</div>
|
|
1356
|
+
|
|
1357
|
+
<MarqueeRect rect={marquee} />
|
|
1358
|
+
</div>
|
|
1359
|
+
</div>
|
|
1360
|
+
|
|
1361
|
+
<!-- Guides span the full stage (rulers → viewport) so they align with ruler numbers -->
|
|
1362
|
+
{#if showGuides}
|
|
1363
|
+
{#each guides as guide (guide.id)}
|
|
1364
|
+
{@const locked = guidesLocked || !!guide.locked}
|
|
1365
|
+
{@const hiding = draggingGuideId === guide.id && guidePreview}
|
|
1366
|
+
{@const screenX = RULER_LEFT + tickX(guide.position)}
|
|
1367
|
+
{@const screenY = RULER_TOP + tickY(guide.position)}
|
|
1368
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1369
|
+
<div
|
|
1370
|
+
data-guide={guide.id}
|
|
1371
|
+
class={[
|
|
1372
|
+
'absolute z-40',
|
|
1373
|
+
guide.orientation === 'vertical'
|
|
1374
|
+
? 'top-0 bottom-0 w-2 -translate-x-1/2 cursor-col-resize'
|
|
1375
|
+
: 'left-0 right-0 h-2 -translate-y-1/2 cursor-row-resize',
|
|
1376
|
+
locked && 'cursor-default',
|
|
1377
|
+
hiding && 'opacity-0'
|
|
1378
|
+
]}
|
|
1379
|
+
style={guide.orientation === 'vertical'
|
|
1380
|
+
? `left:${screenX}px`
|
|
1381
|
+
: `top:${screenY}px`}
|
|
1382
|
+
title={locked ? 'Guide locked' : 'Drag to move · Double-click to remove'}
|
|
1383
|
+
onpointerdown={(e) => beginGuideDrag(guide, e)}
|
|
1384
|
+
ondblclick={() => removeGuide(guide.id)}
|
|
1385
|
+
>
|
|
1386
|
+
<span
|
|
1387
|
+
class={[
|
|
1388
|
+
'pointer-events-none absolute bg-[#00c2ff]',
|
|
1389
|
+
guide.orientation === 'vertical'
|
|
1390
|
+
? 'left-1/2 top-0 h-full w-px -translate-x-1/2'
|
|
1391
|
+
: 'left-0 top-1/2 h-px w-full -translate-y-1/2',
|
|
1392
|
+
locked && 'opacity-60'
|
|
1393
|
+
]}
|
|
1394
|
+
style:box-shadow="0 0 0 1px rgba(0,194,255,0.35)"
|
|
1395
|
+
aria-hidden="true"
|
|
1396
|
+
></span>
|
|
1397
|
+
</div>
|
|
1398
|
+
{/each}
|
|
1399
|
+
{/if}
|
|
1400
|
+
|
|
1401
|
+
<!-- Pen draft preview -->
|
|
1402
|
+
{#if drawMode && draftPoints.length}
|
|
1403
|
+
<svg
|
|
1404
|
+
class="pointer-events-none absolute inset-0 z-45 overflow-visible"
|
|
1405
|
+
aria-hidden="true"
|
|
1406
|
+
>
|
|
1407
|
+
{#each draftPoints as p, i (i)}
|
|
1408
|
+
{@const sx = RULER_LEFT + tickX(p.x)}
|
|
1409
|
+
{@const sy = RULER_TOP + tickY(p.y)}
|
|
1410
|
+
{#if i > 0}
|
|
1411
|
+
{@const prev = draftPoints[i - 1]}
|
|
1412
|
+
<line
|
|
1413
|
+
x1={RULER_LEFT + tickX(prev.x)}
|
|
1414
|
+
y1={RULER_TOP + tickY(prev.y)}
|
|
1415
|
+
x2={sx}
|
|
1416
|
+
y2={sy}
|
|
1417
|
+
stroke="#3b82f6"
|
|
1418
|
+
stroke-width="2"
|
|
1419
|
+
stroke-linecap="round"
|
|
1420
|
+
/>
|
|
1421
|
+
{/if}
|
|
1422
|
+
<circle cx={sx} cy={sy} r="4" fill="#fff" stroke="#3b82f6" stroke-width="2" />
|
|
1423
|
+
{/each}
|
|
1424
|
+
{#if draftCursor && draftPoints.length}
|
|
1425
|
+
{@const last = draftPoints[draftPoints.length - 1]}
|
|
1426
|
+
<line
|
|
1427
|
+
x1={RULER_LEFT + tickX(last.x)}
|
|
1428
|
+
y1={RULER_TOP + tickY(last.y)}
|
|
1429
|
+
x2={RULER_LEFT + tickX(draftCursor.x)}
|
|
1430
|
+
y2={RULER_TOP + tickY(draftCursor.y)}
|
|
1431
|
+
stroke="#3b82f6"
|
|
1432
|
+
stroke-width="2"
|
|
1433
|
+
stroke-dasharray="4 4"
|
|
1434
|
+
stroke-linecap="round"
|
|
1435
|
+
/>
|
|
1436
|
+
{/if}
|
|
1437
|
+
</svg>
|
|
1438
|
+
<div
|
|
1439
|
+
class="pointer-events-none absolute bottom-3 left-1/2 z-50 -translate-x-1/2 rounded-md bg-slate-900/90 px-3 py-1.5 text-[11px] text-white shadow"
|
|
1440
|
+
>
|
|
1441
|
+
Click to add · near start to close · double-click / Enter to finish · Esc cancel
|
|
1442
|
+
</div>
|
|
1443
|
+
{/if}
|
|
1444
|
+
|
|
1445
|
+
<!-- Editable path points -->
|
|
1446
|
+
{#if selectedPath && !drawMode && !selectedPath.locked}
|
|
1447
|
+
{@const docs = pathAbsPoints(selectedPath)}
|
|
1448
|
+
{#each docs as p, i (i)}
|
|
1449
|
+
{@const sx = RULER_LEFT + tickX(p.x)}
|
|
1450
|
+
{@const sy = RULER_TOP + tickY(p.y)}
|
|
1451
|
+
{#if i < docs.length - 1 || selectedPath.closed}
|
|
1452
|
+
{@const next = docs[(i + 1) % docs.length]}
|
|
1453
|
+
{@const mx = RULER_LEFT + tickX((p.x + next.x) / 2)}
|
|
1454
|
+
{@const my = RULER_TOP + tickY((p.y + next.y) / 2)}
|
|
1455
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1456
|
+
<button
|
|
1457
|
+
type="button"
|
|
1458
|
+
class="absolute z-45 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-dashed border-blue-400/80 bg-white/80"
|
|
1459
|
+
style:left={`${mx}px`}
|
|
1460
|
+
style:top={`${my}px`}
|
|
1461
|
+
title="Double-click to add point"
|
|
1462
|
+
ondblclick={(e) => insertPathPoint(i, e)}
|
|
1463
|
+
></button>
|
|
1464
|
+
{/if}
|
|
1465
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1466
|
+
<div
|
|
1467
|
+
data-path-point={i}
|
|
1468
|
+
class={[
|
|
1469
|
+
'absolute z-46 h-3 w-3 -translate-x-1/2 -translate-y-1/2 cursor-grab rounded-full border-2 border-blue-500 bg-white shadow',
|
|
1470
|
+
activePathPoint === i && 'ring-2 ring-blue-400'
|
|
1471
|
+
]}
|
|
1472
|
+
style:left={`${sx}px`}
|
|
1473
|
+
style:top={`${sy}px`}
|
|
1474
|
+
title="Drag to move · Alt+Delete to remove point"
|
|
1475
|
+
onpointerdown={(e) => beginPathPointDrag(i, e)}
|
|
1476
|
+
></div>
|
|
1477
|
+
{/each}
|
|
1478
|
+
{/if}
|
|
1479
|
+
|
|
1480
|
+
<!-- Full-stage guide preview while dragging (edge to edge) -->
|
|
1481
|
+
{#if guidePreview}
|
|
1482
|
+
<div class="pointer-events-none absolute inset-0 z-50" aria-hidden="true">
|
|
1483
|
+
{#if guidePreview.orientation === 'vertical'}
|
|
1484
|
+
<div
|
|
1485
|
+
class="absolute top-0 bottom-0 w-px bg-[#00c2ff]"
|
|
1486
|
+
style:left={`${guidePreview.x}px`}
|
|
1487
|
+
style:box-shadow="0 0 0 1px rgba(0,194,255,0.35)"
|
|
1488
|
+
></div>
|
|
1489
|
+
{:else}
|
|
1490
|
+
<div
|
|
1491
|
+
class="absolute left-0 right-0 h-px bg-[#00c2ff]"
|
|
1492
|
+
style:top={`${guidePreview.y}px`}
|
|
1493
|
+
style:box-shadow="0 0 0 1px rgba(0,194,255,0.35)"
|
|
1494
|
+
></div>
|
|
1495
|
+
{/if}
|
|
1496
|
+
</div>
|
|
1497
|
+
{/if}
|
|
1498
|
+
</div>
|