@r2digisolutions/components 0.1.6 → 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.
Files changed (389) hide show
  1. package/README.md +1 -1
  2. package/dist/components/atoms/Checkbox/Checkbox.svelte +10 -3
  3. package/dist/components/atoms/Checkbox/Checkbox.svelte.d.ts +3 -0
  4. package/dist/components/atoms/IconButton/IconButton.svelte +1 -1
  5. package/dist/components/atoms/Input/Input.svelte +17 -2
  6. package/dist/components/atoms/Input/Input.svelte.d.ts +6 -1
  7. package/dist/components/atoms/MarqueeRect/MarqueeRect.svelte +21 -0
  8. package/dist/components/atoms/MarqueeRect/MarqueeRect.svelte.d.ts +8 -0
  9. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.stories.js +8 -0
  10. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte +122 -0
  11. package/dist/components/atoms/MediaKindIcon/MediaKindIcon.svelte.d.ts +11 -0
  12. package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte +12 -0
  13. package/dist/components/atoms/MediaKindIcon/MediaKindIconStory.svelte.d.ts +18 -0
  14. package/dist/components/atoms/PanKnob/PanKnob.stories.d.ts +9 -0
  15. package/dist/components/atoms/PanKnob/PanKnob.stories.js +8 -0
  16. package/dist/components/atoms/PanKnob/PanKnob.svelte +218 -0
  17. package/dist/components/atoms/PanKnob/PanKnob.svelte.d.ts +19 -0
  18. package/dist/components/atoms/PanKnob/PanKnobStory.svelte +15 -0
  19. package/dist/components/atoms/PanKnob/PanKnobStory.svelte.d.ts +3 -0
  20. package/dist/components/atoms/Playhead/Playhead.stories.js +8 -0
  21. package/dist/components/atoms/Playhead/Playhead.svelte +59 -0
  22. package/dist/components/atoms/Playhead/Playhead.svelte.d.ts +13 -0
  23. package/dist/components/atoms/Playhead/PlayheadStory.svelte +9 -0
  24. package/dist/components/atoms/Playhead/PlayheadStory.svelte.d.ts +18 -0
  25. package/dist/components/atoms/Textarea/Textarea.svelte +4 -1
  26. package/dist/components/atoms/Textarea/Textarea.svelte.d.ts +1 -0
  27. package/dist/components/atoms/TimeRuler/TimeRuler.stories.js +8 -0
  28. package/dist/components/atoms/TimeRuler/TimeRuler.svelte +58 -0
  29. package/dist/components/atoms/TimeRuler/TimeRuler.svelte.d.ts +10 -0
  30. package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte +7 -0
  31. package/dist/components/atoms/TimeRuler/TimeRulerStory.svelte.d.ts +18 -0
  32. package/dist/components/atoms/Toggle/Toggle.svelte +1 -1
  33. package/dist/components/atoms/Tooltip/Tooltip.svelte +1 -1
  34. package/dist/components/atoms/TrimHandle/TrimHandle.stories.js +8 -0
  35. package/dist/components/atoms/TrimHandle/TrimHandle.svelte +63 -0
  36. package/dist/components/atoms/TrimHandle/TrimHandle.svelte.d.ts +12 -0
  37. package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte +11 -0
  38. package/dist/components/atoms/TrimHandle/TrimHandleStory.svelte.d.ts +18 -0
  39. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.d.ts +9 -0
  40. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.stories.js +8 -0
  41. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte +102 -0
  42. package/dist/components/molecules/AiAssistPanel/AiAssistPanel.svelte.d.ts +20 -0
  43. package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte +34 -0
  44. package/dist/components/molecules/AiAssistPanel/AiAssistPanelStory.svelte.d.ts +3 -0
  45. package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +112 -62
  46. package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte.d.ts +2 -0
  47. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.stories.js +8 -0
  48. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte +117 -0
  49. package/dist/components/molecules/BulkEditPanel/BulkEditPanel.svelte.d.ts +23 -0
  50. package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte +18 -0
  51. package/dist/components/molecules/BulkEditPanel/BulkEditPanelStory.svelte.d.ts +18 -0
  52. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.stories.js +8 -0
  53. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte +858 -0
  54. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanel.svelte.d.ts +43 -0
  55. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte +7 -0
  56. package/dist/components/molecules/CanvasElementsPanel/CanvasElementsPanelStory.svelte.d.ts +18 -0
  57. package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.d.ts +9 -0
  58. package/dist/components/molecules/CanvasInspector/CanvasInspector.stories.js +8 -0
  59. package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte +1140 -0
  60. package/dist/components/molecules/CanvasInspector/CanvasInspector.svelte.d.ts +28 -0
  61. package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte +20 -0
  62. package/dist/components/molecules/CanvasInspector/CanvasInspectorStory.svelte.d.ts +3 -0
  63. package/dist/components/molecules/CodeEditor/CodeEditor.svelte +13 -10
  64. package/dist/components/molecules/ColorPicker/ColorPicker.svelte +167 -97
  65. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.d.ts +9 -0
  66. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.stories.js +8 -0
  67. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte +108 -0
  68. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenu.svelte.d.ts +18 -0
  69. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte +15 -0
  70. package/dist/components/molecules/ColumnVisibilityMenu/ColumnVisibilityMenuStory.svelte.d.ts +3 -0
  71. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanel.stories.d.ts +9 -0
  72. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanel.stories.js +8 -0
  73. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanel.svelte +412 -0
  74. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanel.svelte.d.ts +14 -0
  75. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanelStory.svelte +27 -0
  76. package/dist/components/molecules/ConditionalFormatPanel/ConditionalFormatPanelStory.svelte.d.ts +3 -0
  77. package/dist/components/molecules/ConflictResolver/ConflictResolver.stories.js +8 -0
  78. package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte +73 -0
  79. package/dist/components/molecules/ConflictResolver/ConflictResolver.svelte.d.ts +16 -0
  80. package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte +11 -0
  81. package/dist/components/molecules/ConflictResolver/ConflictResolverStory.svelte.d.ts +18 -0
  82. package/dist/components/molecules/ContextMenu/ContextMenu.svelte +237 -41
  83. package/dist/components/molecules/ContextMenu/ContextMenu.svelte.d.ts +17 -1
  84. package/dist/components/molecules/DateTimePicker/DateTimePicker.svelte.d.ts +1 -1
  85. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.d.ts +9 -0
  86. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.stories.js +8 -0
  87. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte +56 -0
  88. package/dist/components/molecules/ExpressionEditor/ExpressionEditor.svelte.d.ts +12 -0
  89. package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte +12 -0
  90. package/dist/components/molecules/ExpressionEditor/ExpressionEditorStory.svelte.d.ts +3 -0
  91. package/dist/components/molecules/FilterList/FilterList.stories.d.ts +9 -0
  92. package/dist/components/molecules/FilterList/FilterList.stories.js +8 -0
  93. package/dist/components/molecules/FilterList/FilterList.svelte +101 -0
  94. package/dist/components/molecules/FilterList/FilterList.svelte.d.ts +18 -0
  95. package/dist/components/molecules/FilterList/FilterListStory.svelte +31 -0
  96. package/dist/components/molecules/FilterList/FilterListStory.svelte.d.ts +3 -0
  97. package/dist/components/molecules/FormDateTimePicker/FormDateTimePicker.svelte.d.ts +1 -1
  98. package/dist/components/molecules/Gauge/Gauge.stories.d.ts +1 -1
  99. package/dist/components/molecules/InfiniteScroll/InfiniteScroll.stories.d.ts +1 -1
  100. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.d.ts +9 -0
  101. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.stories.js +8 -0
  102. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte +94 -0
  103. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowser.svelte.d.ts +15 -0
  104. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte +18 -0
  105. package/dist/components/molecules/MediaAssetBrowser/MediaAssetBrowserStory.svelte.d.ts +3 -0
  106. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.stories.js +8 -0
  107. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte +50 -0
  108. package/dist/components/molecules/MediaAssetCard/MediaAssetCard.svelte.d.ts +11 -0
  109. package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte +13 -0
  110. package/dist/components/molecules/MediaAssetCard/MediaAssetCardStory.svelte.d.ts +18 -0
  111. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.stories.js +8 -0
  112. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte +380 -0
  113. package/dist/components/molecules/MediaLayerItem/MediaLayerItem.svelte.d.ts +33 -0
  114. package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte +14 -0
  115. package/dist/components/molecules/MediaLayerItem/MediaLayerItemStory.svelte.d.ts +18 -0
  116. package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.d.ts +9 -0
  117. package/dist/components/molecules/MediaLayerList/MediaLayerList.stories.js +8 -0
  118. package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte +399 -0
  119. package/dist/components/molecules/MediaLayerList/MediaLayerList.svelte.d.ts +37 -0
  120. package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte +24 -0
  121. package/dist/components/molecules/MediaLayerList/MediaLayerListStory.svelte.d.ts +3 -0
  122. package/dist/components/molecules/MediaStage/MediaStage.stories.d.ts +9 -0
  123. package/dist/components/molecules/MediaStage/MediaStage.stories.js +8 -0
  124. package/dist/components/molecules/MediaStage/MediaStage.svelte +1498 -0
  125. package/dist/components/molecules/MediaStage/MediaStage.svelte.d.ts +45 -0
  126. package/dist/components/molecules/MediaStage/MediaStageStory.svelte +35 -0
  127. package/dist/components/molecules/MediaStage/MediaStageStory.svelte.d.ts +3 -0
  128. package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.d.ts +9 -0
  129. package/dist/components/molecules/MediaTimeline/MediaTimeline.stories.js +8 -0
  130. package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte +131 -0
  131. package/dist/components/molecules/MediaTimeline/MediaTimeline.svelte.d.ts +19 -0
  132. package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte +20 -0
  133. package/dist/components/molecules/MediaTimeline/MediaTimelineStory.svelte.d.ts +3 -0
  134. package/dist/components/molecules/MixerChannel/MixerChannel.stories.d.ts +9 -0
  135. package/dist/components/molecules/MixerChannel/MixerChannel.stories.js +8 -0
  136. package/dist/components/molecules/MixerChannel/MixerChannel.svelte +92 -0
  137. package/dist/components/molecules/MixerChannel/MixerChannel.svelte.d.ts +11 -0
  138. package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte +13 -0
  139. package/dist/components/molecules/MixerChannel/MixerChannelStory.svelte.d.ts +3 -0
  140. package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte +454 -0
  141. package/dist/components/molecules/NodeGraph/GraphBlueprintNode.svelte.d.ts +8 -0
  142. package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte +26 -0
  143. package/dist/components/molecules/NodeGraph/GraphFlowBridge.svelte.d.ts +22 -0
  144. package/dist/components/molecules/NodeGraph/NodeGraph.stories.d.ts +12 -0
  145. package/dist/components/molecules/NodeGraph/NodeGraph.stories.js +9 -0
  146. package/dist/components/molecules/NodeGraph/NodeGraph.svelte +1409 -0
  147. package/dist/components/molecules/NodeGraph/NodeGraph.svelte.d.ts +33 -0
  148. package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte +90 -0
  149. package/dist/components/molecules/NodeGraph/NodeGraphStory.svelte.d.ts +3 -0
  150. package/dist/components/molecules/NodePalette/NodePalette.stories.js +8 -0
  151. package/dist/components/molecules/NodePalette/NodePalette.svelte +86 -0
  152. package/dist/components/molecules/NodePalette/NodePalette.svelte.d.ts +11 -0
  153. package/dist/components/molecules/NodePalette/NodePaletteStory.svelte +11 -0
  154. package/dist/components/molecules/NodePalette/NodePaletteStory.svelte.d.ts +18 -0
  155. package/dist/components/molecules/Poll/Poll.stories.d.ts +1 -1
  156. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.stories.js +8 -0
  157. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte +72 -0
  158. package/dist/components/molecules/ProgramMonitor/ProgramMonitor.svelte.d.ts +12 -0
  159. package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte +16 -0
  160. package/dist/components/molecules/ProgramMonitor/ProgramMonitorStory.svelte.d.ts +18 -0
  161. package/dist/components/molecules/PropertyField/PropertyField.svelte +57 -3
  162. package/dist/components/molecules/PropertyField/PropertyField.svelte.d.ts +10 -0
  163. package/dist/components/molecules/PropertyField/PropertyFieldStory.svelte +21 -0
  164. package/dist/components/molecules/PropertyGroup/PropertyGroup.svelte +1 -1
  165. package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.d.ts +9 -0
  166. package/dist/components/molecules/QueryBuilder/QueryBuilder.stories.js +8 -0
  167. package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte +52 -0
  168. package/dist/components/molecules/QueryBuilder/QueryBuilder.svelte.d.ts +18 -0
  169. package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte +62 -0
  170. package/dist/components/molecules/QueryBuilder/QueryBuilderStory.svelte.d.ts +3 -0
  171. package/dist/components/molecules/RelationPicker/RelationPicker.stories.d.ts +9 -0
  172. package/dist/components/molecules/RelationPicker/RelationPicker.stories.js +8 -0
  173. package/dist/components/molecules/RelationPicker/RelationPicker.svelte +180 -0
  174. package/dist/components/molecules/RelationPicker/RelationPicker.svelte.d.ts +24 -0
  175. package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte +16 -0
  176. package/dist/components/molecules/RelationPicker/RelationPickerStory.svelte.d.ts +3 -0
  177. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.d.ts +9 -0
  178. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.stories.js +8 -0
  179. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte +154 -0
  180. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilder.svelte.d.ts +12 -0
  181. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte +16 -0
  182. package/dist/components/molecules/SchemaFieldBuilder/SchemaFieldBuilderStory.svelte.d.ts +3 -0
  183. package/dist/components/molecules/SecretField/SecretField.stories.d.ts +9 -0
  184. package/dist/components/molecules/SecretField/SecretField.stories.js +8 -0
  185. package/dist/components/molecules/SecretField/SecretField.svelte +58 -0
  186. package/dist/components/molecules/SecretField/SecretField.svelte.d.ts +15 -0
  187. package/dist/components/molecules/SecretField/SecretFieldStory.svelte +8 -0
  188. package/dist/components/molecules/SecretField/SecretFieldStory.svelte.d.ts +3 -0
  189. package/dist/components/molecules/SplitPane/SplitPane.svelte +96 -37
  190. package/dist/components/molecules/SplitPane/SplitPane.svelte.d.ts +10 -2
  191. package/dist/components/molecules/TableToolbar/TableToolbar.svelte.d.ts +1 -1
  192. package/dist/components/molecules/Terminal/Terminal.stories.js +20 -0
  193. package/dist/components/molecules/Terminal/Terminal.svelte +238 -0
  194. package/dist/components/molecules/Terminal/Terminal.svelte.d.ts +19 -0
  195. package/dist/components/molecules/Terminal/TerminalStory.svelte +47 -0
  196. package/dist/components/molecules/Terminal/TerminalStory.svelte.d.ts +6 -0
  197. package/dist/components/molecules/TimelineClip/TimelineClip.stories.js +8 -0
  198. package/dist/components/molecules/TimelineClip/TimelineClip.svelte +116 -0
  199. package/dist/components/molecules/TimelineClip/TimelineClip.svelte.d.ts +15 -0
  200. package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte +18 -0
  201. package/dist/components/molecules/TimelineClip/TimelineClipStory.svelte.d.ts +18 -0
  202. package/dist/components/molecules/TimelineTrack/TimelineTrack.stories.js +8 -0
  203. package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte +56 -0
  204. package/dist/components/molecules/TimelineTrack/TimelineTrack.svelte.d.ts +16 -0
  205. package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte +35 -0
  206. package/dist/components/molecules/TimelineTrack/TimelineTrackStory.svelte.d.ts +18 -0
  207. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.stories.js +8 -0
  208. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte +158 -0
  209. package/dist/components/molecules/TraceWaterfall/TraceWaterfall.svelte.d.ts +18 -0
  210. package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte +55 -0
  211. package/dist/components/molecules/TraceWaterfall/TraceWaterfallStory.svelte.d.ts +18 -0
  212. package/dist/components/molecules/TrackHeader/TrackHeader.stories.d.ts +9 -0
  213. package/dist/components/molecules/TrackHeader/TrackHeader.stories.js +8 -0
  214. package/dist/components/molecules/TrackHeader/TrackHeader.svelte +54 -0
  215. package/dist/components/molecules/TrackHeader/TrackHeader.svelte.d.ts +9 -0
  216. package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte +10 -0
  217. package/dist/components/molecules/TrackHeader/TrackHeaderStory.svelte.d.ts +3 -0
  218. package/dist/components/molecules/TransportControls/TransportControls.stories.d.ts +9 -0
  219. package/dist/components/molecules/TransportControls/TransportControls.stories.js +8 -0
  220. package/dist/components/molecules/TransportControls/TransportControls.svelte +53 -0
  221. package/dist/components/molecules/TransportControls/TransportControls.svelte.d.ts +12 -0
  222. package/dist/components/molecules/TransportControls/TransportControlsStory.svelte +18 -0
  223. package/dist/components/molecules/TransportControls/TransportControlsStory.svelte.d.ts +3 -0
  224. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.stories.js +8 -0
  225. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte +117 -0
  226. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanel.svelte.d.ts +20 -0
  227. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte +36 -0
  228. package/dist/components/molecules/VersionHistoryPanel/VersionHistoryPanelStory.svelte.d.ts +18 -0
  229. package/dist/components/molecules/VideoPlayer/VideoPlayer.stories.d.ts +1 -1
  230. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.d.ts +9 -0
  231. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.stories.js +8 -0
  232. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte +59 -0
  233. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeader.svelte.d.ts +9 -0
  234. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte +10 -0
  235. package/dist/components/molecules/VideoTrackHeader/VideoTrackHeaderStory.svelte.d.ts +3 -0
  236. package/dist/components/molecules/WaveformRegion/WaveformRegion.stories.js +8 -0
  237. package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte +82 -0
  238. package/dist/components/molecules/WaveformRegion/WaveformRegion.svelte.d.ts +13 -0
  239. package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte +17 -0
  240. package/dist/components/molecules/WaveformRegion/WaveformRegionStory.svelte.d.ts +18 -0
  241. package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte +13 -4
  242. package/dist/components/molecules/WidgetCanvas/WidgetCanvas.svelte.d.ts +3 -1
  243. package/dist/components/molecules/WidgetCanvas/widgetCanvasContext.d.ts +2 -0
  244. package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte +206 -37
  245. package/dist/components/molecules/WidgetFrame/WidgetFrame.svelte.d.ts +31 -0
  246. package/dist/components/organisms/AssetPickerPage/AssetPickerPage.svelte.d.ts +1 -1
  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 +273 -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/BacklogPage/BacklogPage.svelte.d.ts +1 -1
  254. package/dist/components/organisms/BlogEditor/BlogEditor.stories.d.ts +12 -0
  255. package/dist/components/organisms/BlogEditor/BlogEditor.stories.js +9 -0
  256. package/dist/components/organisms/BlogEditor/BlogEditor.svelte +1208 -0
  257. package/dist/components/organisms/BlogEditor/BlogEditor.svelte.d.ts +16 -0
  258. package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +144 -0
  259. package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte.d.ts +3 -0
  260. package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte +28 -0
  261. package/dist/components/organisms/BlogEditor/DemoBlogCard.svelte.d.ts +10 -0
  262. package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte +34 -0
  263. package/dist/components/organisms/BlogEditor/DemoBlogStats.svelte.d.ts +11 -0
  264. package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte +13 -0
  265. package/dist/components/organisms/BlogEditor/DemoBlogWidget.svelte.d.ts +8 -0
  266. package/dist/components/organisms/BlogRenderer/BlogRenderer.stories.js +8 -0
  267. package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte +469 -0
  268. package/dist/components/organisms/BlogRenderer/BlogRenderer.svelte.d.ts +14 -0
  269. package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte +20 -0
  270. package/dist/components/organisms/BlogRenderer/BlogRendererStory.svelte.d.ts +18 -0
  271. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.d.ts +12 -0
  272. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.stories.js +9 -0
  273. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte +1012 -0
  274. package/dist/components/organisms/BlueprintEditor/BlueprintEditor.svelte.d.ts +16 -0
  275. package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte +76 -0
  276. package/dist/components/organisms/BlueprintEditor/BlueprintEditorStory.svelte.d.ts +3 -0
  277. package/dist/components/organisms/CalendarApp/CalendarApp.svelte.d.ts +1 -1
  278. package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.d.ts +12 -0
  279. package/dist/components/organisms/CanvasEditor/CanvasEditor.stories.js +9 -0
  280. package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte +1680 -0
  281. package/dist/components/organisms/CanvasEditor/CanvasEditor.svelte.d.ts +18 -0
  282. package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte +329 -0
  283. package/dist/components/organisms/CanvasEditor/CanvasEditorStory.svelte.d.ts +3 -0
  284. package/dist/components/organisms/DashboardBuilderPage/DashboardBuilderPage.svelte.d.ts +1 -1
  285. package/dist/components/organisms/DataGrid/DataGrid.stories.d.ts +65 -0
  286. package/dist/components/organisms/DataGrid/DataGrid.stories.js +95 -0
  287. package/dist/components/organisms/DataGrid/DataGrid.svelte +2264 -0
  288. package/dist/components/organisms/DataGrid/DataGrid.svelte.d.ts +99 -0
  289. package/dist/components/organisms/DataGrid/DataGridStory.svelte +659 -0
  290. package/dist/components/organisms/DataGrid/DataGridStory.svelte.d.ts +11 -0
  291. package/dist/components/organisms/DataGrid/breakpoints.d.ts +10 -0
  292. package/dist/components/organisms/DataGrid/breakpoints.js +20 -0
  293. package/dist/components/organisms/DataGrid/cellEditors.d.ts +8 -0
  294. package/dist/components/organisms/DataGrid/cellEditors.js +59 -0
  295. package/dist/components/organisms/DataGrid/conditionalFormat.d.ts +11 -0
  296. package/dist/components/organisms/DataGrid/conditionalFormat.js +98 -0
  297. package/dist/components/organisms/DataGrid/filterRows.d.ts +5 -0
  298. package/dist/components/organisms/DataGrid/filterRows.js +60 -0
  299. package/dist/components/organisms/DataGrid/marks.d.ts +9 -0
  300. package/dist/components/organisms/DataGrid/marks.js +91 -0
  301. package/dist/components/organisms/DataGrid/types.d.ts +126 -0
  302. package/dist/components/organisms/DataGrid/types.js +21 -0
  303. package/dist/components/organisms/DesktopShell/DesktopShell.svelte.d.ts +1 -1
  304. package/dist/components/organisms/EditorShell/EditorShell.svelte +123 -28
  305. package/dist/components/organisms/EditorShell/EditorShell.svelte.d.ts +21 -1
  306. package/dist/components/organisms/FileUploader/FileUploader.svelte +77 -15
  307. package/dist/components/organisms/FileUploader/FileUploader.svelte.d.ts +11 -0
  308. package/dist/components/organisms/ImportWizard/ImportWizard.stories.js +8 -0
  309. package/dist/components/organisms/ImportWizard/ImportWizard.svelte +209 -0
  310. package/dist/components/organisms/ImportWizard/ImportWizard.svelte.d.ts +22 -0
  311. package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte +16 -0
  312. package/dist/components/organisms/ImportWizard/ImportWizardStory.svelte.d.ts +18 -0
  313. package/dist/components/organisms/ResourceStudio/ResourceStudio.stories.d.ts +33 -0
  314. package/dist/components/organisms/ResourceStudio/ResourceStudio.stories.js +56 -0
  315. package/dist/components/organisms/ResourceStudio/ResourceStudio.svelte +830 -0
  316. package/dist/components/organisms/ResourceStudio/ResourceStudio.svelte.d.ts +68 -0
  317. package/dist/components/organisms/ResourceStudio/ResourceStudioStory.svelte +600 -0
  318. package/dist/components/organisms/ResourceStudio/ResourceStudioStory.svelte.d.ts +7 -0
  319. package/dist/components/organisms/ResourceStudio/fieldsToColumns.d.ts +4 -0
  320. package/dist/components/organisms/ResourceStudio/fieldsToColumns.js +20 -0
  321. package/dist/components/organisms/ResourceStudio/fieldsToSchema.d.ts +6 -0
  322. package/dist/components/organisms/ResourceStudio/fieldsToSchema.js +53 -0
  323. package/dist/components/organisms/ResourceStudio/types.d.ts +69 -0
  324. package/dist/components/organisms/ResourceStudio/types.js +15 -0
  325. package/dist/components/organisms/Scheduler/Scheduler.svelte.d.ts +1 -1
  326. package/dist/components/organisms/SchemaForm/SchemaForm.svelte +10 -3
  327. package/dist/components/organisms/SchemaForm/SchemaForm.svelte.d.ts +1 -0
  328. package/dist/components/organisms/VideoEditor/VideoEditor.stories.d.ts +12 -0
  329. package/dist/components/organisms/VideoEditor/VideoEditor.stories.js +9 -0
  330. package/dist/components/organisms/VideoEditor/VideoEditor.svelte +308 -0
  331. package/dist/components/organisms/VideoEditor/VideoEditor.svelte.d.ts +13 -0
  332. package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte +10 -0
  333. package/dist/components/organisms/VideoEditor/VideoEditorStory.svelte.d.ts +3 -0
  334. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.d.ts +12 -0
  335. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.stories.js +9 -0
  336. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte +22 -0
  337. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte.d.ts +13 -0
  338. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte +8 -0
  339. package/dist/components/templates/AudioEditorTemplate/AudioEditorTemplateStory.svelte.d.ts +3 -0
  340. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.d.ts +12 -0
  341. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.stories.js +9 -0
  342. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte +32 -0
  343. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplate.svelte.d.ts +17 -0
  344. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte +21 -0
  345. package/dist/components/templates/CanvasEditorTemplate/CanvasEditorTemplateStory.svelte.d.ts +3 -0
  346. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplate.stories.d.ts +25 -0
  347. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplate.stories.js +18 -0
  348. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplate.svelte +192 -0
  349. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplate.svelte.d.ts +62 -0
  350. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplateStory.svelte +129 -0
  351. package/dist/components/templates/ResourceStudioTemplate/ResourceStudioTemplateStory.svelte.d.ts +7 -0
  352. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.d.ts +12 -0
  353. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.stories.js +9 -0
  354. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte +22 -0
  355. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte.d.ts +13 -0
  356. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte +8 -0
  357. package/dist/components/templates/VideoEditorTemplate/VideoEditorTemplateStory.svelte.d.ts +3 -0
  358. package/dist/index.d.ts +94 -1
  359. package/dist/index.js +68 -0
  360. package/dist/types/xyflow-svelte.d.ts +62 -0
  361. package/dist/utils/audioProject.d.ts +19 -0
  362. package/dist/utils/audioProject.js +70 -0
  363. package/dist/utils/blogDocument.d.ts +175 -0
  364. package/dist/utils/blogDocument.js +220 -0
  365. package/dist/utils/blueprintRuntime.d.ts +34 -0
  366. package/dist/utils/blueprintRuntime.js +289 -0
  367. package/dist/utils/canvasDocument.d.ts +239 -0
  368. package/dist/utils/canvasDocument.js +443 -0
  369. package/dist/utils/canvasExport.d.ts +10 -0
  370. package/dist/utils/canvasExport.js +469 -0
  371. package/dist/utils/canvasHierarchy.d.ts +189 -0
  372. package/dist/utils/canvasHierarchy.js +674 -0
  373. package/dist/utils/canvasHistory.d.ts +19 -0
  374. package/dist/utils/canvasHistory.js +48 -0
  375. package/dist/utils/canvasWidget.d.ts +81 -0
  376. package/dist/utils/canvasWidget.js +597 -0
  377. package/dist/utils/marqueeSelect.d.ts +48 -0
  378. package/dist/utils/marqueeSelect.js +189 -0
  379. package/dist/utils/mediaTime.d.ts +16 -0
  380. package/dist/utils/mediaTime.js +57 -0
  381. package/dist/utils/mediaTracks.d.ts +61 -0
  382. package/dist/utils/mediaTracks.js +102 -0
  383. package/dist/utils/nodeGraph.d.ts +354 -0
  384. package/dist/utils/nodeGraph.js +1453 -0
  385. package/dist/utils/queryBridge.d.ts +18 -0
  386. package/dist/utils/queryBridge.js +118 -0
  387. package/dist/utils/videoSequence.d.ts +13 -0
  388. package/dist/utils/videoSequence.js +63 -0
  389. package/package.json +5 -2
@@ -0,0 +1,1680 @@
1
+ <script lang="ts">
2
+ import EditorShell from '../EditorShell/EditorShell.svelte';
3
+ import MediaAssetBrowser from '../../molecules/MediaAssetBrowser/MediaAssetBrowser.svelte';
4
+ import MediaLayerList from '../../molecules/MediaLayerList/MediaLayerList.svelte';
5
+ import MediaStage from '../../molecules/MediaStage/MediaStage.svelte';
6
+ import CanvasInspector from '../../molecules/CanvasInspector/CanvasInspector.svelte';
7
+ import CanvasElementsPanel, {
8
+ type CanvasElementDef
9
+ } from '../../molecules/CanvasElementsPanel/CanvasElementsPanel.svelte';
10
+ import ContextMenu from '../../molecules/ContextMenu/ContextMenu.svelte';
11
+ import type { ContextMenuItem } from '../../molecules/ContextMenu/ContextMenu.svelte';
12
+ import BulkActionBar from '../../molecules/BulkActionBar/BulkActionBar.svelte';
13
+ import Panel from '../../molecules/Panel/Panel.svelte';
14
+ import SplitPane from '../../molecules/SplitPane/SplitPane.svelte';
15
+ import IconButton from '../../atoms/IconButton/IconButton.svelte';
16
+ import Select from '../../molecules/Select/Select.svelte';
17
+ import DropdownMenu, {
18
+ type DropdownItem
19
+ } from '../../molecules/DropdownMenu/DropdownMenu.svelte';
20
+ import {
21
+ CANVAS_PRESETS,
22
+ alignLayerRect,
23
+ createCanvasGuide,
24
+ createCanvasLayer,
25
+ createPathFromDocPoints,
26
+ defaultSlotFromRect,
27
+ emptyCanvasDocument,
28
+ migrateCanvasDocument,
29
+ presetIdForSize,
30
+ type CanvasAlign,
31
+ type CanvasDocument,
32
+ type CanvasLayer,
33
+ type CanvasLayerKind
34
+ } from '../../../utils/canvasDocument.js';
35
+ import {
36
+ deleteSubtree,
37
+ duplicateSubtree,
38
+ groupLayers,
39
+ isContainerKind,
40
+ computeAbsoluteRects,
41
+ contentPadding,
42
+ reparentLayer,
43
+ reorderSiblings,
44
+ ungroupLayers,
45
+ wrapSelection,
46
+ slotFromLocalRect
47
+ } from '../../../utils/canvasHierarchy.js';
48
+ import {
49
+ breakWidgetInstance,
50
+ createWidgetFromSelection,
51
+ deleteWidgetDefinition,
52
+ duplicateWidgetDefinition,
53
+ placeWidgetInstance,
54
+ renameWidgetDefinition,
55
+ setExposedProp,
56
+ setInstanceOverride,
57
+ syncInstanceSizesToDefinition,
58
+ updateWidgetDefinition
59
+ } from '../../../utils/canvasWidget.js';
60
+ import {
61
+ createCanvasHistory,
62
+ pushCanvasHistory,
63
+ redoCanvasHistory,
64
+ undoCanvasHistory,
65
+ type CanvasHistoryState
66
+ } from '../../../utils/canvasHistory.js';
67
+ import {
68
+ downloadBlob,
69
+ exportCanvasBlob,
70
+ type CanvasExportFormat
71
+ } from '../../../utils/canvasExport.js';
72
+ import { toast } from '../Toast/toast.svelte.js';
73
+ import type { MediaAsset } from '../../../utils/mediaTracks.js';
74
+ import Undo2 from '@lucide/svelte/icons/undo-2';
75
+ import Redo2 from '@lucide/svelte/icons/redo-2';
76
+ import CopyPlus from '@lucide/svelte/icons/copy-plus';
77
+ import Download from '@lucide/svelte/icons/download';
78
+ import PanelLeft from '@lucide/svelte/icons/panel-left';
79
+ import PanelRight from '@lucide/svelte/icons/panel-right';
80
+ import ZoomIn from '@lucide/svelte/icons/zoom-in';
81
+ import ZoomOut from '@lucide/svelte/icons/zoom-out';
82
+ import Grid3x3 from '@lucide/svelte/icons/grid-3x3';
83
+ import Magnet from '@lucide/svelte/icons/magnet';
84
+ import Copy from '@lucide/svelte/icons/copy';
85
+ import Trash2 from '@lucide/svelte/icons/trash-2';
86
+ import Lock from '@lucide/svelte/icons/lock';
87
+ import Unlock from '@lucide/svelte/icons/unlock';
88
+ import EyeOff from '@lucide/svelte/icons/eye-off';
89
+ import BringToFront from '@lucide/svelte/icons/bring-to-front';
90
+ import ArrowDownToLine from '@lucide/svelte/icons/arrow-down-to-line';
91
+ import AlignHorizontalJustifyCenter from '@lucide/svelte/icons/align-horizontal-justify-center';
92
+ import AlignStartVertical from '@lucide/svelte/icons/align-start-vertical';
93
+ import AlignCenterVertical from '@lucide/svelte/icons/align-center-vertical';
94
+ import AlignEndVertical from '@lucide/svelte/icons/align-end-vertical';
95
+ import AlignStartHorizontal from '@lucide/svelte/icons/align-start-horizontal';
96
+ import AlignCenterHorizontal from '@lucide/svelte/icons/align-center-horizontal';
97
+ import AlignEndHorizontal from '@lucide/svelte/icons/align-end-horizontal';
98
+ import AlignCenter from '@lucide/svelte/icons/align-center';
99
+ import Maximize2 from '@lucide/svelte/icons/maximize-2';
100
+ import FileJson from '@lucide/svelte/icons/file-json';
101
+ import ImageIcon from '@lucide/svelte/icons/image';
102
+ import FileImage from '@lucide/svelte/icons/file-image';
103
+ import SeparatorVertical from '@lucide/svelte/icons/separator-vertical';
104
+ import SeparatorHorizontal from '@lucide/svelte/icons/separator-horizontal';
105
+ import Eye from '@lucide/svelte/icons/eye';
106
+ import Pencil from '@lucide/svelte/icons/pencil';
107
+ import TextCursorInput from '@lucide/svelte/icons/text-cursor-input';
108
+ import Group from '@lucide/svelte/icons/group';
109
+ import Ungroup from '@lucide/svelte/icons/ungroup';
110
+ import Component from '@lucide/svelte/icons/component';
111
+ import ChevronLeft from '@lucide/svelte/icons/chevron-left';
112
+ import Tooltip from '../../atoms/Tooltip/Tooltip.svelte';
113
+
114
+ interface CanvasEditorProps {
115
+ value?: CanvasDocument;
116
+ assets?: MediaAsset[];
117
+ class?: string;
118
+ onchange?: (doc: CanvasDocument) => void;
119
+ onassetschange?: (assets: MediaAsset[]) => void;
120
+ onexport?: (payload: {
121
+ format: CanvasExportFormat;
122
+ document: CanvasDocument;
123
+ blob?: Blob;
124
+ }) => void;
125
+ }
126
+
127
+ let {
128
+ value = $bindable(emptyCanvasDocument()),
129
+ assets = $bindable<MediaAsset[]>([]),
130
+ class: className = '',
131
+ onchange,
132
+ onassetschange,
133
+ onexport
134
+ }: CanvasEditorProps = $props();
135
+
136
+ // Ensure v2 document shape
137
+ $effect.pre(() => {
138
+ if (value.version !== 2 || !value.widgets) {
139
+ value = migrateCanvasDocument(value);
140
+ }
141
+ });
142
+
143
+ let selectedIds = $state<string[]>([]);
144
+ let showSidebar = $state(true);
145
+ let showInspector = $state(true);
146
+ let assetsSize = $state(58);
147
+ let sidebarTab = $state<'elements' | 'uploads' | 'widgets'>('elements');
148
+ let zoom = $state(1);
149
+ let showGrid = $state(false);
150
+ let showGuides = $state(true);
151
+ let snap = $state(true);
152
+ let cellSize = $state(8);
153
+ let drawMode = $state(false);
154
+ let ctxOpen = $state(false);
155
+ let ctxAnchor = $state<{ x: number; y: number } | null>(null);
156
+ let ctxLayerId = $state<string | null>(null);
157
+ let exporting = $state(false);
158
+ /** When set, stage edits this widget definition instead of the scene. */
159
+ let editingWidgetId = $state<string | null>(null);
160
+ let history = $state<CanvasHistoryState>(createCanvasHistory(value));
161
+ let applyingHistory = false;
162
+ let syncInstanceSizes = $state(true);
163
+
164
+ const gridSizeOptions = [
165
+ { value: '4', label: '4px' },
166
+ { value: '8', label: '8px' },
167
+ { value: '16', label: '16px' },
168
+ { value: '24', label: '24px' },
169
+ { value: '32', label: '32px' },
170
+ { value: '48', label: '48px' }
171
+ ];
172
+
173
+ const editingWidget = $derived(
174
+ editingWidgetId
175
+ ? ((value.widgets ?? []).find((w) => w.id === editingWidgetId) ?? null)
176
+ : null
177
+ );
178
+
179
+ const stageDoc = $derived.by((): CanvasDocument => {
180
+ if (!editingWidget) return value;
181
+ return {
182
+ ...value,
183
+ width: editingWidget.width,
184
+ height: editingWidget.height,
185
+ background: editingWidget.background ?? '#ffffff',
186
+ layers: editingWidget.layers,
187
+ guides: [],
188
+ widgets: value.widgets
189
+ };
190
+ });
191
+
192
+ const selectedLayer = $derived(
193
+ selectedIds.length === 1
194
+ ? (stageDoc.layers.find((l) => l.id === selectedIds[0]) ?? null)
195
+ : null
196
+ );
197
+ const zoomLabel = $derived(zoom === 1 ? 'Fit' : `${Math.round(zoom * 100)}%`);
198
+ const presetValue = $derived(presetIdForSize(value.width, value.height));
199
+ const presetOptions = $derived([
200
+ ...CANVAS_PRESETS.map((p) => ({ value: p.id, label: p.label })),
201
+ ...(presetValue === 'custom'
202
+ ? [{ value: 'custom', label: `${value.width}×${value.height}` }]
203
+ : [])
204
+ ]);
205
+
206
+ const wrapItems: DropdownItem[] = [
207
+ {
208
+ id: 'group',
209
+ label: 'Group',
210
+ icon: Group,
211
+ description: 'Logical folder — no layout'
212
+ },
213
+ {
214
+ id: 'border',
215
+ label: 'Border',
216
+ description: 'Background panel that can clip children'
217
+ },
218
+ {
219
+ id: 'overlay',
220
+ label: 'Overlay',
221
+ description: 'Stack children on top of each other'
222
+ },
223
+ {
224
+ id: 'canvasPanel',
225
+ label: 'Canvas Panel',
226
+ description: 'Absolute-positioned children (UMG canvas)'
227
+ },
228
+ {
229
+ id: 'hBox',
230
+ label: 'Horizontal Box',
231
+ description: 'Lay out children in a row'
232
+ },
233
+ {
234
+ id: 'vBox',
235
+ label: 'Vertical Box',
236
+ description: 'Lay out children in a column'
237
+ },
238
+ {
239
+ id: 'scrollBox',
240
+ label: 'Scroll Box',
241
+ description: 'Scrollable container'
242
+ },
243
+ {
244
+ id: 'wrapBox',
245
+ label: 'Wrap Box',
246
+ description: 'Flow children and wrap to next line'
247
+ },
248
+ {
249
+ id: 'uniformGrid',
250
+ label: 'Uniform Grid',
251
+ description: 'Fixed columns grid'
252
+ },
253
+ {
254
+ id: 'sizeBox',
255
+ label: 'Size Box',
256
+ description: 'Constrain child size'
257
+ },
258
+ { id: 'sep-w', label: '', separator: true },
259
+ {
260
+ id: 'widget',
261
+ label: 'Create Widget',
262
+ icon: Component,
263
+ description: 'Reusable User Widget from selection'
264
+ },
265
+ { id: 'sep-u', label: '', separator: true },
266
+ {
267
+ id: 'detach',
268
+ label: 'Remove from parent',
269
+ description: 'Move out of group / wrapper (keep parent)'
270
+ },
271
+ {
272
+ id: 'ungroup',
273
+ label: 'Ungroup / unwrap',
274
+ description: 'Dissolve selected group and keep children'
275
+ }
276
+ ];
277
+
278
+ const alignItems: DropdownItem[] = [
279
+ { id: 'left', label: 'Align left', icon: AlignStartVertical },
280
+ { id: 'center-h', label: 'Align center', icon: AlignCenterVertical },
281
+ { id: 'right', label: 'Align right', icon: AlignEndVertical },
282
+ { id: 'sep-a', label: '', separator: true },
283
+ { id: 'top', label: 'Align top', icon: AlignStartHorizontal },
284
+ { id: 'center-v', label: 'Align middle', icon: AlignCenterHorizontal },
285
+ { id: 'bottom', label: 'Align bottom', icon: AlignEndHorizontal },
286
+ { id: 'sep-b', label: '', separator: true },
287
+ { id: 'center', label: 'Center on canvas', icon: AlignCenter },
288
+ { id: 'full', label: 'Fill canvas', icon: Maximize2 }
289
+ ];
290
+
291
+ const exportItems: DropdownItem[] = [
292
+ { id: 'png', label: 'PNG image', icon: ImageIcon, description: 'Lossless · transparent ok' },
293
+ { id: 'jpeg', label: 'JPEG image', icon: FileImage, description: 'Smaller file size' },
294
+ { id: 'sep-e', label: '', separator: true },
295
+ { id: 'json', label: 'JSON document', icon: FileJson, description: 'Editable project data' }
296
+ ];
297
+
298
+ const ctxItems = $derived.by((): ContextMenuItem[] => {
299
+ const focusIds =
300
+ ctxLayerId && !selectedIds.includes(ctxLayerId) ? [ctxLayerId] : selectedIds;
301
+ const hasParent = focusIds.some((id) => {
302
+ const l = stageDoc.layers.find((x) => x.id === id);
303
+ return !!l?.parentId;
304
+ });
305
+ const canUngroup = focusIds.some((id) => {
306
+ const l = stageDoc.layers.find((x) => x.id === id);
307
+ return !!l && (l.kind === 'group' || isContainerKind(l.kind));
308
+ });
309
+ return [
310
+ { id: 'duplicate', label: 'Duplicate', shortcut: '⌘D' },
311
+ { id: 'bring-front', label: 'Bring to front' },
312
+ { id: 'send-back', label: 'Send to back' },
313
+ { id: 'sep-wrap', label: '', separator: true },
314
+ { id: 'hdr-wrap', label: 'Add parent', header: true },
315
+ {
316
+ id: 'group',
317
+ label: 'Group',
318
+ shortcut: '⌘G',
319
+ description: 'Logical folder — no layout'
320
+ },
321
+ {
322
+ id: 'wrap-border',
323
+ label: 'Border',
324
+ description: 'Background panel that can clip children'
325
+ },
326
+ {
327
+ id: 'wrap-overlay',
328
+ label: 'Overlay',
329
+ description: 'Stack children on top of each other'
330
+ },
331
+ {
332
+ id: 'wrap-canvasPanel',
333
+ label: 'Canvas Panel',
334
+ description: 'Absolute-positioned children'
335
+ },
336
+ {
337
+ id: 'wrap-hBox',
338
+ label: 'Horizontal Box',
339
+ description: 'Lay out children in a row'
340
+ },
341
+ {
342
+ id: 'wrap-vBox',
343
+ label: 'Vertical Box',
344
+ description: 'Lay out children in a column'
345
+ },
346
+ {
347
+ id: 'wrap-scrollBox',
348
+ label: 'Scroll Box',
349
+ description: 'Scrollable container'
350
+ },
351
+ {
352
+ id: 'wrap-wrapBox',
353
+ label: 'Wrap Box',
354
+ description: 'Flow children and wrap lines'
355
+ },
356
+ {
357
+ id: 'wrap-uniformGrid',
358
+ label: 'Uniform Grid',
359
+ description: 'Fixed columns grid'
360
+ },
361
+ {
362
+ id: 'create-widget',
363
+ label: 'Create Widget',
364
+ description: 'Reusable User Widget from selection'
365
+ },
366
+ { id: 'sep-detach', label: '', separator: true },
367
+ {
368
+ id: 'detach',
369
+ label: 'Remove from parent',
370
+ disabled: !hasParent,
371
+ description: 'Move out of group / wrapper'
372
+ },
373
+ {
374
+ id: 'ungroup',
375
+ label: 'Ungroup / unwrap',
376
+ shortcut: '⇧⌘G',
377
+ disabled: !canUngroup,
378
+ description: 'Dissolve selected container'
379
+ },
380
+ { id: 'sep1', label: '', separator: true },
381
+ { id: 'lock', label: 'Lock / Unlock' },
382
+ { id: 'hide', label: 'Hide' },
383
+ { id: 'sep2', label: '', separator: true },
384
+ { id: 'delete', label: 'Delete', destructive: true, shortcut: '⌫' }
385
+ ];
386
+ });
387
+
388
+ function emit(next: CanvasDocument) {
389
+ const migrated = migrateCanvasDocument(next);
390
+ if (!applyingHistory) {
391
+ history = pushCanvasHistory(history, migrated);
392
+ } else {
393
+ history = { ...history, last: JSON.stringify(migrated) };
394
+ }
395
+ value = migrated;
396
+ onchange?.(value);
397
+ }
398
+
399
+ function undo() {
400
+ const result = undoCanvasHistory(history);
401
+ if (!result) return;
402
+ applyingHistory = true;
403
+ history = result.state;
404
+ value = migrateCanvasDocument(result.doc);
405
+ applyingHistory = false;
406
+ onchange?.(value);
407
+ selectedIds = selectedIds.filter((id) => stageDoc.layers.some((l) => l.id === id));
408
+ }
409
+
410
+ function redo() {
411
+ const result = redoCanvasHistory(history);
412
+ if (!result) return;
413
+ applyingHistory = true;
414
+ history = result.state;
415
+ value = migrateCanvasDocument(result.doc);
416
+ applyingHistory = false;
417
+ onchange?.(value);
418
+ selectedIds = selectedIds.filter((id) => stageDoc.layers.some((l) => l.id === id));
419
+ }
420
+
421
+ function emitStageLayers(layers: CanvasLayer[], extra?: Partial<CanvasDocument>) {
422
+ if (editingWidgetId) {
423
+ let next = updateWidgetDefinition(value, editingWidgetId, (d) => ({
424
+ ...d,
425
+ layers,
426
+ width: extra?.width ?? d.width,
427
+ height: extra?.height ?? d.height,
428
+ background: extra?.background ?? d.background
429
+ }));
430
+ const sizeChanged =
431
+ extra?.width != null || extra?.height != null;
432
+ if (sizeChanged && syncInstanceSizes) {
433
+ next = syncInstanceSizesToDefinition(next, editingWidgetId);
434
+ }
435
+ emit(next);
436
+ return;
437
+ }
438
+ emit({ ...value, ...extra, layers });
439
+ }
440
+
441
+ function patchLayer(layer: CanvasLayer) {
442
+ emitStageLayers(stageDoc.layers.map((l) => (l.id === layer.id ? layer : l)));
443
+ }
444
+
445
+ function addLayer(layer: CanvasLayer, opts?: { parentId?: string | null }) {
446
+ let parentId: string | null = null;
447
+ if (opts?.parentId !== undefined) {
448
+ if (opts.parentId) {
449
+ const candidate = stageDoc.layers.find((l) => l.id === opts.parentId);
450
+ if (candidate && isContainerKind(candidate.kind)) parentId = candidate.id;
451
+ }
452
+ } else if (selectedIds.length === 1) {
453
+ const candidate = stageDoc.layers.find((l) => l.id === selectedIds[0]);
454
+ if (candidate && isContainerKind(candidate.kind)) parentId = candidate.id;
455
+ }
456
+
457
+ let nextLayers = [...stageDoc.layers, layer];
458
+ if (parentId) {
459
+ nextLayers = reparentLayer(nextLayers, layer.id, parentId, {
460
+ width: stageDoc.width,
461
+ height: stageDoc.height
462
+ });
463
+ }
464
+ emitStageLayers(nextLayers);
465
+ selectedIds = [layer.id];
466
+ }
467
+
468
+ function nextOffset() {
469
+ const n = stageDoc.layers.length;
470
+ return { x: 80 + (n % 8) * 24, y: 80 + (n % 8) * 24 };
471
+ }
472
+
473
+ function addFromAsset(asset: MediaAsset) {
474
+ const kind = asset.kind === 'audio' ? 'image' : asset.kind;
475
+ const offset = nextOffset();
476
+ addLayer(
477
+ createCanvasLayer(kind, {
478
+ name: asset.name,
479
+ src: asset.src,
480
+ text: asset.kind === 'text' ? 'Text' : undefined,
481
+ zIndex: stageDoc.layers.length,
482
+ rect: {
483
+ x: offset.x,
484
+ y: offset.y,
485
+ w: asset.kind === 'text' ? 280 : 320,
486
+ h: asset.kind === 'text' ? 80 : 240
487
+ }
488
+ })
489
+ );
490
+ }
491
+
492
+ function addElement(
493
+ def: CanvasElementDef,
494
+ at?: { x?: number; y?: number; parentId?: string | null }
495
+ ) {
496
+ if (def.tool === 'pen') {
497
+ drawMode = true;
498
+ return;
499
+ }
500
+ const base = def.partial ?? {};
501
+ const defaults = { w: 240, h: 160 };
502
+ const rectSize = base.rect ?? defaults;
503
+ let w = rectSize.w;
504
+ let h = rectSize.h;
505
+ let x: number;
506
+ let y: number;
507
+
508
+ // Resolve nest target the same way addLayer does (selected container or explicit parentId).
509
+ let nestParentId: string | null | undefined = at?.parentId;
510
+ if (nestParentId === undefined && selectedIds.length === 1) {
511
+ const candidate = stageDoc.layers.find((l) => l.id === selectedIds[0]);
512
+ if (candidate && isContainerKind(candidate.kind)) nestParentId = candidate.id;
513
+ }
514
+ const nesting =
515
+ nestParentId !== undefined ? { parentId: nestParentId } : undefined;
516
+
517
+ const fillRootCanvas =
518
+ def.kind === 'canvasPanel' &&
519
+ !nestParentId &&
520
+ at?.x == null &&
521
+ !stageDoc.layers.some((l) => (l.parentId ?? null) === null && l.kind === 'canvasPanel');
522
+
523
+ if (fillRootCanvas) {
524
+ x = 0;
525
+ y = 0;
526
+ w = stageDoc.width;
527
+ h = stageDoc.height;
528
+ } else if (at?.x != null && at?.y != null) {
529
+ x = Math.min(Math.max(0, Math.round(at.x - w / 2)), Math.max(0, stageDoc.width - w));
530
+ y = Math.min(Math.max(0, Math.round(at.y - h / 2)), Math.max(0, stageDoc.height - h));
531
+ if (snap) {
532
+ x = Math.round(x / cellSize) * cellSize;
533
+ y = Math.round(y / cellSize) * cellSize;
534
+ }
535
+ } else if (nestParentId) {
536
+ // Spawn in absolute coords inside the parent so reparent doesn't create negative locals (e.g. -20).
537
+ const absMap = computeAbsoluteRects(stageDoc.layers, {
538
+ width: stageDoc.width,
539
+ height: stageDoc.height
540
+ });
541
+ const pa = absMap.get(nestParentId);
542
+ const parent = stageDoc.layers.find((l) => l.id === nestParentId);
543
+ if (pa) {
544
+ const pad = parent ? contentPadding(parent) : { left: 0, top: 0, right: 0, bottom: 0 };
545
+ const cw = Math.max(1, pa.w - pad.left - pad.right);
546
+ const ch = Math.max(1, pa.h - pad.top - pad.bottom);
547
+ x = Math.round(pa.x + pad.left + Math.max(0, (cw - w) / 2));
548
+ y = Math.round(pa.y + pad.top + Math.max(0, (ch - h) / 2));
549
+ } else {
550
+ const offset = nextOffset();
551
+ x = offset.x;
552
+ y = offset.y;
553
+ }
554
+ } else {
555
+ const offset = nextOffset();
556
+ x = offset.x;
557
+ y = offset.y;
558
+ }
559
+ addLayer(
560
+ createCanvasLayer(def.kind, {
561
+ ...base,
562
+ name: fillRootCanvas ? (base.name ?? 'Root Canvas') : base.name,
563
+ zIndex: stageDoc.layers.length,
564
+ rect: { x, y, w, h },
565
+ slot: fillRootCanvas
566
+ ? {
567
+ anchors: { minX: 0, minY: 0, maxX: 1, maxY: 1 },
568
+ offsets: { left: 0, top: 0, right: 0, bottom: 0 }
569
+ }
570
+ : defaultSlotFromRect({ x, y, w, h }),
571
+ slotName: base.slotName,
572
+ columns: base.columns,
573
+ gap: base.gap,
574
+ clipChildren: base.clipChildren
575
+ }),
576
+ nesting
577
+ );
578
+ }
579
+
580
+ function setZoom(next: number) {
581
+ zoom = Math.min(3, Math.max(0.25, Math.round(next * 100) / 100));
582
+ }
583
+
584
+ function addGuide(orientation: 'horizontal' | 'vertical') {
585
+ const position =
586
+ orientation === 'vertical'
587
+ ? Math.round(stageDoc.width / 2)
588
+ : Math.round(stageDoc.height / 2);
589
+ emit({
590
+ ...value,
591
+ guides: [...(value.guides ?? []), createCanvasGuide(orientation, position)]
592
+ });
593
+ showGuides = true;
594
+ }
595
+
596
+ function toggleGuidesLocked() {
597
+ emit({ ...value, guidesLocked: !value.guidesLocked });
598
+ }
599
+
600
+ function nudgeSelected(dx: number, dy: number) {
601
+ if (!selectedIds.length) return;
602
+ const set = new Set(selectedIds);
603
+ emitStageLayers(
604
+ stageDoc.layers.map((l) => {
605
+ if (!set.has(l.id) || l.locked) return l;
606
+ const x = l.rect.x + dx;
607
+ const y = l.rect.y + dy;
608
+ const rect = { ...l.rect, x, y };
609
+ return {
610
+ ...l,
611
+ rect,
612
+ slot: l.slot
613
+ ? {
614
+ ...l.slot,
615
+ offsets: {
616
+ ...l.slot.offsets,
617
+ left: l.slot.offsets.left + dx,
618
+ top: l.slot.offsets.top + dy
619
+ }
620
+ }
621
+ : defaultSlotFromRect(rect)
622
+ };
623
+ })
624
+ );
625
+ }
626
+
627
+ function applyPreset(id: string) {
628
+ if (editingWidgetId) return;
629
+ const preset = CANVAS_PRESETS.find((p) => p.id === id);
630
+ if (!preset) return;
631
+ emit({ ...value, width: preset.width, height: preset.height });
632
+ zoom = 1;
633
+ }
634
+
635
+ function alignSelected(align: CanvasAlign) {
636
+ if (!selectedIds.length) return;
637
+ const set = new Set(selectedIds);
638
+ const absMap = computeAbsoluteRects(stageDoc.layers, {
639
+ width: stageDoc.width,
640
+ height: stageDoc.height
641
+ });
642
+ emitStageLayers(
643
+ stageDoc.layers.map((l) => {
644
+ if (!set.has(l.id)) return l;
645
+ const parentId = l.parentId ?? null;
646
+ let bounds = { width: stageDoc.width, height: stageDoc.height };
647
+ if (parentId) {
648
+ const parent = stageDoc.layers.find((p) => p.id === parentId);
649
+ const parentAbs = absMap.get(parentId);
650
+ if (parent && parentAbs) {
651
+ const pad = contentPadding(parent);
652
+ bounds = {
653
+ width: Math.max(1, parentAbs.w - pad.left - pad.right),
654
+ height: Math.max(1, parentAbs.h - pad.top - pad.bottom)
655
+ };
656
+ }
657
+ }
658
+ const rect = alignLayerRect(l.rect, bounds, align);
659
+ const anchors = l.slot?.anchors ?? { minX: 0, minY: 0, maxX: 0, maxY: 0 };
660
+ const slot = {
661
+ ...slotFromLocalRect(bounds, rect, anchors),
662
+ padding: l.slot?.padding,
663
+ sizeRule: l.slot?.sizeRule,
664
+ alignment: l.slot?.alignment,
665
+ order: l.slot?.order
666
+ };
667
+ return { ...l, rect, slot };
668
+ })
669
+ );
670
+ }
671
+
672
+ function deleteSelected() {
673
+ if (!selectedIds.length) return;
674
+ const next = deleteSubtree(
675
+ { ...stageDoc, widgets: value.widgets },
676
+ selectedIds
677
+ );
678
+ emitStageLayers(next.layers);
679
+ selectedIds = [];
680
+ }
681
+
682
+ function duplicateSelected() {
683
+ const result = duplicateSubtree(
684
+ { ...stageDoc, widgets: value.widgets },
685
+ selectedIds
686
+ );
687
+ emitStageLayers(result.doc.layers);
688
+ selectedIds = result.newIds;
689
+ }
690
+
691
+ function bringFront() {
692
+ const set = new Set(selectedIds);
693
+ const byParent = new Map<string | null, string[]>();
694
+ for (const l of stageDoc.layers) {
695
+ if (!set.has(l.id)) continue;
696
+ const pid = l.parentId ?? null;
697
+ const list = byParent.get(pid) ?? [];
698
+ list.push(l.id);
699
+ byParent.set(pid, list);
700
+ }
701
+ let layers = stageDoc.layers;
702
+ for (const [pid, ids] of byParent) {
703
+ const siblings = layers
704
+ .filter((l) => (l.parentId ?? null) === pid)
705
+ .sort((a, b) => a.zIndex - b.zIndex)
706
+ .map((l) => l.id);
707
+ const rest = siblings.filter((id) => !set.has(id));
708
+ const selected = siblings.filter((id) => set.has(id));
709
+ layers = reorderSiblings(layers, pid, [...rest, ...selected]);
710
+ }
711
+ emitStageLayers(layers);
712
+ }
713
+
714
+ function sendBack() {
715
+ const set = new Set(selectedIds);
716
+ let layers = stageDoc.layers;
717
+ const parents = new Set(stageDoc.layers.filter((l) => set.has(l.id)).map((l) => l.parentId ?? null));
718
+ for (const pid of parents) {
719
+ const siblings = layers
720
+ .filter((l) => (l.parentId ?? null) === pid)
721
+ .sort((a, b) => a.zIndex - b.zIndex)
722
+ .map((l) => l.id);
723
+ const rest = siblings.filter((id) => !set.has(id));
724
+ const selected = siblings.filter((id) => set.has(id));
725
+ layers = reorderSiblings(layers, pid, [...selected, ...rest]);
726
+ }
727
+ emitStageLayers(layers);
728
+ }
729
+
730
+ function wrapSelected(kind: CanvasLayerKind) {
731
+ const result = wrapSelection(
732
+ { ...stageDoc, version: 2, widgets: value.widgets ?? [] },
733
+ selectedIds,
734
+ kind
735
+ );
736
+ if (!result) return;
737
+ emitStageLayers(result.doc.layers);
738
+ selectedIds = [result.wrapperId];
739
+ }
740
+
741
+ function groupSelected() {
742
+ const result = groupLayers(
743
+ { ...stageDoc, version: 2, widgets: value.widgets ?? [] },
744
+ selectedIds
745
+ );
746
+ if (!result) return;
747
+ emitStageLayers(result.doc.layers);
748
+ selectedIds = [result.wrapperId];
749
+ }
750
+
751
+ function ungroupSelected() {
752
+ if (selectedIds.length !== 1) return;
753
+ const id = selectedIds[0];
754
+ const layer = stageDoc.layers.find((l) => l.id === id);
755
+ if (!layer || (layer.kind !== 'group' && !isContainerKind(layer.kind))) return;
756
+ const kids = stageDoc.layers.filter((l) => (l.parentId ?? null) === id).map((l) => l.id);
757
+ const next = ungroupLayers({ ...stageDoc, version: 2, widgets: value.widgets ?? [] }, id);
758
+ emitStageLayers(next.layers);
759
+ selectedIds = kids;
760
+ }
761
+
762
+ /** Move selected layers out of their current parent (grandparent or root). */
763
+ function detachSelected() {
764
+ const rootSize = { width: stageDoc.width, height: stageDoc.height };
765
+ let layers = stageDoc.layers;
766
+ let changed = false;
767
+ for (const id of selectedIds) {
768
+ const layer = layers.find((l) => l.id === id);
769
+ if (!layer?.parentId) continue;
770
+ const parent = layers.find((l) => l.id === layer.parentId);
771
+ const newParentId = parent?.parentId ?? null;
772
+ layers = reparentLayer(layers, id, newParentId, rootSize);
773
+ changed = true;
774
+ }
775
+ if (changed) emitStageLayers(layers);
776
+ }
777
+
778
+ function createWidgetSelected() {
779
+ if (editingWidgetId) return;
780
+ if (!selectedIds.length) {
781
+ toast.warning('Select one or more layers to create a widget');
782
+ return;
783
+ }
784
+ const result = createWidgetFromSelection(value, selectedIds, 'Widget');
785
+ if (!result) {
786
+ toast.warning('Create Widget failed', 'Layers must share the same parent');
787
+ return;
788
+ }
789
+ emit(result.doc);
790
+ selectedIds = [result.instanceId];
791
+ toast.success('Widget created');
792
+ }
793
+
794
+ function handleWrapMenu(id: string) {
795
+ if (id === 'group') groupSelected();
796
+ else if (id === 'widget') createWidgetSelected();
797
+ else if (id === 'detach') detachSelected();
798
+ else if (id === 'ungroup') ungroupSelected();
799
+ else wrapSelected(id as CanvasLayerKind);
800
+ }
801
+
802
+ function handleCtx(id: string) {
803
+ const targetId = ctxLayerId;
804
+ if (targetId && !selectedIds.includes(targetId)) selectedIds = [targetId];
805
+ if (id === 'duplicate') duplicateSelected();
806
+ else if (id === 'delete') deleteSelected();
807
+ else if (id === 'bring-front') bringFront();
808
+ else if (id === 'send-back') sendBack();
809
+ else if (id === 'group') groupSelected();
810
+ else if (id === 'ungroup') ungroupSelected();
811
+ else if (id === 'detach') detachSelected();
812
+ else if (id.startsWith('wrap-')) wrapSelected(id.slice(5) as CanvasLayerKind);
813
+ else if (id === 'create-widget') createWidgetSelected();
814
+ else if (id === 'lock') {
815
+ const set = new Set(selectedIds);
816
+ emitStageLayers(
817
+ stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, locked: !l.locked } : l))
818
+ );
819
+ } else if (id === 'hide') {
820
+ const set = new Set(selectedIds);
821
+ emitStageLayers(
822
+ stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, visible: false } : l))
823
+ );
824
+ selectedIds = [];
825
+ }
826
+ }
827
+
828
+ function handleBulk(id: string) {
829
+ if (id === 'duplicate') duplicateSelected();
830
+ else if (id === 'delete') deleteSelected();
831
+ else if (id === 'lock') {
832
+ const set = new Set(selectedIds);
833
+ emitStageLayers(
834
+ stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, locked: true } : l))
835
+ );
836
+ } else if (id === 'hide') {
837
+ const set = new Set(selectedIds);
838
+ emitStageLayers(
839
+ stageDoc.layers.map((l) => (set.has(l.id) ? { ...l, visible: false } : l))
840
+ );
841
+ selectedIds = [];
842
+ } else if (id === 'front') bringFront();
843
+ else if (id === 'back') sendBack();
844
+ else if (id === 'group') groupSelected();
845
+ else if (id === 'detach') detachSelected();
846
+ else if (id === 'ungroup') ungroupSelected();
847
+ else if (id === 'widget') createWidgetSelected();
848
+ else if (
849
+ [
850
+ 'border',
851
+ 'overlay',
852
+ 'canvasPanel',
853
+ 'hBox',
854
+ 'vBox',
855
+ 'scrollBox',
856
+ 'wrapBox',
857
+ 'uniformGrid',
858
+ 'sizeBox'
859
+ ].includes(id)
860
+ ) {
861
+ wrapSelected(id as CanvasLayerKind);
862
+ }
863
+ }
864
+
865
+ function selectFromList(id: string, e?: MouseEvent) {
866
+ if (e?.shiftKey || e?.metaKey || e?.ctrlKey) {
867
+ const set = new Set(selectedIds);
868
+ if (set.has(id)) set.delete(id);
869
+ else set.add(id);
870
+ selectedIds = [...set];
871
+ } else {
872
+ selectedIds = [id];
873
+ }
874
+ }
875
+
876
+ function handleKeydown(e: KeyboardEvent) {
877
+ const t = e.target as HTMLElement | null;
878
+ if (t?.closest?.('input, textarea, select, [contenteditable="true"]')) return;
879
+
880
+ if (drawMode) {
881
+ if (e.key === 'Escape') drawMode = false;
882
+ return;
883
+ }
884
+
885
+ if (e.key === 'Delete' || e.key === 'Backspace') {
886
+ if (!selectedIds.length) return;
887
+ e.preventDefault();
888
+ deleteSelected();
889
+ }
890
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'd') {
891
+ e.preventDefault();
892
+ duplicateSelected();
893
+ }
894
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'g') {
895
+ e.preventDefault();
896
+ if (e.shiftKey) ungroupSelected();
897
+ else groupSelected();
898
+ }
899
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') {
900
+ e.preventDefault();
901
+ if (e.shiftKey) redo();
902
+ else undo();
903
+ return;
904
+ }
905
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'y') {
906
+ e.preventDefault();
907
+ redo();
908
+ return;
909
+ }
910
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'a') {
911
+ e.preventDefault();
912
+ // Contextual select: siblings under shared parent, or root layers
913
+ if (selectedIds.length === 1) {
914
+ const cur = stageDoc.layers.find((l) => l.id === selectedIds[0]);
915
+ const pid = cur?.parentId ?? null;
916
+ selectedIds = stageDoc.layers
917
+ .filter((l) => (l.parentId ?? null) === pid)
918
+ .map((l) => l.id);
919
+ } else if (selectedIds.length > 1) {
920
+ const parents = new Set(
921
+ selectedIds.map((id) => stageDoc.layers.find((l) => l.id === id)?.parentId ?? null)
922
+ );
923
+ if (parents.size === 1) {
924
+ const pid = [...parents][0];
925
+ selectedIds = stageDoc.layers
926
+ .filter((l) => (l.parentId ?? null) === pid)
927
+ .map((l) => l.id);
928
+ } else {
929
+ selectedIds = stageDoc.layers
930
+ .filter((l) => (l.parentId ?? null) === null)
931
+ .map((l) => l.id);
932
+ }
933
+ } else {
934
+ selectedIds = stageDoc.layers
935
+ .filter((l) => (l.parentId ?? null) === null)
936
+ .map((l) => l.id);
937
+ }
938
+ return;
939
+ }
940
+ if ((e.metaKey || e.ctrlKey) && (e.key === '=' || e.key === '+')) {
941
+ e.preventDefault();
942
+ setZoom(zoom + 0.1);
943
+ }
944
+ if ((e.metaKey || e.ctrlKey) && e.key === '-') {
945
+ e.preventDefault();
946
+ setZoom(zoom - 0.1);
947
+ }
948
+ if ((e.metaKey || e.ctrlKey) && e.key === '0') {
949
+ e.preventDefault();
950
+ setZoom(1);
951
+ }
952
+ if (e.key === 'Escape') {
953
+ if (drawMode) {
954
+ drawMode = false;
955
+ return;
956
+ }
957
+ if (selectedIds.length) {
958
+ selectedIds = [];
959
+ return;
960
+ }
961
+ if (editingWidgetId) {
962
+ exitWidgetEdit();
963
+ return;
964
+ }
965
+ }
966
+
967
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
968
+ if (!selectedIds.length) return;
969
+ e.preventDefault();
970
+ const step = e.shiftKey ? (snap ? cellSize * 2 : 10) : snap ? cellSize : 1;
971
+ if (e.key === 'ArrowLeft') nudgeSelected(-step, 0);
972
+ else if (e.key === 'ArrowRight') nudgeSelected(step, 0);
973
+ else if (e.key === 'ArrowUp') nudgeSelected(0, -step);
974
+ else nudgeSelected(0, step);
975
+ }
976
+ }
977
+
978
+ async function handleExport(format: CanvasExportFormat) {
979
+ if (exporting) return;
980
+ exporting = true;
981
+ try {
982
+ if (format === 'json') {
983
+ const blob = new Blob([JSON.stringify(value, null, 2)], { type: 'application/json' });
984
+ onexport?.({ format: 'json', document: value, blob });
985
+ downloadBlob(blob, 'canvas-document.json');
986
+ return;
987
+ }
988
+ const blob = await exportCanvasBlob(value, format);
989
+ onexport?.({ format, document: value, blob });
990
+ downloadBlob(blob, format === 'jpeg' ? 'canvas.jpg' : 'canvas.png');
991
+ } catch (err) {
992
+ console.error('Canvas export failed', err);
993
+ } finally {
994
+ exporting = false;
995
+ }
996
+ }
997
+
998
+ function exitWidgetEdit() {
999
+ if (!editingWidgetId) return;
1000
+ editingWidgetId = null;
1001
+ history = createCanvasHistory(value);
1002
+ selectedIds = selectedIds.filter((id) => value.layers.some((l) => l.id === id));
1003
+ }
1004
+
1005
+ function enterWidgetEdit(definitionId: string) {
1006
+ const def = (value.widgets ?? []).find((w) => w.id === definitionId);
1007
+ if (!def) return;
1008
+ editingWidgetId = definitionId;
1009
+ history = createCanvasHistory(value);
1010
+ const roots = def.layers.filter((l) => (l.parentId ?? null) === null);
1011
+ selectedIds = roots.length ? [roots.sort((a, b) => a.zIndex - b.zIndex)[0].id] : [];
1012
+ zoom = 1;
1013
+ sidebarTab = 'elements';
1014
+ }
1015
+
1016
+ function deleteWidget(definitionId: string) {
1017
+ const next = deleteWidgetDefinition(value, definitionId);
1018
+ if (editingWidgetId === definitionId) {
1019
+ exitWidgetEdit();
1020
+ selectedIds = [];
1021
+ } else {
1022
+ selectedIds = selectedIds.filter((id) => next.layers.some((l) => l.id === id));
1023
+ }
1024
+ emit(next);
1025
+ }
1026
+
1027
+ function renameWidget(definitionId: string) {
1028
+ const def = (value.widgets ?? []).find((w) => w.id === definitionId);
1029
+ if (!def) return;
1030
+ const name = window.prompt('Widget name', def.name);
1031
+ if (name == null) return;
1032
+ emit(renameWidgetDefinition(value, definitionId, name));
1033
+ }
1034
+
1035
+ function duplicateWidget(definitionId: string) {
1036
+ const result = duplicateWidgetDefinition(value, definitionId);
1037
+ if (!result) return;
1038
+ emit(result.doc);
1039
+ toast.success('Widget duplicated');
1040
+ }
1041
+
1042
+ function enterLayer(id: string) {
1043
+ const layer = stageDoc.layers.find((l) => l.id === id);
1044
+ if (!layer) return;
1045
+ if (layer.kind === 'widget' && layer.definitionId && !editingWidgetId) {
1046
+ enterWidgetEdit(layer.definitionId);
1047
+ return;
1048
+ }
1049
+ // Double-click a scene child that lives under a widget instance → enter that blueprint
1050
+ if (!editingWidgetId && layer.parentId) {
1051
+ const parent = stageDoc.layers.find((l) => l.id === layer.parentId);
1052
+ if (parent?.kind === 'widget' && parent.definitionId) {
1053
+ enterWidgetEdit(parent.definitionId);
1054
+ return;
1055
+ }
1056
+ }
1057
+ selectedIds = [id];
1058
+ }
1059
+ </script>
1060
+
1061
+ <svelte:window onkeydown={handleKeydown} />
1062
+
1063
+ <div class={['relative flex h-full min-h-0 flex-col', className]}>
1064
+ <header
1065
+ class="flex shrink-0 items-center gap-3 border-b border-border bg-surface-elevated px-3 py-2"
1066
+ >
1067
+ <div class="flex min-w-0 items-center gap-2">
1068
+ {#if editingWidget}
1069
+ <button
1070
+ type="button"
1071
+ class="inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs font-medium text-secondary hover:bg-surface-overlay"
1072
+ onclick={() => {
1073
+ exitWidgetEdit();
1074
+ selectedIds = [];
1075
+ }}
1076
+ >
1077
+ <ChevronLeft class="h-3.5 w-3.5" />
1078
+ Scene
1079
+ </button>
1080
+ <span class="text-muted">/</span>
1081
+ <span class="truncate text-sm font-semibold text-primary">{editingWidget.name}</span>
1082
+ {:else}
1083
+ <span class="hidden text-sm font-semibold text-primary sm:inline">Canvas</span>
1084
+ <Select
1085
+ size="sm"
1086
+ class="w-44"
1087
+ options={presetOptions}
1088
+ value={presetValue}
1089
+ onchange={(id) => {
1090
+ if (id !== 'custom') applyPreset(id);
1091
+ }}
1092
+ />
1093
+ {/if}
1094
+ </div>
1095
+
1096
+ <div class="hidden h-5 w-px bg-border sm:block"></div>
1097
+
1098
+ <div class="flex items-center rounded-lg border border-border bg-surface p-0.5">
1099
+ <IconButton label="Zoom out" size="sm" onclick={() => setZoom(zoom - 0.1)}>
1100
+ <ZoomOut class="h-3.5 w-3.5" />
1101
+ </IconButton>
1102
+ <button
1103
+ type="button"
1104
+ class="min-w-11 rounded-md px-1 py-1 font-mono text-[11px] tabular-nums text-secondary hover:bg-surface-overlay"
1105
+ onclick={() => setZoom(1)}
1106
+ title="Fit to view"
1107
+ >
1108
+ {zoomLabel}
1109
+ </button>
1110
+ <IconButton label="Zoom in" size="sm" onclick={() => setZoom(zoom + 0.1)}>
1111
+ <ZoomIn class="h-3.5 w-3.5" />
1112
+ </IconButton>
1113
+ </div>
1114
+
1115
+ <div class="flex items-center rounded-lg border border-border bg-surface p-0.5">
1116
+ <IconButton
1117
+ label="Undo"
1118
+ size="sm"
1119
+ disabled={!history.past.length}
1120
+ onclick={undo}
1121
+ >
1122
+ <Undo2 class="h-3.5 w-3.5" />
1123
+ </IconButton>
1124
+ <IconButton
1125
+ label="Redo"
1126
+ size="sm"
1127
+ disabled={!history.future.length}
1128
+ onclick={redo}
1129
+ >
1130
+ <Redo2 class="h-3.5 w-3.5" />
1131
+ </IconButton>
1132
+ </div>
1133
+
1134
+ <div class="flex items-center gap-0.5">
1135
+ <Tooltip content="Toggle grid">
1136
+ <IconButton
1137
+ label="Toggle grid"
1138
+ size="sm"
1139
+ onclick={() => (showGrid = !showGrid)}
1140
+ class={showGrid ? 'bg-brand-500/10 text-brand-600' : undefined}
1141
+ >
1142
+ <Grid3x3 class="h-3.5 w-3.5" />
1143
+ </IconButton>
1144
+ </Tooltip>
1145
+ {#if showGrid}
1146
+ <Select
1147
+ size="sm"
1148
+ class="w-[4.5rem] shrink-0"
1149
+ options={gridSizeOptions}
1150
+ value={String(cellSize)}
1151
+ onchange={(v) => (cellSize = Number(v))}
1152
+ />
1153
+ {/if}
1154
+ <Tooltip content="Snap to grid & guides">
1155
+ <IconButton
1156
+ label="Toggle snap"
1157
+ size="sm"
1158
+ onclick={() => (snap = !snap)}
1159
+ class={snap ? 'bg-brand-500/10 text-brand-600' : undefined}
1160
+ >
1161
+ <Magnet class="h-3.5 w-3.5" />
1162
+ </IconButton>
1163
+ </Tooltip>
1164
+ </div>
1165
+
1166
+ <div class="hidden h-5 w-px bg-border sm:block"></div>
1167
+
1168
+ <div class="flex items-center gap-0.5">
1169
+ <Tooltip content={drawMode ? 'Exit pen tool (Esc)' : 'Pen — draw path / shape'}>
1170
+ <IconButton
1171
+ label="Pen tool"
1172
+ size="sm"
1173
+ onclick={() => (drawMode = !drawMode)}
1174
+ class={drawMode ? 'bg-brand-500/10 text-brand-600' : undefined}
1175
+ >
1176
+ <Pencil class="h-3.5 w-3.5" />
1177
+ </IconButton>
1178
+ </Tooltip>
1179
+ </div>
1180
+
1181
+ <div class="hidden h-5 w-px bg-border sm:block"></div>
1182
+
1183
+ <div class="flex items-center gap-0.5">
1184
+ <Tooltip content={showGuides ? 'Hide guides' : 'Show guides'}>
1185
+ <IconButton
1186
+ label="Toggle guides"
1187
+ size="sm"
1188
+ onclick={() => (showGuides = !showGuides)}
1189
+ class={showGuides ? 'bg-brand-500/10 text-brand-600' : undefined}
1190
+ >
1191
+ <Eye class="h-3.5 w-3.5" />
1192
+ </IconButton>
1193
+ </Tooltip>
1194
+ <Tooltip content="Add vertical guide">
1195
+ <IconButton label="Add vertical guide" size="sm" onclick={() => addGuide('vertical')}>
1196
+ <SeparatorVertical class="h-3.5 w-3.5" />
1197
+ </IconButton>
1198
+ </Tooltip>
1199
+ <Tooltip content="Add horizontal guide">
1200
+ <IconButton label="Add horizontal guide" size="sm" onclick={() => addGuide('horizontal')}>
1201
+ <SeparatorHorizontal class="h-3.5 w-3.5" />
1202
+ </IconButton>
1203
+ </Tooltip>
1204
+ <Tooltip content={value.guidesLocked ? 'Unlock guides' : 'Lock guides'}>
1205
+ <IconButton
1206
+ label={value.guidesLocked ? 'Unlock guides' : 'Lock guides'}
1207
+ size="sm"
1208
+ onclick={toggleGuidesLocked}
1209
+ class={value.guidesLocked ? 'bg-brand-500/10 text-brand-600' : undefined}
1210
+ >
1211
+ {#if value.guidesLocked}
1212
+ <Lock class="h-3.5 w-3.5" />
1213
+ {:else}
1214
+ <Unlock class="h-3.5 w-3.5" />
1215
+ {/if}
1216
+ </IconButton>
1217
+ </Tooltip>
1218
+ </div>
1219
+
1220
+ {#if selectedIds.length}
1221
+ <div class="hidden h-5 w-px bg-border md:block"></div>
1222
+ <div class="hidden items-center gap-1 md:flex">
1223
+ <DropdownMenu
1224
+ size="sm"
1225
+ align="start"
1226
+ label="Align"
1227
+ items={alignItems}
1228
+ onselect={(id) => alignSelected(id as CanvasAlign)}
1229
+ >
1230
+ {#snippet trigger()}
1231
+ <span
1232
+ class="inline-flex items-center gap-1.5 text-xs font-medium text-secondary"
1233
+ >
1234
+ <AlignHorizontalJustifyCenter class="h-3.5 w-3.5" />
1235
+ Align
1236
+ </span>
1237
+ {/snippet}
1238
+ </DropdownMenu>
1239
+ <DropdownMenu
1240
+ size="sm"
1241
+ align="start"
1242
+ label="Group"
1243
+ items={wrapItems}
1244
+ onselect={handleWrapMenu}
1245
+ >
1246
+ {#snippet trigger()}
1247
+ <span class="inline-flex items-center gap-1.5 text-xs font-medium text-secondary">
1248
+ <Group class="h-3.5 w-3.5" />
1249
+ Group
1250
+ </span>
1251
+ {/snippet}
1252
+ </DropdownMenu>
1253
+ </div>
1254
+ {/if}
1255
+
1256
+ <div class="ml-auto flex items-center gap-1">
1257
+ <IconButton
1258
+ label="Toggle sidebar"
1259
+ size="sm"
1260
+ onclick={() => (showSidebar = !showSidebar)}
1261
+ class={!showSidebar ? 'text-muted' : undefined}
1262
+ >
1263
+ <PanelLeft class="h-3.5 w-3.5" />
1264
+ </IconButton>
1265
+ <IconButton
1266
+ label="Toggle inspector"
1267
+ size="sm"
1268
+ onclick={() => (showInspector = !showInspector)}
1269
+ class={!showInspector ? 'text-muted' : undefined}
1270
+ >
1271
+ <PanelRight class="h-3.5 w-3.5" />
1272
+ </IconButton>
1273
+
1274
+ <DropdownMenu
1275
+ size="sm"
1276
+ align="end"
1277
+ label="Export"
1278
+ items={exportItems}
1279
+ disabled={exporting}
1280
+ onselect={(id) => handleExport(id as CanvasExportFormat)}
1281
+ >
1282
+ {#snippet trigger()}
1283
+ <span class="inline-flex items-center gap-1.5 px-1 text-xs font-medium">
1284
+ <Download class="h-3.5 w-3.5" />
1285
+ {exporting ? 'Exporting…' : 'Export'}
1286
+ </span>
1287
+ {/snippet}
1288
+ </DropdownMenu>
1289
+ </div>
1290
+ </header>
1291
+
1292
+ <div class="relative min-h-0 flex-1">
1293
+ <EditorShell
1294
+ sidebarTitle="Assets"
1295
+ inspectorTitle="Inspector"
1296
+ workspaceTitle="Artboard"
1297
+ showBottom={false}
1298
+ {showSidebar}
1299
+ {showInspector}
1300
+ wrapSidebar={false}
1301
+ wrapInspector={true}
1302
+ wrapWorkspace={false}
1303
+ collapsiblePanels={false}
1304
+ sidebarSize={300}
1305
+ inspectorSize={300}
1306
+ sizeUnit="px"
1307
+ sidebarMin={220}
1308
+ sidebarMax={480}
1309
+ inspectorMin={240}
1310
+ inspectorMax={420}
1311
+ fullHeight
1312
+ class="rounded-none"
1313
+ >
1314
+ {#snippet sidebar()}
1315
+ <SplitPane
1316
+ orientation="vertical"
1317
+ bind:size={assetsSize}
1318
+ unit="percent"
1319
+ minSize={28}
1320
+ maxSize={78}
1321
+ revealOnHover
1322
+ class="h-full"
1323
+ >
1324
+ {#snippet start()}
1325
+ <div class="flex h-full min-h-0 flex-col bg-surface-elevated">
1326
+ <div class="flex shrink-0 gap-1 border-b border-border p-2">
1327
+ <button
1328
+ type="button"
1329
+ class={[
1330
+ 'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
1331
+ sidebarTab === 'elements'
1332
+ ? 'bg-brand-500 text-white'
1333
+ : 'text-secondary hover:bg-surface-overlay'
1334
+ ]}
1335
+ onclick={() => (sidebarTab = 'elements')}
1336
+ >
1337
+ Elements
1338
+ </button>
1339
+ <button
1340
+ type="button"
1341
+ class={[
1342
+ 'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
1343
+ sidebarTab === 'widgets'
1344
+ ? 'bg-brand-500 text-white'
1345
+ : 'text-secondary hover:bg-surface-overlay'
1346
+ ]}
1347
+ onclick={() => (sidebarTab = 'widgets')}
1348
+ >
1349
+ Widgets
1350
+ </button>
1351
+ <button
1352
+ type="button"
1353
+ class={[
1354
+ 'flex-1 rounded-md px-2 py-1.5 text-xs font-medium transition-colors',
1355
+ sidebarTab === 'uploads'
1356
+ ? 'bg-brand-500 text-white'
1357
+ : 'text-secondary hover:bg-surface-overlay'
1358
+ ]}
1359
+ onclick={() => (sidebarTab = 'uploads')}
1360
+ >
1361
+ Uploads
1362
+ </button>
1363
+ </div>
1364
+ <div class="min-h-0 flex-1 overflow-auto">
1365
+ {#if sidebarTab === 'elements'}
1366
+ <CanvasElementsPanel onadd={addElement} />
1367
+ {:else if sidebarTab === 'widgets'}
1368
+ <div class="flex flex-col gap-2 p-3">
1369
+ <p class="text-[11px] text-muted">
1370
+ Click to place · pencil to edit · dblclick name to rename
1371
+ </p>
1372
+ <label class="flex items-center gap-2 text-[11px] text-secondary">
1373
+ <input
1374
+ type="checkbox"
1375
+ class="rounded border-border"
1376
+ bind:checked={syncInstanceSizes}
1377
+ />
1378
+ Match instance size when blueprint size changes
1379
+ </label>
1380
+ {#each value.widgets ?? [] as w (w.id)}
1381
+ <div
1382
+ class={[
1383
+ 'flex items-center gap-0.5 rounded-lg border bg-surface px-1 py-1 text-xs',
1384
+ editingWidgetId === w.id
1385
+ ? 'border-brand-500 ring-1 ring-brand-500/30'
1386
+ : 'border-border'
1387
+ ]}
1388
+ >
1389
+ <button
1390
+ type="button"
1391
+ class="flex min-w-0 flex-1 items-center gap-2 rounded-md px-1.5 py-1.5 text-left hover:bg-surface-overlay"
1392
+ title="Place instance on canvas"
1393
+ onclick={() => {
1394
+ if (editingWidgetId) {
1395
+ enterWidgetEdit(w.id);
1396
+ return;
1397
+ }
1398
+ const next = placeWidgetInstance(value, w.id);
1399
+ if (next) {
1400
+ emit(next);
1401
+ const inst = next.layers[next.layers.length - 1];
1402
+ selectedIds = [inst.id];
1403
+ }
1404
+ }}
1405
+ >
1406
+ <Component class="h-4 w-4 shrink-0 text-brand-600" />
1407
+ <span class="min-w-0 flex-1 truncate font-medium" title={w.name}>
1408
+ {w.name}
1409
+ </span>
1410
+ <span class="shrink-0 text-[10px] text-muted"
1411
+ >{w.width}×{w.height}</span
1412
+ >
1413
+ </button>
1414
+ <IconButton
1415
+ label="Rename widget"
1416
+ size="xs"
1417
+ onclick={(e) => {
1418
+ e.stopPropagation();
1419
+ renameWidget(w.id);
1420
+ }}
1421
+ >
1422
+ <TextCursorInput class="h-3.5 w-3.5" />
1423
+ </IconButton>
1424
+ <IconButton
1425
+ label="Edit widget"
1426
+ size="xs"
1427
+ onclick={(e) => {
1428
+ e.stopPropagation();
1429
+ enterWidgetEdit(w.id);
1430
+ }}
1431
+ >
1432
+ <Pencil class="h-3.5 w-3.5" />
1433
+ </IconButton>
1434
+ <IconButton
1435
+ label="Duplicate widget"
1436
+ size="xs"
1437
+ onclick={(e) => {
1438
+ e.stopPropagation();
1439
+ duplicateWidget(w.id);
1440
+ }}
1441
+ >
1442
+ <CopyPlus class="h-3.5 w-3.5" />
1443
+ </IconButton>
1444
+ <IconButton
1445
+ label="Delete widget"
1446
+ size="xs"
1447
+ onclick={(e) => {
1448
+ e.stopPropagation();
1449
+ deleteWidget(w.id);
1450
+ }}
1451
+ >
1452
+ <Trash2 class="h-3.5 w-3.5" />
1453
+ </IconButton>
1454
+ </div>
1455
+ {:else}
1456
+ <p class="py-6 text-center text-xs text-muted">
1457
+ Select layers and Create Widget, or Group → Create Widget
1458
+ </p>
1459
+ {/each}
1460
+ </div>
1461
+ {:else}
1462
+ <MediaAssetBrowser
1463
+ bind:assets
1464
+ selectedId={null}
1465
+ accept="image/*,video/*"
1466
+ helperText="Images or video"
1467
+ allowText={false}
1468
+ onassetschange={onassetschange}
1469
+ onselect={(id) => {
1470
+ const asset = assets.find((a) => a.id === id);
1471
+ if (asset) addFromAsset(asset);
1472
+ }}
1473
+ />
1474
+ {/if}
1475
+ </div>
1476
+ </div>
1477
+ {/snippet}
1478
+ {#snippet end()}
1479
+ <Panel title="Layers" collapsible={false} class="h-full border-0 shadow-none">
1480
+ <MediaLayerList
1481
+ layers={stageDoc.layers.map((l) => {
1482
+ if (l.kind !== 'widget' || !l.definitionId) return l;
1483
+ const def = (value.widgets ?? []).find((w) => w.id === l.definitionId);
1484
+ const namedSlotCount = def
1485
+ ? def.layers.filter((x) => x.kind === 'namedSlot').length
1486
+ : 0;
1487
+ return { ...l, namedSlotCount };
1488
+ })}
1489
+ {selectedIds}
1490
+ onselect={selectFromList}
1491
+ ontogglevisible={(id) =>
1492
+ emitStageLayers(
1493
+ stageDoc.layers.map((l) =>
1494
+ l.id === id ? { ...l, visible: !l.visible } : l
1495
+ )
1496
+ )}
1497
+ ontogglelocked={(id) =>
1498
+ emitStageLayers(
1499
+ stageDoc.layers.map((l) =>
1500
+ l.id === id ? { ...l, locked: !l.locked } : l
1501
+ )
1502
+ )}
1503
+ onreorder={(ids, parentId) =>
1504
+ emitStageLayers(reorderSiblings(stageDoc.layers, parentId, ids))}
1505
+ onreparent={(layerId, newParentId) =>
1506
+ emitStageLayers(
1507
+ reparentLayer(stageDoc.layers, layerId, newParentId, {
1508
+ width: stageDoc.width,
1509
+ height: stageDoc.height
1510
+ })
1511
+ )}
1512
+ ondropelement={({ def, parentId }) => addElement(def, { parentId })}
1513
+ oneditwidget={(layerId) => {
1514
+ const layer = stageDoc.layers.find((l) => l.id === layerId);
1515
+ if (layer?.kind === 'widget' && layer.definitionId) {
1516
+ enterWidgetEdit(layer.definitionId);
1517
+ }
1518
+ }}
1519
+ />
1520
+ </Panel>
1521
+ {/snippet}
1522
+ </SplitPane>
1523
+ {/snippet}
1524
+
1525
+ {#snippet workspace()}
1526
+ <div class="h-full min-h-0">
1527
+ <MediaStage
1528
+ document={stageDoc}
1529
+ {selectedIds}
1530
+ {zoom}
1531
+ {showGrid}
1532
+ {showGuides}
1533
+ {snap}
1534
+ {cellSize}
1535
+ {drawMode}
1536
+ expandWidgets={!editingWidget}
1537
+ onselect={(ids) => (selectedIds = ids)}
1538
+ onzoom={setZoom}
1539
+ onlayerchange={patchLayer}
1540
+ ondocumentchange={(doc) => {
1541
+ if (editingWidget) {
1542
+ emitStageLayers(doc.layers, {
1543
+ width: doc.width,
1544
+ height: doc.height,
1545
+ background: doc.background
1546
+ });
1547
+ } else {
1548
+ emit(doc);
1549
+ if ((doc.guides?.length ?? 0) > 0) showGuides = true;
1550
+ }
1551
+ }}
1552
+ oncontextlayer={({ id, x, y }) => {
1553
+ ctxLayerId = id;
1554
+ ctxAnchor = { x, y };
1555
+ ctxOpen = true;
1556
+ }}
1557
+ ondropelement={({ def, x, y }) => addElement(def, { x, y })}
1558
+ ondrawcomplete={({ points, closed }) => {
1559
+ const layer = createPathFromDocPoints(points, {
1560
+ closed,
1561
+ zIndex: stageDoc.layers.length
1562
+ });
1563
+ addLayer(layer);
1564
+ drawMode = false;
1565
+ }}
1566
+ ondrawcancel={() => (drawMode = false)}
1567
+ onenterlayer={enterLayer}
1568
+ />
1569
+ </div>
1570
+ {/snippet}
1571
+
1572
+ {#snippet inspector()}
1573
+ <CanvasInspector
1574
+ document={editingWidget ? stageDoc : value}
1575
+ layer={selectedLayer}
1576
+ selectionCount={selectedIds.length}
1577
+ widgetDefinition={editingWidget}
1578
+ ondocumentchange={(doc) => {
1579
+ if (editingWidget) {
1580
+ emitStageLayers(doc.layers, {
1581
+ width: doc.width,
1582
+ height: doc.height,
1583
+ background: doc.background
1584
+ });
1585
+ } else {
1586
+ emit(doc);
1587
+ }
1588
+ }}
1589
+ onchange={patchLayer}
1590
+ onalign={alignSelected}
1591
+ onexpose={({ layerId, field, exposed, label }) => {
1592
+ if (!editingWidgetId) return;
1593
+ emit(
1594
+ updateWidgetDefinition(value, editingWidgetId, (d) =>
1595
+ setExposedProp(d, layerId, field, exposed, label)
1596
+ )
1597
+ );
1598
+ }}
1599
+ onoverride={({ propId, value: v }) => {
1600
+ if (!selectedLayer || selectedLayer.kind !== 'widget') return;
1601
+ emit(setInstanceOverride(value, selectedLayer.id, propId, v));
1602
+ }}
1603
+ oneditwidget={(definitionId) => {
1604
+ enterWidgetEdit(definitionId);
1605
+ }}
1606
+ onbreakwidget={() => {
1607
+ if (!selectedLayer || selectedLayer.kind !== 'widget') return;
1608
+ emit(breakWidgetInstance(value, selectedLayer.id));
1609
+ selectedIds = [];
1610
+ }}
1611
+ />
1612
+ {/snippet}
1613
+ </EditorShell>
1614
+
1615
+ {#if selectedIds.length > 1}
1616
+ <div class="pointer-events-none absolute inset-x-0 bottom-4 z-30 flex justify-center px-4">
1617
+ <div class="pointer-events-auto">
1618
+ <BulkActionBar
1619
+ count={selectedIds.length}
1620
+ itemLabel="layer"
1621
+ itemLabelPlural="layers"
1622
+ placement="dock"
1623
+ compact
1624
+ showSelectAll={false}
1625
+ maxPrimary={5}
1626
+ actions={[
1627
+ { id: 'duplicate', label: 'Duplicate', icon: Copy, variant: 'ghost' },
1628
+ { id: 'front', label: 'Bring to front', icon: BringToFront, variant: 'ghost' },
1629
+ { id: 'back', label: 'Send to back', icon: ArrowDownToLine, variant: 'ghost' },
1630
+ { id: 'detach', label: 'Remove from parent', icon: Ungroup, variant: 'ghost' },
1631
+ { id: 'lock', label: 'Lock', icon: Lock, variant: 'ghost', overflow: true },
1632
+ { id: 'hide', label: 'Hide', icon: EyeOff, variant: 'ghost', overflow: true },
1633
+ {
1634
+ id: 'delete',
1635
+ label: 'Delete',
1636
+ icon: Trash2,
1637
+ variant: 'destructive',
1638
+ confirm: true,
1639
+ overflow: true
1640
+ }
1641
+ ]}
1642
+ onaction={handleBulk}
1643
+ onclear={() => (selectedIds = [])}
1644
+ >
1645
+ {#snippet extra()}
1646
+ <DropdownMenu
1647
+ size="sm"
1648
+ align="start"
1649
+ side="top"
1650
+ label="Add parent"
1651
+ items={wrapItems}
1652
+ onselect={handleWrapMenu}
1653
+ >
1654
+ {#snippet trigger()}
1655
+ <Tooltip content="Wrap in parent" side="top">
1656
+ <span
1657
+ class="inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary hover:bg-surface-overlay hover:text-primary"
1658
+ aria-label="Wrap in parent"
1659
+ >
1660
+ <Group class="h-3.5 w-3.5" />
1661
+ </span>
1662
+ </Tooltip>
1663
+ {/snippet}
1664
+ </DropdownMenu>
1665
+ {/snippet}
1666
+ </BulkActionBar>
1667
+ </div>
1668
+ </div>
1669
+ {/if}
1670
+ </div>
1671
+ </div>
1672
+
1673
+ <ContextMenu
1674
+ items={ctxItems}
1675
+ bind:open={ctxOpen}
1676
+ bind:anchor={ctxAnchor}
1677
+ searchable
1678
+ searchPlaceholder="Filter actions…"
1679
+ onselect={handleCtx}
1680
+ />