@r2digisolutions/components 0.1.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/dist/components/atoms/IconButton/IconButton.svelte +1 -1
  2. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.stories.js +8 -0
  3. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte +122 -0
  4. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte.d.ts +11 -0
  5. package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte +12 -0
  6. package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte.d.ts +18 -0
  7. package/dist/components/atoms/PanKnob/PanKnob.stories.d.ts +9 -0
  8. package/dist/components/atoms/PanKnob/PanKnob.stories.js +8 -0
  9. package/dist/components/atoms/PanKnob/PanKnob.svelte +218 -0
  10. package/dist/components/atoms/PanKnob/PanKnob.svelte.d.ts +19 -0
  11. package/dist/components/atoms/PanKnob/PanKnobStory.svelte +15 -0
  12. package/dist/components/atoms/PanKnob/PanKnobStory.svelte.d.ts +3 -0
  13. package/dist/components/atoms/Playhead/Playhead.stories.js +8 -0
  14. package/dist/components/atoms/Playhead/Playhead.svelte +98 -0
  15. package/dist/components/atoms/Playhead/Playhead.svelte.d.ts +21 -0
  16. package/dist/components/atoms/Playhead/PlayheadStory.svelte +9 -0
  17. package/dist/components/atoms/Playhead/PlayheadStory.svelte.d.ts +18 -0
  18. package/dist/components/atoms/TimeRuler/TimeRuler.stories.js +8 -0
  19. package/dist/components/atoms/TimeRuler/TimeRuler.svelte +117 -0
  20. package/dist/components/atoms/TimeRuler/TimeRuler.svelte.d.ts +17 -0
  21. package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte +7 -0
  22. package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte.d.ts +18 -0
  23. package/dist/components/atoms/TimelineGrid/TimelineGrid.stories.js +8 -0
  24. package/dist/components/atoms/TimelineGrid/TimelineGrid.svelte +39 -0
  25. package/dist/components/atoms/TimelineGrid/TimelineGrid.svelte.d.ts +11 -0
  26. package/dist/components/atoms/TimelineGrid/TimelineGridStory.svelte +7 -0
  27. package/dist/components/atoms/TimelineGrid/TimelineGridStory.svelte.d.ts +18 -0
  28. package/dist/components/atoms/Tooltip/Tooltip.svelte +1 -1
  29. package/dist/components/atoms/TrimHandle/TrimHandle.stories.js +8 -0
  30. package/dist/components/atoms/TrimHandle/TrimHandle.svelte +63 -0
  31. package/dist/components/atoms/TrimHandle/TrimHandle.svelte.d.ts +12 -0
  32. package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte +11 -0
  33. package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte.d.ts +18 -0
  34. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.d.ts +9 -0
  35. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.js +8 -0
  36. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte +102 -0
  37. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte.d.ts +20 -0
  38. package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte +34 -0
  39. package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte.d.ts +3 -0
  40. package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +97 -61
  41. package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte.d.ts +2 -0
  42. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.stories.js +8 -0
  43. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte +117 -0
  44. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte.d.ts +23 -0
  45. package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte +18 -0
  46. package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte.d.ts +18 -0
  47. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.stories.js +8 -0
  48. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte +858 -0
  49. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte.d.ts +43 -0
  50. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte +7 -0
  51. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte.d.ts +18 -0
  52. package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.d.ts +9 -0
  53. package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.js +8 -0
  54. package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte +1140 -0
  55. package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte.d.ts +28 -0
  56. package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte +20 -0
  57. package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte.d.ts +3 -0
  58. package/dist/components/molecules/ColorPicker/ColorPicker.svelte +46 -5
  59. package/dist/components/molecules/ColorPicker/ColorPicker.svelte.d.ts +2 -0
  60. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.d.ts +9 -0
  61. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.js +8 -0
  62. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte +108 -0
  63. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte.d.ts +18 -0
  64. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte +15 -0
  65. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte.d.ts +3 -0
  66. package/dist/components/molecules/ConflictResolver/ConflictResolver.stories.js +8 -0
  67. package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte +73 -0
  68. package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte.d.ts +16 -0
  69. package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte +11 -0
  70. package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte.d.ts +18 -0
  71. package/dist/components/molecules/ContextMenu/ContextMenu.svelte +237 -66
  72. package/dist/components/molecules/ContextMenu/ContextMenu.svelte.d.ts +9 -0
  73. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.d.ts +9 -0
  74. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.js +8 -0
  75. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte +56 -0
  76. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte.d.ts +12 -0
  77. package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte +12 -0
  78. package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte.d.ts +3 -0
  79. package/dist/components/molecules/FilterList/FilterList.stories.d.ts +9 -0
  80. package/dist/components/molecules/FilterList/FilterList.stories.js +8 -0
  81. package/dist/components/molecules/FilterList/FilterList.svelte +101 -0
  82. package/dist/components/molecules/FilterList/FilterList.svelte.d.ts +18 -0
  83. package/dist/components/molecules/FilterList/FilterListStory.svelte +31 -0
  84. package/dist/components/molecules/FilterList/FilterListStory.svelte.d.ts +3 -0
  85. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.d.ts +9 -0
  86. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.js +8 -0
  87. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte +109 -0
  88. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte.d.ts +20 -0
  89. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte +18 -0
  90. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte.d.ts +3 -0
  91. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.stories.js +8 -0
  92. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte +50 -0
  93. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte.d.ts +11 -0
  94. package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte +13 -0
  95. package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte.d.ts +18 -0
  96. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.stories.js +8 -0
  97. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte +380 -0
  98. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte.d.ts +33 -0
  99. package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte +14 -0
  100. package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte.d.ts +18 -0
  101. package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.d.ts +9 -0
  102. package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.js +8 -0
  103. package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte +399 -0
  104. package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte.d.ts +37 -0
  105. package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte +24 -0
  106. package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte.d.ts +3 -0
  107. package/dist/components/molecules/MediaStage/MediaStage.stories.d.ts +9 -0
  108. package/dist/components/molecules/MediaStage/MediaStage.stories.js +8 -0
  109. package/dist/components/molecules/MediaStage/MediaStage.svelte +1498 -0
  110. package/dist/components/molecules/MediaStage/MediaStage.svelte.d.ts +45 -0
  111. package/dist/components/molecules/MediaStage/MediaStageStory.svelte +35 -0
  112. package/dist/components/molecules/MediaStage/MediaStageStory.svelte.d.ts +3 -0
  113. package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.d.ts +9 -0
  114. package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.js +8 -0
  115. package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte +441 -0
  116. package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte.d.ts +39 -0
  117. package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte +20 -0
  118. package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte.d.ts +3 -0
  119. package/dist/components/molecules/MixerChannel/MixerChannel.stories.d.ts +9 -0
  120. package/dist/components/molecules/MixerChannel/MixerChannel.stories.js +8 -0
  121. package/dist/components/molecules/MixerChannel/MixerChannel.svelte +92 -0
  122. package/dist/components/molecules/MixerChannel/MixerChannel.svelte.d.ts +11 -0
  123. package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte +13 -0
  124. package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte.d.ts +3 -0
  125. package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte +454 -0
  126. package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte.d.ts +8 -0
  127. package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte +26 -0
  128. package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte.d.ts +22 -0
  129. package/dist/components/molecules/NodeGraph/NodeGraph.stories.d.ts +12 -0
  130. package/dist/components/molecules/NodeGraph/NodeGraph.stories.js +9 -0
  131. package/dist/components/molecules/NodeGraph/NodeGraph.svelte +1409 -0
  132. package/dist/components/molecules/NodeGraph/NodeGraph.svelte.d.ts +33 -0
  133. package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte +90 -0
  134. package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte.d.ts +3 -0
  135. package/dist/components/molecules/NodePalette/NodePalette.stories.js +8 -0
  136. package/dist/components/molecules/NodePalette/NodePalette.svelte +86 -0
  137. package/dist/components/molecules/NodePalette/NodePalette.svelte.d.ts +11 -0
  138. package/dist/components/molecules/NodePalette/NodePaletteStory.svelte +11 -0
  139. package/dist/components/molecules/NodePalette/NodePaletteStory.svelte.d.ts +18 -0
  140. package/dist/components/molecules/PdfViewer/PdfViewer.svelte.d.ts +1 -1
  141. package/dist/components/molecules/Poll/Poll.stories.d.ts +1 -1
  142. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.stories.js +8 -0
  143. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte +794 -0
  144. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte.d.ts +25 -0
  145. package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte +16 -0
  146. package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte.d.ts +18 -0
  147. package/dist/components/molecules/PropertyField/PropertyField.svelte +57 -3
  148. package/dist/components/molecules/PropertyField/PropertyField.svelte.d.ts +10 -0
  149. package/dist/components/molecules/PropertyField/PropertyFieldStory.svelte +21 -0
  150. package/dist/components/molecules/PropertyGroup/PropertyGroup.svelte +1 -1
  151. package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.d.ts +9 -0
  152. package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.js +8 -0
  153. package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte +52 -0
  154. package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte.d.ts +18 -0
  155. package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte +62 -0
  156. package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte.d.ts +3 -0
  157. package/dist/components/molecules/RelationPicker/RelationPicker.stories.d.ts +9 -0
  158. package/dist/components/molecules/RelationPicker/RelationPicker.stories.js +8 -0
  159. package/dist/components/molecules/RelationPicker/RelationPicker.svelte +180 -0
  160. package/dist/components/molecules/RelationPicker/RelationPicker.svelte.d.ts +24 -0
  161. package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte +16 -0
  162. package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte.d.ts +3 -0
  163. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.d.ts +9 -0
  164. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.js +8 -0
  165. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte +154 -0
  166. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte.d.ts +12 -0
  167. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte +16 -0
  168. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte.d.ts +3 -0
  169. package/dist/components/molecules/SecretField/SecretField.stories.d.ts +9 -0
  170. package/dist/components/molecules/SecretField/SecretField.stories.js +8 -0
  171. package/dist/components/molecules/SecretField/SecretField.svelte +58 -0
  172. package/dist/components/molecules/SecretField/SecretField.svelte.d.ts +15 -0
  173. package/dist/components/molecules/SecretField/SecretFieldStory.svelte +8 -0
  174. package/dist/components/molecules/SecretField/SecretFieldStory.svelte.d.ts +3 -0
  175. package/dist/components/molecules/SplitPane/SplitPane.svelte +99 -40
  176. package/dist/components/molecules/SplitPane/SplitPane.svelte.d.ts +10 -2
  177. package/dist/components/molecules/TableToolbar/TableToolbar.svelte.d.ts +1 -1
  178. package/dist/components/molecules/Terminal/Terminal.stories.js +20 -0
  179. package/dist/components/molecules/Terminal/Terminal.svelte +238 -0
  180. package/dist/components/molecules/Terminal/Terminal.svelte.d.ts +19 -0
  181. package/dist/components/molecules/Terminal/TerminalStory.svelte +47 -0
  182. package/dist/components/molecules/Terminal/TerminalStory.svelte.d.ts +6 -0
  183. package/dist/components/molecules/TimelineClip/TimelineClip.stories.js +8 -0
  184. package/dist/components/molecules/TimelineClip/TimelineClip.svelte +193 -0
  185. package/dist/components/molecules/TimelineClip/TimelineClip.svelte.d.ts +18 -0
  186. package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte +18 -0
  187. package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte.d.ts +18 -0
  188. package/dist/components/molecules/TimelineTrack/TimelineTrack.stories.js +8 -0
  189. package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte +126 -0
  190. package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte.d.ts +27 -0
  191. package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte +35 -0
  192. package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte.d.ts +18 -0
  193. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.stories.js +8 -0
  194. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte +158 -0
  195. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte.d.ts +18 -0
  196. package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte +55 -0
  197. package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte.d.ts +18 -0
  198. package/dist/components/molecules/TrackHeader/TrackHeader.stories.d.ts +9 -0
  199. package/dist/components/molecules/TrackHeader/TrackHeader.stories.js +8 -0
  200. package/dist/components/molecules/TrackHeader/TrackHeader.svelte +54 -0
  201. package/dist/components/molecules/TrackHeader/TrackHeader.svelte.d.ts +9 -0
  202. package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte +10 -0
  203. package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte.d.ts +3 -0
  204. package/dist/components/molecules/TransportControls/TransportControls.stories.d.ts +9 -0
  205. package/dist/components/molecules/TransportControls/TransportControls.stories.js +8 -0
  206. package/dist/components/molecules/TransportControls/TransportControls.svelte +135 -0
  207. package/dist/components/molecules/TransportControls/TransportControls.svelte.d.ts +24 -0
  208. package/dist/components/molecules/TransportControls/TransportControlsStory.svelte +40 -0
  209. package/dist/components/molecules/TransportControls/TransportControlsStory.svelte.d.ts +3 -0
  210. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.stories.js +8 -0
  211. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte +117 -0
  212. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte.d.ts +20 -0
  213. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte +36 -0
  214. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte.d.ts +18 -0
  215. package/dist/components/molecules/VideoAnimationsPanel/VideoAnimationsPanel.stories.js +8 -0
  216. package/dist/components/molecules/VideoAnimationsPanel/VideoAnimationsPanel.svelte +213 -0
  217. package/dist/components/molecules/VideoAnimationsPanel/VideoAnimationsPanel.svelte.d.ts +22 -0
  218. package/dist/components/molecules/VideoAnimationsPanel/VideoAnimationsPanelStory.svelte +7 -0
  219. package/dist/components/molecules/VideoAnimationsPanel/VideoAnimationsPanelStory.svelte.d.ts +18 -0
  220. package/dist/components/molecules/VideoComponentsPanel/VideoComponentsPanel.stories.js +8 -0
  221. package/dist/components/molecules/VideoComponentsPanel/VideoComponentsPanel.svelte +524 -0
  222. package/dist/components/molecules/VideoComponentsPanel/VideoComponentsPanel.svelte.d.ts +32 -0
  223. package/dist/components/molecules/VideoComponentsPanel/VideoComponentsPanelStory.svelte +7 -0
  224. package/dist/components/molecules/VideoComponentsPanel/VideoComponentsPanelStory.svelte.d.ts +18 -0
  225. package/dist/components/molecules/VideoPlayer/VideoPlayer.stories.d.ts +1 -1
  226. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.d.ts +9 -0
  227. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.js +8 -0
  228. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte +112 -0
  229. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte.d.ts +15 -0
  230. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte +10 -0
  231. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte.d.ts +3 -0
  232. package/dist/components/molecules/VideoTransitionsPanel/VideoTransitionsPanel.stories.js +8 -0
  233. package/dist/components/molecules/VideoTransitionsPanel/VideoTransitionsPanel.svelte +113 -0
  234. package/dist/components/molecules/VideoTransitionsPanel/VideoTransitionsPanel.svelte.d.ts +18 -0
  235. package/dist/components/molecules/VideoTransitionsPanel/VideoTransitionsPanelStory.svelte +7 -0
  236. package/dist/components/molecules/VideoTransitionsPanel/VideoTransitionsPanelStory.svelte.d.ts +18 -0
  237. package/dist/components/molecules/WaveformRegion/WaveformRegion.stories.js +8 -0
  238. package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte +82 -0
  239. package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte.d.ts +13 -0
  240. package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte +17 -0
  241. package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte.d.ts +18 -0
  242. package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte +13 -4
  243. package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte.d.ts +3 -1
  244. package/dist/components/molecules/WidgetCanvas/widgetCanvasContext.d.ts +2 -0
  245. package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte +206 -37
  246. package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte.d.ts +31 -0
  247. package/dist/components/organisms/AudioEditor/AudioEditor.stories.d.ts +12 -0
  248. package/dist/components/organisms/AudioEditor/AudioEditor.stories.js +9 -0
  249. package/dist/components/organisms/AudioEditor/AudioEditor.svelte +324 -0
  250. package/dist/components/organisms/AudioEditor/AudioEditor.svelte.d.ts +13 -0
  251. package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte +10 -0
  252. package/dist/components/organisms/AudioEditor/AudioEditorStory.svelte.d.ts +3 -0
  253. package/dist/components/organisms/BlogEditor/BlogEditor.stories.d.ts +12 -0
  254. package/dist/components/organisms/BlogEditor/BlogEditor.stories.js +9 -0
  255. package/dist/components/organisms/BlogEditor/BlogEditor.svelte +1208 -0
  256. package/dist/components/organisms/BlogEditor/BlogEditor.svelte.d.ts +16 -0
  257. package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +144 -0
  258. package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte.d.ts +3 -0
  259. package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte +28 -0
  260. package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte.d.ts +10 -0
  261. package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte +34 -0
  262. package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte.d.ts +11 -0
  263. package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte +13 -0
  264. package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte.d.ts +8 -0
  265. package/dist/components/organisms/BlogRenderer/BlogRenderer.stories.js +8 -0
  266. package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte +469 -0
  267. package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte.d.ts +14 -0
  268. package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte +20 -0
  269. package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte.d.ts +18 -0
  270. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.d.ts +12 -0
  271. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.js +9 -0
  272. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte +1012 -0
  273. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte.d.ts +16 -0
  274. package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte +76 -0
  275. package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte.d.ts +3 -0
  276. package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.d.ts +12 -0
  277. package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.js +9 -0
  278. package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte +1680 -0
  279. package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte.d.ts +18 -0
  280. package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte +329 -0
  281. package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte.d.ts +3 -0
  282. package/dist/components/organisms/DashboardBuilderPage/DashboardBuilderPage.svelte.d.ts +1 -1
  283. package/dist/components/organisms/DesktopShell/DesktopShell.svelte.d.ts +1 -1
  284. package/dist/components/organisms/EditorShell/EditorShell.svelte +162 -57
  285. package/dist/components/organisms/EditorShell/EditorShell.svelte.d.ts +21 -1
  286. package/dist/components/organisms/FileUploader/FileUploader.stories.d.ts +10 -0
  287. package/dist/components/organisms/FileUploader/FileUploader.stories.js +21 -0
  288. package/dist/components/organisms/FileUploader/FileUploader.svelte +127 -25
  289. package/dist/components/organisms/FileUploader/FileUploader.svelte.d.ts +20 -0
  290. package/dist/components/organisms/FileUploader/FileUploaderStory.svelte +7 -1
  291. package/dist/components/organisms/FileUploader/FileUploaderStory.svelte.d.ts +3 -0
  292. package/dist/components/organisms/ImportWizard/ImportWizard.stories.js +8 -0
  293. package/dist/components/organisms/ImportWizard/ImportWizard.svelte +209 -0
  294. package/dist/components/organisms/ImportWizard/ImportWizard.svelte.d.ts +22 -0
  295. package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte +16 -0
  296. package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte.d.ts +18 -0
  297. package/dist/components/organisms/VideoEditor/VideoEditor.stories.d.ts +12 -0
  298. package/dist/components/organisms/VideoEditor/VideoEditor.stories.js +9 -0
  299. package/dist/components/organisms/VideoEditor/VideoEditor.svelte +1687 -0
  300. package/dist/components/organisms/VideoEditor/VideoEditor.svelte.d.ts +14 -0
  301. package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte +11 -0
  302. package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte.d.ts +3 -0
  303. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.d.ts +12 -0
  304. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.js +9 -0
  305. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte +22 -0
  306. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte.d.ts +13 -0
  307. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte +8 -0
  308. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte.d.ts +3 -0
  309. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.d.ts +12 -0
  310. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.js +9 -0
  311. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte +32 -0
  312. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte.d.ts +17 -0
  313. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte +21 -0
  314. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte.d.ts +3 -0
  315. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.d.ts +12 -0
  316. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.js +9 -0
  317. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte +22 -0
  318. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte.d.ts +13 -0
  319. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte +8 -0
  320. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte.d.ts +3 -0
  321. package/dist/index.d.ts +91 -1
  322. package/dist/index.js +64 -0
  323. package/dist/types/xyflow-svelte.d.ts +62 -0
  324. package/dist/utils/audioProject.d.ts +19 -0
  325. package/dist/utils/audioProject.js +70 -0
  326. package/dist/utils/blogDocument.d.ts +175 -0
  327. package/dist/utils/blogDocument.js +220 -0
  328. package/dist/utils/blueprintRuntime.d.ts +34 -0
  329. package/dist/utils/blueprintRuntime.js +289 -0
  330. package/dist/utils/canvasDocument.d.ts +239 -0
  331. package/dist/utils/canvasDocument.js +443 -0
  332. package/dist/utils/canvasExport.d.ts +10 -0
  333. package/dist/utils/canvasExport.js +469 -0
  334. package/dist/utils/canvasHierarchy.d.ts +189 -0
  335. package/dist/utils/canvasHierarchy.js +674 -0
  336. package/dist/utils/canvasHistory.d.ts +19 -0
  337. package/dist/utils/canvasHistory.js +48 -0
  338. package/dist/utils/canvasWidget.d.ts +81 -0
  339. package/dist/utils/canvasWidget.js +597 -0
  340. package/dist/utils/mediaTime.d.ts +26 -0
  341. package/dist/utils/mediaTime.js +76 -0
  342. package/dist/utils/mediaTracks.d.ts +215 -0
  343. package/dist/utils/mediaTracks.js +798 -0
  344. package/dist/utils/nodeGraph.d.ts +354 -0
  345. package/dist/utils/nodeGraph.js +1453 -0
  346. package/dist/utils/queryBridge.d.ts +18 -0
  347. package/dist/utils/queryBridge.js +118 -0
  348. package/dist/utils/videoSequence.d.ts +13 -0
  349. package/dist/utils/videoSequence.js +94 -0
  350. 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>