@dxos/react-ui-canvas 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/index.mjs +9044 -1135
  3. package/dist/lib/index.mjs.map +1 -1
  4. package/dist/types/src/components/About/About.d.ts +13 -0
  5. package/dist/types/src/components/About/About.d.ts.map +1 -0
  6. package/dist/types/src/components/About/About.stories.d.ts +7 -0
  7. package/dist/types/src/components/About/About.stories.d.ts.map +1 -0
  8. package/dist/types/src/components/About/index.d.ts +2 -0
  9. package/dist/types/src/components/About/index.d.ts.map +1 -0
  10. package/dist/types/src/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
  11. package/dist/types/src/components/ControlFrame/ControlFrame.d.ts +8 -2
  12. package/dist/types/src/components/ControlFrame/ControlFrame.d.ts.map +1 -1
  13. package/dist/types/src/components/Layers/Layers.d.ts +30 -0
  14. package/dist/types/src/components/Layers/Layers.d.ts.map +1 -0
  15. package/dist/types/src/components/Layers/Layers.stories.d.ts +11 -0
  16. package/dist/types/src/components/Layers/Layers.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Layers/index.d.ts +2 -0
  18. package/dist/types/src/components/Layers/index.d.ts.map +1 -0
  19. package/dist/types/src/components/Palette/Palette.d.ts +20 -4
  20. package/dist/types/src/components/Palette/Palette.d.ts.map +1 -1
  21. package/dist/types/src/components/Palette/Palette.stories.d.ts +22 -0
  22. package/dist/types/src/components/Palette/Palette.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/PartEditor/PartEditor.d.ts.map +1 -1
  24. package/dist/types/src/components/Properties/AlignField.d.ts +23 -0
  25. package/dist/types/src/components/Properties/AlignField.d.ts.map +1 -0
  26. package/dist/types/src/components/Properties/ClassField.d.ts +23 -0
  27. package/dist/types/src/components/Properties/ClassField.d.ts.map +1 -0
  28. package/dist/types/src/components/Properties/FontField.d.ts +7 -0
  29. package/dist/types/src/components/Properties/FontField.d.ts.map +1 -0
  30. package/dist/types/src/components/Properties/LayerField.d.ts +17 -0
  31. package/dist/types/src/components/Properties/LayerField.d.ts.map +1 -0
  32. package/dist/types/src/components/Properties/Properties.d.ts +27 -3
  33. package/dist/types/src/components/Properties/Properties.d.ts.map +1 -1
  34. package/dist/types/src/components/Properties/Properties.stories.d.ts +20 -0
  35. package/dist/types/src/components/Properties/Properties.stories.d.ts.map +1 -0
  36. package/dist/types/src/components/Properties/SceneField.d.ts +8 -0
  37. package/dist/types/src/components/Properties/SceneField.d.ts.map +1 -0
  38. package/dist/types/src/components/Properties/StyleGrid.d.ts +15 -5
  39. package/dist/types/src/components/Properties/StyleGrid.d.ts.map +1 -1
  40. package/dist/types/src/components/Properties/StyleGrid.stories.d.ts +7 -0
  41. package/dist/types/src/components/Properties/StyleGrid.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/SceneLayer/SceneLayer.d.ts +31 -7
  43. package/dist/types/src/components/SceneLayer/SceneLayer.d.ts.map +1 -1
  44. package/dist/types/src/components/SceneView/Dock.d.ts +22 -0
  45. package/dist/types/src/components/SceneView/Dock.d.ts.map +1 -0
  46. package/dist/types/src/components/SceneView/Dock.stories.d.ts +15 -0
  47. package/dist/types/src/components/SceneView/Dock.stories.d.ts.map +1 -0
  48. package/dist/types/src/components/SceneView/SceneView.d.ts +33 -7
  49. package/dist/types/src/components/SceneView/SceneView.d.ts.map +1 -1
  50. package/dist/types/src/components/SceneView/SceneView.stories.d.ts +16 -6
  51. package/dist/types/src/components/SceneView/SceneView.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/SceneView/SceneViewContext.d.ts +36 -4
  53. package/dist/types/src/components/SceneView/SceneViewContext.d.ts.map +1 -1
  54. package/dist/types/src/components/SceneView/Scored.stories.d.ts +4 -4
  55. package/dist/types/src/components/SceneView/Scored.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/SceneView/usePointerMachine.d.ts +3 -1
  57. package/dist/types/src/components/SceneView/usePointerMachine.d.ts.map +1 -1
  58. package/dist/types/src/components/SceneView/useSceneCamera.d.ts +2 -2
  59. package/dist/types/src/components/SceneView/useSceneCamera.d.ts.map +1 -1
  60. package/dist/types/src/components/SceneView/useSceneClipboard.d.ts.map +1 -1
  61. package/dist/types/src/components/SceneView/useSceneKeys.d.ts.map +1 -1
  62. package/dist/types/src/components/SceneView/useSceneNavigation.d.ts +7 -4
  63. package/dist/types/src/components/SceneView/useSceneNavigation.d.ts.map +1 -1
  64. package/dist/types/src/components/Toolbar/Toolbar.d.ts +2 -0
  65. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  66. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +2 -0
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/hooks/useWheel.d.ts +5 -0
  70. package/dist/types/src/hooks/useWheel.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +4 -1
  72. package/dist/types/src/index.d.ts.map +1 -1
  73. package/dist/types/src/model/atoms.d.ts +21 -3
  74. package/dist/types/src/model/atoms.d.ts.map +1 -1
  75. package/dist/types/src/model/projection.d.ts.map +1 -1
  76. package/dist/types/src/model/projections/constrained.d.ts +2 -1
  77. package/dist/types/src/model/projections/constrained.d.ts.map +1 -1
  78. package/dist/types/src/model/projections/dynamic.d.ts.map +1 -1
  79. package/dist/types/src/model/projections/lattice.d.ts.map +1 -1
  80. package/dist/types/src/model/registry.d.ts +21 -3
  81. package/dist/types/src/model/registry.d.ts.map +1 -1
  82. package/dist/types/src/model/store.d.ts +10 -2
  83. package/dist/types/src/model/store.d.ts.map +1 -1
  84. package/dist/types/src/model/types.d.ts +699 -338
  85. package/dist/types/src/model/types.d.ts.map +1 -1
  86. package/dist/types/src/playwright/SceneManager.d.ts +1 -1
  87. package/dist/types/src/playwright/SceneManager.d.ts.map +1 -1
  88. package/dist/types/src/utils/builder.d.ts +22 -41
  89. package/dist/types/src/utils/builder.d.ts.map +1 -1
  90. package/dist/types/src/utils/camera.d.ts +19 -10
  91. package/dist/types/src/utils/camera.d.ts.map +1 -1
  92. package/dist/types/src/utils/clipboard.d.ts +4 -2
  93. package/dist/types/src/utils/clipboard.d.ts.map +1 -1
  94. package/dist/types/src/utils/diagram.d.ts.map +1 -1
  95. package/dist/types/src/utils/group.d.ts +14 -0
  96. package/dist/types/src/utils/group.d.ts.map +1 -0
  97. package/dist/types/src/utils/group.test.d.ts +2 -0
  98. package/dist/types/src/utils/group.test.d.ts.map +1 -0
  99. package/dist/types/src/utils/gutter-route.d.ts +7 -0
  100. package/dist/types/src/utils/gutter-route.d.ts.map +1 -1
  101. package/dist/types/src/utils/hit.d.ts +3 -20
  102. package/dist/types/src/utils/hit.d.ts.map +1 -1
  103. package/dist/types/src/utils/index.d.ts +3 -0
  104. package/dist/types/src/utils/index.d.ts.map +1 -1
  105. package/dist/types/src/utils/lattice.d.ts +5 -0
  106. package/dist/types/src/utils/lattice.d.ts.map +1 -1
  107. package/dist/types/src/utils/layers.d.ts +35 -0
  108. package/dist/types/src/utils/layers.d.ts.map +1 -0
  109. package/dist/types/src/utils/layers.test.d.ts +2 -0
  110. package/dist/types/src/utils/layers.test.d.ts.map +1 -0
  111. package/dist/types/src/utils/parts.d.ts +25 -9
  112. package/dist/types/src/utils/parts.d.ts.map +1 -1
  113. package/dist/types/src/utils/ports.d.ts +2 -0
  114. package/dist/types/src/utils/ports.d.ts.map +1 -1
  115. package/dist/types/src/utils/properties.d.ts +4 -3
  116. package/dist/types/src/utils/properties.d.ts.map +1 -1
  117. package/dist/types/src/utils/route.d.ts +5 -0
  118. package/dist/types/src/utils/route.d.ts.map +1 -1
  119. package/dist/types/src/utils/scenes.d.ts +14 -0
  120. package/dist/types/src/utils/scenes.d.ts.map +1 -0
  121. package/dist/types/src/utils/scenes.test.d.ts +2 -0
  122. package/dist/types/src/utils/scenes.test.d.ts.map +1 -0
  123. package/dist/types/src/utils/shapes.d.ts +23 -9
  124. package/dist/types/src/utils/shapes.d.ts.map +1 -1
  125. package/dist/types/src/utils/style.d.ts +29 -6
  126. package/dist/types/src/utils/style.d.ts.map +1 -1
  127. package/dist/types/src/utils/style.test.d.ts +2 -0
  128. package/dist/types/src/utils/style.test.d.ts.map +1 -0
  129. package/dist/types/src/utils/testing.d.ts +2 -2
  130. package/dist/types/src/utils/testing.d.ts.map +1 -1
  131. package/dist/types/src/utils/undo.d.ts +15 -4
  132. package/dist/types/src/utils/undo.d.ts.map +1 -1
  133. package/dist/types/tsconfig.tsbuildinfo +1 -1
  134. package/package.json +20 -26
  135. package/src/components/About/About.stories.tsx +32 -0
  136. package/src/components/About/About.tsx +38 -0
  137. package/src/components/About/index.ts +5 -0
  138. package/src/components/Breadcrumbs/Breadcrumbs.tsx +17 -3
  139. package/src/components/ControlFrame/ControlFrame.tsx +138 -82
  140. package/src/components/Grid/Grid.stories.tsx +1 -1
  141. package/src/components/Layers/Layers.stories.tsx +131 -0
  142. package/src/components/Layers/Layers.tsx +216 -0
  143. package/src/components/Layers/index.ts +5 -0
  144. package/src/components/Palette/Palette.stories.tsx +135 -0
  145. package/src/components/Palette/Palette.tsx +230 -39
  146. package/src/components/PartEditor/PartEditor.tsx +4 -3
  147. package/src/components/Properties/AlignField.tsx +117 -0
  148. package/src/components/Properties/ClassField.tsx +44 -0
  149. package/src/components/Properties/FontField.tsx +73 -0
  150. package/src/components/Properties/LayerField.tsx +61 -0
  151. package/src/components/Properties/Properties.stories.tsx +113 -0
  152. package/src/components/Properties/Properties.tsx +240 -60
  153. package/src/components/Properties/SceneField.tsx +22 -0
  154. package/src/components/Properties/StyleGrid.stories.tsx +50 -0
  155. package/src/components/Properties/StyleGrid.tsx +54 -26
  156. package/src/components/SceneLayer/SceneLayer.tsx +497 -188
  157. package/src/components/SceneView/Constrained.stories.tsx +1 -1
  158. package/src/components/SceneView/Dock.stories.tsx +95 -0
  159. package/src/components/SceneView/Dock.tsx +146 -0
  160. package/src/components/SceneView/Dynamic.stories.tsx +1 -1
  161. package/src/components/SceneView/SceneView.stories.tsx +170 -18
  162. package/src/components/SceneView/SceneView.tsx +565 -151
  163. package/src/components/SceneView/SceneViewContext.ts +23 -2
  164. package/src/components/SceneView/Scored.stories.tsx +11 -11
  165. package/src/components/SceneView/usePointerMachine.ts +80 -20
  166. package/src/components/SceneView/useSceneCamera.ts +15 -8
  167. package/src/components/SceneView/useSceneClipboard.ts +4 -0
  168. package/src/components/SceneView/useSceneKeys.ts +6 -4
  169. package/src/components/SceneView/useSceneNavigation.ts +44 -33
  170. package/src/components/Toolbar/Toolbar.stories.tsx +14 -15
  171. package/src/components/Toolbar/Toolbar.tsx +15 -4
  172. package/src/components/index.ts +2 -0
  173. package/src/hooks/useWheel.ts +35 -0
  174. package/src/index.ts +4 -3
  175. package/src/model/atoms.ts +27 -3
  176. package/src/model/projection.ts +5 -0
  177. package/src/model/projections/constrained.test.ts +18 -10
  178. package/src/model/projections/constrained.ts +19 -8
  179. package/src/model/projections/dynamic.ts +5 -2
  180. package/src/model/projections/lattice.ts +15 -3
  181. package/src/model/registry.test.ts +14 -9
  182. package/src/model/registry.ts +39 -34
  183. package/src/model/store.ts +12 -2
  184. package/src/model/types.ts +124 -36
  185. package/src/playwright/SceneManager.ts +2 -2
  186. package/src/playwright/create.spec.ts +5 -3
  187. package/src/playwright/demo.spec.ts +2 -2
  188. package/src/playwright/lattice.spec.ts +4 -5
  189. package/src/playwright/scene.spec.ts +27 -25
  190. package/src/utils/builder.test.ts +46 -9
  191. package/src/utils/builder.ts +42 -18
  192. package/src/utils/camera.test.ts +22 -51
  193. package/src/utils/camera.ts +39 -32
  194. package/src/utils/clipboard.test.ts +23 -2
  195. package/src/utils/clipboard.ts +8 -4
  196. package/src/utils/diagram.test.ts +3 -1
  197. package/src/utils/diagram.ts +3 -25
  198. package/src/utils/group.test.ts +69 -0
  199. package/src/utils/group.ts +94 -0
  200. package/src/utils/gutter-route.test.ts +49 -0
  201. package/src/utils/gutter-route.ts +142 -16
  202. package/src/utils/hit.test.ts +4 -38
  203. package/src/utils/hit.ts +4 -26
  204. package/src/utils/index.ts +3 -0
  205. package/src/utils/lattice.ts +7 -1
  206. package/src/utils/layers.test.ts +139 -0
  207. package/src/utils/layers.ts +175 -0
  208. package/src/utils/parts.test.ts +66 -32
  209. package/src/utils/parts.ts +63 -41
  210. package/src/utils/ports.ts +7 -4
  211. package/src/utils/properties.test.ts +23 -6
  212. package/src/utils/properties.ts +66 -16
  213. package/src/utils/route.test.ts +51 -2
  214. package/src/utils/route.ts +133 -23
  215. package/src/utils/scenes.test.ts +38 -0
  216. package/src/utils/scenes.ts +54 -0
  217. package/src/utils/shapes.test.ts +33 -8
  218. package/src/utils/shapes.ts +51 -35
  219. package/src/utils/style.test.ts +54 -0
  220. package/src/utils/style.ts +153 -40
  221. package/src/utils/testing.test.ts +3 -3
  222. package/src/utils/testing.ts +21 -30
  223. package/src/utils/undo.test.ts +25 -2
  224. package/src/utils/undo.ts +50 -13
  225. package/dist/lib/chunk-Grid.mjs +0 -99
  226. package/dist/lib/chunk-Grid.mjs.map +0 -1
  227. package/dist/lib/scene.mjs +0 -6835
  228. package/dist/lib/scene.mjs.map +0 -1
  229. package/dist/lib/types.mjs +0 -16
  230. package/dist/lib/types.mjs.map +0 -1
  231. package/dist/types/src/archive/components/Canvas/Canvas.d.ts +0 -15
  232. package/dist/types/src/archive/components/Canvas/Canvas.d.ts.map +0 -1
  233. package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts +0 -18
  234. package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts.map +0 -1
  235. package/dist/types/src/archive/components/Canvas/index.d.ts +0 -2
  236. package/dist/types/src/archive/components/Canvas/index.d.ts.map +0 -1
  237. package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts +0 -21
  238. package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts.map +0 -1
  239. package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts +0 -21
  240. package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts.map +0 -1
  241. package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts +0 -15
  242. package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts.map +0 -1
  243. package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts +0 -19
  244. package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts.map +0 -1
  245. package/dist/types/src/archive/components/CellGrid/headers/index.d.ts +0 -3
  246. package/dist/types/src/archive/components/CellGrid/headers/index.d.ts.map +0 -1
  247. package/dist/types/src/archive/components/CellGrid/index.d.ts +0 -6
  248. package/dist/types/src/archive/components/CellGrid/index.d.ts.map +0 -1
  249. package/dist/types/src/archive/components/CellGrid/input/index.d.ts +0 -3
  250. package/dist/types/src/archive/components/CellGrid/input/index.d.ts.map +0 -1
  251. package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts +0 -33
  252. package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts.map +0 -1
  253. package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts +0 -14
  254. package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts.map +0 -1
  255. package/dist/types/src/archive/components/CellGrid/render/index.d.ts +0 -3
  256. package/dist/types/src/archive/components/CellGrid/render/index.d.ts.map +0 -1
  257. package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts +0 -21
  258. package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts.map +0 -1
  259. package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts +0 -36
  260. package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts.map +0 -1
  261. package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts +0 -23
  262. package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts.map +0 -1
  263. package/dist/types/src/archive/components/CellGrid/state/index.d.ts +0 -4
  264. package/dist/types/src/archive/components/CellGrid/state/index.d.ts.map +0 -1
  265. package/dist/types/src/archive/components/CellGrid/state/types.d.ts +0 -39
  266. package/dist/types/src/archive/components/CellGrid/state/types.d.ts.map +0 -1
  267. package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts +0 -52
  268. package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts.map +0 -1
  269. package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts +0 -2
  270. package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts.map +0 -1
  271. package/dist/types/src/archive/components/FPS.d.ts +0 -9
  272. package/dist/types/src/archive/components/FPS.d.ts.map +0 -1
  273. package/dist/types/src/archive/components/Grid/Grid.d.ts +0 -6
  274. package/dist/types/src/archive/components/Grid/Grid.d.ts.map +0 -1
  275. package/dist/types/src/archive/components/Grid/index.d.ts +0 -2
  276. package/dist/types/src/archive/components/Grid/index.d.ts.map +0 -1
  277. package/dist/types/src/archive/components/index.d.ts +0 -5
  278. package/dist/types/src/archive/components/index.d.ts.map +0 -1
  279. package/dist/types/src/archive/hooks/index.d.ts +0 -5
  280. package/dist/types/src/archive/hooks/index.d.ts.map +0 -1
  281. package/dist/types/src/archive/hooks/projection.d.ts +0 -60
  282. package/dist/types/src/archive/hooks/projection.d.ts.map +0 -1
  283. package/dist/types/src/archive/hooks/useCanvasContext.d.ts +0 -13
  284. package/dist/types/src/archive/hooks/useCanvasContext.d.ts.map +0 -1
  285. package/dist/types/src/archive/hooks/useDrag.d.ts +0 -6
  286. package/dist/types/src/archive/hooks/useDrag.d.ts.map +0 -1
  287. package/dist/types/src/archive/hooks/useWheel.d.ts +0 -8
  288. package/dist/types/src/archive/hooks/useWheel.d.ts.map +0 -1
  289. package/dist/types/src/archive/index.d.ts +0 -5
  290. package/dist/types/src/archive/index.d.ts.map +0 -1
  291. package/dist/types/src/archive/types.d.ts +0 -19
  292. package/dist/types/src/archive/types.d.ts.map +0 -1
  293. package/dist/types/src/archive/util/index.d.ts +0 -4
  294. package/dist/types/src/archive/util/index.d.ts.map +0 -1
  295. package/dist/types/src/archive/util/svg-path.d.ts +0 -4
  296. package/dist/types/src/archive/util/svg-path.d.ts.map +0 -1
  297. package/dist/types/src/archive/util/svg.d.ts +0 -32
  298. package/dist/types/src/archive/util/svg.d.ts.map +0 -1
  299. package/dist/types/src/archive/util/svg.stories.d.ts +0 -15
  300. package/dist/types/src/archive/util/svg.stories.d.ts.map +0 -1
  301. package/dist/types/src/archive/util/util.d.ts +0 -17
  302. package/dist/types/src/archive/util/util.d.ts.map +0 -1
  303. package/dist/types/src/scene.d.ts +0 -5
  304. package/dist/types/src/scene.d.ts.map +0 -1
  305. package/src/archive/components/Canvas/Canvas.stories.tsx +0 -111
  306. package/src/archive/components/Canvas/Canvas.tsx +0 -87
  307. package/src/archive/components/Canvas/index.ts +0 -5
  308. package/src/archive/components/CellGrid/CellGrid.stories.tsx +0 -239
  309. package/src/archive/components/CellGrid/CellGrid.tsx +0 -270
  310. package/src/archive/components/CellGrid/headers/Ruler.tsx +0 -71
  311. package/src/archive/components/CellGrid/headers/TrackHeader.tsx +0 -58
  312. package/src/archive/components/CellGrid/headers/index.ts +0 -6
  313. package/src/archive/components/CellGrid/index.ts +0 -9
  314. package/src/archive/components/CellGrid/input/index.ts +0 -6
  315. package/src/archive/components/CellGrid/input/pointer.ts +0 -236
  316. package/src/archive/components/CellGrid/input/wheel.ts +0 -68
  317. package/src/archive/components/CellGrid/render/index.ts +0 -6
  318. package/src/archive/components/CellGrid/render/overlay-layer.ts +0 -66
  319. package/src/archive/components/CellGrid/render/static-layer.ts +0 -112
  320. package/src/archive/components/CellGrid/state/atoms.ts +0 -43
  321. package/src/archive/components/CellGrid/state/index.ts +0 -7
  322. package/src/archive/components/CellGrid/state/types.ts +0 -40
  323. package/src/archive/components/CellGrid/state/viewport.test.ts +0 -50
  324. package/src/archive/components/CellGrid/state/viewport.ts +0 -94
  325. package/src/archive/components/FPS.tsx +0 -98
  326. package/src/archive/components/Grid/Grid.tsx +0 -17
  327. package/src/archive/components/Grid/index.ts +0 -5
  328. package/src/archive/components/index.ts +0 -8
  329. package/src/archive/hooks/index.ts +0 -8
  330. package/src/archive/hooks/projection.tsx +0 -156
  331. package/src/archive/hooks/useCanvasContext.ts +0 -32
  332. package/src/archive/hooks/useDrag.tsx +0 -96
  333. package/src/archive/hooks/useWheel.tsx +0 -80
  334. package/src/archive/index.ts +0 -8
  335. package/src/archive/types.ts +0 -14
  336. package/src/archive/util/index.ts +0 -7
  337. package/src/archive/util/svg-path.ts +0 -13
  338. package/src/archive/util/svg.stories.tsx +0 -46
  339. package/src/archive/util/svg.tsx +0 -128
  340. package/src/archive/util/util.ts +0 -50
  341. package/src/scene.ts +0 -9
@@ -87,7 +87,7 @@ const DefaultStory = () => {
87
87
  };
88
88
 
89
89
  const meta: Meta = {
90
- title: 'ui/react-ui-canvas/scene/Constrained',
90
+ title: 'ui/react-ui-canvas/Constrained',
91
91
  render: DefaultStory,
92
92
  decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
93
93
  };
@@ -0,0 +1,95 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useLayoutEffect, useMemo } from 'react';
7
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
8
+
9
+ import { translations as formTranslations } from '@dxos/react-ui-form/translations';
10
+ import { withLayout, withRegistry, withTheme } from '@dxos/react-ui/testing';
11
+ import { translations as uiTranslations } from '@dxos/react-ui/translations';
12
+
13
+ import { useRegistry } from '../../hooks/index.ts';
14
+ import { createSceneViewAtoms } from '../../model/atoms.ts';
15
+ import { createMemoryStore } from '../../model/store.ts';
16
+ import { type Layer } from '../../model/types.ts';
17
+ import { SceneBuilder } from '../../utils/builder.ts';
18
+ import { between } from '../../utils/order.ts';
19
+ import { SceneView } from './SceneView.tsx';
20
+
21
+ const box = (x: number, y = 0) => ({ x, y, width: 256, height: 128 });
22
+
23
+ /** A scene with two layers and a shape on each. */
24
+ const createTree = () => {
25
+ const background: Layer = { id: 'background', name: 'Background', z: between() };
26
+ const diagram: Layer = { id: 'diagram', name: 'Diagram', z: between(background.z) };
27
+ const tree = SceneBuilder.scene('root', [
28
+ SceneBuilder.rect('a', box(0)).properties({ label: 'Rect', layer: 'diagram' }),
29
+ SceneBuilder.rect('b', box(400)).properties({ label: 'Other', layer: 'background' }),
30
+ ]).build();
31
+ const [scene, ...rest] = tree.scenes;
32
+ return { root: tree.root, scenes: [{ ...scene, layers: { background, diagram } }, ...rest] };
33
+ };
34
+
35
+ type StoryArgs = {
36
+ /** The element selected, whose properties fill the dock's first section. */
37
+ select?: string;
38
+ };
39
+
40
+ /** The docked panels alone: the properties, layers and about sections, without the canvas they sit beside. */
41
+ const DefaultStory = ({ select }: StoryArgs) => {
42
+ const registry = useRegistry();
43
+ const { store, root } = useMemo(() => {
44
+ const tree = createTree();
45
+ return { store: createMemoryStore(tree.scenes), root: tree.root };
46
+ }, []);
47
+ const atoms = useMemo(() => createSceneViewAtoms(root), [root]);
48
+ useLayoutEffect(() => {
49
+ registry.set(atoms.selection, new Set(select ? [select] : []));
50
+ }, [registry, atoms, select]);
51
+ return (
52
+ <SceneView.Root store={store} root={root} atoms={atoms}>
53
+ <SceneView.Properties />
54
+ <SceneView.Layers />
55
+ <SceneView.About />
56
+ </SceneView.Root>
57
+ );
58
+ };
59
+
60
+ const meta: Meta<StoryArgs> = {
61
+ title: 'ui/react-ui-canvas/Dock',
62
+ render: DefaultStory,
63
+ decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
64
+ args: { select: 'a' },
65
+ parameters: { translations: [...uiTranslations, ...formTranslations] },
66
+ };
67
+
68
+ export default meta;
69
+
70
+ type Story = StoryObj<StoryArgs>;
71
+
72
+ /** A shape selected: its properties above the scene's layers. */
73
+ export const Default: Story = {};
74
+
75
+ /** Nothing selected: the properties section says so rather than going away. */
76
+ export const Empty: Story = { args: { select: undefined } };
77
+
78
+ /** Each section's header collapses it. */
79
+ export const Test: Story = {
80
+ play: async ({ canvasElement }) => {
81
+ const canvas = within(canvasElement);
82
+ // 1. Both sections are open, the properties showing the selected shape's layer.
83
+ const dock = await canvas.findByTestId('scene-view-dock');
84
+ await waitFor(() => expect(within(dock).getByTestId('dock-section-layers')).toHaveAttribute('data-state', 'open'));
85
+ await expect(await within(dock).findByRole('combobox', { name: 'Layer' })).toHaveTextContent('Diagram');
86
+ // 2. The properties header collapses its section.
87
+ await userEvent.click(within(dock).getByRole('button', { name: 'Properties' }));
88
+ await waitFor(() =>
89
+ expect(within(dock).getByTestId('dock-section-properties')).toHaveAttribute('data-state', 'closed'),
90
+ );
91
+ // 3. The about section follows the layers, its figures those of the scene.
92
+ await expect(within(dock).getByTestId('about-nodes')).toHaveTextContent('2');
93
+ await expect(within(dock).getByTestId('about-layers')).toHaveTextContent('2');
94
+ },
95
+ };
@@ -0,0 +1,146 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // The dock: a column beside the canvas that panels move into when the view's panels are docked, one accordion
7
+ // section per panel. A panel registers its section and portals itself into the section's slot.
8
+ //
9
+
10
+ import React, {
11
+ type PropsWithChildren,
12
+ type ReactNode,
13
+ type SyntheticEvent,
14
+ createContext,
15
+ useCallback,
16
+ useContext,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useState,
20
+ } from 'react';
21
+ import { createPortal } from 'react-dom';
22
+
23
+ import * as Accordion from '@dxos/react-ui/Accordion';
24
+ import * as ScrollArea from '@dxos/react-ui/ScrollArea';
25
+
26
+ /** A panel's section in the dock. */
27
+ export type DockSection = {
28
+ id: string;
29
+ title: string;
30
+ icon: string;
31
+ /** Sections stack by it, lowest first. */
32
+ order: number;
33
+ };
34
+
35
+ type DockContextValue = {
36
+ sections: readonly DockSection[];
37
+ slots: Readonly<Record<string, HTMLDivElement | null>>;
38
+ closed: ReadonlySet<string>;
39
+ register: (section: DockSection) => () => void;
40
+ setSlot: (id: string, element: HTMLDivElement | null) => void;
41
+ setClosed: (closed: ReadonlySet<string>) => void;
42
+ };
43
+
44
+ const DockContext = createContext<DockContextValue | undefined>(undefined);
45
+
46
+ /** Holds the docked sections and their slots for a view's `Dock` and its panels. */
47
+ export const DockProvider = ({ children }: PropsWithChildren) => {
48
+ const [sections, setSections] = useState<DockSection[]>([]);
49
+ const [slots, setSlots] = useState<Record<string, HTMLDivElement | null>>({});
50
+ // Closed rather than open, so a section that docks later starts open.
51
+ const [closed, setClosed] = useState<ReadonlySet<string>>(new Set());
52
+ const register = useCallback((section: DockSection) => {
53
+ setSections((current) =>
54
+ [...current.filter((entry) => entry.id !== section.id), section].sort((left, right) => left.order - right.order),
55
+ );
56
+ return () => setSections((current) => current.filter((entry) => entry.id !== section.id));
57
+ }, []);
58
+ const setSlot = useCallback(
59
+ (id: string, element: HTMLDivElement | null) =>
60
+ setSlots((current) => (current[id] === element ? current : { ...current, [id]: element })),
61
+ [],
62
+ );
63
+ const value = useMemo(
64
+ () => ({ sections, slots, closed, register, setSlot, setClosed }),
65
+ [sections, slots, closed, register, setSlot],
66
+ );
67
+ return <DockContext.Provider value={value}>{children}</DockContext.Provider>;
68
+ };
69
+
70
+ /** A section's slot; its ref callback is stable, since a new one each render would detach and reattach (a loop). */
71
+ const DockSlot = ({ id, setSlot }: { id: string; setSlot: DockContextValue['setSlot'] }) => {
72
+ const ref = useCallback((element: HTMLDivElement | null) => setSlot(id, element), [id, setSlot]);
73
+ return <div ref={ref} />;
74
+ };
75
+
76
+ /** Stops a docked panel's events at the dock: through the portal they would reach the canvas root (hover, menus). */
77
+ const stop = (event: SyntheticEvent) => event.stopPropagation();
78
+
79
+ /**
80
+ * The dock column: an accordion of the registered sections, any number open, each as tall as its panel, the column
81
+ * scrolling them together. Nothing (and no space) while no panel is docked.
82
+ */
83
+ export const Dock = () => {
84
+ const dock = useContext(DockContext);
85
+ if (!dock || dock.sections.length === 0) {
86
+ return null;
87
+ }
88
+ const { sections, closed, setSlot, setClosed } = dock;
89
+ return (
90
+ // The column is the scroll area itself: each section is as tall as its panel, and it scrolls them together.
91
+ <ScrollArea.Root
92
+ classNames='w-80 shrink-0 border-l border-separator-subtle bg-base-surface'
93
+ data-testid='scene-view-dock'
94
+ >
95
+ <ScrollArea.Viewport>
96
+ <Accordion.Root
97
+ border={false}
98
+ onPointerDown={stop}
99
+ onPointerMove={stop}
100
+ onPointerUp={stop}
101
+ onDoubleClick={stop}
102
+ onContextMenu={stop}
103
+ onKeyDown={stop}
104
+ value={sections.filter((section) => !closed.has(section.id)).map((section) => section.id)}
105
+ onValueChange={(open) =>
106
+ setClosed(new Set(sections.filter((section) => !open.includes(section.id)).map((section) => section.id)))
107
+ }
108
+ >
109
+ {sections.map((section) => (
110
+ <Accordion.Item key={section.id} value={section.id} data-testid={`dock-section-${section.id}`}>
111
+ {/* A distinct surface, so a section's header reads apart from its panel's own toolbar. */}
112
+ <Accordion.ItemTrigger icon={section.icon} classNames='bg-group-surface'>
113
+ {section.title}
114
+ </Accordion.ItemTrigger>
115
+ <Accordion.ItemContent>
116
+ <DockSlot id={section.id} setSlot={setSlot} />
117
+ </Accordion.ItemContent>
118
+ </Accordion.Item>
119
+ ))}
120
+ </Accordion.Root>
121
+ </ScrollArea.Viewport>
122
+ </ScrollArea.Root>
123
+ );
124
+ };
125
+
126
+ /**
127
+ * Registers a section while `docked`, and renders `panel` into it: docked, the panel's portal into the dock (once its
128
+ * slot mounts); floating, `null`, so the caller renders the panel where it floats.
129
+ */
130
+ export const useDockSection = (section: DockSection, docked: boolean, panel: ReactNode): ReactNode | null => {
131
+ const dock = useContext(DockContext);
132
+ const { id, title, icon, order } = section;
133
+ useLayoutEffect(() => {
134
+ if (!dock || !docked) {
135
+ return;
136
+ }
137
+ return dock.register({ id, title, icon, order });
138
+ // `register` is stable; the section re-registers only when it changes.
139
+ // eslint-disable-next-line react-hooks/exhaustive-deps
140
+ }, [dock?.register, docked, id, title, icon, order]);
141
+ if (!docked) {
142
+ return null;
143
+ }
144
+ const slot = dock?.slots[id];
145
+ return slot ? createPortal(panel, slot) : null;
146
+ };
@@ -126,7 +126,7 @@ const DefaultStory = () => {
126
126
  };
127
127
 
128
128
  const meta: Meta = {
129
- title: 'ui/react-ui-canvas/scene/Dynamic',
129
+ title: 'ui/react-ui-canvas/Dynamic',
130
130
  render: DefaultStory,
131
131
  decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
132
132
  };
@@ -4,18 +4,20 @@
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useMemo } from 'react';
7
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
7
8
 
8
9
  import { translations as formTranslations } from '@dxos/react-ui-form/translations';
9
10
  import { withLayout, withRegistry, withTheme } from '@dxos/react-ui/testing';
10
11
  import { translations as uiTranslations } from '@dxos/react-ui/translations';
11
12
 
13
+ import { type PanelMode } from '../../model/atoms.ts';
12
14
  import { createLatticeProjection } from '../../model/projections/lattice.ts';
13
15
  import { createMemoryStore } from '../../model/store.ts';
14
16
  import { type Box, SceneBuilder } from '../../utils/builder.ts';
15
17
  import { DEFAULT_LATTICE, cellBounds } from '../../utils/lattice.ts';
16
18
  import { DEFAULT_SHAPE_SIZE } from '../../utils/shapes.ts';
17
19
  import { TONES } from '../../utils/style.ts';
18
- import { createClassSceneTree, createSceneTree } from '../../utils/testing.ts';
20
+ import { createModelSceneTree, createSceneTree } from '../../utils/testing.ts';
19
21
  import { SceneView } from './SceneView.tsx';
20
22
 
21
23
  /**
@@ -26,9 +28,9 @@ import { SceneView } from './SceneView.tsx';
26
28
  * 3. L / K / P pick the line, curve or spline link tool: every port shows; dropping onto empty canvas creates a
27
29
  * rectangle and links to it. A selected spline shows a diamond per control point and a dot per span midpoint:
28
30
  * drag a diamond to move a point, drag a dot to add one there, alt-click a diamond to remove one.
29
- * 4. R / E / C / T / S then drag draws a rectangle, ellipse, UML class, text or nested scene; Delete removes the
30
- * selection (nodes or links).
31
- * 5. Double-click a portal (or zoom until it fills the view) drills in; Escape, Up or the breadcrumb drills out.
31
+ * 4. R / E / T / F then drag draws a rectangle, ellipse, text or frame (the UML class is plugin-uml's);
32
+ * Delete removes the selection (nodes or links).
33
+ * 5. Double-click a frame (or zoom until it fills the view) drills in; Escape, Up or the breadcrumb drills out.
32
34
  * 6. G (or the Grid button) toggles the grid; with it off nothing snaps. The floating panel (top right) edits the
33
35
  * selected element.
34
36
  */
@@ -36,7 +38,8 @@ type StoryArgs = {
36
38
  depth: number;
37
39
  liveDepth: number;
38
40
  readonly?: boolean;
39
- fixture?: 'elements' | 'classes' | 'square' | 'lattice';
41
+ fixture?: 'elements' | 'model' | 'square' | 'lattice' | 'scenes' | 'frame';
42
+ panels?: PanelMode;
40
43
  };
41
44
 
42
45
  type EditorProps = {
@@ -45,13 +48,15 @@ type EditorProps = {
45
48
  liveDepth: number;
46
49
  readonly?: boolean;
47
50
  lattice?: boolean;
51
+ panels?: PanelMode;
48
52
  };
49
53
 
50
- const Editor = ({ store, root, liveDepth, readonly, lattice }: EditorProps) => (
54
+ const Editor = ({ store, root, liveDepth, readonly, lattice, panels }: EditorProps) => (
51
55
  <SceneView.Root
52
56
  store={store}
53
57
  root={root}
54
58
  readonly={readonly}
59
+ panels={panels}
55
60
  createProjection={lattice ? createLatticeProjection : undefined}
56
61
  >
57
62
  <SceneView.Canvas liveDepth={liveDepth} />
@@ -60,6 +65,8 @@ const Editor = ({ store, root, liveDepth, readonly, lattice }: EditorProps) => (
60
65
  <SceneView.Debug />
61
66
  <SceneView.Palette />
62
67
  <SceneView.Properties />
68
+ <SceneView.Layers />
69
+ <SceneView.About />
63
70
  </SceneView.Root>
64
71
  );
65
72
 
@@ -92,11 +99,11 @@ const createLatticeTree = () => {
92
99
  ...[box('a', at(-1, -1), 'A'), box('b', at(-1, 0), 'B'), box('c', at(-1, 1), 'C')].map((element) =>
93
100
  element.properties({ style: { hue: 'neutral' } }),
94
101
  ),
95
- box('d', at(0, 0), 'D').properties({ style: { hue: 'green', tone: 1 } }),
96
- box('e', at(0, 1), 'E').properties({ style: { hue: 'green', tone: 3 } }),
102
+ box('d', at(0, 0), 'D').properties({ style: { hue: 'green', tone: 3 } }),
103
+ box('e', at(0, 1), 'E').properties({ style: { hue: 'green', tone: 1 } }),
97
104
  // Unlabelled, so the scene shows its contents.
98
105
  SceneBuilder.scene('f', [
99
- // One hue at each of its tones, lightest to strongest.
106
+ // One hue at each of its tones: outline, then strongest to lightest.
100
107
  ...TONES.map((tone, index) =>
101
108
  box(`f${index + 1}`, at(0, index - 1), `F${index + 1}`).properties({ style: { hue: 'blue', tone } }),
102
109
  ),
@@ -123,17 +130,105 @@ const createLatticeTree = () => {
123
130
  .build();
124
131
  };
125
132
 
126
- const DefaultStory = ({ depth, liveDepth, readonly, fixture }: StoryArgs) => {
133
+ /**
134
+ * Three levels, every shape 256×128: A, scene B, C; inside B, D, scene E, F, with two rectangles either side
135
+ * of E; inside E, X, Y, Z with a box above X, below Z and beside each; each level's shapes linked. Open the
136
+ * scenes to check that a shape is the same size at the same zoom on every level.
137
+ */
138
+ const createScenesTree = () => {
139
+ const root = 'scene:root';
140
+ const size = { width: 256, height: 128 };
141
+ const at = (y: number, x = 0) => ({ x: x - size.width / 2, y: y - size.height / 2, ...size });
142
+ const rect = (id: string, y: number, x = 0) =>
143
+ SceneBuilder.rect(id, at(y, x)).properties({ label: id.toUpperCase() });
144
+ // An unlabelled box beside the column.
145
+ const box = (id: string, y: number, x: number) => SceneBuilder.rect(id, at(y, x));
146
+ const link = (from: string, to: string) => SceneBuilder.link('smart', from, to);
147
+ return SceneBuilder.scene(root, [
148
+ rect('a', -256),
149
+ SceneBuilder.scene('b', [
150
+ rect('d', -256),
151
+ // X, Y, Z down the middle, with a box above X, below Z and either side of each.
152
+ SceneBuilder.scene('e', [
153
+ box('n', -512, 0),
154
+ box('xw', -256, -384),
155
+ rect('x', -256),
156
+ box('xe', -256, 384),
157
+ rect('y', 0),
158
+ box('zw', 256, -384),
159
+ rect('z', 256),
160
+ box('ze', 256, 384),
161
+ box('s', 512, 0),
162
+ link('n', 'x'),
163
+ link('xw', 'x'),
164
+ link('x', 'xe'),
165
+ link('x', 'y'),
166
+ link('y', 'z'),
167
+ link('zw', 'z'),
168
+ link('z', 'ze'),
169
+ link('z', 's'),
170
+ ])
171
+ .at(at(0))
172
+ .name('E'),
173
+ rect('f', 256),
174
+ // Two rectangles either side of E.
175
+ rect('g', 0, -768),
176
+ rect('h', 0, -384),
177
+ rect('i', 0, 384),
178
+ rect('j', 0, 768),
179
+ link('d', 'e'),
180
+ link('e', 'f'),
181
+ link('g', 'h'),
182
+ link('h', 'e'),
183
+ link('e', 'i'),
184
+ link('i', 'j'),
185
+ ])
186
+ .at(at(0))
187
+ .name('B'),
188
+ rect('c', 256),
189
+ link('a', 'b'),
190
+ link('b', 'c'),
191
+ ])
192
+ .name('root')
193
+ .build();
194
+ };
195
+
196
+ /** A labelled frame onto a nested scene of two linked shapes, beside a rectangle. */
197
+ const createFrameTree = () => {
198
+ const root = 'scene:root';
199
+ const size = { width: 256, height: 128 };
200
+ const at = (x: number, y = 0) => ({ x: x - size.width / 2, y: y - size.height / 2, ...size });
201
+ return SceneBuilder.scene(root, [
202
+ SceneBuilder.rect('a', at(-384)).properties({ label: 'A' }),
203
+ SceneBuilder.scene('frame', [
204
+ SceneBuilder.rect('b', at(-192)).properties({ label: 'B' }),
205
+ SceneBuilder.rect('c', at(192)).properties({ label: 'C' }),
206
+ SceneBuilder.link('smart', 'b', 'c'),
207
+ ])
208
+ .name('Inner')
209
+ .at({ x: 0, y: -128, width: 512, height: 256 })
210
+ .properties({ label: 'Frame' }),
211
+ SceneBuilder.link('smart', 'a', 'frame'),
212
+ ])
213
+ .name('root')
214
+ .build();
215
+ };
216
+
217
+ const DefaultStory = ({ depth, liveDepth, readonly, fixture, panels }: StoryArgs) => {
127
218
  const { store, root } = useMemo(() => {
128
- // The class fixture is a fixed three levels, so `depth` does not apply to it.
219
+ // The model fixture is a fixed three levels, so `depth` does not apply to it.
129
220
  const tree =
130
- fixture === 'classes'
131
- ? createClassSceneTree()
221
+ fixture === 'model'
222
+ ? createModelSceneTree()
132
223
  : fixture === 'square'
133
224
  ? createSquareTree()
134
225
  : fixture === 'lattice'
135
226
  ? createLatticeTree()
136
- : createSceneTree(depth);
227
+ : fixture === 'scenes'
228
+ ? createScenesTree()
229
+ : fixture === 'frame'
230
+ ? createFrameTree()
231
+ : createSceneTree(depth);
137
232
  return { store: createMemoryStore(tree.scenes), root: tree.root };
138
233
  }, [depth, fixture]);
139
234
 
@@ -146,17 +241,23 @@ const DefaultStory = ({ depth, liveDepth, readonly, fixture }: StoryArgs) => {
146
241
  liveDepth={liveDepth}
147
242
  readonly={readonly}
148
243
  lattice={fixture === 'lattice'}
244
+ panels={panels}
149
245
  />
150
246
  );
151
247
  };
152
248
 
153
249
  const meta: Meta<StoryArgs> = {
154
- title: 'ui/react-ui-canvas/scene/SceneView',
250
+ title: 'ui/react-ui-canvas/SceneView',
155
251
  render: DefaultStory,
156
252
  decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
157
253
  // The properties panel is a react-ui-form form; its strings (e.g. "Mixed") and its controls' come from their bundles.
158
254
  parameters: { translations: [...uiTranslations, ...formTranslations] },
159
255
  argTypes: {
256
+ panels: {
257
+ control: { type: 'inline-radio' },
258
+ options: ['docked', 'floating'],
259
+ description: 'Where the properties and layers panels sit',
260
+ },
160
261
  depth: {
161
262
  control: { type: 'range', min: 0, max: 5, step: 1 },
162
263
  description: 'Levels of nested scenes in the fixture; 0 is an empty scene',
@@ -192,15 +293,66 @@ export const Readonly: Story = {
192
293
  args: { depth: 1, liveDepth: 1, readonly: true },
193
294
  };
194
295
 
195
- /** A three-level class model: drill into a subsystem's portal to open its own classes. */
196
- export const Classes: Story = {
197
- args: { depth: 0, liveDepth: 1, fixture: 'classes' },
296
+ /** A three-level model, a box per class: drill into a subsystem's portal to open its own. */
297
+ export const Model: Story = {
298
+ args: { depth: 0, liveDepth: 1, fixture: 'model' },
198
299
  };
199
300
 
200
301
  /**
201
302
  * Lattice mode (DESIGN §8b): shapes snap to whole cells of a 256x128 lattice with 128x64 gutters, span any number
202
303
  * of cells, and may not overlap; a drag onto occupied cells previews in red and is refused.
203
304
  */
305
+ /** A, an empty scene B and C in a column: open B, draw in it, and compare sizes at 100% on both levels. */
306
+ export const Scenes: Story = {
307
+ args: { depth: 0, liveDepth: 1, fixture: 'scenes' },
308
+ };
309
+
204
310
  export const Lattice: Story = {
205
311
  args: { depth: 0, liveDepth: 1, fixture: 'lattice' },
206
312
  };
313
+
314
+ /** The panels dock by default: one accordion section each beside the canvas; a section collapses to its header. */
315
+ export const Docked: Story = {
316
+ args: { depth: 0, liveDepth: 1, fixture: 'square' },
317
+ play: async ({ canvasElement }) => {
318
+ const canvas = within(canvasElement);
319
+ // 1. The dock holds the properties and layers sections, both open.
320
+ const dock = await canvas.findByTestId('scene-view-dock');
321
+ await waitFor(() => expect(within(dock).getByTestId('dock-section-layers')).toHaveAttribute('data-state', 'open'));
322
+ await expect(within(dock).getByTestId('dock-section-properties')).toHaveAttribute('data-state', 'open');
323
+ // 2. A section's header collapses it.
324
+ await userEvent.click(
325
+ within(within(dock).getByTestId('dock-section-properties')).getByRole('button', { name: 'Properties' }),
326
+ );
327
+ await waitFor(() =>
328
+ expect(within(dock).getByTestId('dock-section-properties')).toHaveAttribute('data-state', 'closed'),
329
+ );
330
+ },
331
+ };
332
+
333
+ /** Floating panels: no dock, the layers over the canvas until something is selected. */
334
+ export const Floating: Story = {
335
+ args: { depth: 0, liveDepth: 1, fixture: 'square', panels: 'floating' },
336
+ play: async ({ canvasElement }) => {
337
+ const canvas = within(canvasElement);
338
+ await expect(await canvas.findByTestId('layers')).toBeInTheDocument();
339
+ await expect(canvas.queryByTestId('scene-view-dock')).not.toBeInTheDocument();
340
+ // About lives only in the dock.
341
+ await expect(canvas.queryByTestId('about')).not.toBeInTheDocument();
342
+ },
343
+ };
344
+
345
+ /** A frame onto a nested scene: its open control drills into the child scene, and Up returns. */
346
+ export const Frame: Story = {
347
+ args: { depth: 0, liveDepth: 1, fixture: 'frame' },
348
+ play: async ({ canvasElement }) => {
349
+ const canvas = within(canvasElement);
350
+ // 1. The frame shows its label and an open control; the root has nowhere up to go.
351
+ await waitFor(() => expect(canvasElement.querySelector('[data-node-id="frame"]')).toHaveTextContent('Frame'));
352
+ await waitFor(() => expect(canvas.getByTestId('toolbar-up')).toBeDisabled());
353
+ // 2. Opening the frame drills into its scene.
354
+ await userEvent.click(await canvas.findByTestId('portal-open'));
355
+ await waitFor(() => expect(canvas.getByTestId('toolbar-up')).toBeEnabled());
356
+ await waitFor(() => expect(canvasElement.querySelector('[data-node-id="b"]')).not.toBeNull());
357
+ },
358
+ };