@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
@@ -16,19 +16,15 @@
16
16
  import type * as Schema from 'effect/Schema';
17
17
  import { type ComponentType } from 'react';
18
18
 
19
- import {
20
- BoxNodeView,
21
- ClassNodeView,
22
- EllipseNodeView,
23
- NoteNodeView,
24
- PortalNodeView,
25
- } from '../components/SceneLayer/SceneLayer.tsx';
26
- import { type PartEditing } from '../utils/parts.ts';
27
- import { DEFAULT_SIZES, createNode } from '../utils/shapes.ts';
19
+ import { type FormFieldMap } from '@dxos/react-ui-form';
20
+
21
+ import { BoxNodeView, EllipseNodeView, FrameNodeView, NoteNodeView } from '../components/SceneLayer/SceneLayer.tsx';
22
+ import { type PartEditing, type PartField } from '../utils/parts.ts';
23
+ import { NOMINAL_SIZES, createNode } from '../utils/shapes.ts';
28
24
  import { type SceneStore } from './store.ts';
29
25
  import {
30
- ClassNode,
31
26
  EllipseNode,
27
+ FrameNode,
32
28
  type LinkType,
33
29
  type Node,
34
30
  type NodeBase,
@@ -36,7 +32,6 @@ import {
36
32
  NoteNode,
37
33
  type Point,
38
34
  type Port,
39
- PortalNode,
40
35
  RectNode,
41
36
  type Scene,
42
37
  type Size,
@@ -58,6 +53,8 @@ export type NodeViewProps = {
58
53
  opening?: boolean;
59
54
  /** The text part of this node being edited in place, with the editor's callbacks. */
60
55
  editing?: PartEditing;
56
+ /** The node's embedded content takes input (`SceneViewAtoms.active`); otherwise it should be inert. */
57
+ active?: boolean;
61
58
  /** Opens the node (drills into a portal); absent where the layer is read-only. */
62
59
  onOpen?: () => void;
63
60
  };
@@ -77,6 +74,7 @@ export type NodeDef = {
77
74
  component: ComponentType<NodeViewProps>;
78
75
  /** A new node of the type with its default content, for the palette tool and drop-on-canvas. */
79
76
  create: (props: CreateProps) => Node;
77
+ /** Size when created, in nominal units: one unit is the drawing's major grid cell. */
80
78
  defaultSize: Size;
81
79
  /** Explicit port layout; absent, the type gets `portsPerSide` ports spread along each side. */
82
80
  ports?: (node: Node) => readonly Port[];
@@ -85,8 +83,22 @@ export type NodeDef = {
85
83
  resizable?: boolean;
86
84
  minSize?: Size;
87
85
  maxSize?: Size;
88
- /** Double-click opens the node (a portal drills in; a text node edits, later). */
86
+ /** Double-click opens the node (a frame drills in; a text node edits, later). */
89
87
  openable?: boolean;
88
+ /**
89
+ * The host's own way to open this node, when it has one (a frame showing an object rather than a scene):
90
+ * opening calls it instead of drilling in, and auto-drill passes the node by.
91
+ */
92
+ hostOpen?: (node: Node) => (() => void) | undefined;
93
+ /**
94
+ * Controls floating above the node, flush with its right edge and at screen size, so they neither cover the node's
95
+ * content nor scale with the camera; renders nothing when it returns `null`.
96
+ */
97
+ toolbar?: ComponentType<NodeViewProps>;
98
+ /** The text properties edited in place, in order; the first is the node's main text. */
99
+ parts?: readonly PartField[];
100
+ /** Properties-panel renderers for this type's fields, by path (e.g. a list as lines). */
101
+ fields?: FormFieldMap;
90
102
  };
91
103
 
92
104
  /**
@@ -148,11 +160,12 @@ const MIN_SIZE: Size = { width: 64, height: 32 };
148
160
 
149
161
  /**
150
162
  * A framed shape with a centred, editable label: resizable, with ports spread along every side. The
151
- * built-in rectangle and scene are both boxes; a host type can extend it too.
163
+ * built-in rectangle and frame are both boxes; a host type can extend it too.
152
164
  */
153
165
  export const boxPrototype: NodeDefSpec = {
154
166
  component: BoxNodeView,
155
- defaultSize: DEFAULT_SIZES.rect,
167
+ parts: [{ field: 'label' }],
168
+ defaultSize: NOMINAL_SIZES.rect,
156
169
  resizable: true,
157
170
  minSize: MIN_SIZE,
158
171
  };
@@ -175,43 +188,35 @@ export const defaultNodeTypes: Readonly<Record<NodeType, NodeDefSpec>> = {
175
188
  schema: EllipseNode,
176
189
  component: EllipseNodeView,
177
190
  create: (props) => createNode({ type: 'ellipse', ...props }),
178
- defaultSize: DEFAULT_SIZES.ellipse,
191
+ defaultSize: NOMINAL_SIZES.ellipse,
179
192
  // Only the side centres of the frame lie on the curve.
180
193
  portsPerSide: 1,
194
+ parts: [{ field: 'label' }],
181
195
  resizable: true,
182
196
  minSize: MIN_SIZE,
183
197
  },
184
- class: {
185
- name: 'Class',
186
- icon: 'ph--rows--regular',
187
- key: 'C',
188
- schema: ClassNode,
189
- component: ClassNodeView,
190
- create: (props) => createNode({ type: 'class', ...props }),
191
- defaultSize: DEFAULT_SIZES.class,
192
- resizable: true,
193
- minSize: { width: 128, height: 96 },
194
- },
198
+
195
199
  note: {
196
200
  name: 'Note',
197
201
  icon: 'ph--text-t--regular',
198
202
  key: 'T',
199
203
  schema: NoteNode,
200
204
  component: NoteNodeView,
205
+ parts: [{ field: 'text', multiline: true }],
201
206
  create: (props) => createNode({ type: 'note', ...props }),
202
- defaultSize: DEFAULT_SIZES.note,
207
+ defaultSize: NOMINAL_SIZES.note,
203
208
  resizable: true,
204
209
  minSize: MIN_SIZE,
205
210
  },
206
- scene: {
211
+ frame: {
207
212
  extends: 'box',
208
- name: 'Scene',
213
+ name: 'Frame',
209
214
  icon: 'ph--frame-corners--regular',
210
- key: 'S',
211
- schema: PortalNode,
212
- component: PortalNodeView,
213
- create: (props) => createNode({ type: 'scene', ...props }),
214
- defaultSize: DEFAULT_SIZES.scene,
215
+ key: 'F',
216
+ schema: FrameNode,
217
+ component: FrameNodeView,
218
+ create: (props) => createNode({ type: 'frame', ...props }),
219
+ defaultSize: NOMINAL_SIZES.frame,
215
220
  minSize: { width: 96, height: 60 },
216
221
  openable: true,
217
222
  },
@@ -10,7 +10,7 @@
10
10
  import * as Atom from 'effect/reactivity/Atom';
11
11
  import type * as Registry from 'effect/reactivity/AtomRegistry';
12
12
 
13
- import { type Scene, type SceneId } from './types.ts';
13
+ import { type Scene, type SceneId, type StyleMap } from './types.ts';
14
14
 
15
15
  export type SceneMap = Readonly<Record<SceneId, Scene>>;
16
16
 
@@ -19,13 +19,20 @@ export type SceneStore = {
19
19
  readonly scenes: Atom.Writable<SceneMap>;
20
20
  /** Derived atom for one scene; stable per id so subscriptions do not churn. */
21
21
  scene: (id: SceneId) => Atom.Atom<Scene | undefined>;
22
+ /** The drawing's style classes; a store without them offers none. */
23
+ readonly styles?: Atom.Writable<StyleMap>;
22
24
  };
23
25
 
24
- export const createMemoryStore = (initial: readonly Scene[] = []): SceneStore => {
26
+ /** A store held in memory, which always keeps style classes. */
27
+ export type MemorySceneStore = SceneStore & { readonly styles: Atom.Writable<StyleMap> };
28
+
29
+ export const createMemoryStore = (initial: readonly Scene[] = [], initialStyles: StyleMap = {}): MemorySceneStore => {
30
+ const styles = Atom.keepAlive(Atom.make<StyleMap>(initialStyles));
25
31
  const scenes = Atom.keepAlive(Atom.make<SceneMap>(Object.fromEntries(initial.map((scene) => [scene.id, scene]))));
26
32
  const derived = new Map<SceneId, Atom.Atom<Scene | undefined>>();
27
33
  return {
28
34
  scenes,
35
+ styles,
29
36
  scene: (id) => {
30
37
  let atom = derived.get(id);
31
38
  if (!atom) {
@@ -58,3 +65,6 @@ export const updateScene = (
58
65
  export const putScene = (registry: Registry.AtomRegistry, store: SceneStore, scene: Scene): void => {
59
66
  registry.set(store.scenes, { ...registry.get(store.scenes), [scene.id]: scene });
60
67
  };
68
+
69
+ /** No classes: what a store without `styles` reads as. */
70
+ export const NO_STYLES: Atom.Atom<StyleMap> = Atom.keepAlive(Atom.make<StyleMap>({}));
@@ -83,14 +83,50 @@ export type Port = Schema.Schema.Type<typeof Port>;
83
83
  // Nodes
84
84
  //
85
85
 
86
- /** How strongly a hue fills a frame: 0 is an outline (transparent), 1 to 3 are lighter to stronger fills. */
86
+ /** The hues the style pickers offer, neutral first, then in hue order; any theme hue still renders on a node. */
87
+ export const STYLE_HUES = ['neutral', 'red', 'orange', 'amber', 'green', 'teal', 'sky', 'blue', 'violet'] as const;
88
+ export const StyleHue = Schema.Literals(STYLE_HUES);
89
+ export type StyleHue = Schema.Schema.Type<typeof StyleHue>;
90
+
91
+ /** How strongly a hue fills a frame: 0 is an outline (transparent), 1 to 3 are stronger to lighter fills. */
87
92
  export const NodeTone = Schema.Literals([0, 1, 2, 3]);
88
93
  export type NodeTone = Schema.Schema.Type<typeof NodeTone>;
89
94
 
90
- /** Presentation choices a node carries; every field is optional and the frame supplies the default look. */
91
- export const NodeStyle = Schema.Struct({
92
- /** One of the theme's hues, colouring fill, text and border together. */
95
+ /** The typefaces a shape's text may use: the theme's body face, or its fixed-width one. */
96
+ export const FONT_FAMILIES = ['default', 'monospace'] as const;
97
+ export type FontFamily = (typeof FONT_FAMILIES)[number];
98
+
99
+ /** Where text sits across a shape, and down it. */
100
+ export const HORIZONTAL_ALIGNS = ['left', 'center', 'right'] as const;
101
+ export const VERTICAL_ALIGNS = ['top', 'middle', 'bottom'] as const;
102
+ export type HorizontalAlign = (typeof HORIZONTAL_ALIGNS)[number];
103
+ export type VerticalAlign = (typeof VERTICAL_ALIGNS)[number];
104
+
105
+ /** How a line is drawn: a link's stroke, or a node's border. */
106
+ export const LINE_STYLES = ['solid', 'dashed', 'dotted'] as const;
107
+
108
+ /**
109
+ * The common base style every element shares, nodes and links alike: its colour and its line. Each kind extends it
110
+ * (`NodeStyle`), and a selection of several kinds edits only what they share. One field object per property, so
111
+ * the kinds declare the same field and the properties panel sees it as shared.
112
+ */
113
+ export const lineStyleFields = {
114
+ /** One of the theme's hues: a link's stroke; a node's fill, text and border together. */
93
115
  hue: Schema.optional(Schema.String.annotate({ title: 'Hue', [HueAnnotationId]: true })),
116
+ /** Solid, dashed or dotted: a link's stroke, or a node's border. */
117
+ lineStyle: Schema.optional(Schema.Literals(LINE_STYLES).annotate({ title: 'Line style' })),
118
+ };
119
+
120
+ /** A link's style: the common base, unset drawn neutral and solid. */
121
+ export const LineStyle = Schema.Struct(lineStyleFields);
122
+ export type LineStyle = Schema.Schema.Type<typeof LineStyle>;
123
+
124
+ /**
125
+ * The common base plus a frame and text: what a shape draws. A shape with more to style extends it
126
+ * (`Schema.Struct({ ...styleFields, … })`).
127
+ */
128
+ export const styleFields = {
129
+ ...lineStyleFields,
94
130
  /** The hue's fill; unset is 2, the look a hue had before tones. */
95
131
  tone: Schema.optional(NodeTone),
96
132
  rounded: Schema.optional(Schema.Boolean),
@@ -98,11 +134,18 @@ export const NodeStyle = Schema.Struct({
98
134
  border: Schema.optional(Schema.Boolean),
99
135
  /** A guide: drawn dashed and unfilled, an annotation rather than content. */
100
136
  guide: Schema.optional(Schema.Boolean),
137
+ /** The face of the shape's text; unset is the theme's body face. */
138
+ fontFamily: Schema.optional(Schema.Literals(FONT_FAMILIES).annotate({ title: 'Font' })),
101
139
  /** Text size in the node's own scene units (the editor offers a readable range; stored values are not checked). */
102
140
  fontSize: Schema.optional(Schema.Number.annotate({ title: 'Font size' })),
103
- /** Extra classes on the frame, for a host's own look. */
104
- className: Schema.optional(Schema.String),
105
- });
141
+ /** Where the text sits across the shape; unset is the type's own (a label centres, a note starts at the left). */
142
+ alignHorizontal: Schema.optional(Schema.Literals(HORIZONTAL_ALIGNS).annotate({ title: 'Horizontal' })),
143
+ /** Where the text sits down the shape; unset is the type's own (a label is middled, a note starts at the top). */
144
+ alignVertical: Schema.optional(Schema.Literals(VERTICAL_ALIGNS).annotate({ title: 'Vertical' })),
145
+ };
146
+
147
+ /** Presentation choices a node carries: the shape style. */
148
+ export const NodeStyle = Schema.Struct(styleFields);
106
149
  export type NodeStyle = Schema.Schema.Type<typeof NodeStyle>;
107
150
 
108
151
  /** The fields every node type shares; a type's schema is `Schema.Struct({ ...nodeBase, type: Literal, ... })`. */
@@ -118,6 +161,10 @@ export const nodeBase = {
118
161
  ports: Schema.optional(Schema.Array(Port)),
119
162
  /** Ports spread along each side, overriding the type's layout; ignored when the node carries `ports`. */
120
163
  portsPerSide: Schema.optional(Schema.Number.annotate({ title: 'Ports per side' })),
164
+ /** A style class of the drawing (`StyleClass`) the node takes its look from; its own `style` wins over it. */
165
+ /** The scene layer the element is on; unset, or naming no layer of the scene, it is on the bottom one. */
166
+ layer: Schema.optional(Schema.String.annotate({ title: 'Layer' })),
167
+ class: Schema.optional(Schema.String.annotate({ title: 'Class' })),
121
168
  style: Schema.optional(NodeStyle),
122
169
  };
123
170
 
@@ -130,6 +177,14 @@ export const boxFields = {
130
177
  label: Schema.optional(Schema.String),
131
178
  };
132
179
 
180
+ /**
181
+ * The core base every node falls back to: its frame, ports, style and `label`. A node whose type the registry does
182
+ * not know (its plugin is off) is drawn and edited as this, a box showing its label, so a type that keeps its title
183
+ * in `label` still reads as itself.
184
+ */
185
+ export const BaseNode = Schema.Struct({ type: Schema.String, ...nodeBase, ...boxFields });
186
+ export type BaseNode = Schema.Schema.Type<typeof BaseNode>;
187
+
133
188
  export const RectNode = Schema.Struct({
134
189
  type: Schema.Literal('rect'),
135
190
  ...nodeBase,
@@ -145,16 +200,6 @@ export const EllipseNode = Schema.Struct({
145
200
  });
146
201
  export type EllipseNode = Schema.Schema.Type<typeof EllipseNode>;
147
202
 
148
- /** UML class box: a name compartment over attribute and method compartments. */
149
- export const ClassNode = Schema.Struct({
150
- type: Schema.Literal('class'),
151
- ...nodeBase,
152
- name: Schema.String,
153
- attributes: Schema.Array(Schema.String),
154
- methods: Schema.Array(Schema.String),
155
- });
156
- export type ClassNode = Schema.Schema.Type<typeof ClassNode>;
157
-
158
203
  /** Free text on the canvas. Named for what it is, so a host may keep `text` for a type of its own. */
159
204
  export const NoteNode = Schema.Struct({
160
205
  type: Schema.Literal('note'),
@@ -163,22 +208,22 @@ export const NoteNode = Schema.Struct({
163
208
  });
164
209
  export type NoteNode = Schema.Schema.Type<typeof NoteNode>;
165
210
 
166
- /** Portal to the next depth: renders the referenced scene scaled into this node's bounds. */
167
- export const PortalNode = Schema.Struct({
168
- type: Schema.Literal('scene'),
211
+ /** A frame onto the next depth: renders its child scene scaled into this node's bounds. */
212
+ export const FrameNode = Schema.Struct({
213
+ type: Schema.Literal('frame'),
169
214
  ...nodeBase,
170
215
  ...boxFields,
171
216
  scene: Schema.String,
172
217
  /** Draw the child scene inside the frame rather than the label; unset, it does so while there is no label. */
173
218
  contents: Schema.optional(Schema.Boolean.annotate({ title: 'Show contents' })),
174
219
  });
175
- export type PortalNode = Schema.Schema.Type<typeof PortalNode>;
220
+ export type FrameNode = Schema.Schema.Type<typeof FrameNode>;
176
221
 
177
222
  /** The engine's own node types. A host may add its own (decision 1); those are `NodeBase` to the engine. */
178
- export const BuiltinNode = Schema.Union([RectNode, EllipseNode, ClassNode, NoteNode, PortalNode]);
223
+ export const BuiltinNode = Schema.Union([RectNode, EllipseNode, NoteNode, FrameNode]);
179
224
  export type BuiltinNode = Schema.Schema.Type<typeof BuiltinNode>;
180
225
  export type BuiltinNodeType = BuiltinNode['type'];
181
- export const NODE_TYPES: readonly BuiltinNodeType[] = ['rect', 'ellipse', 'class', 'note', 'scene'];
226
+ export const NODE_TYPES: readonly BuiltinNodeType[] = ['rect', 'ellipse', 'note', 'frame'];
182
227
 
183
228
  /** A node of the scene: the engine handles any `NodeBase`; built-in code narrows with the guards below. */
184
229
  export type Node = NodeBase;
@@ -188,13 +233,12 @@ export type NodeType = string;
188
233
  /** Narrows a node to one built-in type; sound because the registry maps each type name to one schema. */
189
234
  export const isRectNode = (node: NodeBase): node is RectNode => node.type === 'rect';
190
235
  export const isEllipseNode = (node: NodeBase): node is EllipseNode => node.type === 'ellipse';
191
- export const isClassNode = (node: NodeBase): node is ClassNode => node.type === 'class';
192
236
  export const isNoteNode = (node: NodeBase): node is NoteNode => node.type === 'note';
193
- export const isPortalNode = (node: NodeBase): node is PortalNode => node.type === 'scene';
237
+ export const isFrameNode = (node: NodeBase): node is FrameNode => node.type === 'frame';
194
238
  /** A node built on the `box` prototype, carrying a centred, editable label. */
195
- export const isBoxNode = (node: NodeBase): node is RectNode | PortalNode => isRectNode(node) || isPortalNode(node);
196
- /** Whether a portal draws its child scene: as set, else while it has no label to show instead. */
197
- export const showsContents = (node: PortalNode): boolean => node.contents ?? node.label === undefined;
239
+ export const isBoxNode = (node: NodeBase): node is RectNode | FrameNode => isRectNode(node) || isFrameNode(node);
240
+ /** Whether a frame draws its child scene: as set, else while it has no label to show instead. */
241
+ export const showsContents = (node: FrameNode): boolean => node.contents ?? node.label === undefined;
198
242
  export const isBuiltinNode = (node: NodeBase): node is BuiltinNode => NODE_TYPES.some((type) => type === node.type);
199
243
 
200
244
  //
@@ -220,7 +264,8 @@ export const isPointEndpoint = (end: Endpoint): end is PointEndpoint => 'point'
220
264
  export const endpointNode = (end: Endpoint): NodeId | undefined => ('node' in end ? end.node : undefined);
221
265
 
222
266
  /** What is drawn at a link end. */
223
- export const Marker = Schema.Literals(['arrow', 'circle']);
267
+ /** An end marker: a filled `arrow`, a `circle`, or a hollow `triangle` (inheritance). */
268
+ export const Marker = Schema.Literals(['arrow', 'circle', 'triangle']);
224
269
  export type Marker = Schema.Schema.Type<typeof Marker>;
225
270
 
226
271
  /** Markers at the source (`start`) and target (`end`) of a link. */
@@ -231,16 +276,35 @@ export const LinkEnds = Schema.Struct({
231
276
  }).pipe(Annotation.FormLayoutAnnotation.set({ [Annotation.DEFAULT_LAYOUT_NAME]: pairLayout('start', 'end') }));
232
277
  export type LinkEnds = Schema.Schema.Type<typeof LinkEnds>;
233
278
 
279
+ /**
280
+ * A named look of a drawing, kept beside its scenes: the nodes and links naming it derive their `style` from it (a
281
+ * link takes the common base, its colour and line style), so one class restyles both together.
282
+ */
283
+ export const StyleClass = Schema.Struct({
284
+ id: Schema.String,
285
+ name: Schema.String,
286
+ style: Schema.optional(NodeStyle),
287
+ });
288
+ export type StyleClass = Schema.Schema.Type<typeof StyleClass>;
289
+
290
+ /** A drawing's style classes by id. */
291
+ export type StyleMap = Readonly<Record<string, StyleClass>>;
292
+
234
293
  const linkBase = {
235
294
  id: Schema.String,
236
295
  z: Schema.String,
237
296
  locked: Schema.optional(Schema.Boolean),
238
297
  source: Endpoint,
239
298
  target: Endpoint,
240
- /** Shorthand for `ends: { end: 'arrow' }`; ports with `accepts` constrain which end lands where. */
241
- directed: Schema.optional(Schema.Boolean),
242
- /** Explicit end markers; when present they replace what `directed` implies. */
299
+ /** End markers; an arrow at `end` reads as the link's direction. */
243
300
  ends: Schema.optional(LinkEnds),
301
+ /** A caption drawn at the middle of the link's route. */
302
+ text: Schema.optional(Schema.String.annotate({ title: 'Label' })),
303
+ /** A style class of the drawing the link takes its style from; its own `style` wins over it. */
304
+ /** The scene layer the element is on; unset, or naming no layer of the scene, it is on the bottom one. */
305
+ layer: Schema.optional(Schema.String.annotate({ title: 'Layer' })),
306
+ class: Schema.optional(Schema.String.annotate({ title: 'Class' })),
307
+ style: Schema.optional(LineStyle),
244
308
  };
245
309
 
246
310
  export const LineLink = Schema.Struct({ type: Schema.Literal('line'), ...linkBase });
@@ -272,8 +336,8 @@ export const LINK_TYPES: readonly LinkType[] = ['line', 'curve', 'spline', 'smar
272
336
 
273
337
  export type Element = Node | Link;
274
338
 
275
- /** The markers a link draws: its explicit `ends`, else an arrowhead at the target when it is `directed`. */
276
- export const linkMarkers = (link: Link): LinkEnds => link.ends ?? (link.directed ? { end: 'arrow' } : {});
339
+ /** The markers a link draws. */
340
+ export const linkMarkers = (link: Link): LinkEnds => link.ends ?? {};
277
341
 
278
342
  export const isNode = (element: Element): element is Node => 'center' in element;
279
343
  export const isLink = (element: Element): element is Link => 'source' in element;
@@ -282,6 +346,22 @@ export const isLink = (element: Element): element is Link => 'source' in element
282
346
  // Scene
283
347
  //
284
348
 
349
+ /** A layer id, unique within its scene. */
350
+ export type LayerId = string;
351
+
352
+ /**
353
+ * A scene's layer: elements paint by their layer's order (`z`, bottom first), then by their own `z`. A hidden layer
354
+ * is neither drawn nor hit. A scene with no layers has one, implicitly (`DEFAULT_LAYER`).
355
+ */
356
+ export const Layer = Schema.Struct({
357
+ id: Schema.String,
358
+ name: Schema.String,
359
+ /** Fractional order key (see `order.ts`): the bottom layer has the least. */
360
+ z: Schema.String,
361
+ hidden: Schema.optional(Schema.Boolean),
362
+ });
363
+ export type Layer = Schema.Schema.Type<typeof Layer>;
364
+
285
365
  /**
286
366
  * The scene schema over a set of node schemas: a host composes it from its registry's types, so the
287
367
  * schema stays exact for every type while the engine only ever sees `NodeBase`.
@@ -292,10 +372,11 @@ export const createSceneSchema = <const Nodes extends readonly Schema.Codec<Node
292
372
  name: Schema.optional(Schema.String),
293
373
  nodes: Schema.Record(Schema.String, Schema.Union(nodes)),
294
374
  links: Schema.Record(Schema.String, Link),
375
+ layers: Schema.optional(Schema.Record(Schema.String, Layer)),
295
376
  });
296
377
 
297
378
  /** The scene schema over the built-in node types. */
298
- export const Scene = createSceneSchema([RectNode, EllipseNode, ClassNode, NoteNode, PortalNode]);
379
+ export const Scene = createSceneSchema([RectNode, EllipseNode, NoteNode, FrameNode]);
299
380
 
300
381
  /** The scene schema over any node with the shared fields: what the engine itself can validate for a host. */
301
382
  export const OpenScene = createSceneSchema([NodeBase]);
@@ -304,6 +385,7 @@ export type Scene = {
304
385
  readonly name?: string;
305
386
  readonly nodes: Readonly<Record<NodeId, Node>>;
306
387
  readonly links: Readonly<Record<LinkId, Link>>;
388
+ readonly layers?: Readonly<Record<LayerId, Layer>>;
307
389
  };
308
390
 
309
391
  /** A node or link of the scene by id. */
@@ -311,7 +393,9 @@ export const getElement = (scene: Scene, id: ElementId): Element | undefined =>
311
393
 
312
394
  /** Property edits: the shared fields typed, a type's own fields by name. */
313
395
  export type NodeValues = Partial<Omit<NodeBase, 'id' | 'type'>> & { readonly [key: string]: unknown };
314
- export type LinkValues = Partial<Omit<Link, 'id' | 'type'>>;
396
+ /** Per link type, so a spline's `points` are among the values an `update` may set. */
397
+ type ValuesOf<T> = T extends unknown ? Partial<Omit<T, 'id' | 'type'>> : never;
398
+ export type LinkValues = ValuesOf<Link>;
315
399
 
316
400
  /**
317
401
  * What the surface asks of a projection (§3). The surface never writes coordinates itself: a
@@ -331,6 +415,10 @@ export type Intent =
331
415
  * rather than a view action, so a projection that owns its own positions may rewrite or refuse it.
332
416
  */
333
417
  | { kind: 'layout'; ids?: ElementId[] }
418
+ /** Adds a layer, or replaces the one of its id (a rename, a visibility toggle, a move in the order). */
419
+ | { kind: 'layer'; layer: Layer }
420
+ /** Removes a layer and every element on it; the last layer of a scene stays. */
421
+ | { kind: 'removeLayer'; id: LayerId }
334
422
  /** Several intents applied as one model change (one undo step), e.g. a paste. */
335
423
  | { kind: 'batch'; intents: Intent[] };
336
424
 
@@ -42,7 +42,7 @@ export class SceneManager {
42
42
  return Math.max(...boxes.map(({ x, width }) => x + width));
43
43
  }
44
44
 
45
- /** The debug bar's zoom readout, as whole percent; the navigation bar carries the path and depth. */
45
+ /** The debug bar's zoom readout, as whole percent; the navigation bar carries the path. */
46
46
  async zoom(): Promise<number> {
47
47
  const readout = await this.page.getByTestId('canvas-debug').textContent();
48
48
  const percent = readout?.match(/(\d+)%/);
@@ -56,7 +56,7 @@ export class SceneManager {
56
56
  selectedNodes(): Promise<string[]> {
57
57
  return this.page.evaluate(() =>
58
58
  Array.from(document.querySelectorAll<HTMLElement>('[data-node-id]'))
59
- .filter((element) => element.className.includes('border-primary-500 '))
59
+ .filter((element) => element.className.includes('border-focus '))
60
60
  .map((element) => element.dataset.nodeId ?? ''),
61
61
  );
62
62
  }
@@ -6,11 +6,12 @@ import { type Page, expect, test } from '@playwright/test';
6
6
 
7
7
  import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
8
8
 
9
+ import { NOMINAL_SIZES, nominalSize } from '../utils/shapes.ts';
9
10
  import { SceneManager } from './SceneManager.ts';
10
11
 
11
12
  const PORT = 9006;
12
13
  // The empty canvas: every gesture here lands on blank scene, so a create is never a hit on a fixture node.
13
- const DEFAULT_URL = storybookUrl('ui-react-ui-canvas-scene-sceneview--default', PORT);
14
+ const DEFAULT_URL = storybookUrl('ui-react-ui-canvas-sceneview--default', PORT);
14
15
 
15
16
  test.describe('create sizing', () => {
16
17
  let page: Page;
@@ -103,10 +104,11 @@ test.describe('create sizing', () => {
103
104
  return (await scene.box(scene.node(id!))).width;
104
105
  };
105
106
 
106
- // A new shape is the shared default (256 scene units) at any zoom, so on screen it scales with the zoom.
107
+ // A new rectangle is its nominal width (two major cells, 128 scene units) at any zoom, so on screen it
108
+ // scales with the zoom.
107
109
  const expectDefault = async () => {
108
110
  const width = await widthAt();
109
- expect(Math.abs(width - (256 * (await scene.zoom())) / 100)).toBeLessThan(4);
111
+ expect(Math.abs(width - (nominalSize(NOMINAL_SIZES.rect).width * (await scene.zoom())) / 100)).toBeLessThan(4);
110
112
  };
111
113
  await expectDefault();
112
114
  await scene.zoomIn(2);
@@ -14,8 +14,8 @@ import { test } from '@playwright/test';
14
14
  import { storybookUrl } from '@dxos/test-utils/playwright';
15
15
 
16
16
  const PORT = 9006;
17
- const DEFAULT_URL = storybookUrl('ui-react-ui-canvas-scene-sceneview--default', PORT);
18
- const FREEHAND_URL = storybookUrl('ui-react-ui-canvas-scene-sceneview--freehand', PORT);
17
+ const DEFAULT_URL = storybookUrl('ui-react-ui-canvas-sceneview--default', PORT);
18
+ const FREEHAND_URL = storybookUrl('ui-react-ui-canvas-sceneview--freehand', PORT);
19
19
 
20
20
  /** Long enough to read on playback; the recording is for a person, not a machine. */
21
21
  const BEAT = 900;
@@ -9,7 +9,7 @@ import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
9
9
  import { SceneManager } from './SceneManager.ts';
10
10
 
11
11
  const PORT = 9006;
12
- const LATTICE_URL = storybookUrl('ui-react-ui-canvas-scene-sceneview--lattice', PORT);
12
+ const LATTICE_URL = storybookUrl('ui-react-ui-canvas-sceneview--lattice', PORT);
13
13
 
14
14
  // The fixture, on the default 256x128 lattice with 128x64 gutters, columns and rows -1..1: A, B, C down
15
15
  // column -1; a free cell above D and E in column 0; F spanning all three rows of column 1.
@@ -37,15 +37,12 @@ test.describe('SceneView lattice', () => {
37
37
  await expect(page.getByTestId('lattice-grid')).toHaveCount(1);
38
38
  });
39
39
 
40
- test('the guides toggle hides and restores the page frame and the lattice cells', async () => {
41
- await expect(page.getByTestId('scene-frame')).toHaveCount(1);
40
+ test('the guides toggle hides and restores the lattice cells', async () => {
42
41
  await page.getByTestId('toolbar-guides').click();
43
42
  await expect(page.getByTestId('lattice-grid')).toHaveCount(0);
44
- await expect(page.getByTestId('scene-frame')).toHaveCount(0);
45
43
  await scene.focus();
46
44
  await page.keyboard.press(';');
47
45
  await expect(page.getByTestId('lattice-grid')).toHaveCount(1);
48
- await expect(page.getByTestId('scene-frame')).toHaveCount(1);
49
46
  });
50
47
 
51
48
  test('a drag onto occupied cells previews in red and is refused', async () => {
@@ -107,6 +104,8 @@ test.describe('SceneView lattice', () => {
107
104
  });
108
105
 
109
106
  test('dragging a face steps it one cell and leaves the opposite face where it was', async () => {
107
+ // Fit frames the shapes tightly, so step out to leave room below E for the drag.
108
+ await scene.zoomOut();
110
109
  await scene.clickNode('e');
111
110
  const before = await scene.box(scene.node('e'));
112
111
  // E's east neighbour is F, so grow downwards: the south face most of a row down.