@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
@@ -1,270 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import { RegistryContext } from '@effect/atom-react/RegistryContext';
6
- import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
7
- import { useResizeDetector } from 'react-resize-detector';
8
-
9
- import type * as Util from '@dxos/react-ui/Util';
10
- import { mx } from '@dxos/ui-theme';
11
-
12
- import { Ruler, TrackHeader } from './headers/index.ts';
13
- import { type PointerHandlers, attachPointerHandlers, attachWheelHandlers } from './input/index.ts';
14
- import { type OverlayStyle, type RenderCell, type StaticLayerStyle, drawCells, drawOverlay } from './render/index.ts';
15
- import type { CellGridAtoms } from './state/atoms.ts';
16
- import type { Cell, Headers, Row } from './state/types.ts';
17
-
18
- export type CellGridProps<T = unknown> = Util.ThemedClassName<
19
- PointerHandlers & {
20
- atoms: CellGridAtoms<T>;
21
- rows: ReadonlyArray<Row>;
22
- renderCell: RenderCell<T>;
23
- headers?: Partial<Headers> | false;
24
- staticStyle?: Partial<StaticLayerStyle>;
25
- overlayStyle?: Partial<OverlayStyle>;
26
- }
27
- >;
28
-
29
- const defaultHeaders: Headers = { left: 80, top: 24 };
30
-
31
- const defaultStaticStyle: StaticLayerStyle = {
32
- gridLine: 'rgba(128,128,128,0.25)',
33
- rowBand: 'rgba(128,128,128,0.06)',
34
- };
35
-
36
- const defaultOverlayStyle: OverlayStyle = {
37
- playhead: 'rgb(220, 38, 38)',
38
- selectionFill: 'rgba(59, 130, 246, 0.15)',
39
- selectionStroke: 'rgb(59, 130, 246)',
40
- };
41
-
42
- const setupCanvas = (canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D | null => {
43
- const dpr = window.devicePixelRatio || 1;
44
- canvas.width = Math.max(1, Math.floor(width * dpr));
45
- canvas.height = Math.max(1, Math.floor(height * dpr));
46
- canvas.style.width = `${width}px`;
47
- canvas.style.height = `${height}px`;
48
- const ctx = canvas.getContext('2d');
49
- if (!ctx) {
50
- return null;
51
- }
52
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
53
- return ctx;
54
- };
55
-
56
- /**
57
- * Canvas-based 2D grid where cells contain pluggable shapes. Suitable for music sequencers,
58
- * time-series data viz, and similar workloads at ~1k visible cells per viewport.
59
- */
60
- export const CellGrid = <T,>({
61
- atoms,
62
- rows,
63
- renderCell,
64
- headers: headersProp,
65
- staticStyle: staticStyleProp,
66
- overlayStyle: overlayStyleProp,
67
- classNames,
68
- onCellToggle,
69
- onSelectionCommit,
70
- onDrawUpdate,
71
- onDrawCommit,
72
- }: CellGridProps<T>) => {
73
- const registry = useContext(RegistryContext);
74
-
75
- const headers = useMemo<Headers>(() => {
76
- if (headersProp === false) {
77
- return { left: 0, top: 0 };
78
- }
79
- return { ...defaultHeaders, ...(headersProp ?? {}) };
80
- }, [headersProp]);
81
-
82
- const staticStyle = useMemo<StaticLayerStyle>(
83
- () => ({ ...defaultStaticStyle, ...(staticStyleProp ?? {}) }),
84
- [staticStyleProp],
85
- );
86
- const overlayStyle = useMemo<OverlayStyle>(
87
- () => ({ ...defaultOverlayStyle, ...(overlayStyleProp ?? {}) }),
88
- [overlayStyleProp],
89
- );
90
-
91
- const { ref: containerRef, width = 0, height = 0 } = useResizeDetector<HTMLDivElement>();
92
- const staticCanvasRef = useRef<HTMLCanvasElement>(null);
93
- const overlayCanvasRef = useRef<HTMLCanvasElement>(null);
94
- const overlayInputRef = useRef<HTMLDivElement>(null);
95
-
96
- const [staticCtx, setStaticCtx] = useState<CanvasRenderingContext2D | null>(null);
97
- const [overlayCtx, setOverlayCtx] = useState<CanvasRenderingContext2D | null>(null);
98
-
99
- // Track header rerenders for the React-side ruler/track-header.
100
- const [viewportState, setViewportState] = useState(() => registry.get(atoms.viewport));
101
-
102
- // Resize canvases on container resize.
103
- useEffect(() => {
104
- if (!width || !height) {
105
- return;
106
- }
107
- if (staticCanvasRef.current) {
108
- const ctx = setupCanvas(staticCanvasRef.current, width, height);
109
- setStaticCtx(ctx);
110
- }
111
- if (overlayCanvasRef.current) {
112
- const ctx = setupCanvas(overlayCanvasRef.current, width, height);
113
- setOverlayCtx(ctx);
114
- }
115
- }, [width, height]);
116
-
117
- // Mirror viewport into React state so headers re-render on scroll/zoom.
118
- useEffect(() => registry.subscribe(atoms.viewport, (next) => setViewportState(next)), [registry, atoms.viewport]);
119
-
120
- // Static-layer redraw on (cells, viewport, rows, headers, style, size) change.
121
- useEffect(() => {
122
- if (!staticCtx || !width || !height) {
123
- return;
124
- }
125
- let raf: number | null = null;
126
- const schedule = () => {
127
- if (raf !== null) {
128
- return;
129
- }
130
- raf = requestAnimationFrame(() => {
131
- raf = null;
132
- drawCells({
133
- ctx: staticCtx,
134
- size: { width, height },
135
- viewport: registry.get(atoms.viewport),
136
- headers,
137
- rows,
138
- cells: registry.get(atoms.cells),
139
- renderCell,
140
- style: staticStyle,
141
- });
142
- });
143
- };
144
- schedule();
145
- const unsubCells = registry.subscribe(atoms.cells, schedule);
146
- const unsubViewport = registry.subscribe(atoms.viewport, schedule);
147
- return () => {
148
- if (raf !== null) {
149
- cancelAnimationFrame(raf);
150
- }
151
- unsubCells();
152
- unsubViewport();
153
- };
154
- }, [staticCtx, width, height, registry, atoms.cells, atoms.viewport, headers, rows, renderCell, staticStyle]);
155
-
156
- // Overlay rAF loop while playhead or active selection drag.
157
- useEffect(() => {
158
- if (!overlayCtx || !width || !height) {
159
- return;
160
- }
161
- let raf: number | null = null;
162
- let stopped = false;
163
-
164
- const paint = () => {
165
- drawOverlay({
166
- ctx: overlayCtx,
167
- size: { width, height },
168
- viewport: registry.get(atoms.viewport),
169
- headers,
170
- selection: registry.get(atoms.selection),
171
- playhead: registry.get(atoms.playhead),
172
- style: overlayStyle,
173
- });
174
- };
175
-
176
- const isAnimating = () => registry.get(atoms.playhead) !== null;
177
-
178
- const loop = () => {
179
- if (stopped) {
180
- return;
181
- }
182
- paint();
183
- if (isAnimating()) {
184
- raf = requestAnimationFrame(loop);
185
- } else {
186
- raf = null;
187
- }
188
- };
189
-
190
- const kick = () => {
191
- paint();
192
- if (raf === null && isAnimating()) {
193
- raf = requestAnimationFrame(loop);
194
- }
195
- };
196
-
197
- kick();
198
- const unsubSelection = registry.subscribe(atoms.selection, () => paint());
199
- const unsubPlayhead = registry.subscribe(atoms.playhead, kick);
200
- const unsubViewport = registry.subscribe(atoms.viewport, () => paint());
201
-
202
- return () => {
203
- stopped = true;
204
- if (raf !== null) {
205
- cancelAnimationFrame(raf);
206
- }
207
- unsubSelection();
208
- unsubPlayhead();
209
- unsubViewport();
210
- };
211
- }, [overlayCtx, width, height, registry, atoms.selection, atoms.playhead, atoms.viewport, headers, overlayStyle]);
212
-
213
- // Input wiring. Keep the callbacks in a ref so the listener attachment is stable
214
- // across consumer re-renders — otherwise an in-progress drag is torn down when
215
- // the parent's onCellToggle identity changes (e.g. after the very mutation we
216
- // just triggered), and subsequent pointermove events see drag === null.
217
- const callbacksRef = useRef<PointerHandlers>({ onCellToggle, onSelectionCommit, onDrawUpdate, onDrawCommit });
218
- callbacksRef.current = { onCellToggle, onSelectionCommit, onDrawUpdate, onDrawCommit };
219
- useEffect(() => {
220
- const element = overlayInputRef.current;
221
- if (!element) {
222
- return;
223
- }
224
- const detachPointer = attachPointerHandlers(element, {
225
- registry,
226
- atoms,
227
- headers,
228
- handlers: {
229
- onCellToggle: (coord, mode) => callbacksRef.current.onCellToggle?.(coord, mode),
230
- onSelectionCommit: (range) => callbacksRef.current.onSelectionCommit?.(range),
231
- onDrawUpdate: (start, end) => callbacksRef.current.onDrawUpdate?.(start, end),
232
- onDrawCommit: (start, end) => callbacksRef.current.onDrawCommit?.(start, end),
233
- },
234
- });
235
- const detachWheel = attachWheelHandlers(element, { registry, atoms, headers });
236
- return () => {
237
- detachPointer();
238
- detachWheel();
239
- };
240
- }, [registry, atoms, headers]);
241
-
242
- return (
243
- <div ref={containerRef} className={mx('relative dx-fill overflow-hidden dx-base-surface', classNames)}>
244
- {/*
245
- Canvases are nudged up + left 1 CSS pixel so the gridlines (drawn at the TOP
246
- and LEFT edges of each cell) sit on top of the frozen header dividers — the
247
- TrackHeader's right border and bottom-row box-shadow, which both render 1px
248
- inside the box. Without this offset the canvas gridlines land 1px down/right
249
- of the header dividers and the columns read as misaligned.
250
- */}
251
- <canvas ref={staticCanvasRef} className='dx-cover pointer-events-none' style={{ top: -1, left: -1 }} />
252
- <canvas ref={overlayCanvasRef} className='dx-cover pointer-events-none' style={{ top: -1, left: -1 }} />
253
- <div
254
- ref={overlayInputRef}
255
- className='dx-cover touch-none'
256
- style={{ paddingLeft: headers.left, paddingTop: headers.top }}
257
- />
258
- {headers.top > 0 && <Ruler viewport={viewportState} headers={headers} width={width} />}
259
- {headers.left > 0 && <TrackHeader viewport={viewportState} headers={headers} rows={rows} height={height} />}
260
- {headers.top > 0 && headers.left > 0 && (
261
- <div
262
- className='absolute top-0 left-0 border-b border-r border-neutral-200 dark:border-neutral-700 bg-base-surface'
263
- style={{ width: headers.left, height: headers.top }}
264
- />
265
- )}
266
- </div>
267
- );
268
- };
269
-
270
- export type { Cell };
@@ -1,71 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import React, { useMemo } from 'react';
6
-
7
- import { mx } from '@dxos/ui-theme';
8
-
9
- import type { Headers, Viewport } from '../state/types.ts';
10
- import { cellWidth } from '../state/viewport.ts';
11
-
12
- export type RulerProps = {
13
- viewport: Viewport;
14
- headers: Headers;
15
- width: number;
16
- /** Number of columns to label between major ticks. */
17
- majorEvery?: number;
18
- classNames?: string;
19
- };
20
-
21
- /**
22
- * Frozen top ruler. Ticks reflect the current viewport scroll and zoom.
23
- */
24
- export const Ruler = ({ viewport, headers, width, majorEvery = 4, classNames }: RulerProps) => {
25
- // Clamp to >= 1 — a zero or negative `majorEvery` makes `col % majorEvery`
26
- // NaN / always-zero and breaks major-tick detection.
27
- const safeMajorEvery = Math.max(1, Math.floor(majorEvery));
28
- const ticks = useMemo(() => {
29
- const w = cellWidth(viewport);
30
- if (w < 1 || width <= headers.left) {
31
- return [];
32
- }
33
- const innerWidth = width - headers.left;
34
- const startCol = Math.floor(viewport.scrollX / w);
35
- const endCol = Math.ceil((viewport.scrollX + innerWidth) / w);
36
- const result: Array<{ col: number; x: number; major: boolean }> = [];
37
- for (let col = startCol; col <= endCol; col++) {
38
- result.push({
39
- col,
40
- x: headers.left + col * w - viewport.scrollX,
41
- major: col % safeMajorEvery === 0,
42
- });
43
- }
44
- return result;
45
- }, [viewport, headers.left, width, safeMajorEvery]);
46
-
47
- return (
48
- <div
49
- className={mx(
50
- 'absolute top-0 left-0 right-0 border-b border-neutral-200 dark:border-neutral-700 bg-base-surface select-none overflow-hidden',
51
- classNames,
52
- )}
53
- style={{ height: headers.top }}
54
- >
55
- {ticks.map(({ col, x, major }) => (
56
- <div
57
- key={col}
58
- className={mx(
59
- 'absolute top-0 bottom-0 text-[10px] text-neutral-500 dark:text-neutral-400',
60
- major
61
- ? 'border-l border-neutral-400 dark:border-neutral-500'
62
- : 'border-l border-neutral-200 dark:border-neutral-700',
63
- )}
64
- style={{ transform: `translateX(${x}px)` }}
65
- >
66
- {major ? <span className='absolute left-1 top-0'>{col}</span> : null}
67
- </div>
68
- ))}
69
- </div>
70
- );
71
- };
@@ -1,58 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import React from 'react';
6
-
7
- import { mx } from '@dxos/ui-theme';
8
-
9
- import type { Headers, Row, Viewport } from '../state/types.ts';
10
-
11
- export type TrackHeaderProps = {
12
- viewport: Viewport;
13
- headers: Headers;
14
- rows: ReadonlyArray<Row>;
15
- height: number;
16
- classNames?: string;
17
- };
18
-
19
- /**
20
- * Frozen left column listing row labels. Translates vertically in sync with viewport scroll.
21
- *
22
- * Row dividers and alternating shading intentionally MATCH the canvas — opaque borders
23
- * and opaque alternating fills make the labels look out of phase with the cell area
24
- * even when the y-positions align. We mirror the canvas's transparent-overlay model
25
- * here so the frozen column reads as a direct continuation of the grid.
26
- */
27
- export const TrackHeader = ({ viewport, headers, rows, height, classNames }: TrackHeaderProps) => {
28
- return (
29
- <div
30
- className={mx(
31
- 'absolute left-0 border-r border-neutral-200 dark:border-neutral-700 select-none overflow-hidden',
32
- classNames,
33
- )}
34
- style={{ top: headers.top, width: headers.left, height: Math.max(0, height - headers.top) }}
35
- >
36
- <div style={{ transform: `translateY(${-viewport.scrollY}px)` }}>
37
- {rows.map((row, index) => (
38
- <div
39
- key={row.id}
40
- className='flex items-center px-2 text-xs text-neutral-700 dark:text-neutral-300'
41
- style={{
42
- height: viewport.cellHeight,
43
- // Match the canvas's row-band: a translucent gray overlay on odd rows,
44
- // transparent on even rows. The container's overall background bleeds
45
- // through, so the labels stay legible in both themes.
46
- backgroundColor: index % 2 === 0 ? 'transparent' : 'rgba(128, 128, 128, 0.08)',
47
- // Match the canvas gridline color (rgba(128, 128, 128, 0.25)). Use a
48
- // half-pixel inset to keep crisp single-pixel rendering on retina.
49
- boxShadow: 'inset 0 -1px 0 rgba(128, 128, 128, 0.25)',
50
- }}
51
- >
52
- {row.label ?? row.id}
53
- </div>
54
- ))}
55
- </div>
56
- </div>
57
- );
58
- };
@@ -1,6 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- export * from './Ruler.tsx';
6
- export * from './TrackHeader.tsx';
@@ -1,9 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- export * from './CellGrid.tsx';
6
- export * from './headers/index.ts';
7
- export * from './input/index.ts';
8
- export * from './render/index.ts';
9
- export * from './state/index.ts';
@@ -1,6 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- export * from './pointer.ts';
6
- export * from './wheel.ts';
@@ -1,236 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import type * as Registry from 'effect/reactivity/AtomRegistry';
6
-
7
- import type { CellGridAtoms } from '../state/atoms.ts';
8
- import type { Cell, CellCoord, Headers, SelectionRange, Tool } from '../state/types.ts';
9
- import { cellKey, hitTestCell } from '../state/viewport.ts';
10
-
11
- /**
12
- * 'set' / 'unset' are idempotent — the receiver must add or remove the cell
13
- * regardless of current state. 'toggle' flips it. Drag operations always pick
14
- * a fixed mode (set or unset) based on the cell under the initial pointerdown
15
- * so the user paints a uniform stroke instead of flipping each cell.
16
- */
17
- export type ToggleMode = 'set' | 'unset' | 'toggle';
18
-
19
- export type PointerHandlers = {
20
- onCellToggle?: (coord: CellCoord, mode: ToggleMode) => void;
21
- onSelectionCommit?: (range: SelectionRange) => void;
22
- /** Called each time the draw cursor moves to a new cell (edit tool). */
23
- onDrawUpdate?: (startCoord: CellCoord, endCoord: CellCoord) => void;
24
- /** Called when the user releases after an edit-tool drag; caller commits the note. */
25
- onDrawCommit?: (startCoord: CellCoord, endCoord: CellCoord) => void;
26
- };
27
-
28
- type DragState =
29
- | { kind: 'toggle'; mode: 'set' | 'unset'; touched: Set<string> }
30
- | { kind: 'draw'; startCoord: CellCoord; endCoord: CellCoord }
31
- | { kind: 'select'; origin: CellCoord }
32
- | { kind: 'pan'; lastX: number; lastY: number };
33
-
34
- export type PointerControllerOptions<T> = {
35
- registry: Registry.AtomRegistry;
36
- atoms: CellGridAtoms<T>;
37
- headers: Headers;
38
- handlers: PointerHandlers;
39
- };
40
-
41
- /**
42
- * Attach pointer handlers to an element. Returns an unsubscribe.
43
- */
44
- export const attachPointerHandlers = <T>(
45
- element: HTMLElement,
46
- { registry, atoms, headers, handlers }: PointerControllerOptions<T>,
47
- ): (() => void) => {
48
- let drag: DragState | null = null;
49
-
50
- const local = (event: PointerEvent) => {
51
- const rect = element.getBoundingClientRect();
52
- return { x: event.clientX - rect.left, y: event.clientY - rect.top };
53
- };
54
-
55
- // setPointerCapture throws for synthetic / untrusted PointerEvents (e.g. those
56
- // dispatched by tests). Capture is a best-effort UX nicety — never let it abort
57
- // the click path.
58
- const tryCapture = (pointerId: number) => {
59
- try {
60
- element.setPointerCapture(pointerId);
61
- } catch {
62
- // Ignore — drag tracking still works without capture.
63
- }
64
- };
65
-
66
- const onPointerDown = (event: PointerEvent) => {
67
- // Middle-mouse or space-held pan: pan tool regardless of `tool` atom.
68
- if (event.button === 1 || (event.button === 0 && event.altKey)) {
69
- drag = { kind: 'pan', lastX: event.clientX, lastY: event.clientY };
70
- tryCapture(event.pointerId);
71
- event.preventDefault();
72
- return;
73
- }
74
-
75
- if (event.button !== 0) {
76
- return;
77
- }
78
-
79
- const viewport = registry.get(atoms.viewport);
80
- const point = local(event);
81
- const coord = hitTestCell(viewport, headers, point);
82
- if (!coord) {
83
- return;
84
- }
85
-
86
- const tool = registry.get(atoms.tool) as Tool;
87
- tryCapture(event.pointerId);
88
-
89
- switch (tool) {
90
- case 'toggle':
91
- case 'resize': {
92
- // Inspect the cells atom under the pointer to decide whether the gesture
93
- // is a paint (set) or an erase (unset). Subsequent drag movements apply the
94
- // same operation idempotently to every cell the cursor crosses.
95
- const cells = registry.get(atoms.cells) as ReadonlyMap<string, unknown>;
96
- const key = cellKey(coord.col, coord.row);
97
- const mode: 'set' | 'unset' = cells.has(key) ? 'unset' : 'set';
98
- handlers.onCellToggle?.(coord, mode);
99
- drag = { kind: 'toggle', mode, touched: new Set([key]) };
100
- break;
101
- }
102
- case 'edit': {
103
- // Start a draw gesture: track extent and fire preview/commit callbacks
104
- // instead of toggling individual cells. Constrained to the starting row.
105
- drag = { kind: 'draw', startCoord: coord, endCoord: coord };
106
- handlers.onDrawUpdate?.(coord, coord);
107
- break;
108
- }
109
- case 'delete': {
110
- // Always removes notes regardless of current cell state.
111
- const key = cellKey(coord.col, coord.row);
112
- handlers.onCellToggle?.(coord, 'unset');
113
- drag = { kind: 'toggle', mode: 'unset', touched: new Set([key]) };
114
- break;
115
- }
116
- case 'select': {
117
- drag = { kind: 'select', origin: coord };
118
- registry.set(atoms.selection, {
119
- range: { col0: coord.col, row0: coord.row, col1: coord.col, row1: coord.row },
120
- });
121
- break;
122
- }
123
- }
124
- };
125
-
126
- const onPointerMove = (event: PointerEvent) => {
127
- if (!drag) {
128
- return;
129
- }
130
- if (drag.kind === 'pan') {
131
- const dx = event.clientX - drag.lastX;
132
- const dy = event.clientY - drag.lastY;
133
- drag.lastX = event.clientX;
134
- drag.lastY = event.clientY;
135
- registry.update(atoms.viewport, (current) => ({
136
- ...current,
137
- scrollX: Math.max(0, current.scrollX - dx),
138
- scrollY: Math.max(0, current.scrollY - dy),
139
- }));
140
- return;
141
- }
142
-
143
- const viewport = registry.get(atoms.viewport);
144
- const coord = hitTestCell(viewport, headers, local(event));
145
- if (!coord) {
146
- return;
147
- }
148
-
149
- if (drag.kind === 'toggle') {
150
- // Apply the drag's chosen mode to every cell the cursor enters, across rows,
151
- // de-duplicating per cell so we don't fire the same coord twice.
152
- const key = cellKey(coord.col, coord.row);
153
- if (!drag.touched.has(key)) {
154
- drag.touched.add(key);
155
- handlers.onCellToggle?.(coord, drag.mode);
156
- }
157
- } else if (drag.kind === 'draw') {
158
- // Constrain to the starting row so the note stays at the same pitch.
159
- const constrainedCol = coord.col;
160
- if (constrainedCol !== drag.endCoord.col) {
161
- drag.endCoord = { col: constrainedCol, row: drag.startCoord.row };
162
- handlers.onDrawUpdate?.(drag.startCoord, drag.endCoord);
163
- }
164
- } else if (drag.kind === 'select') {
165
- registry.set(atoms.selection, {
166
- range: { col0: drag.origin.col, row0: drag.origin.row, col1: coord.col, row1: coord.row },
167
- });
168
- }
169
- };
170
-
171
- const releaseCapture = (event: PointerEvent) => {
172
- if (element.hasPointerCapture(event.pointerId)) {
173
- element.releasePointerCapture(event.pointerId);
174
- }
175
- };
176
-
177
- const onPointerUp = (event: PointerEvent) => {
178
- if (!drag) {
179
- return;
180
- }
181
- if (drag.kind === 'draw') {
182
- handlers.onDrawCommit?.(drag.startCoord, drag.endCoord);
183
- } else if (drag.kind === 'select') {
184
- const range = registry.get(atoms.selection).range;
185
- if (range) {
186
- handlers.onSelectionCommit?.(range);
187
- }
188
- }
189
- drag = null;
190
- releaseCapture(event);
191
- };
192
-
193
- // pointercancel signals an interrupted gesture (system gesture, palm rejection,
194
- // capture lost). Per the Pointer Events spec this should clean up local state
195
- // ONLY and not be treated as a successful completion — so we never commit a
196
- // selection from cancel.
197
- const onPointerCancel = (event: PointerEvent) => {
198
- drag = null;
199
- releaseCapture(event);
200
- };
201
-
202
- element.addEventListener('pointerdown', onPointerDown);
203
- element.addEventListener('pointermove', onPointerMove);
204
- element.addEventListener('pointerup', onPointerUp);
205
- element.addEventListener('pointercancel', onPointerCancel);
206
-
207
- return () => {
208
- element.removeEventListener('pointerdown', onPointerDown);
209
- element.removeEventListener('pointermove', onPointerMove);
210
- element.removeEventListener('pointerup', onPointerUp);
211
- element.removeEventListener('pointercancel', onPointerCancel);
212
- };
213
- };
214
-
215
- /**
216
- * Utility for consumers: toggle, set, or unset membership of a cell in the cells atom.
217
- */
218
- export const toggleCell = <T>(
219
- registry: Registry.AtomRegistry,
220
- atoms: CellGridAtoms<T>,
221
- coord: CellCoord,
222
- factory: (coord: CellCoord) => Cell<T>,
223
- mode: ToggleMode = 'toggle',
224
- ): void => {
225
- registry.update(atoms.cells, (current) => {
226
- const next = new Map(current);
227
- const key = cellKey(coord.col, coord.row);
228
- const exists = next.has(key);
229
- if (mode === 'set' || (mode === 'toggle' && !exists)) {
230
- next.set(key, factory(coord));
231
- } else if (mode === 'unset' || (mode === 'toggle' && exists)) {
232
- next.delete(key);
233
- }
234
- return next;
235
- });
236
- };