@workbench-kit/react 0.0.2-prototype.0.2.5 → 0.0.2-prototype.0.2.6

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 (562) hide show
  1. package/package.json +10 -10
  2. package/src/brand/TilepaperAppIcon.tsx +181 -181
  3. package/src/brand/index.ts +2 -2
  4. package/src/brand/tilepaper-app-icon-geometry.ts +201 -201
  5. package/src/icons/WorkbenchIcon.tsx +57 -57
  6. package/src/icons/WorkbenchIconContext.tsx +25 -25
  7. package/src/icons/file-icon.ts +154 -154
  8. package/src/icons/index.ts +17 -17
  9. package/src/icons/resolveWorkbenchIcon.ts +48 -48
  10. package/src/icons/types.ts +28 -28
  11. package/src/index.ts +735 -735
  12. package/src/jdw/JdwPreview.tsx +195 -195
  13. package/src/jdw/JdwPreviewViewport.tsx +190 -190
  14. package/src/jdw/JsonCodeEditorPane.tsx +424 -424
  15. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +130 -130
  16. package/src/jdw/createBuiltinJdwRegistry.ts +436 -436
  17. package/src/jdw/cssRenderBackend.tsx +223 -223
  18. package/src/jdw/document.ts +35 -35
  19. package/src/jdw/fixtures/jdw-fixtures.ts +52 -52
  20. package/src/jdw/fixtures/jdw-sample-screens.ts +524 -524
  21. package/src/jdw/index.ts +50 -50
  22. package/src/jdw/jdw-preview-viewport.css +41 -41
  23. package/src/jdw/parse.ts +1 -1
  24. package/src/jdw/renderJdw.tsx +73 -73
  25. package/src/json-config/JsonConfigWorkbench.tsx +313 -313
  26. package/src/json-config/index.ts +6 -6
  27. package/src/json-config/json-config-editor-state.ts +42 -42
  28. package/src/layout/ViewEmptyState.tsx +14 -14
  29. package/src/layout/WorkbenchCanvas.tsx +956 -956
  30. package/src/layout/WorkbenchFullscreen.tsx +368 -368
  31. package/src/layout/WorkbenchLayout.tsx +8 -8
  32. package/src/layout/WorkbenchLayoutBase.tsx +335 -335
  33. package/src/layout/WorkbenchMediaPreviewViewport.tsx +122 -122
  34. package/src/layout/WorkbenchPropertyOverrideLabel.tsx +90 -90
  35. package/src/layout/WorkbenchPropertyPanel.tsx +633 -633
  36. package/src/layout/WorkbenchPropertySearch.tsx +51 -51
  37. package/src/layout/WorkbenchTree.tsx +350 -350
  38. package/src/layout/canvas/canvas-drag-ghost.css +21 -21
  39. package/src/layout/canvas/canvas-drag-preview-frame.css +15 -15
  40. package/src/layout/canvas/canvas-drop-indicator.css +16 -16
  41. package/src/layout/canvas/canvas-frame-handle.css +37 -37
  42. package/src/layout/canvas/canvas-frame-surface.css +24 -24
  43. package/src/layout/canvas/canvas-guide-block.css +17 -17
  44. package/src/layout/canvas/canvas-guide-layer.css +6 -6
  45. package/src/layout/canvas/canvas-guide-line.css +22 -22
  46. package/src/layout/canvas/canvas-item-badge.css +23 -23
  47. package/src/layout/canvas/canvas-item-frame.css +54 -54
  48. package/src/layout/canvas/canvas-pane-surface.css +9 -9
  49. package/src/layout/canvas/canvas-placeholder.css +21 -21
  50. package/src/layout/canvas/canvas-resize-frame.css +9 -9
  51. package/src/layout/canvas/canvas-resize-handle.css +74 -74
  52. package/src/layout/canvas/canvas-resize-preview.css +11 -11
  53. package/src/layout/canvas/canvas-selection-marquee.css +14 -14
  54. package/src/layout/canvas/canvas-viewport.css +18 -18
  55. package/src/layout/canvas/index.css +19 -19
  56. package/src/layout/canvas/preview-canvas.css +144 -144
  57. package/src/layout/editor/editor-body.css +9 -9
  58. package/src/layout/editor/editor-bottom-panel.css +44 -44
  59. package/src/layout/editor/editor-frame.css +14 -14
  60. package/src/layout/editor/editor-viewport.css +7 -7
  61. package/src/layout/editor/floating-menu.css +91 -91
  62. package/src/layout/editor/index.css +10 -10
  63. package/src/layout/editor/parse-error.css +9 -9
  64. package/src/layout/editor/problem-item.css +52 -52
  65. package/src/layout/editor/problem-list.css +7 -7
  66. package/src/layout/editor/render-surface.css +13 -13
  67. package/src/layout/fillScrollContract.ts +49 -49
  68. package/src/layout/fullscreen/fullscreen-backdrop.css +31 -31
  69. package/src/layout/fullscreen/fullscreen-button.css +44 -44
  70. package/src/layout/fullscreen/fullscreen-carousel.css +20 -20
  71. package/src/layout/fullscreen/fullscreen-content.css +8 -8
  72. package/src/layout/fullscreen/fullscreen-empty.css +16 -16
  73. package/src/layout/fullscreen/fullscreen-header.css +77 -77
  74. package/src/layout/fullscreen/fullscreen-hero.css +41 -41
  75. package/src/layout/fullscreen/fullscreen-nav-button.css +29 -29
  76. package/src/layout/fullscreen/fullscreen-option.css +83 -83
  77. package/src/layout/fullscreen/fullscreen-pill-row.css +6 -6
  78. package/src/layout/fullscreen/fullscreen-pill.css +10 -10
  79. package/src/layout/fullscreen/fullscreen-root.css +11 -11
  80. package/src/layout/fullscreen/index.css +13 -13
  81. package/src/layout/index.ts +156 -156
  82. package/src/layout/layout-base/banner.css +32 -32
  83. package/src/layout/layout-base/center.css +7 -7
  84. package/src/layout/layout-base/column.css +7 -7
  85. package/src/layout/layout-base/divider.css +5 -5
  86. package/src/layout/layout-base/empty.css +12 -12
  87. package/src/layout/layout-base/fill-scroll.css +36 -36
  88. package/src/layout/layout-base/fill.css +18 -18
  89. package/src/layout/layout-base/index.css +13 -13
  90. package/src/layout/layout-base/pane.css +6 -6
  91. package/src/layout/layout-base/panel-scroll.css +5 -5
  92. package/src/layout/layout-base/panel-surface.css +14 -14
  93. package/src/layout/layout-base/root.css +9 -9
  94. package/src/layout/layout-base/section-title.css +8 -8
  95. package/src/layout/layout.css +10 -10
  96. package/src/layout/layoutHelpers.ts +27 -27
  97. package/src/layout/media/index.css +2 -2
  98. package/src/layout/media/media-preview-viewport.css +69 -69
  99. package/src/layout/panel/Panel.tsx +161 -161
  100. package/src/layout/panel/TemplateGallery.tsx +90 -90
  101. package/src/layout/panel/WorkbenchAuthoringShell.tsx +57 -57
  102. package/src/layout/panel/WorkbenchLabeledPane.tsx +81 -81
  103. package/src/layout/panel/WorkbenchSidebarStack.tsx +15 -15
  104. package/src/layout/panel/filter-bar.css +59 -59
  105. package/src/layout/panel/index.css +7 -7
  106. package/src/layout/panel/index.ts +70 -70
  107. package/src/layout/panel/preview-pane.css +63 -63
  108. package/src/layout/panel/template-gallery.css +131 -131
  109. package/src/layout/panel/workbench-authoring-shell.css +48 -48
  110. package/src/layout/panel/workbench-labeled-pane.css +69 -69
  111. package/src/layout/panel/workbench-sidebar-stack.css +18 -18
  112. package/src/layout/property/color-input.css +8 -8
  113. package/src/layout/property/color-row.css +6 -6
  114. package/src/layout/property/index.css +15 -15
  115. package/src/layout/property/property-card.css +8 -8
  116. package/src/layout/property/property-grid.css +24 -24
  117. package/src/layout/property/property-hint.css +4 -4
  118. package/src/layout/property/property-inline.css +10 -10
  119. package/src/layout/property/property-key-value.css +29 -29
  120. package/src/layout/property/property-override-label.css +34 -34
  121. package/src/layout/property/property-panel.css +20 -20
  122. package/src/layout/property/property-row.css +17 -17
  123. package/src/layout/property/property-search.css +43 -43
  124. package/src/layout/property/property-section.css +97 -97
  125. package/src/layout/property/property-stack.css +11 -11
  126. package/src/layout/property/property-toggle.css +14 -14
  127. package/src/layout/propertyFieldFilter.ts +84 -84
  128. package/src/layout/sidebar/SideBarViewFrame.tsx +235 -235
  129. package/src/layout/sidebar/SideBarViewTabStrip.tsx +60 -60
  130. package/src/layout/sidebar/SideBarViewTitleMenu.tsx +75 -75
  131. package/src/layout/sidebar/SidebarActionIconBar.tsx +242 -242
  132. package/src/layout/sidebar/SidebarSectionNestingContext.tsx +21 -21
  133. package/src/layout/sidebar/SidebarSlotChrome.tsx +32 -32
  134. package/src/layout/sidebar/SidebarToolbar.tsx +10 -10
  135. package/src/layout/sidebar/WorkbenchActionSidebar.tsx +73 -73
  136. package/src/layout/sidebar/WorkbenchSidebarActions.tsx +328 -328
  137. package/src/layout/sidebar/WorkbenchSidebarSectionStack.tsx +165 -165
  138. package/src/layout/sidebar/index.css +5 -5
  139. package/src/layout/sidebar/index.ts +55 -55
  140. package/src/layout/sidebar/sidebar-chrome.css +95 -95
  141. package/src/layout/sidebar/sidebar-section-stack.css +25 -25
  142. package/src/layout/sidebar/sidebar-section.css +124 -124
  143. package/src/layout/sidebar/sidebar-view.css +290 -290
  144. package/src/layout/tree/drag-preview.css +25 -25
  145. package/src/layout/tree/index.css +10 -10
  146. package/src/layout/tree/template-glyph.css +96 -96
  147. package/src/layout/tree/tree-action.css +20 -20
  148. package/src/layout/tree/tree-drag-overlay.css +13 -13
  149. package/src/layout/tree/tree-drop-line.css +17 -17
  150. package/src/layout/tree/tree-drop-zone.css +13 -13
  151. package/src/layout/tree/tree-expander.css +23 -23
  152. package/src/layout/tree/tree-item.css +93 -93
  153. package/src/layout/tree/tree.css +49 -49
  154. package/src/layout/usePreviewViewport.ts +428 -428
  155. package/src/modal/ConfirmDialog.tsx +61 -61
  156. package/src/modal/Modal.tsx +213 -213
  157. package/src/modal/ModalResizeHandles.tsx +40 -40
  158. package/src/modal/ModalTitlebar.tsx +86 -86
  159. package/src/modal/modal.css +291 -291
  160. package/src/modal/modalContainer.ts +36 -36
  161. package/src/modal/modalPosition.ts +36 -36
  162. package/src/modal/modalSize.ts +84 -84
  163. package/src/modal/modalTypes.ts +13 -13
  164. package/src/modal/useModalContainerViewport.ts +41 -41
  165. package/src/modal/useModalDrag.ts +118 -118
  166. package/src/modal/useModalResize.ts +122 -122
  167. package/src/modal/useModalWindowFrame.ts +122 -122
  168. package/src/overlay/ContextMenu.tsx +124 -124
  169. package/src/overlay/clampFixedOverlayPosition.ts +22 -22
  170. package/src/overlay/context-menu.css +127 -127
  171. package/src/overlay/index.ts +31 -31
  172. package/src/overlay/measureAnchoredOverlayPanel.ts +107 -107
  173. package/src/overlay/overlay.css +2 -2
  174. package/src/overlay/pointerPassthroughRegion.ts +83 -83
  175. package/src/overlay/useAnchoredOverlayPanel.ts +222 -222
  176. package/src/overlay/useClampedFixedOverlayPosition.ts +27 -27
  177. package/src/overlay/useContextMenuState.ts +59 -59
  178. package/src/overlay/useFixedOverlayDismiss.ts +49 -49
  179. package/src/overlay/usePointerPassthroughRegion.ts +87 -87
  180. package/src/primitives/absolute-box/AbsoluteBox.tsx +46 -46
  181. package/src/primitives/absolute-box/index.ts +1 -1
  182. package/src/primitives/badge/Badge.tsx +14 -14
  183. package/src/primitives/badge/badge.css +20 -20
  184. package/src/primitives/badge/index.ts +1 -1
  185. package/src/primitives/button/Button.tsx +45 -45
  186. package/src/primitives/button/button.css +76 -76
  187. package/src/primitives/button/index.ts +1 -1
  188. package/src/primitives/catalog-browse-card/CatalogBrowseCard.tsx +121 -121
  189. package/src/primitives/catalog-browse-card/catalog-browse-card.css +175 -175
  190. package/src/primitives/catalog-browse-card/index.ts +1 -1
  191. package/src/primitives/catalog-browse-pane/CatalogBrowseFacetChips.tsx +59 -59
  192. package/src/primitives/catalog-browse-pane/CatalogBrowsePane.tsx +360 -360
  193. package/src/primitives/catalog-browse-pane/catalog-browse-facet-chips.css +8 -8
  194. package/src/primitives/catalog-browse-pane/catalog-browse-pane.css +84 -84
  195. package/src/primitives/catalog-browse-pane/filterCatalogBrowseItems.ts +27 -27
  196. package/src/primitives/catalog-browse-pane/index.ts +3 -3
  197. package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +73 -73
  198. package/src/primitives/catalog-filter-overlay/catalog-filter-overlay.css +65 -65
  199. package/src/primitives/catalog-filter-overlay/index.ts +1 -1
  200. package/src/primitives/checkbox/Checkbox.tsx +40 -40
  201. package/src/primitives/checkbox/checkbox.css +81 -81
  202. package/src/primitives/checkbox/index.ts +1 -1
  203. package/src/primitives/chip/Chip.tsx +43 -43
  204. package/src/primitives/chip/chip.css +110 -110
  205. package/src/primitives/chip/index.ts +2 -2
  206. package/src/primitives/clearable-text-input/ClearableTextInput.tsx +78 -78
  207. package/src/primitives/clearable-text-input/clearable-text-input.css +15 -15
  208. package/src/primitives/clearable-text-input/index.ts +1 -1
  209. package/src/primitives/codicon/Codicon.tsx +20 -20
  210. package/src/primitives/codicon/index.ts +1 -1
  211. package/src/primitives/empty-state/EmptyState.tsx +33 -33
  212. package/src/primitives/empty-state/empty-state.css +20 -20
  213. package/src/primitives/empty-state/index.ts +1 -1
  214. package/src/primitives/external-link-button/ExternalLinkButton.tsx +56 -56
  215. package/src/primitives/external-link-button/index.ts +1 -1
  216. package/src/primitives/field/Field.tsx +39 -39
  217. package/src/primitives/field/field.css +51 -51
  218. package/src/primitives/field/index.ts +1 -1
  219. package/src/primitives/file-icon/FileIcon.tsx +21 -21
  220. package/src/primitives/file-icon/index.ts +1 -1
  221. package/src/primitives/icon-button/IconButton.tsx +37 -37
  222. package/src/primitives/icon-button/icon-button.css +46 -46
  223. package/src/primitives/icon-button/index.ts +1 -1
  224. package/src/primitives/index.ts +265 -265
  225. package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +135 -135
  226. package/src/primitives/library-detail-layout/index.ts +1 -1
  227. package/src/primitives/library-detail-layout/library-detail-layout.css +169 -169
  228. package/src/primitives/library-facet-filter-panel/LibraryFacetFilterPanel.tsx +454 -454
  229. package/src/primitives/library-facet-filter-panel/index.ts +1 -1
  230. package/src/primitives/library-facet-filter-panel/library-facet-filter-panel.css +205 -205
  231. package/src/primitives/library-facet-filter-strip/LibraryFacetFilterStrip.tsx +527 -527
  232. package/src/primitives/library-facet-filter-strip/index.ts +1 -1
  233. package/src/primitives/library-facet-filter-strip/library-facet-filter-strip.css +31 -31
  234. package/src/primitives/list/List.tsx +120 -120
  235. package/src/primitives/list/index.ts +1 -1
  236. package/src/primitives/list/list-empty-state.css +10 -10
  237. package/src/primitives/list/list-item.css +93 -93
  238. package/src/primitives/list/list.css +6 -6
  239. package/src/primitives/number-input/NumberInput.tsx +235 -235
  240. package/src/primitives/number-input/index.ts +1 -1
  241. package/src/primitives/number-input/number-input.css +105 -105
  242. package/src/primitives/panel-loading/PanelLoading.tsx +29 -29
  243. package/src/primitives/panel-loading/index.ts +1 -1
  244. package/src/primitives/panel-loading/panel-loading.css +16 -16
  245. package/src/primitives/panel-surface/panel-surface.css +77 -77
  246. package/src/primitives/primitives.css +36 -36
  247. package/src/primitives/record-media-hero/RecordMediaHero.tsx +81 -81
  248. package/src/primitives/record-media-hero/index.ts +1 -1
  249. package/src/primitives/record-media-hero/record-media-hero.css +84 -84
  250. package/src/primitives/scroll-area/ScrollArea.tsx +200 -200
  251. package/src/primitives/scroll-area/index.ts +1 -1
  252. package/src/primitives/scroll-area/scroll-area-overlay.css +69 -69
  253. package/src/primitives/scroll-area-infinite-load/ScrollAreaInfiniteSentinel.tsx +22 -22
  254. package/src/primitives/scroll-area-infinite-load/index.ts +2 -2
  255. package/src/primitives/scroll-area-infinite-load/scroll-area-infinite-sentinel.css +6 -6
  256. package/src/primitives/scroll-area-infinite-load/useScrollAreaInfiniteLoad.ts +84 -84
  257. package/src/primitives/searchable-multi-select/SearchableMultiSelect.tsx +378 -378
  258. package/src/primitives/searchable-multi-select/index.ts +9 -9
  259. package/src/primitives/searchable-multi-select/overlay.ts +125 -125
  260. package/src/primitives/searchable-multi-select/searchable-multi-select.css +129 -129
  261. package/src/primitives/select/Select.tsx +284 -284
  262. package/src/primitives/select/index.ts +2 -2
  263. package/src/primitives/select/options.ts +38 -38
  264. package/src/primitives/select/overlay.ts +82 -82
  265. package/src/primitives/select/select.app.css +12 -12
  266. package/src/primitives/select/select.css +177 -177
  267. package/src/primitives/select/types.ts +18 -18
  268. package/src/primitives/styles.css +6 -6
  269. package/src/primitives/tabbed-panels/TabbedPanels.tsx +68 -68
  270. package/src/primitives/tabbed-panels/index.ts +2 -2
  271. package/src/primitives/tabbed-panels/tabbed-panels.css +58 -58
  272. package/src/primitives/text-area/TextArea.tsx +37 -37
  273. package/src/primitives/text-area/index.ts +1 -1
  274. package/src/primitives/text-input/TextInput.tsx +34 -34
  275. package/src/primitives/text-input/index.ts +1 -1
  276. package/src/primitives/text-input/input.css +54 -54
  277. package/src/primitives/toolbar/Toolbar.tsx +8 -8
  278. package/src/primitives/toolbar/index.ts +1 -1
  279. package/src/primitives/visually-hidden/visually-hidden.css +11 -11
  280. package/src/primitives/workbench-editor/WorkbenchEditor.tsx +432 -432
  281. package/src/primitives/workbench-editor/button-group.css +10 -10
  282. package/src/primitives/workbench-editor/editor-tabs.css +201 -201
  283. package/src/primitives/workbench-editor/index.ts +1 -1
  284. package/src/primitives/workbench-editor/resizable-panels.css +53 -53
  285. package/src/primitives/workbench-editor/segmented-control.css +98 -98
  286. package/src/primitives/workbench-media-slot/WorkbenchMediaPlaceholder.tsx +25 -25
  287. package/src/primitives/workbench-media-slot/WorkbenchMediaSlot.tsx +60 -60
  288. package/src/primitives/workbench-media-slot/index.ts +2 -2
  289. package/src/primitives/workbench-media-slot/media-slot.css +72 -72
  290. package/src/primitives/workbench-thumbnail/WorkbenchThumbnail.tsx +38 -38
  291. package/src/primitives/workbench-thumbnail/index.ts +1 -1
  292. package/src/primitives/workbench-thumbnail/thumbnail.css +69 -69
  293. package/src/scrollbars.css +144 -144
  294. package/src/styles.css +17 -17
  295. package/src/test-utils/workbenchMonacoMock.tsx +37 -37
  296. package/src/utils/codicon.ts +17 -17
  297. package/src/utils/dragMime.ts +89 -89
  298. package/src/utils/useWorkbenchMediaImage.ts +37 -37
  299. package/src/widget-asset/WidgetAssetEditor.tsx +142 -142
  300. package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -82
  301. package/src/widget-asset/WidgetAssetModeControls.tsx +42 -42
  302. package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -50
  303. package/src/widget-asset/WidgetAssetWorkbench.tsx +111 -111
  304. package/src/widget-asset/index.ts +17 -17
  305. package/src/widget-asset/widget-asset-document.ts +37 -37
  306. package/src/widget-asset/widget-asset-mode.ts +3 -3
  307. package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -12
  308. package/src/widget-studio/create-widget-studio-workspace-editor.tsx +78 -78
  309. package/src/widget-studio/index.ts +10 -10
  310. package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +43 -43
  311. package/src/widget-tree/WidgetAssetPalette.tsx +105 -105
  312. package/src/widget-tree/WidgetInspectorPanel.tsx +316 -316
  313. package/src/widget-tree/WidgetSourceEditor.tsx +143 -143
  314. package/src/widget-tree/WidgetTreeCanvasPreview.tsx +875 -875
  315. package/src/widget-tree/WidgetTreeLab.tsx +600 -600
  316. package/src/widget-tree/WidgetTreeModeControls.tsx +73 -73
  317. package/src/widget-tree/WidgetTreeSidePanel.tsx +39 -39
  318. package/src/widget-tree/WidgetTreeView.tsx +797 -797
  319. package/src/widget-tree/WidgetTreeWorkbench.tsx +155 -155
  320. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +172 -172
  321. package/src/widget-tree/demo-registry.ts +32 -32
  322. package/src/widget-tree/demo-widget-assets.ts +4 -4
  323. package/src/widget-tree/index.ts +45 -45
  324. package/src/widget-tree/widget-placement-asset-dnd.ts +35 -35
  325. package/src/widget-tree/widget-tree-document.ts +14 -14
  326. package/src/widget-tree/widget-tree-editor-state.ts +40 -40
  327. package/src/widget-tree/widget-tree-layout.ts +76 -76
  328. package/src/widget-tree/widget-tree-mode.ts +7 -7
  329. package/src/widget-tree/widget-tree-play-helpers.ts +85 -85
  330. package/src/workbench/ActivityBar.tsx +159 -159
  331. package/src/workbench/ArtifactShell.tsx +397 -397
  332. package/src/workbench/CommandGroupShell.tsx +131 -131
  333. package/src/workbench/CommandList.tsx +167 -167
  334. package/src/workbench/CommandPalette.tsx +417 -417
  335. package/src/workbench/ConfirmationFlow.tsx +243 -243
  336. package/src/workbench/MarkdownPreview.tsx +147 -147
  337. package/src/workbench/ShortcutCommandBridge.tsx +365 -365
  338. package/src/workbench/SplitView.tsx +485 -485
  339. package/src/workbench/StatusBar.tsx +180 -180
  340. package/src/workbench/StructuredArtifactEditor.tsx +205 -205
  341. package/src/workbench/Timeline.tsx +297 -297
  342. package/src/workbench/WorkbenchCanvasShell.defaults.ts +106 -106
  343. package/src/workbench/WorkbenchCanvasShell.model.ts +46 -46
  344. package/src/workbench/WorkbenchCanvasShell.tsx +827 -827
  345. package/src/workbench/WorkbenchDesktopTitleBar.tsx +126 -126
  346. package/src/workbench/WorkbenchEditorTabs.tsx +52 -52
  347. package/src/workbench/WorkbenchModalPortal.tsx +52 -52
  348. package/src/workbench/WorkbenchPlatformContext.tsx +31 -31
  349. package/src/workbench/WorkbenchShell.tsx +192 -192
  350. package/src/workbench/WorkbenchShellTitleBarLayoutControls.tsx +80 -80
  351. package/src/workbench/WorkbenchStandaloneShell.tsx +604 -604
  352. package/src/workbench/WorkbenchThemeProvider.tsx +142 -142
  353. package/src/workbench/WorkbenchViewEditor.tsx +75 -75
  354. package/src/workbench/WorkbenchViewSidebar.tsx +85 -85
  355. package/src/workbench/WorkbenchWindowChromeControls.tsx +290 -290
  356. package/src/workbench/activityBarOrder.ts +98 -98
  357. package/src/workbench/appearanceLabels.ts +28 -28
  358. package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -61
  359. package/src/workbench/auth/WorkbenchLoginView.tsx +633 -633
  360. package/src/workbench/auth/auth-flows.css +440 -440
  361. package/src/workbench/auth/auth.css +3 -3
  362. package/src/workbench/auth/index.ts +19 -19
  363. package/src/workbench/auth/profile-modals.css +115 -115
  364. package/src/workbench/bootstrap/WorkbenchBootstrapGate.tsx +45 -45
  365. package/src/workbench/bootstrap/WorkbenchBootstrapView.tsx +100 -100
  366. package/src/workbench/bootstrap/index.ts +12 -12
  367. package/src/workbench/bootstrap/useWorkbenchBootstrap.ts +130 -130
  368. package/src/workbench/chat/ChatCommandProposalCard.tsx +115 -115
  369. package/src/workbench/chat/ChatComposer.tsx +163 -163
  370. package/src/workbench/chat/ChatConversationBar.tsx +134 -134
  371. package/src/workbench/chat/ChatHistoryMenu.tsx +171 -171
  372. package/src/workbench/chat/ChatHistoryMenuRow.tsx +74 -74
  373. package/src/workbench/chat/ChatMessageCollapsible.tsx +115 -115
  374. package/src/workbench/chat/ChatMessageItem.tsx +318 -318
  375. package/src/workbench/chat/ChatMessageList.tsx +134 -134
  376. package/src/workbench/chat/ChatPanel.tsx +168 -168
  377. package/src/workbench/chat/ChatPhasedRunProgress.tsx +202 -202
  378. package/src/workbench/chat/ChatRenameDialog.tsx +77 -77
  379. package/src/workbench/chat/chat-conversation.css +273 -273
  380. package/src/workbench/chat/chat-ui.css +1124 -1124
  381. package/src/workbench/chat/chat.css +3 -3
  382. package/src/workbench/chat/chatConversation.ts +46 -46
  383. package/src/workbench/chat/chatHistoryRowMenuItems.ts +29 -29
  384. package/src/workbench/chat/chatMessageMeta.tsx +252 -252
  385. package/src/workbench/chat/chatScroll.ts +22 -22
  386. package/src/workbench/chat/index.ts +64 -64
  387. package/src/workbench/chat/slashCommand.ts +26 -26
  388. package/src/workbench/chat/types.ts +45 -45
  389. package/src/workbench/chat/useCancelRuntimeOnUnmount.ts +60 -60
  390. package/src/workbench/chat/useChatPrependPagination.ts +233 -233
  391. package/src/workbench/chat/useChatRuntimeState.ts +64 -64
  392. package/src/workbench/chat/useSlashCommandSuggest.ts +154 -154
  393. package/src/workbench/chrome/index.css +6 -6
  394. package/src/workbench/chrome/workbench-desktop-titlebar.css +60 -60
  395. package/src/workbench/chrome/workbench-host.css +85 -85
  396. package/src/workbench/chrome/workbench-layout-regions.css +42 -42
  397. package/src/workbench/chrome/workbench-platform-chrome.css +142 -142
  398. package/src/workbench/chrome/workbench-shell-titlebar.css +124 -124
  399. package/src/workbench/command-execution-policy.ts +61 -61
  400. package/src/workbench/command-model.ts +275 -275
  401. package/src/workbench/command-palette/command-palette.css +156 -156
  402. package/src/workbench/commands.ts +576 -576
  403. package/src/workbench/editor-tabs/index.ts +22 -22
  404. package/src/workbench/editor-tabs-dnd.ts +162 -162
  405. package/src/workbench/editorTabContextMenu.ts +111 -111
  406. package/src/workbench/hardReset.ts +64 -64
  407. package/src/workbench/index.ts +675 -675
  408. package/src/workbench/jdw/jdw-editors.css +4 -4
  409. package/src/workbench/jdw/jdw.css +2 -2
  410. package/src/workbench/keyboard.ts +53 -53
  411. package/src/workbench/management/AccountManagementPanel.tsx +82 -82
  412. package/src/workbench/management/CommandInspectorPanel.tsx +127 -127
  413. package/src/workbench/management/CommandManagementPanel.tsx +129 -129
  414. package/src/workbench/management/CommandManagementSidebar.tsx +318 -318
  415. package/src/workbench/management/ExtensionManagementPanel.tsx +498 -498
  416. package/src/workbench/management/ExtensionManagementSidebar.tsx +492 -492
  417. package/src/workbench/management/IntegrationCommandActions.tsx +78 -78
  418. package/src/workbench/management/IntegrationSettingsSurface.tsx +290 -290
  419. package/src/workbench/management/IntegrationsShell.tsx +53 -53
  420. package/src/workbench/management/KeybindingCaptureField.tsx +142 -142
  421. package/src/workbench/management/KeybindingManagementPanel.tsx +120 -120
  422. package/src/workbench/management/LibraryCatalogPickerDialog.tsx +208 -208
  423. package/src/workbench/management/LibraryFacetFilterDialog.tsx +62 -62
  424. package/src/workbench/management/ManagementCard.tsx +89 -89
  425. package/src/workbench/management/ManagementFilterChips.tsx +41 -41
  426. package/src/workbench/management/ManagementGroup.tsx +56 -56
  427. package/src/workbench/management/ManagementPanelControls.tsx +43 -43
  428. package/src/workbench/management/ManagementPanelFrame.tsx +109 -109
  429. package/src/workbench/management/WorkbenchDialogFrame.tsx +148 -148
  430. package/src/workbench/management/WorkbenchNotice.tsx +211 -211
  431. package/src/workbench/management/build-command-management-groups.ts +157 -157
  432. package/src/workbench/management/command-inspector-uri.ts +26 -26
  433. package/src/workbench/management/extension-category-display.ts +42 -42
  434. package/src/workbench/management/format-command-run-state.ts +19 -19
  435. package/src/workbench/management/index.ts +130 -130
  436. package/src/workbench/management/integration-command-action.ts +25 -25
  437. package/src/workbench/management/library-catalog-picker-dialog.css +56 -56
  438. package/src/workbench/management/library-facet-filter-dialog.css +8 -8
  439. package/src/workbench/management/management-panel.css +444 -444
  440. package/src/workbench/management/management.css +4 -4
  441. package/src/workbench/management/types.ts +126 -126
  442. package/src/workbench/markdownRemarkPlugins.ts +4 -4
  443. package/src/workbench/schema/index.ts +47 -47
  444. package/src/workbench/schema/workbenchDocument.ts +31 -31
  445. package/src/workbench/schema/workbenchDocumentActions.ts +6 -6
  446. package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -9
  447. package/src/workbench/schema/workbenchDocumentPatch.ts +13 -13
  448. package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -702
  449. package/src/workbench/settings/NavigationPanel.tsx +86 -86
  450. package/src/workbench/settings/SchemaForm.tsx +433 -433
  451. package/src/workbench/settings/SectionTabPanel.tsx +172 -172
  452. package/src/workbench/settings/SectionedPanel.tsx +169 -169
  453. package/src/workbench/settings/StructuredDataForm.tsx +619 -619
  454. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +811 -811
  455. package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -21
  456. package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -76
  457. package/src/workbench/settings/StructuredDataTableView.tsx +127 -127
  458. package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -46
  459. package/src/workbench/settings/WorkbenchSettingsModal.tsx +251 -251
  460. package/src/workbench/settings/WorkbenchSettingsNav.tsx +57 -57
  461. package/src/workbench/settings/WorkbenchSettingsSection.tsx +30 -30
  462. package/src/workbench/settings/extensionSettingsForm.ts +34 -34
  463. package/src/workbench/settings/index.ts +177 -177
  464. package/src/workbench/settings/navigation-panel.css +59 -59
  465. package/src/workbench/settings/panel-region.css +22 -22
  466. package/src/workbench/settings/schema-form.css +36 -36
  467. package/src/workbench/settings/schemaFormSettingSpec.ts +177 -177
  468. package/src/workbench/settings/schemaFormSettingsCategory.tsx +21 -21
  469. package/src/workbench/settings/section-tab-panel.css +157 -157
  470. package/src/workbench/settings/sectionScrollSpyDom.ts +139 -139
  471. package/src/workbench/settings/sectioned-panel.css +162 -162
  472. package/src/workbench/settings/sectionedPanelScrollSpy.ts +273 -273
  473. package/src/workbench/settings/settings-responsive.css +58 -58
  474. package/src/workbench/settings/settings.css +11 -11
  475. package/src/workbench/settings/settingsCommit.tsx +55 -55
  476. package/src/workbench/settings/structured-data-form.css +114 -114
  477. package/src/workbench/settings/structured-data-schema-panel.css +344 -344
  478. package/src/workbench/settings/structured-data-table-view.css +85 -85
  479. package/src/workbench/settings/structuredDataFormModel.ts +140 -140
  480. package/src/workbench/settings/structuredDataSchema.ts +339 -339
  481. package/src/workbench/settings/structuredDataSchemaDate.ts +197 -197
  482. package/src/workbench/settings/structuredDataSchemaField.ts +105 -105
  483. package/src/workbench/settings/structuredDataSchemaSection.ts +74 -74
  484. package/src/workbench/settings/structuredDataSchemaTable.ts +120 -120
  485. package/src/workbench/settings/structuredDataSchemaTypes.ts +308 -308
  486. package/src/workbench/settings/structuredDataSchemaValidation.ts +166 -166
  487. package/src/workbench/settings/types.ts +16 -16
  488. package/src/workbench/settings/useSectionScrollSpy.ts +420 -420
  489. package/src/workbench/settings/workbench-settings-modal.css +273 -273
  490. package/src/workbench/shell/activity-bar.css +245 -245
  491. package/src/workbench/shell/confirmation-flow.css +87 -87
  492. package/src/workbench/shell/index.css +5 -5
  493. package/src/workbench/shell/split-view.css +188 -188
  494. package/src/workbench/shell/status-bar.css +141 -141
  495. package/src/workbench/shell.ts +146 -146
  496. package/src/workbench/shellPresets.ts +36 -36
  497. package/src/workbench/shellState.ts +196 -196
  498. package/src/workbench/shellViewModel.ts +160 -160
  499. package/src/workbench/sidebarViewPlacementDnd.ts +61 -61
  500. package/src/workbench/standalone.ts +101 -101
  501. package/src/workbench/status.ts +133 -133
  502. package/src/workbench/theme.ts +50 -50
  503. package/src/workbench/themePresets.ts +191 -191
  504. package/src/workbench/useDeferredWorkbenchMount.ts +71 -71
  505. package/src/workbench/useEditorTabsStripDnd.ts +262 -262
  506. package/src/workbench/useWorkbenchAppearanceDocumentSync.tsx +17 -17
  507. package/src/workbench/useWorkbenchEditorTabContextMenu.tsx +66 -66
  508. package/src/workbench/useWorkbenchSidebarActionBarDnd.ts +227 -227
  509. package/src/workbench/useWorkbenchSidebarLayout.tsx +232 -232
  510. package/src/workbench/useWorkbenchSidebarViewPlacementDropZone.ts +194 -194
  511. package/src/workbench/useWorkbenchStandaloneShellStateSync.tsx +112 -112
  512. package/src/workbench/views/action-list.css +144 -144
  513. package/src/workbench/views/artifact-preview.css +184 -184
  514. package/src/workbench/views/command-inspector.css +98 -98
  515. package/src/workbench/views/command-list.css +356 -356
  516. package/src/workbench/views/commands-sidebar.css +51 -51
  517. package/src/workbench/views/extensions-sidebar.css +175 -175
  518. package/src/workbench/views/multi-provider-explorer.css +182 -182
  519. package/src/workbench/views/search.css +10 -10
  520. package/src/workbench/views/timeline.css +239 -239
  521. package/src/workbench/views/view-hosts.css +37 -37
  522. package/src/workbench/views/view-sidebar.css +21 -21
  523. package/src/workbench/views/views.css +12 -12
  524. package/src/workbench/widgets/widget-tree.css +598 -598
  525. package/src/workbench/widgets/widgets.css +2 -2
  526. package/src/workbench/workbench-chrome.css +4 -4
  527. package/src/workbench/workbenchContextMenu.ts +18 -18
  528. package/src/workbench/workbenchModalViewState.ts +52 -52
  529. package/src/workbench/workbenchOverlaysContext.tsx +21 -21
  530. package/src/workbench/workbenchPlatformChrome.ts +40 -40
  531. package/src/workbench/workbenchSidebarLayoutContext.ts +46 -46
  532. package/src/workbench/workbenchStandaloneShellReactContext.tsx +80 -80
  533. package/src/workbench/workbenchViewRouteState.ts +151 -151
  534. package/src/workbench/workspace/WorkspaceDraftsContext.tsx +99 -99
  535. package/src/workbench/workspace/WorkspaceEditor.tsx +165 -165
  536. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +393 -393
  537. package/src/workbench/workspace/WorkspaceExplorer.tsx +630 -630
  538. package/src/workbench/workspace/WorkspaceExplorerPanel.tsx +73 -73
  539. package/src/workbench/workspace/WorkspaceFileIcon.tsx +23 -23
  540. package/src/workbench/workspace/WorkspaceHighlightedText.tsx +22 -22
  541. package/src/workbench/workspace/WorkspacePathLabel.tsx +42 -42
  542. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +116 -116
  543. package/src/workbench/workspace/WorkspaceSearchResults.tsx +51 -51
  544. package/src/workbench/workspace/build-workspace-explorer-nodes.ts +44 -44
  545. package/src/workbench/workspace/createVirtualWorkspaceExplorerPort.ts +141 -141
  546. package/src/workbench/workspace/explorer-tree-style.ts +18 -18
  547. package/src/workbench/workspace/index.ts +169 -169
  548. package/src/workbench/workspace/json-config-workbench.css +57 -57
  549. package/src/workbench/workspace/mimeType.ts +45 -45
  550. package/src/workbench/workspace/monaco-panel.css +71 -71
  551. package/src/workbench/workspace/path.ts +12 -12
  552. package/src/workbench/workspace/search.ts +6 -6
  553. package/src/workbench/workspace/tree.ts +1 -1
  554. package/src/workbench/workspace/types.ts +10 -10
  555. package/src/workbench/workspace/useVirtualWorkspace.ts +111 -111
  556. package/src/workbench/workspace/useWorkspaceExplorerController.ts +490 -490
  557. package/src/workbench/workspace/workspace-editor.css +235 -235
  558. package/src/workbench/workspace/workspace-explorer-panel.css +33 -33
  559. package/src/workbench/workspace/workspace.css +5 -5
  560. package/src/workbench/workspace/workspaceExplorerController.ts +193 -193
  561. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -83
  562. package/src/workbench-menu-surfaces.css +57 -57
@@ -1,875 +1,875 @@
1
- import { useMemo, useState, type DragEvent, type FocusEvent, type PointerEvent } from 'react';
2
- import type { WidgetRegistryContract } from '@workbench-kit/contracts';
3
- import type { WidgetPlacementAsset } from '@workbench-kit/contracts';
4
- import {
5
- computeGridChildRect,
6
- createWidgetDragPatch,
7
- createWidgetReparentPatch,
8
- createWidgetResizePatch,
9
- DEFAULT_LAYOUT_CONSTRAINTS,
10
- findLayoutNodeByPath,
11
- getWidgetChildren,
12
- getWidgetAtPath,
13
- hitTestLayoutTree,
14
- layoutWidget,
15
- parseWidgetPathKey,
16
- widgetPathEquals,
17
- widgetPathKey,
18
- type GenericWidget,
19
- type JsonWidgetValueMap,
20
- type LayoutConstraints,
21
- type LayoutNodeResult,
22
- type LayoutPoint,
23
- type Rect,
24
- type WidgetPatch,
25
- type WidgetPath,
26
- type WidgetResizeHandlePosition,
27
- } from '@workbench-kit/jdw';
28
-
29
- import {
30
- WorkbenchCanvasFrameHandle,
31
- WorkbenchCanvasItemFrame,
32
- WorkbenchPreviewCanvas,
33
- WorkbenchCanvasResizeHandle,
34
- WorkbenchCanvasDropIndicator,
35
- WorkbenchCanvasDragPreviewFrame,
36
- WorkbenchCanvasGuideLayer,
37
- WorkbenchCanvasGuideLine,
38
- } from '../layout/WorkbenchCanvas.js';
39
- import { usePreviewViewport } from '../layout/usePreviewViewport.js';
40
- import { JdwPreview } from '../jdw/JdwPreview.js';
41
- import { readWidgetPlacementAssetDragData } from './widget-placement-asset-dnd.js';
42
- import { canAddChildren, insertedWidgetPathForParent } from './widget-tree-layout.js';
43
-
44
- const RESIZE_HANDLE_POSITIONS = [
45
- 'n',
46
- 'ne',
47
- 'e',
48
- 'se',
49
- 's',
50
- 'sw',
51
- 'w',
52
- 'nw',
53
- ] as const satisfies readonly WidgetResizeHandlePosition[];
54
-
55
- export interface WidgetTreeCanvasPreviewProps {
56
- readonly json: string;
57
- readonly layoutConstraints?: LayoutConstraints | undefined;
58
- readonly previewValues?: JsonWidgetValueMap | undefined;
59
- readonly readOnly?: boolean | undefined;
60
- readonly registry?: WidgetRegistryContract<unknown> | undefined;
61
- readonly root: GenericWidget | null;
62
- readonly selectedPath: WidgetPath | null;
63
- readonly documentPath?: string | null | undefined;
64
- readonly loadDocument?: ((path: string) => string | null) | undefined;
65
- readonly onPatch: (patch: WidgetPatch) => boolean;
66
- readonly onPlaceAssetPath?: ((operation: WidgetTreeCanvasAssetDropOperation) => void) | undefined;
67
- readonly onSelectPath: (path: WidgetPath) => void;
68
- }
69
-
70
- export interface WidgetTreeCanvasAssetDropOperation {
71
- readonly asset: WidgetPlacementAsset;
72
- readonly parentPath: WidgetPath;
73
- readonly insertIndex: number;
74
- readonly nextPath: WidgetPath;
75
- }
76
-
77
- interface WidgetTreeCanvasAssetDropTarget {
78
- readonly insertIndex: number;
79
- readonly markerRect: Rect;
80
- readonly nextPath: WidgetPath;
81
- readonly parentType: string;
82
- readonly path: WidgetPath;
83
- readonly rect: Rect;
84
- readonly type: WidgetTreeCanvasAssetDropTargetType;
85
- }
86
-
87
- type WidgetTreeCanvasAssetDropTargetType =
88
- 'append-column' | 'append-container' | 'append-grid' | 'append-row' | 'append-stack';
89
-
90
- interface WidgetTreeCanvasDragPreviewState {
91
- readonly deltaX: number;
92
- readonly deltaY: number;
93
- readonly ghostRect: Rect;
94
- readonly patchType: WidgetPatch['type'] | 'none';
95
- readonly reparentTarget: WidgetTreeCanvasReparentDropTarget | null;
96
- readonly selectedPath: WidgetPath;
97
- readonly selectedType: string;
98
- }
99
-
100
- interface WidgetTreeCanvasResizePreviewState {
101
- readonly deltaX: number;
102
- readonly deltaY: number;
103
- readonly position: WidgetResizeHandlePosition;
104
- readonly previewRect: Rect;
105
- readonly selectedPath: WidgetPath;
106
- }
107
-
108
- interface WidgetTreeCanvasReparentDropTarget {
109
- readonly insertIndex: number;
110
- readonly markerRect: Rect;
111
- readonly parentType: string;
112
- readonly path: WidgetPath;
113
- readonly rect: Rect;
114
- readonly type: WidgetTreeCanvasAssetDropTargetType;
115
- }
116
-
117
- interface WidgetTreeCanvasAppendDropTarget {
118
- readonly insertIndex: number;
119
- readonly markerRect: Rect;
120
- readonly parentType: string;
121
- readonly path: WidgetPath;
122
- readonly rect: Rect;
123
- readonly type: WidgetTreeCanvasAssetDropTargetType;
124
- }
125
-
126
- function canDragSelectedPath(root: GenericWidget, path: WidgetPath): boolean {
127
- const segment = path[path.length - 1];
128
- if (!segment || segment.kind !== 'children') return false;
129
-
130
- const parent = getWidgetAtPath(root, path.slice(0, -1));
131
- return parent?.type === 'stack' || parent?.type === 'grid';
132
- }
133
-
134
- function canResizeSelectedPath(root: GenericWidget, path: WidgetPath): boolean {
135
- const segment = path[path.length - 1];
136
- if (!segment) return false;
137
-
138
- const parent = getWidgetAtPath(root, path.slice(0, -1));
139
- if (segment.kind === 'child') {
140
- return (
141
- parent?.type === 'box' ||
142
- parent?.type === 'container' ||
143
- parent?.type === 'padding' ||
144
- parent?.type === 'align' ||
145
- parent?.type === 'center' ||
146
- parent?.type === 'sized_box'
147
- );
148
- }
149
-
150
- if (segment.kind !== 'children') return false;
151
-
152
- return (
153
- parent?.type === 'stack' ||
154
- parent?.type === 'grid' ||
155
- parent?.type === 'row' ||
156
- parent?.type === 'column'
157
- );
158
- }
159
-
160
- /** Live chrome-only resize rect (commit still happens on pointer-up). */
161
- function resolveResizePreviewRect(
162
- base: Rect,
163
- position: WidgetResizeHandlePosition,
164
- deltaX: number,
165
- deltaY: number,
166
- minWidth = 1,
167
- minHeight = 1,
168
- ): Rect {
169
- let { x, y, width, height } = base;
170
-
171
- if (position.includes('e')) {
172
- width = Math.max(minWidth, width + deltaX);
173
- }
174
- if (position.includes('w')) {
175
- const nextWidth = Math.max(minWidth, width - deltaX);
176
- x += width - nextWidth;
177
- width = nextWidth;
178
- }
179
- if (position.includes('s')) {
180
- height = Math.max(minHeight, height + deltaY);
181
- }
182
- if (position.includes('n')) {
183
- const nextHeight = Math.max(minHeight, height - deltaY);
184
- y += height - nextHeight;
185
- height = nextHeight;
186
- }
187
-
188
- return { x, y, width, height };
189
- }
190
-
191
- function widgetPathParent(path: WidgetPath): WidgetPath | null {
192
- if (path.length === 0) return null;
193
- return path.slice(0, -1);
194
- }
195
-
196
- function widgetPathFromEventTarget(target: EventTarget | null): WidgetPath | null {
197
- if (!(target instanceof Element)) return null;
198
-
199
- const pathKey = target.closest<HTMLElement>('[data-widget-path]')?.dataset.widgetPath;
200
- return pathKey ? parseWidgetPathKey(pathKey) : null;
201
- }
202
-
203
- function eventPointInLayout(
204
- event: DragEvent<HTMLElement>,
205
- stage: HTMLElement,
206
- frameWidth: number,
207
- frameHeight: number,
208
- ): LayoutPoint {
209
- const rect = stage.getBoundingClientRect();
210
- const scaleX = rect.width > 0 ? frameWidth / rect.width : 1;
211
- const scaleY = rect.height > 0 ? frameHeight / rect.height : 1;
212
-
213
- return {
214
- x: (event.clientX - rect.left) * scaleX,
215
- y: (event.clientY - rect.top) * scaleY,
216
- };
217
- }
218
-
219
- function readPositiveInteger(value: unknown): number | null {
220
- if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) return null;
221
- return Math.floor(value);
222
- }
223
-
224
- function readNumber(value: unknown): number | undefined {
225
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
226
- }
227
-
228
- function appendLineRect(rect: Rect, axis: 'x' | 'y', children: readonly LayoutNodeResult[]): Rect {
229
- const lastChildRect = children[children.length - 1]?.rect;
230
- if (axis === 'x') {
231
- const x = lastChildRect
232
- ? Math.min(rect.x + rect.width - 2, lastChildRect.x + lastChildRect.width)
233
- : rect.x;
234
- return { x, y: rect.y, width: 2, height: rect.height };
235
- }
236
-
237
- const y = lastChildRect
238
- ? Math.min(rect.y + rect.height - 2, lastChildRect.y + lastChildRect.height)
239
- : rect.y;
240
- return { x: rect.x, y, width: rect.width, height: 2 };
241
- }
242
-
243
- function createCanvasAppendDropTarget(
244
- parent: GenericWidget,
245
- target: LayoutNodeResult,
246
- path: WidgetPath,
247
- insertIndex: number,
248
- ): WidgetTreeCanvasAppendDropTarget {
249
- const rect = target.rect;
250
-
251
- if (parent.type === 'row') {
252
- return {
253
- insertIndex,
254
- markerRect: appendLineRect(rect, 'x', target.children),
255
- parentType: parent.type,
256
- path,
257
- rect,
258
- type: 'append-row',
259
- };
260
- }
261
-
262
- if (parent.type === 'column') {
263
- return {
264
- insertIndex,
265
- markerRect: appendLineRect(rect, 'y', target.children),
266
- parentType: parent.type,
267
- path,
268
- rect,
269
- type: 'append-column',
270
- };
271
- }
272
-
273
- if (parent.type === 'grid') {
274
- const columns = readPositiveInteger(parent.columns) ?? 2;
275
- const gap = readNumber(parent.gap);
276
- const padding = readNumber(parent.padding);
277
- const rows = readPositiveInteger(parent.rows) ?? undefined;
278
- const markerRect = computeGridChildRect(
279
- {
280
- columns,
281
- ...(gap !== undefined ? { gap } : {}),
282
- ...(padding !== undefined ? { padding } : {}),
283
- ...(rows !== undefined ? { rows } : {}),
284
- },
285
- {
286
- col: insertIndex % columns,
287
- row: Math.floor(insertIndex / columns),
288
- },
289
- rect,
290
- );
291
-
292
- return {
293
- insertIndex,
294
- markerRect,
295
- parentType: parent.type,
296
- path,
297
- rect,
298
- type: 'append-grid',
299
- };
300
- }
301
-
302
- return {
303
- insertIndex,
304
- markerRect: rect,
305
- parentType: parent.type,
306
- path,
307
- rect,
308
- type: parent.type === 'stack' ? 'append-stack' : 'append-container',
309
- };
310
- }
311
-
312
- function createAssetDropTarget(
313
- operation: WidgetTreeCanvasAssetDropOperation,
314
- parent: GenericWidget,
315
- target: LayoutNodeResult,
316
- ): WidgetTreeCanvasAssetDropTarget {
317
- return {
318
- ...createCanvasAppendDropTarget(parent, target, operation.parentPath, operation.insertIndex),
319
- nextPath: operation.nextPath,
320
- };
321
- }
322
-
323
- function createReparentDropTarget(
324
- patch: Extract<WidgetPatch, { type: 'reparent-widget' }>,
325
- parent: GenericWidget,
326
- target: LayoutNodeResult,
327
- ): WidgetTreeCanvasReparentDropTarget {
328
- return createCanvasAppendDropTarget(parent, target, patch.toParentPath, patch.insertIndex);
329
- }
330
-
331
- export function resolveWidgetCanvasAssetDropOperation(
332
- asset: WidgetPlacementAsset,
333
- root: GenericWidget,
334
- layout: LayoutNodeResult,
335
- point: LayoutPoint,
336
- ): WidgetTreeCanvasAssetDropOperation | null {
337
- const hit = hitTestLayoutTree(layout, point);
338
- if (!hit) return null;
339
-
340
- let candidatePath: WidgetPath | null = hit.path;
341
- while (candidatePath !== null) {
342
- const candidate = getWidgetAtPath(root, candidatePath);
343
- if (candidate && canAddChildren(candidate)) {
344
- const insertIndex = getWidgetChildren(candidate).length;
345
- return {
346
- asset,
347
- parentPath: candidatePath,
348
- insertIndex,
349
- nextPath: insertedWidgetPathForParent(candidate, candidatePath, insertIndex),
350
- };
351
- }
352
-
353
- candidatePath = widgetPathParent(candidatePath);
354
- }
355
-
356
- return null;
357
- }
358
-
359
- export function WidgetTreeCanvasPreview({
360
- json,
361
- layoutConstraints = DEFAULT_LAYOUT_CONSTRAINTS,
362
- previewValues,
363
- readOnly = false,
364
- registry,
365
- root,
366
- selectedPath,
367
- documentPath = null,
368
- loadDocument,
369
- onPatch,
370
- onPlaceAssetPath,
371
- onSelectPath,
372
- }: WidgetTreeCanvasPreviewProps) {
373
- const [assetDropTarget, setAssetDropTarget] = useState<WidgetTreeCanvasAssetDropTarget | null>(
374
- null,
375
- );
376
- const [dragPreview, setDragPreview] = useState<WidgetTreeCanvasDragPreviewState | null>(null);
377
- const [resizePreview, setResizePreview] = useState<WidgetTreeCanvasResizePreviewState | null>(
378
- null,
379
- );
380
- const [hoveredPath, setHoveredPath] = useState<WidgetPath | null>(null);
381
- const [focusedPath, setFocusedPath] = useState<WidgetPath | null>(null);
382
- const layout = useMemo(
383
- () => (root ? layoutWidget(root, layoutConstraints, { x: 0, y: 0 }, { registry }) : null),
384
- [layoutConstraints, registry, root],
385
- );
386
- const selectedLayout = useMemo(
387
- () => (layout && selectedPath ? findLayoutNodeByPath(layout, selectedPath) : null),
388
- [layout, selectedPath],
389
- );
390
- const hoveredLayout = useMemo(
391
- () => (layout && hoveredPath ? findLayoutNodeByPath(layout, hoveredPath) : null),
392
- [hoveredPath, layout],
393
- );
394
- const focusedLayout = useMemo(
395
- () => (layout && focusedPath ? findLayoutNodeByPath(layout, focusedPath) : null),
396
- [focusedPath, layout],
397
- );
398
- const selectedLayoutFocused = Boolean(
399
- focusedPath && selectedPath && widgetPathEquals(focusedPath, selectedPath),
400
- );
401
- const showHoveredLayout = Boolean(
402
- hoveredLayout &&
403
- hoveredPath &&
404
- (!selectedPath || !widgetPathEquals(hoveredPath, selectedPath)) &&
405
- (!focusedPath || !widgetPathEquals(hoveredPath, focusedPath)) &&
406
- !assetDropTarget &&
407
- !dragPreview,
408
- );
409
- const showFocusedLayout = Boolean(
410
- focusedLayout &&
411
- focusedPath &&
412
- (!selectedPath || !widgetPathEquals(focusedPath, selectedPath)) &&
413
- !assetDropTarget &&
414
- !dragPreview,
415
- );
416
- const canDrag = Boolean(
417
- root && selectedPath && !readOnly && canDragSelectedPath(root, selectedPath),
418
- );
419
- const canResize = Boolean(
420
- root && selectedPath && !readOnly && canResizeSelectedPath(root, selectedPath),
421
- );
422
- const frameWidth = Math.max(1, layout?.rect.width ?? layoutConstraints.maxWidth);
423
- const frameHeight = Math.max(1, layout?.rect.height ?? layoutConstraints.maxHeight);
424
- const viewport = usePreviewViewport({
425
- contentHeight: frameHeight,
426
- contentWidth: frameWidth,
427
- enablePrimaryPointerPan: false,
428
- });
429
-
430
- const resolveDragPreview = (
431
- deltaX: number,
432
- deltaY: number,
433
- ): WidgetTreeCanvasDragPreviewState | null => {
434
- if (!root || !layout || !selectedPath || !selectedLayout) return null;
435
- if (deltaX === 0 && deltaY === 0) return null;
436
-
437
- const maybeReparentPatch = createWidgetReparentPatch({
438
- deltaX,
439
- deltaY,
440
- layout,
441
- path: selectedPath,
442
- root,
443
- });
444
- const reparentPatch =
445
- maybeReparentPatch?.type === 'reparent-widget' ? maybeReparentPatch : null;
446
- const dragPatch = reparentPatch
447
- ? null
448
- : createWidgetDragPatch({
449
- deltaX,
450
- deltaY,
451
- layout,
452
- path: selectedPath,
453
- root,
454
- });
455
- const targetLayout =
456
- reparentPatch !== null ? findLayoutNodeByPath(layout, reparentPatch.toParentPath) : null;
457
- const targetParent =
458
- reparentPatch !== null ? getWidgetAtPath(root, reparentPatch.toParentPath) : null;
459
-
460
- return {
461
- deltaX,
462
- deltaY,
463
- ghostRect: {
464
- x: selectedLayout.node.rect.x + deltaX,
465
- y: selectedLayout.node.rect.y + deltaY,
466
- width: selectedLayout.node.rect.width,
467
- height: selectedLayout.node.rect.height,
468
- },
469
- patchType: reparentPatch?.type ?? dragPatch?.type ?? 'none',
470
- reparentTarget:
471
- reparentPatch && targetLayout && targetParent
472
- ? createReparentDropTarget(reparentPatch, targetParent, targetLayout.node)
473
- : null,
474
- selectedPath,
475
- selectedType: selectedLayout.node.widget.type,
476
- };
477
- };
478
-
479
- const commitDrag = (deltaX: number, deltaY: number) => {
480
- setDragPreview(null);
481
- if (!root || !layout || !selectedPath) return;
482
-
483
- const reparentPatch = createWidgetReparentPatch({
484
- deltaX,
485
- deltaY,
486
- layout,
487
- path: selectedPath,
488
- root,
489
- });
490
- if (reparentPatch && onPatch(reparentPatch)) {
491
- return;
492
- }
493
-
494
- const patch = createWidgetDragPatch({
495
- deltaX,
496
- deltaY,
497
- layout,
498
- path: selectedPath,
499
- root,
500
- });
501
- if (patch) {
502
- onPatch(patch);
503
- }
504
- };
505
-
506
- const resolveResizePreview = (
507
- position: WidgetResizeHandlePosition,
508
- deltaX: number,
509
- deltaY: number,
510
- ): WidgetTreeCanvasResizePreviewState | null => {
511
- if (!selectedPath || !selectedLayout) return null;
512
- if (deltaX === 0 && deltaY === 0) return null;
513
-
514
- return {
515
- deltaX,
516
- deltaY,
517
- position,
518
- previewRect: resolveResizePreviewRect(selectedLayout.node.rect, position, deltaX, deltaY),
519
- selectedPath,
520
- };
521
- };
522
-
523
- const commitResize = (position: WidgetResizeHandlePosition, deltaX: number, deltaY: number) => {
524
- setResizePreview(null);
525
- if (!root || !layout || !selectedPath) return;
526
-
527
- const patch = createWidgetResizePatch({
528
- deltaX,
529
- deltaY,
530
- layout,
531
- path: selectedPath,
532
- position,
533
- root,
534
- });
535
- if (patch) {
536
- onPatch(patch);
537
- }
538
- };
539
-
540
- const resolveAssetDrop = (
541
- event: DragEvent<HTMLElement>,
542
- ): WidgetTreeCanvasAssetDropOperation | null => {
543
- if (readOnly || !root || !layout || !onPlaceAssetPath) return null;
544
-
545
- const asset = readWidgetPlacementAssetDragData(event.dataTransfer);
546
- if (!asset) return null;
547
-
548
- const point = eventPointInLayout(event, event.currentTarget, frameWidth, frameHeight);
549
- return resolveWidgetCanvasAssetDropOperation(asset, root, layout, point);
550
- };
551
-
552
- const handleAssetDragOver = (event: DragEvent<HTMLDivElement>) => {
553
- if (dragPreview) {
554
- setAssetDropTarget(null);
555
- return;
556
- }
557
-
558
- const operation = resolveAssetDrop(event);
559
- if (!operation || !layout || !root) {
560
- setAssetDropTarget(null);
561
- return;
562
- }
563
-
564
- const target = findLayoutNodeByPath(layout, operation.parentPath);
565
- const parent = getWidgetAtPath(root, operation.parentPath);
566
- if (!target || !parent) {
567
- setAssetDropTarget(null);
568
- return;
569
- }
570
-
571
- event.preventDefault();
572
- event.dataTransfer.dropEffect = 'copy';
573
- setAssetDropTarget(createAssetDropTarget(operation, parent, target.node));
574
- };
575
-
576
- const handleAssetDragLeave = (event: DragEvent<HTMLDivElement>) => {
577
- const relatedTarget = event.relatedTarget;
578
- if (relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) return;
579
-
580
- setAssetDropTarget(null);
581
- };
582
-
583
- const handleAssetDrop = (event: DragEvent<HTMLDivElement>) => {
584
- const operation = resolveAssetDrop(event);
585
- setAssetDropTarget(null);
586
- if (!operation) return;
587
-
588
- event.preventDefault();
589
- onPlaceAssetPath?.(operation);
590
- };
591
-
592
- const handlePreviewPointerMove = (event: PointerEvent<HTMLDivElement>) => {
593
- const nextPath = widgetPathFromEventTarget(event.target);
594
- if (!nextPath) {
595
- setHoveredPath(null);
596
- return;
597
- }
598
-
599
- setHoveredPath((current) =>
600
- current && widgetPathEquals(current, nextPath) ? current : nextPath,
601
- );
602
- };
603
-
604
- const handlePreviewPointerLeave = () => {
605
- setHoveredPath(null);
606
- };
607
-
608
- const handlePreviewFocus = (event: FocusEvent<HTMLDivElement>) => {
609
- setFocusedPath(widgetPathFromEventTarget(event.target));
610
- };
611
-
612
- const handlePreviewBlur = (event: FocusEvent<HTMLDivElement>) => {
613
- const relatedTarget = event.relatedTarget;
614
- if (relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) {
615
- setFocusedPath(widgetPathFromEventTarget(relatedTarget));
616
- return;
617
- }
618
-
619
- setFocusedPath(null);
620
- };
621
-
622
- return (
623
- <WorkbenchPreviewCanvas
624
- className="widget-tree-canvas-preview"
625
- data-testid="widget-tree-canvas-preview"
626
- frameHeight={frameHeight}
627
- frameTitle="JDW Preview"
628
- frameWidth={frameWidth}
629
- help="Ctrl + Scroll to zoom | Middle-drag to pan"
630
- isPanning={viewport.isPanning}
631
- onResetView={viewport.resetView}
632
- resetLabel={`Reset (${Math.round(viewport.effectiveZoom * 100)}%)`}
633
- resetTitle="Reset zoom and pan"
634
- showViewportGrid
635
- showWindowFrame={false}
636
- stageStyle={viewport.stageStyle}
637
- viewportGridSize={8}
638
- viewportProps={{
639
- className: 'widget-tree-canvas-preview__viewport',
640
- ref: viewport.setViewportElement,
641
- }}
642
- >
643
- <div
644
- className="widget-tree-canvas-preview__stage"
645
- data-testid="widget-tree-canvas-stage"
646
- style={{
647
- width: frameWidth,
648
- height: frameHeight,
649
- }}
650
- onDragLeave={handleAssetDragLeave}
651
- onDragOver={handleAssetDragOver}
652
- onDrop={handleAssetDrop}
653
- onBlur={handlePreviewBlur}
654
- onFocus={handlePreviewFocus}
655
- onPointerLeave={handlePreviewPointerLeave}
656
- onPointerMove={handlePreviewPointerMove}
657
- >
658
- <JdwPreview
659
- className="widget-tree-canvas-preview__render"
660
- documentPath={documentPath}
661
- json={json}
662
- layoutConstraints={layoutConstraints}
663
- loadDocument={loadDocument}
664
- registry={registry}
665
- selectedPath={selectedPath}
666
- values={previewValues}
667
- onSelectPath={onSelectPath}
668
- />
669
- <div className="widget-tree-canvas-preview__frames" data-testid="widget-tree-canvas-frames">
670
- {showHoveredLayout && hoveredPath && hoveredLayout ? (
671
- <WorkbenchCanvasItemFrame
672
- className="widget-tree-canvas-preview__hover-frame"
673
- data-testid="widget-tree-canvas-hover-frame"
674
- data-widget-path={widgetPathKey(hoveredPath)}
675
- data-widget-type={hoveredLayout.node.widget.type}
676
- hovered
677
- transient
678
- width={hoveredLayout.node.rect.width}
679
- height={hoveredLayout.node.rect.height}
680
- x={hoveredLayout.node.rect.x}
681
- y={hoveredLayout.node.rect.y}
682
- />
683
- ) : null}
684
- {showFocusedLayout && focusedPath && focusedLayout ? (
685
- <WorkbenchCanvasItemFrame
686
- className="widget-tree-canvas-preview__focus-frame"
687
- data-testid="widget-tree-canvas-focus-frame"
688
- data-widget-path={widgetPathKey(focusedPath)}
689
- data-widget-type={focusedLayout.node.widget.type}
690
- focused
691
- transient
692
- width={focusedLayout.node.rect.width}
693
- height={focusedLayout.node.rect.height}
694
- x={focusedLayout.node.rect.x}
695
- y={focusedLayout.node.rect.y}
696
- zIndex={95}
697
- />
698
- ) : null}
699
- {assetDropTarget && !dragPreview ? (
700
- <>
701
- <WorkbenchCanvasDropIndicator
702
- className="widget-tree-canvas-preview__asset-drop-target"
703
- data-testid="widget-tree-canvas-asset-drop-indicator"
704
- data-insert-index={assetDropTarget.insertIndex}
705
- data-next-widget-path={widgetPathKey(assetDropTarget.nextPath)}
706
- data-parent-type={assetDropTarget.parentType}
707
- data-widget-path={widgetPathKey(assetDropTarget.path)}
708
- width={assetDropTarget.rect.width}
709
- height={assetDropTarget.rect.height}
710
- x={assetDropTarget.rect.x}
711
- y={assetDropTarget.rect.y}
712
- zIndex={30}
713
- />
714
- <WorkbenchCanvasDropIndicator
715
- className="widget-tree-canvas-preview__asset-drop-marker"
716
- data-testid="widget-tree-canvas-asset-drop-marker"
717
- data-drop-target-type={assetDropTarget.type}
718
- data-insert-index={assetDropTarget.insertIndex}
719
- data-next-widget-path={widgetPathKey(assetDropTarget.nextPath)}
720
- data-parent-type={assetDropTarget.parentType}
721
- data-widget-path={widgetPathKey(assetDropTarget.path)}
722
- width={assetDropTarget.markerRect.width}
723
- height={assetDropTarget.markerRect.height}
724
- x={assetDropTarget.markerRect.x}
725
- y={assetDropTarget.markerRect.y}
726
- zIndex={35}
727
- />
728
- </>
729
- ) : null}
730
- {dragPreview ? (
731
- <>
732
- {dragPreview.reparentTarget ? (
733
- <>
734
- <WorkbenchCanvasDropIndicator
735
- className="widget-tree-canvas-preview__reparent-drop-target"
736
- data-testid="widget-tree-canvas-reparent-drop-indicator"
737
- data-drop-target-type={dragPreview.reparentTarget.type}
738
- data-insert-index={dragPreview.reparentTarget.insertIndex}
739
- data-parent-type={dragPreview.reparentTarget.parentType}
740
- data-widget-path={widgetPathKey(dragPreview.reparentTarget.path)}
741
- width={dragPreview.reparentTarget.rect.width}
742
- height={dragPreview.reparentTarget.rect.height}
743
- x={dragPreview.reparentTarget.rect.x}
744
- y={dragPreview.reparentTarget.rect.y}
745
- zIndex={40}
746
- />
747
- <WorkbenchCanvasDropIndicator
748
- className="widget-tree-canvas-preview__reparent-drop-marker"
749
- data-testid="widget-tree-canvas-reparent-drop-marker"
750
- data-drop-target-type={dragPreview.reparentTarget.type}
751
- data-insert-index={dragPreview.reparentTarget.insertIndex}
752
- data-parent-type={dragPreview.reparentTarget.parentType}
753
- data-widget-path={widgetPathKey(dragPreview.reparentTarget.path)}
754
- width={dragPreview.reparentTarget.markerRect.width}
755
- height={dragPreview.reparentTarget.markerRect.height}
756
- x={dragPreview.reparentTarget.markerRect.x}
757
- y={dragPreview.reparentTarget.markerRect.y}
758
- zIndex={45}
759
- />
760
- </>
761
- ) : null}
762
- <WorkbenchCanvasDragPreviewFrame
763
- className="widget-tree-canvas-preview__drag-ghost"
764
- data-testid="widget-tree-canvas-drag-ghost"
765
- data-delta-x={dragPreview.deltaX}
766
- data-delta-y={dragPreview.deltaY}
767
- data-patch-type={dragPreview.patchType}
768
- data-widget-path={widgetPathKey(dragPreview.selectedPath)}
769
- data-widget-type={dragPreview.selectedType}
770
- width={dragPreview.ghostRect.width}
771
- height={dragPreview.ghostRect.height}
772
- x={dragPreview.ghostRect.x}
773
- y={dragPreview.ghostRect.y}
774
- zIndex={80}
775
- />
776
- <WorkbenchCanvasGuideLayer
777
- className="widget-tree-canvas-preview__snap-guides"
778
- data-testid="widget-tree-canvas-snap-guides"
779
- >
780
- <WorkbenchCanvasGuideLine
781
- axis="x"
782
- data-testid="widget-tree-canvas-snap-guide-x"
783
- data-widget-path={widgetPathKey(dragPreview.selectedPath)}
784
- end={frameHeight}
785
- position={dragPreview.ghostRect.x}
786
- source="grid"
787
- start={0}
788
- />
789
- <WorkbenchCanvasGuideLine
790
- axis="y"
791
- data-testid="widget-tree-canvas-snap-guide-y"
792
- data-widget-path={widgetPathKey(dragPreview.selectedPath)}
793
- end={frameWidth}
794
- position={dragPreview.ghostRect.y}
795
- source="grid"
796
- start={0}
797
- />
798
- </WorkbenchCanvasGuideLayer>
799
- </>
800
- ) : null}
801
- {selectedLayout && selectedPath ? (
802
- <WorkbenchCanvasItemFrame
803
- className="widget-tree-canvas-preview__selection-frame"
804
- data-testid="widget-tree-canvas-selection-frame"
805
- data-widget-path={widgetPathKey(selectedPath)}
806
- data-widget-type={selectedLayout.node.widget.type}
807
- focused={selectedLayoutFocused}
808
- interactive={canDrag || canResize}
809
- selected
810
- touchAction="none"
811
- width={
812
- resizePreview?.previewRect.width ??
813
- dragPreview?.ghostRect.width ??
814
- selectedLayout.node.rect.width
815
- }
816
- height={
817
- resizePreview?.previewRect.height ??
818
- dragPreview?.ghostRect.height ??
819
- selectedLayout.node.rect.height
820
- }
821
- x={
822
- resizePreview?.previewRect.x ??
823
- dragPreview?.ghostRect.x ??
824
- selectedLayout.node.rect.x
825
- }
826
- y={
827
- resizePreview?.previewRect.y ??
828
- dragPreview?.ghostRect.y ??
829
- selectedLayout.node.rect.y
830
- }
831
- >
832
- {canDrag ? (
833
- <WorkbenchCanvasFrameHandle
834
- aria-label="Move selected widget"
835
- data-testid="widget-tree-canvas-drag-handle"
836
- label={selectedLayout.node.widget.type}
837
- onDragCancel={() => setDragPreview(null)}
838
- onDragEnd={commitDrag}
839
- onDragMove={(deltaX, deltaY) =>
840
- setDragPreview(resolveDragPreview(deltaX, deltaY))
841
- }
842
- onDragStart={() => {
843
- setAssetDropTarget(null);
844
- setResizePreview(null);
845
- setDragPreview(null);
846
- }}
847
- />
848
- ) : null}
849
- {canResize
850
- ? RESIZE_HANDLE_POSITIONS.map((position) => (
851
- <WorkbenchCanvasResizeHandle
852
- key={position}
853
- data-testid={`widget-tree-canvas-resize-handle-${position}`}
854
- label={`Resize selected widget ${position}`}
855
- position={position}
856
- onResizeEnd={(deltaX, deltaY) => commitResize(position, deltaX, deltaY)}
857
- onResizeMove={(deltaX, deltaY) =>
858
- setResizePreview(resolveResizePreview(position, deltaX, deltaY))
859
- }
860
- onResizeCancel={() => setResizePreview(null)}
861
- onResizeStart={() => {
862
- setAssetDropTarget(null);
863
- setDragPreview(null);
864
- setResizePreview(null);
865
- }}
866
- />
867
- ))
868
- : null}
869
- </WorkbenchCanvasItemFrame>
870
- ) : null}
871
- </div>
872
- </div>
873
- </WorkbenchPreviewCanvas>
874
- );
875
- }
1
+ import { useMemo, useState, type DragEvent, type FocusEvent, type PointerEvent } from 'react';
2
+ import type { WidgetRegistryContract } from '@workbench-kit/contracts';
3
+ import type { WidgetPlacementAsset } from '@workbench-kit/contracts';
4
+ import {
5
+ computeGridChildRect,
6
+ createWidgetDragPatch,
7
+ createWidgetReparentPatch,
8
+ createWidgetResizePatch,
9
+ DEFAULT_LAYOUT_CONSTRAINTS,
10
+ findLayoutNodeByPath,
11
+ getWidgetChildren,
12
+ getWidgetAtPath,
13
+ hitTestLayoutTree,
14
+ layoutWidget,
15
+ parseWidgetPathKey,
16
+ widgetPathEquals,
17
+ widgetPathKey,
18
+ type GenericWidget,
19
+ type JsonWidgetValueMap,
20
+ type LayoutConstraints,
21
+ type LayoutNodeResult,
22
+ type LayoutPoint,
23
+ type Rect,
24
+ type WidgetPatch,
25
+ type WidgetPath,
26
+ type WidgetResizeHandlePosition,
27
+ } from '@workbench-kit/jdw';
28
+
29
+ import {
30
+ WorkbenchCanvasFrameHandle,
31
+ WorkbenchCanvasItemFrame,
32
+ WorkbenchPreviewCanvas,
33
+ WorkbenchCanvasResizeHandle,
34
+ WorkbenchCanvasDropIndicator,
35
+ WorkbenchCanvasDragPreviewFrame,
36
+ WorkbenchCanvasGuideLayer,
37
+ WorkbenchCanvasGuideLine,
38
+ } from '../layout/WorkbenchCanvas.js';
39
+ import { usePreviewViewport } from '../layout/usePreviewViewport.js';
40
+ import { JdwPreview } from '../jdw/JdwPreview.js';
41
+ import { readWidgetPlacementAssetDragData } from './widget-placement-asset-dnd.js';
42
+ import { canAddChildren, insertedWidgetPathForParent } from './widget-tree-layout.js';
43
+
44
+ const RESIZE_HANDLE_POSITIONS = [
45
+ 'n',
46
+ 'ne',
47
+ 'e',
48
+ 'se',
49
+ 's',
50
+ 'sw',
51
+ 'w',
52
+ 'nw',
53
+ ] as const satisfies readonly WidgetResizeHandlePosition[];
54
+
55
+ export interface WidgetTreeCanvasPreviewProps {
56
+ readonly json: string;
57
+ readonly layoutConstraints?: LayoutConstraints | undefined;
58
+ readonly previewValues?: JsonWidgetValueMap | undefined;
59
+ readonly readOnly?: boolean | undefined;
60
+ readonly registry?: WidgetRegistryContract<unknown> | undefined;
61
+ readonly root: GenericWidget | null;
62
+ readonly selectedPath: WidgetPath | null;
63
+ readonly documentPath?: string | null | undefined;
64
+ readonly loadDocument?: ((path: string) => string | null) | undefined;
65
+ readonly onPatch: (patch: WidgetPatch) => boolean;
66
+ readonly onPlaceAssetPath?: ((operation: WidgetTreeCanvasAssetDropOperation) => void) | undefined;
67
+ readonly onSelectPath: (path: WidgetPath) => void;
68
+ }
69
+
70
+ export interface WidgetTreeCanvasAssetDropOperation {
71
+ readonly asset: WidgetPlacementAsset;
72
+ readonly parentPath: WidgetPath;
73
+ readonly insertIndex: number;
74
+ readonly nextPath: WidgetPath;
75
+ }
76
+
77
+ interface WidgetTreeCanvasAssetDropTarget {
78
+ readonly insertIndex: number;
79
+ readonly markerRect: Rect;
80
+ readonly nextPath: WidgetPath;
81
+ readonly parentType: string;
82
+ readonly path: WidgetPath;
83
+ readonly rect: Rect;
84
+ readonly type: WidgetTreeCanvasAssetDropTargetType;
85
+ }
86
+
87
+ type WidgetTreeCanvasAssetDropTargetType =
88
+ 'append-column' | 'append-container' | 'append-grid' | 'append-row' | 'append-stack';
89
+
90
+ interface WidgetTreeCanvasDragPreviewState {
91
+ readonly deltaX: number;
92
+ readonly deltaY: number;
93
+ readonly ghostRect: Rect;
94
+ readonly patchType: WidgetPatch['type'] | 'none';
95
+ readonly reparentTarget: WidgetTreeCanvasReparentDropTarget | null;
96
+ readonly selectedPath: WidgetPath;
97
+ readonly selectedType: string;
98
+ }
99
+
100
+ interface WidgetTreeCanvasResizePreviewState {
101
+ readonly deltaX: number;
102
+ readonly deltaY: number;
103
+ readonly position: WidgetResizeHandlePosition;
104
+ readonly previewRect: Rect;
105
+ readonly selectedPath: WidgetPath;
106
+ }
107
+
108
+ interface WidgetTreeCanvasReparentDropTarget {
109
+ readonly insertIndex: number;
110
+ readonly markerRect: Rect;
111
+ readonly parentType: string;
112
+ readonly path: WidgetPath;
113
+ readonly rect: Rect;
114
+ readonly type: WidgetTreeCanvasAssetDropTargetType;
115
+ }
116
+
117
+ interface WidgetTreeCanvasAppendDropTarget {
118
+ readonly insertIndex: number;
119
+ readonly markerRect: Rect;
120
+ readonly parentType: string;
121
+ readonly path: WidgetPath;
122
+ readonly rect: Rect;
123
+ readonly type: WidgetTreeCanvasAssetDropTargetType;
124
+ }
125
+
126
+ function canDragSelectedPath(root: GenericWidget, path: WidgetPath): boolean {
127
+ const segment = path[path.length - 1];
128
+ if (!segment || segment.kind !== 'children') return false;
129
+
130
+ const parent = getWidgetAtPath(root, path.slice(0, -1));
131
+ return parent?.type === 'stack' || parent?.type === 'grid';
132
+ }
133
+
134
+ function canResizeSelectedPath(root: GenericWidget, path: WidgetPath): boolean {
135
+ const segment = path[path.length - 1];
136
+ if (!segment) return false;
137
+
138
+ const parent = getWidgetAtPath(root, path.slice(0, -1));
139
+ if (segment.kind === 'child') {
140
+ return (
141
+ parent?.type === 'box' ||
142
+ parent?.type === 'container' ||
143
+ parent?.type === 'padding' ||
144
+ parent?.type === 'align' ||
145
+ parent?.type === 'center' ||
146
+ parent?.type === 'sized_box'
147
+ );
148
+ }
149
+
150
+ if (segment.kind !== 'children') return false;
151
+
152
+ return (
153
+ parent?.type === 'stack' ||
154
+ parent?.type === 'grid' ||
155
+ parent?.type === 'row' ||
156
+ parent?.type === 'column'
157
+ );
158
+ }
159
+
160
+ /** Live chrome-only resize rect (commit still happens on pointer-up). */
161
+ function resolveResizePreviewRect(
162
+ base: Rect,
163
+ position: WidgetResizeHandlePosition,
164
+ deltaX: number,
165
+ deltaY: number,
166
+ minWidth = 1,
167
+ minHeight = 1,
168
+ ): Rect {
169
+ let { x, y, width, height } = base;
170
+
171
+ if (position.includes('e')) {
172
+ width = Math.max(minWidth, width + deltaX);
173
+ }
174
+ if (position.includes('w')) {
175
+ const nextWidth = Math.max(minWidth, width - deltaX);
176
+ x += width - nextWidth;
177
+ width = nextWidth;
178
+ }
179
+ if (position.includes('s')) {
180
+ height = Math.max(minHeight, height + deltaY);
181
+ }
182
+ if (position.includes('n')) {
183
+ const nextHeight = Math.max(minHeight, height - deltaY);
184
+ y += height - nextHeight;
185
+ height = nextHeight;
186
+ }
187
+
188
+ return { x, y, width, height };
189
+ }
190
+
191
+ function widgetPathParent(path: WidgetPath): WidgetPath | null {
192
+ if (path.length === 0) return null;
193
+ return path.slice(0, -1);
194
+ }
195
+
196
+ function widgetPathFromEventTarget(target: EventTarget | null): WidgetPath | null {
197
+ if (!(target instanceof Element)) return null;
198
+
199
+ const pathKey = target.closest<HTMLElement>('[data-widget-path]')?.dataset.widgetPath;
200
+ return pathKey ? parseWidgetPathKey(pathKey) : null;
201
+ }
202
+
203
+ function eventPointInLayout(
204
+ event: DragEvent<HTMLElement>,
205
+ stage: HTMLElement,
206
+ frameWidth: number,
207
+ frameHeight: number,
208
+ ): LayoutPoint {
209
+ const rect = stage.getBoundingClientRect();
210
+ const scaleX = rect.width > 0 ? frameWidth / rect.width : 1;
211
+ const scaleY = rect.height > 0 ? frameHeight / rect.height : 1;
212
+
213
+ return {
214
+ x: (event.clientX - rect.left) * scaleX,
215
+ y: (event.clientY - rect.top) * scaleY,
216
+ };
217
+ }
218
+
219
+ function readPositiveInteger(value: unknown): number | null {
220
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) return null;
221
+ return Math.floor(value);
222
+ }
223
+
224
+ function readNumber(value: unknown): number | undefined {
225
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
226
+ }
227
+
228
+ function appendLineRect(rect: Rect, axis: 'x' | 'y', children: readonly LayoutNodeResult[]): Rect {
229
+ const lastChildRect = children[children.length - 1]?.rect;
230
+ if (axis === 'x') {
231
+ const x = lastChildRect
232
+ ? Math.min(rect.x + rect.width - 2, lastChildRect.x + lastChildRect.width)
233
+ : rect.x;
234
+ return { x, y: rect.y, width: 2, height: rect.height };
235
+ }
236
+
237
+ const y = lastChildRect
238
+ ? Math.min(rect.y + rect.height - 2, lastChildRect.y + lastChildRect.height)
239
+ : rect.y;
240
+ return { x: rect.x, y, width: rect.width, height: 2 };
241
+ }
242
+
243
+ function createCanvasAppendDropTarget(
244
+ parent: GenericWidget,
245
+ target: LayoutNodeResult,
246
+ path: WidgetPath,
247
+ insertIndex: number,
248
+ ): WidgetTreeCanvasAppendDropTarget {
249
+ const rect = target.rect;
250
+
251
+ if (parent.type === 'row') {
252
+ return {
253
+ insertIndex,
254
+ markerRect: appendLineRect(rect, 'x', target.children),
255
+ parentType: parent.type,
256
+ path,
257
+ rect,
258
+ type: 'append-row',
259
+ };
260
+ }
261
+
262
+ if (parent.type === 'column') {
263
+ return {
264
+ insertIndex,
265
+ markerRect: appendLineRect(rect, 'y', target.children),
266
+ parentType: parent.type,
267
+ path,
268
+ rect,
269
+ type: 'append-column',
270
+ };
271
+ }
272
+
273
+ if (parent.type === 'grid') {
274
+ const columns = readPositiveInteger(parent.columns) ?? 2;
275
+ const gap = readNumber(parent.gap);
276
+ const padding = readNumber(parent.padding);
277
+ const rows = readPositiveInteger(parent.rows) ?? undefined;
278
+ const markerRect = computeGridChildRect(
279
+ {
280
+ columns,
281
+ ...(gap !== undefined ? { gap } : {}),
282
+ ...(padding !== undefined ? { padding } : {}),
283
+ ...(rows !== undefined ? { rows } : {}),
284
+ },
285
+ {
286
+ col: insertIndex % columns,
287
+ row: Math.floor(insertIndex / columns),
288
+ },
289
+ rect,
290
+ );
291
+
292
+ return {
293
+ insertIndex,
294
+ markerRect,
295
+ parentType: parent.type,
296
+ path,
297
+ rect,
298
+ type: 'append-grid',
299
+ };
300
+ }
301
+
302
+ return {
303
+ insertIndex,
304
+ markerRect: rect,
305
+ parentType: parent.type,
306
+ path,
307
+ rect,
308
+ type: parent.type === 'stack' ? 'append-stack' : 'append-container',
309
+ };
310
+ }
311
+
312
+ function createAssetDropTarget(
313
+ operation: WidgetTreeCanvasAssetDropOperation,
314
+ parent: GenericWidget,
315
+ target: LayoutNodeResult,
316
+ ): WidgetTreeCanvasAssetDropTarget {
317
+ return {
318
+ ...createCanvasAppendDropTarget(parent, target, operation.parentPath, operation.insertIndex),
319
+ nextPath: operation.nextPath,
320
+ };
321
+ }
322
+
323
+ function createReparentDropTarget(
324
+ patch: Extract<WidgetPatch, { type: 'reparent-widget' }>,
325
+ parent: GenericWidget,
326
+ target: LayoutNodeResult,
327
+ ): WidgetTreeCanvasReparentDropTarget {
328
+ return createCanvasAppendDropTarget(parent, target, patch.toParentPath, patch.insertIndex);
329
+ }
330
+
331
+ export function resolveWidgetCanvasAssetDropOperation(
332
+ asset: WidgetPlacementAsset,
333
+ root: GenericWidget,
334
+ layout: LayoutNodeResult,
335
+ point: LayoutPoint,
336
+ ): WidgetTreeCanvasAssetDropOperation | null {
337
+ const hit = hitTestLayoutTree(layout, point);
338
+ if (!hit) return null;
339
+
340
+ let candidatePath: WidgetPath | null = hit.path;
341
+ while (candidatePath !== null) {
342
+ const candidate = getWidgetAtPath(root, candidatePath);
343
+ if (candidate && canAddChildren(candidate)) {
344
+ const insertIndex = getWidgetChildren(candidate).length;
345
+ return {
346
+ asset,
347
+ parentPath: candidatePath,
348
+ insertIndex,
349
+ nextPath: insertedWidgetPathForParent(candidate, candidatePath, insertIndex),
350
+ };
351
+ }
352
+
353
+ candidatePath = widgetPathParent(candidatePath);
354
+ }
355
+
356
+ return null;
357
+ }
358
+
359
+ export function WidgetTreeCanvasPreview({
360
+ json,
361
+ layoutConstraints = DEFAULT_LAYOUT_CONSTRAINTS,
362
+ previewValues,
363
+ readOnly = false,
364
+ registry,
365
+ root,
366
+ selectedPath,
367
+ documentPath = null,
368
+ loadDocument,
369
+ onPatch,
370
+ onPlaceAssetPath,
371
+ onSelectPath,
372
+ }: WidgetTreeCanvasPreviewProps) {
373
+ const [assetDropTarget, setAssetDropTarget] = useState<WidgetTreeCanvasAssetDropTarget | null>(
374
+ null,
375
+ );
376
+ const [dragPreview, setDragPreview] = useState<WidgetTreeCanvasDragPreviewState | null>(null);
377
+ const [resizePreview, setResizePreview] = useState<WidgetTreeCanvasResizePreviewState | null>(
378
+ null,
379
+ );
380
+ const [hoveredPath, setHoveredPath] = useState<WidgetPath | null>(null);
381
+ const [focusedPath, setFocusedPath] = useState<WidgetPath | null>(null);
382
+ const layout = useMemo(
383
+ () => (root ? layoutWidget(root, layoutConstraints, { x: 0, y: 0 }, { registry }) : null),
384
+ [layoutConstraints, registry, root],
385
+ );
386
+ const selectedLayout = useMemo(
387
+ () => (layout && selectedPath ? findLayoutNodeByPath(layout, selectedPath) : null),
388
+ [layout, selectedPath],
389
+ );
390
+ const hoveredLayout = useMemo(
391
+ () => (layout && hoveredPath ? findLayoutNodeByPath(layout, hoveredPath) : null),
392
+ [hoveredPath, layout],
393
+ );
394
+ const focusedLayout = useMemo(
395
+ () => (layout && focusedPath ? findLayoutNodeByPath(layout, focusedPath) : null),
396
+ [focusedPath, layout],
397
+ );
398
+ const selectedLayoutFocused = Boolean(
399
+ focusedPath && selectedPath && widgetPathEquals(focusedPath, selectedPath),
400
+ );
401
+ const showHoveredLayout = Boolean(
402
+ hoveredLayout &&
403
+ hoveredPath &&
404
+ (!selectedPath || !widgetPathEquals(hoveredPath, selectedPath)) &&
405
+ (!focusedPath || !widgetPathEquals(hoveredPath, focusedPath)) &&
406
+ !assetDropTarget &&
407
+ !dragPreview,
408
+ );
409
+ const showFocusedLayout = Boolean(
410
+ focusedLayout &&
411
+ focusedPath &&
412
+ (!selectedPath || !widgetPathEquals(focusedPath, selectedPath)) &&
413
+ !assetDropTarget &&
414
+ !dragPreview,
415
+ );
416
+ const canDrag = Boolean(
417
+ root && selectedPath && !readOnly && canDragSelectedPath(root, selectedPath),
418
+ );
419
+ const canResize = Boolean(
420
+ root && selectedPath && !readOnly && canResizeSelectedPath(root, selectedPath),
421
+ );
422
+ const frameWidth = Math.max(1, layout?.rect.width ?? layoutConstraints.maxWidth);
423
+ const frameHeight = Math.max(1, layout?.rect.height ?? layoutConstraints.maxHeight);
424
+ const viewport = usePreviewViewport({
425
+ contentHeight: frameHeight,
426
+ contentWidth: frameWidth,
427
+ enablePrimaryPointerPan: false,
428
+ });
429
+
430
+ const resolveDragPreview = (
431
+ deltaX: number,
432
+ deltaY: number,
433
+ ): WidgetTreeCanvasDragPreviewState | null => {
434
+ if (!root || !layout || !selectedPath || !selectedLayout) return null;
435
+ if (deltaX === 0 && deltaY === 0) return null;
436
+
437
+ const maybeReparentPatch = createWidgetReparentPatch({
438
+ deltaX,
439
+ deltaY,
440
+ layout,
441
+ path: selectedPath,
442
+ root,
443
+ });
444
+ const reparentPatch =
445
+ maybeReparentPatch?.type === 'reparent-widget' ? maybeReparentPatch : null;
446
+ const dragPatch = reparentPatch
447
+ ? null
448
+ : createWidgetDragPatch({
449
+ deltaX,
450
+ deltaY,
451
+ layout,
452
+ path: selectedPath,
453
+ root,
454
+ });
455
+ const targetLayout =
456
+ reparentPatch !== null ? findLayoutNodeByPath(layout, reparentPatch.toParentPath) : null;
457
+ const targetParent =
458
+ reparentPatch !== null ? getWidgetAtPath(root, reparentPatch.toParentPath) : null;
459
+
460
+ return {
461
+ deltaX,
462
+ deltaY,
463
+ ghostRect: {
464
+ x: selectedLayout.node.rect.x + deltaX,
465
+ y: selectedLayout.node.rect.y + deltaY,
466
+ width: selectedLayout.node.rect.width,
467
+ height: selectedLayout.node.rect.height,
468
+ },
469
+ patchType: reparentPatch?.type ?? dragPatch?.type ?? 'none',
470
+ reparentTarget:
471
+ reparentPatch && targetLayout && targetParent
472
+ ? createReparentDropTarget(reparentPatch, targetParent, targetLayout.node)
473
+ : null,
474
+ selectedPath,
475
+ selectedType: selectedLayout.node.widget.type,
476
+ };
477
+ };
478
+
479
+ const commitDrag = (deltaX: number, deltaY: number) => {
480
+ setDragPreview(null);
481
+ if (!root || !layout || !selectedPath) return;
482
+
483
+ const reparentPatch = createWidgetReparentPatch({
484
+ deltaX,
485
+ deltaY,
486
+ layout,
487
+ path: selectedPath,
488
+ root,
489
+ });
490
+ if (reparentPatch && onPatch(reparentPatch)) {
491
+ return;
492
+ }
493
+
494
+ const patch = createWidgetDragPatch({
495
+ deltaX,
496
+ deltaY,
497
+ layout,
498
+ path: selectedPath,
499
+ root,
500
+ });
501
+ if (patch) {
502
+ onPatch(patch);
503
+ }
504
+ };
505
+
506
+ const resolveResizePreview = (
507
+ position: WidgetResizeHandlePosition,
508
+ deltaX: number,
509
+ deltaY: number,
510
+ ): WidgetTreeCanvasResizePreviewState | null => {
511
+ if (!selectedPath || !selectedLayout) return null;
512
+ if (deltaX === 0 && deltaY === 0) return null;
513
+
514
+ return {
515
+ deltaX,
516
+ deltaY,
517
+ position,
518
+ previewRect: resolveResizePreviewRect(selectedLayout.node.rect, position, deltaX, deltaY),
519
+ selectedPath,
520
+ };
521
+ };
522
+
523
+ const commitResize = (position: WidgetResizeHandlePosition, deltaX: number, deltaY: number) => {
524
+ setResizePreview(null);
525
+ if (!root || !layout || !selectedPath) return;
526
+
527
+ const patch = createWidgetResizePatch({
528
+ deltaX,
529
+ deltaY,
530
+ layout,
531
+ path: selectedPath,
532
+ position,
533
+ root,
534
+ });
535
+ if (patch) {
536
+ onPatch(patch);
537
+ }
538
+ };
539
+
540
+ const resolveAssetDrop = (
541
+ event: DragEvent<HTMLElement>,
542
+ ): WidgetTreeCanvasAssetDropOperation | null => {
543
+ if (readOnly || !root || !layout || !onPlaceAssetPath) return null;
544
+
545
+ const asset = readWidgetPlacementAssetDragData(event.dataTransfer);
546
+ if (!asset) return null;
547
+
548
+ const point = eventPointInLayout(event, event.currentTarget, frameWidth, frameHeight);
549
+ return resolveWidgetCanvasAssetDropOperation(asset, root, layout, point);
550
+ };
551
+
552
+ const handleAssetDragOver = (event: DragEvent<HTMLDivElement>) => {
553
+ if (dragPreview) {
554
+ setAssetDropTarget(null);
555
+ return;
556
+ }
557
+
558
+ const operation = resolveAssetDrop(event);
559
+ if (!operation || !layout || !root) {
560
+ setAssetDropTarget(null);
561
+ return;
562
+ }
563
+
564
+ const target = findLayoutNodeByPath(layout, operation.parentPath);
565
+ const parent = getWidgetAtPath(root, operation.parentPath);
566
+ if (!target || !parent) {
567
+ setAssetDropTarget(null);
568
+ return;
569
+ }
570
+
571
+ event.preventDefault();
572
+ event.dataTransfer.dropEffect = 'copy';
573
+ setAssetDropTarget(createAssetDropTarget(operation, parent, target.node));
574
+ };
575
+
576
+ const handleAssetDragLeave = (event: DragEvent<HTMLDivElement>) => {
577
+ const relatedTarget = event.relatedTarget;
578
+ if (relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) return;
579
+
580
+ setAssetDropTarget(null);
581
+ };
582
+
583
+ const handleAssetDrop = (event: DragEvent<HTMLDivElement>) => {
584
+ const operation = resolveAssetDrop(event);
585
+ setAssetDropTarget(null);
586
+ if (!operation) return;
587
+
588
+ event.preventDefault();
589
+ onPlaceAssetPath?.(operation);
590
+ };
591
+
592
+ const handlePreviewPointerMove = (event: PointerEvent<HTMLDivElement>) => {
593
+ const nextPath = widgetPathFromEventTarget(event.target);
594
+ if (!nextPath) {
595
+ setHoveredPath(null);
596
+ return;
597
+ }
598
+
599
+ setHoveredPath((current) =>
600
+ current && widgetPathEquals(current, nextPath) ? current : nextPath,
601
+ );
602
+ };
603
+
604
+ const handlePreviewPointerLeave = () => {
605
+ setHoveredPath(null);
606
+ };
607
+
608
+ const handlePreviewFocus = (event: FocusEvent<HTMLDivElement>) => {
609
+ setFocusedPath(widgetPathFromEventTarget(event.target));
610
+ };
611
+
612
+ const handlePreviewBlur = (event: FocusEvent<HTMLDivElement>) => {
613
+ const relatedTarget = event.relatedTarget;
614
+ if (relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) {
615
+ setFocusedPath(widgetPathFromEventTarget(relatedTarget));
616
+ return;
617
+ }
618
+
619
+ setFocusedPath(null);
620
+ };
621
+
622
+ return (
623
+ <WorkbenchPreviewCanvas
624
+ className="widget-tree-canvas-preview"
625
+ data-testid="widget-tree-canvas-preview"
626
+ frameHeight={frameHeight}
627
+ frameTitle="JDW Preview"
628
+ frameWidth={frameWidth}
629
+ help="Ctrl + Scroll to zoom | Middle-drag to pan"
630
+ isPanning={viewport.isPanning}
631
+ onResetView={viewport.resetView}
632
+ resetLabel={`Reset (${Math.round(viewport.effectiveZoom * 100)}%)`}
633
+ resetTitle="Reset zoom and pan"
634
+ showViewportGrid
635
+ showWindowFrame={false}
636
+ stageStyle={viewport.stageStyle}
637
+ viewportGridSize={8}
638
+ viewportProps={{
639
+ className: 'widget-tree-canvas-preview__viewport',
640
+ ref: viewport.setViewportElement,
641
+ }}
642
+ >
643
+ <div
644
+ className="widget-tree-canvas-preview__stage"
645
+ data-testid="widget-tree-canvas-stage"
646
+ style={{
647
+ width: frameWidth,
648
+ height: frameHeight,
649
+ }}
650
+ onDragLeave={handleAssetDragLeave}
651
+ onDragOver={handleAssetDragOver}
652
+ onDrop={handleAssetDrop}
653
+ onBlur={handlePreviewBlur}
654
+ onFocus={handlePreviewFocus}
655
+ onPointerLeave={handlePreviewPointerLeave}
656
+ onPointerMove={handlePreviewPointerMove}
657
+ >
658
+ <JdwPreview
659
+ className="widget-tree-canvas-preview__render"
660
+ documentPath={documentPath}
661
+ json={json}
662
+ layoutConstraints={layoutConstraints}
663
+ loadDocument={loadDocument}
664
+ registry={registry}
665
+ selectedPath={selectedPath}
666
+ values={previewValues}
667
+ onSelectPath={onSelectPath}
668
+ />
669
+ <div className="widget-tree-canvas-preview__frames" data-testid="widget-tree-canvas-frames">
670
+ {showHoveredLayout && hoveredPath && hoveredLayout ? (
671
+ <WorkbenchCanvasItemFrame
672
+ className="widget-tree-canvas-preview__hover-frame"
673
+ data-testid="widget-tree-canvas-hover-frame"
674
+ data-widget-path={widgetPathKey(hoveredPath)}
675
+ data-widget-type={hoveredLayout.node.widget.type}
676
+ hovered
677
+ transient
678
+ width={hoveredLayout.node.rect.width}
679
+ height={hoveredLayout.node.rect.height}
680
+ x={hoveredLayout.node.rect.x}
681
+ y={hoveredLayout.node.rect.y}
682
+ />
683
+ ) : null}
684
+ {showFocusedLayout && focusedPath && focusedLayout ? (
685
+ <WorkbenchCanvasItemFrame
686
+ className="widget-tree-canvas-preview__focus-frame"
687
+ data-testid="widget-tree-canvas-focus-frame"
688
+ data-widget-path={widgetPathKey(focusedPath)}
689
+ data-widget-type={focusedLayout.node.widget.type}
690
+ focused
691
+ transient
692
+ width={focusedLayout.node.rect.width}
693
+ height={focusedLayout.node.rect.height}
694
+ x={focusedLayout.node.rect.x}
695
+ y={focusedLayout.node.rect.y}
696
+ zIndex={95}
697
+ />
698
+ ) : null}
699
+ {assetDropTarget && !dragPreview ? (
700
+ <>
701
+ <WorkbenchCanvasDropIndicator
702
+ className="widget-tree-canvas-preview__asset-drop-target"
703
+ data-testid="widget-tree-canvas-asset-drop-indicator"
704
+ data-insert-index={assetDropTarget.insertIndex}
705
+ data-next-widget-path={widgetPathKey(assetDropTarget.nextPath)}
706
+ data-parent-type={assetDropTarget.parentType}
707
+ data-widget-path={widgetPathKey(assetDropTarget.path)}
708
+ width={assetDropTarget.rect.width}
709
+ height={assetDropTarget.rect.height}
710
+ x={assetDropTarget.rect.x}
711
+ y={assetDropTarget.rect.y}
712
+ zIndex={30}
713
+ />
714
+ <WorkbenchCanvasDropIndicator
715
+ className="widget-tree-canvas-preview__asset-drop-marker"
716
+ data-testid="widget-tree-canvas-asset-drop-marker"
717
+ data-drop-target-type={assetDropTarget.type}
718
+ data-insert-index={assetDropTarget.insertIndex}
719
+ data-next-widget-path={widgetPathKey(assetDropTarget.nextPath)}
720
+ data-parent-type={assetDropTarget.parentType}
721
+ data-widget-path={widgetPathKey(assetDropTarget.path)}
722
+ width={assetDropTarget.markerRect.width}
723
+ height={assetDropTarget.markerRect.height}
724
+ x={assetDropTarget.markerRect.x}
725
+ y={assetDropTarget.markerRect.y}
726
+ zIndex={35}
727
+ />
728
+ </>
729
+ ) : null}
730
+ {dragPreview ? (
731
+ <>
732
+ {dragPreview.reparentTarget ? (
733
+ <>
734
+ <WorkbenchCanvasDropIndicator
735
+ className="widget-tree-canvas-preview__reparent-drop-target"
736
+ data-testid="widget-tree-canvas-reparent-drop-indicator"
737
+ data-drop-target-type={dragPreview.reparentTarget.type}
738
+ data-insert-index={dragPreview.reparentTarget.insertIndex}
739
+ data-parent-type={dragPreview.reparentTarget.parentType}
740
+ data-widget-path={widgetPathKey(dragPreview.reparentTarget.path)}
741
+ width={dragPreview.reparentTarget.rect.width}
742
+ height={dragPreview.reparentTarget.rect.height}
743
+ x={dragPreview.reparentTarget.rect.x}
744
+ y={dragPreview.reparentTarget.rect.y}
745
+ zIndex={40}
746
+ />
747
+ <WorkbenchCanvasDropIndicator
748
+ className="widget-tree-canvas-preview__reparent-drop-marker"
749
+ data-testid="widget-tree-canvas-reparent-drop-marker"
750
+ data-drop-target-type={dragPreview.reparentTarget.type}
751
+ data-insert-index={dragPreview.reparentTarget.insertIndex}
752
+ data-parent-type={dragPreview.reparentTarget.parentType}
753
+ data-widget-path={widgetPathKey(dragPreview.reparentTarget.path)}
754
+ width={dragPreview.reparentTarget.markerRect.width}
755
+ height={dragPreview.reparentTarget.markerRect.height}
756
+ x={dragPreview.reparentTarget.markerRect.x}
757
+ y={dragPreview.reparentTarget.markerRect.y}
758
+ zIndex={45}
759
+ />
760
+ </>
761
+ ) : null}
762
+ <WorkbenchCanvasDragPreviewFrame
763
+ className="widget-tree-canvas-preview__drag-ghost"
764
+ data-testid="widget-tree-canvas-drag-ghost"
765
+ data-delta-x={dragPreview.deltaX}
766
+ data-delta-y={dragPreview.deltaY}
767
+ data-patch-type={dragPreview.patchType}
768
+ data-widget-path={widgetPathKey(dragPreview.selectedPath)}
769
+ data-widget-type={dragPreview.selectedType}
770
+ width={dragPreview.ghostRect.width}
771
+ height={dragPreview.ghostRect.height}
772
+ x={dragPreview.ghostRect.x}
773
+ y={dragPreview.ghostRect.y}
774
+ zIndex={80}
775
+ />
776
+ <WorkbenchCanvasGuideLayer
777
+ className="widget-tree-canvas-preview__snap-guides"
778
+ data-testid="widget-tree-canvas-snap-guides"
779
+ >
780
+ <WorkbenchCanvasGuideLine
781
+ axis="x"
782
+ data-testid="widget-tree-canvas-snap-guide-x"
783
+ data-widget-path={widgetPathKey(dragPreview.selectedPath)}
784
+ end={frameHeight}
785
+ position={dragPreview.ghostRect.x}
786
+ source="grid"
787
+ start={0}
788
+ />
789
+ <WorkbenchCanvasGuideLine
790
+ axis="y"
791
+ data-testid="widget-tree-canvas-snap-guide-y"
792
+ data-widget-path={widgetPathKey(dragPreview.selectedPath)}
793
+ end={frameWidth}
794
+ position={dragPreview.ghostRect.y}
795
+ source="grid"
796
+ start={0}
797
+ />
798
+ </WorkbenchCanvasGuideLayer>
799
+ </>
800
+ ) : null}
801
+ {selectedLayout && selectedPath ? (
802
+ <WorkbenchCanvasItemFrame
803
+ className="widget-tree-canvas-preview__selection-frame"
804
+ data-testid="widget-tree-canvas-selection-frame"
805
+ data-widget-path={widgetPathKey(selectedPath)}
806
+ data-widget-type={selectedLayout.node.widget.type}
807
+ focused={selectedLayoutFocused}
808
+ interactive={canDrag || canResize}
809
+ selected
810
+ touchAction="none"
811
+ width={
812
+ resizePreview?.previewRect.width ??
813
+ dragPreview?.ghostRect.width ??
814
+ selectedLayout.node.rect.width
815
+ }
816
+ height={
817
+ resizePreview?.previewRect.height ??
818
+ dragPreview?.ghostRect.height ??
819
+ selectedLayout.node.rect.height
820
+ }
821
+ x={
822
+ resizePreview?.previewRect.x ??
823
+ dragPreview?.ghostRect.x ??
824
+ selectedLayout.node.rect.x
825
+ }
826
+ y={
827
+ resizePreview?.previewRect.y ??
828
+ dragPreview?.ghostRect.y ??
829
+ selectedLayout.node.rect.y
830
+ }
831
+ >
832
+ {canDrag ? (
833
+ <WorkbenchCanvasFrameHandle
834
+ aria-label="Move selected widget"
835
+ data-testid="widget-tree-canvas-drag-handle"
836
+ label={selectedLayout.node.widget.type}
837
+ onDragCancel={() => setDragPreview(null)}
838
+ onDragEnd={commitDrag}
839
+ onDragMove={(deltaX, deltaY) =>
840
+ setDragPreview(resolveDragPreview(deltaX, deltaY))
841
+ }
842
+ onDragStart={() => {
843
+ setAssetDropTarget(null);
844
+ setResizePreview(null);
845
+ setDragPreview(null);
846
+ }}
847
+ />
848
+ ) : null}
849
+ {canResize
850
+ ? RESIZE_HANDLE_POSITIONS.map((position) => (
851
+ <WorkbenchCanvasResizeHandle
852
+ key={position}
853
+ data-testid={`widget-tree-canvas-resize-handle-${position}`}
854
+ label={`Resize selected widget ${position}`}
855
+ position={position}
856
+ onResizeEnd={(deltaX, deltaY) => commitResize(position, deltaX, deltaY)}
857
+ onResizeMove={(deltaX, deltaY) =>
858
+ setResizePreview(resolveResizePreview(position, deltaX, deltaY))
859
+ }
860
+ onResizeCancel={() => setResizePreview(null)}
861
+ onResizeStart={() => {
862
+ setAssetDropTarget(null);
863
+ setDragPreview(null);
864
+ setResizePreview(null);
865
+ }}
866
+ />
867
+ ))
868
+ : null}
869
+ </WorkbenchCanvasItemFrame>
870
+ ) : null}
871
+ </div>
872
+ </div>
873
+ </WorkbenchPreviewCanvas>
874
+ );
875
+ }