@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.
- package/README.md +1 -1
- package/dist/lib/index.mjs +9044 -1135
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/components/About/About.d.ts +13 -0
- package/dist/types/src/components/About/About.d.ts.map +1 -0
- package/dist/types/src/components/About/About.stories.d.ts +7 -0
- package/dist/types/src/components/About/About.stories.d.ts.map +1 -0
- package/dist/types/src/components/About/index.d.ts +2 -0
- package/dist/types/src/components/About/index.d.ts.map +1 -0
- package/dist/types/src/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/types/src/components/ControlFrame/ControlFrame.d.ts +8 -2
- package/dist/types/src/components/ControlFrame/ControlFrame.d.ts.map +1 -1
- package/dist/types/src/components/Layers/Layers.d.ts +30 -0
- package/dist/types/src/components/Layers/Layers.d.ts.map +1 -0
- package/dist/types/src/components/Layers/Layers.stories.d.ts +11 -0
- package/dist/types/src/components/Layers/Layers.stories.d.ts.map +1 -0
- package/dist/types/src/components/Layers/index.d.ts +2 -0
- package/dist/types/src/components/Layers/index.d.ts.map +1 -0
- package/dist/types/src/components/Palette/Palette.d.ts +20 -4
- package/dist/types/src/components/Palette/Palette.d.ts.map +1 -1
- package/dist/types/src/components/Palette/Palette.stories.d.ts +22 -0
- package/dist/types/src/components/Palette/Palette.stories.d.ts.map +1 -0
- package/dist/types/src/components/PartEditor/PartEditor.d.ts.map +1 -1
- package/dist/types/src/components/Properties/AlignField.d.ts +23 -0
- package/dist/types/src/components/Properties/AlignField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/ClassField.d.ts +23 -0
- package/dist/types/src/components/Properties/ClassField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/FontField.d.ts +7 -0
- package/dist/types/src/components/Properties/FontField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/LayerField.d.ts +17 -0
- package/dist/types/src/components/Properties/LayerField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/Properties.d.ts +27 -3
- package/dist/types/src/components/Properties/Properties.d.ts.map +1 -1
- package/dist/types/src/components/Properties/Properties.stories.d.ts +20 -0
- package/dist/types/src/components/Properties/Properties.stories.d.ts.map +1 -0
- package/dist/types/src/components/Properties/SceneField.d.ts +8 -0
- package/dist/types/src/components/Properties/SceneField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/StyleGrid.d.ts +15 -5
- package/dist/types/src/components/Properties/StyleGrid.d.ts.map +1 -1
- package/dist/types/src/components/Properties/StyleGrid.stories.d.ts +7 -0
- package/dist/types/src/components/Properties/StyleGrid.stories.d.ts.map +1 -0
- package/dist/types/src/components/SceneLayer/SceneLayer.d.ts +31 -7
- package/dist/types/src/components/SceneLayer/SceneLayer.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/Dock.d.ts +22 -0
- package/dist/types/src/components/SceneView/Dock.d.ts.map +1 -0
- package/dist/types/src/components/SceneView/Dock.stories.d.ts +15 -0
- package/dist/types/src/components/SceneView/Dock.stories.d.ts.map +1 -0
- package/dist/types/src/components/SceneView/SceneView.d.ts +33 -7
- package/dist/types/src/components/SceneView/SceneView.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/SceneView.stories.d.ts +16 -6
- package/dist/types/src/components/SceneView/SceneView.stories.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/SceneViewContext.d.ts +36 -4
- package/dist/types/src/components/SceneView/SceneViewContext.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/Scored.stories.d.ts +4 -4
- package/dist/types/src/components/SceneView/Scored.stories.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/usePointerMachine.d.ts +3 -1
- package/dist/types/src/components/SceneView/usePointerMachine.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneCamera.d.ts +2 -2
- package/dist/types/src/components/SceneView/useSceneCamera.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneClipboard.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneKeys.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneNavigation.d.ts +7 -4
- package/dist/types/src/components/SceneView/useSceneNavigation.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +2 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useWheel.d.ts +5 -0
- package/dist/types/src/hooks/useWheel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +4 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/atoms.d.ts +21 -3
- package/dist/types/src/model/atoms.d.ts.map +1 -1
- package/dist/types/src/model/projection.d.ts.map +1 -1
- package/dist/types/src/model/projections/constrained.d.ts +2 -1
- package/dist/types/src/model/projections/constrained.d.ts.map +1 -1
- package/dist/types/src/model/projections/dynamic.d.ts.map +1 -1
- package/dist/types/src/model/projections/lattice.d.ts.map +1 -1
- package/dist/types/src/model/registry.d.ts +21 -3
- package/dist/types/src/model/registry.d.ts.map +1 -1
- package/dist/types/src/model/store.d.ts +10 -2
- package/dist/types/src/model/store.d.ts.map +1 -1
- package/dist/types/src/model/types.d.ts +699 -338
- package/dist/types/src/model/types.d.ts.map +1 -1
- package/dist/types/src/playwright/SceneManager.d.ts +1 -1
- package/dist/types/src/playwright/SceneManager.d.ts.map +1 -1
- package/dist/types/src/utils/builder.d.ts +22 -41
- package/dist/types/src/utils/builder.d.ts.map +1 -1
- package/dist/types/src/utils/camera.d.ts +19 -10
- package/dist/types/src/utils/camera.d.ts.map +1 -1
- package/dist/types/src/utils/clipboard.d.ts +4 -2
- package/dist/types/src/utils/clipboard.d.ts.map +1 -1
- package/dist/types/src/utils/diagram.d.ts.map +1 -1
- package/dist/types/src/utils/group.d.ts +14 -0
- package/dist/types/src/utils/group.d.ts.map +1 -0
- package/dist/types/src/utils/group.test.d.ts +2 -0
- package/dist/types/src/utils/group.test.d.ts.map +1 -0
- package/dist/types/src/utils/gutter-route.d.ts +7 -0
- package/dist/types/src/utils/gutter-route.d.ts.map +1 -1
- package/dist/types/src/utils/hit.d.ts +3 -20
- package/dist/types/src/utils/hit.d.ts.map +1 -1
- package/dist/types/src/utils/index.d.ts +3 -0
- package/dist/types/src/utils/index.d.ts.map +1 -1
- package/dist/types/src/utils/lattice.d.ts +5 -0
- package/dist/types/src/utils/lattice.d.ts.map +1 -1
- package/dist/types/src/utils/layers.d.ts +35 -0
- package/dist/types/src/utils/layers.d.ts.map +1 -0
- package/dist/types/src/utils/layers.test.d.ts +2 -0
- package/dist/types/src/utils/layers.test.d.ts.map +1 -0
- package/dist/types/src/utils/parts.d.ts +25 -9
- package/dist/types/src/utils/parts.d.ts.map +1 -1
- package/dist/types/src/utils/ports.d.ts +2 -0
- package/dist/types/src/utils/ports.d.ts.map +1 -1
- package/dist/types/src/utils/properties.d.ts +4 -3
- package/dist/types/src/utils/properties.d.ts.map +1 -1
- package/dist/types/src/utils/route.d.ts +5 -0
- package/dist/types/src/utils/route.d.ts.map +1 -1
- package/dist/types/src/utils/scenes.d.ts +14 -0
- package/dist/types/src/utils/scenes.d.ts.map +1 -0
- package/dist/types/src/utils/scenes.test.d.ts +2 -0
- package/dist/types/src/utils/scenes.test.d.ts.map +1 -0
- package/dist/types/src/utils/shapes.d.ts +23 -9
- package/dist/types/src/utils/shapes.d.ts.map +1 -1
- package/dist/types/src/utils/style.d.ts +29 -6
- package/dist/types/src/utils/style.d.ts.map +1 -1
- package/dist/types/src/utils/style.test.d.ts +2 -0
- package/dist/types/src/utils/style.test.d.ts.map +1 -0
- package/dist/types/src/utils/testing.d.ts +2 -2
- package/dist/types/src/utils/testing.d.ts.map +1 -1
- package/dist/types/src/utils/undo.d.ts +15 -4
- package/dist/types/src/utils/undo.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -26
- package/src/components/About/About.stories.tsx +32 -0
- package/src/components/About/About.tsx +38 -0
- package/src/components/About/index.ts +5 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +17 -3
- package/src/components/ControlFrame/ControlFrame.tsx +138 -82
- package/src/components/Grid/Grid.stories.tsx +1 -1
- package/src/components/Layers/Layers.stories.tsx +131 -0
- package/src/components/Layers/Layers.tsx +216 -0
- package/src/components/Layers/index.ts +5 -0
- package/src/components/Palette/Palette.stories.tsx +135 -0
- package/src/components/Palette/Palette.tsx +230 -39
- package/src/components/PartEditor/PartEditor.tsx +4 -3
- package/src/components/Properties/AlignField.tsx +117 -0
- package/src/components/Properties/ClassField.tsx +44 -0
- package/src/components/Properties/FontField.tsx +73 -0
- package/src/components/Properties/LayerField.tsx +61 -0
- package/src/components/Properties/Properties.stories.tsx +113 -0
- package/src/components/Properties/Properties.tsx +240 -60
- package/src/components/Properties/SceneField.tsx +22 -0
- package/src/components/Properties/StyleGrid.stories.tsx +50 -0
- package/src/components/Properties/StyleGrid.tsx +54 -26
- package/src/components/SceneLayer/SceneLayer.tsx +497 -188
- package/src/components/SceneView/Constrained.stories.tsx +1 -1
- package/src/components/SceneView/Dock.stories.tsx +95 -0
- package/src/components/SceneView/Dock.tsx +146 -0
- package/src/components/SceneView/Dynamic.stories.tsx +1 -1
- package/src/components/SceneView/SceneView.stories.tsx +170 -18
- package/src/components/SceneView/SceneView.tsx +565 -151
- package/src/components/SceneView/SceneViewContext.ts +23 -2
- package/src/components/SceneView/Scored.stories.tsx +11 -11
- package/src/components/SceneView/usePointerMachine.ts +80 -20
- package/src/components/SceneView/useSceneCamera.ts +15 -8
- package/src/components/SceneView/useSceneClipboard.ts +4 -0
- package/src/components/SceneView/useSceneKeys.ts +6 -4
- package/src/components/SceneView/useSceneNavigation.ts +44 -33
- package/src/components/Toolbar/Toolbar.stories.tsx +14 -15
- package/src/components/Toolbar/Toolbar.tsx +15 -4
- package/src/components/index.ts +2 -0
- package/src/hooks/useWheel.ts +35 -0
- package/src/index.ts +4 -3
- package/src/model/atoms.ts +27 -3
- package/src/model/projection.ts +5 -0
- package/src/model/projections/constrained.test.ts +18 -10
- package/src/model/projections/constrained.ts +19 -8
- package/src/model/projections/dynamic.ts +5 -2
- package/src/model/projections/lattice.ts +15 -3
- package/src/model/registry.test.ts +14 -9
- package/src/model/registry.ts +39 -34
- package/src/model/store.ts +12 -2
- package/src/model/types.ts +124 -36
- package/src/playwright/SceneManager.ts +2 -2
- package/src/playwright/create.spec.ts +5 -3
- package/src/playwright/demo.spec.ts +2 -2
- package/src/playwright/lattice.spec.ts +4 -5
- package/src/playwright/scene.spec.ts +27 -25
- package/src/utils/builder.test.ts +46 -9
- package/src/utils/builder.ts +42 -18
- package/src/utils/camera.test.ts +22 -51
- package/src/utils/camera.ts +39 -32
- package/src/utils/clipboard.test.ts +23 -2
- package/src/utils/clipboard.ts +8 -4
- package/src/utils/diagram.test.ts +3 -1
- package/src/utils/diagram.ts +3 -25
- package/src/utils/group.test.ts +69 -0
- package/src/utils/group.ts +94 -0
- package/src/utils/gutter-route.test.ts +49 -0
- package/src/utils/gutter-route.ts +142 -16
- package/src/utils/hit.test.ts +4 -38
- package/src/utils/hit.ts +4 -26
- package/src/utils/index.ts +3 -0
- package/src/utils/lattice.ts +7 -1
- package/src/utils/layers.test.ts +139 -0
- package/src/utils/layers.ts +175 -0
- package/src/utils/parts.test.ts +66 -32
- package/src/utils/parts.ts +63 -41
- package/src/utils/ports.ts +7 -4
- package/src/utils/properties.test.ts +23 -6
- package/src/utils/properties.ts +66 -16
- package/src/utils/route.test.ts +51 -2
- package/src/utils/route.ts +133 -23
- package/src/utils/scenes.test.ts +38 -0
- package/src/utils/scenes.ts +54 -0
- package/src/utils/shapes.test.ts +33 -8
- package/src/utils/shapes.ts +51 -35
- package/src/utils/style.test.ts +54 -0
- package/src/utils/style.ts +153 -40
- package/src/utils/testing.test.ts +3 -3
- package/src/utils/testing.ts +21 -30
- package/src/utils/undo.test.ts +25 -2
- package/src/utils/undo.ts +50 -13
- package/dist/lib/chunk-Grid.mjs +0 -99
- package/dist/lib/chunk-Grid.mjs.map +0 -1
- package/dist/lib/scene.mjs +0 -6835
- package/dist/lib/scene.mjs.map +0 -1
- package/dist/lib/types.mjs +0 -16
- package/dist/lib/types.mjs.map +0 -1
- package/dist/types/src/archive/components/Canvas/Canvas.d.ts +0 -15
- package/dist/types/src/archive/components/Canvas/Canvas.d.ts.map +0 -1
- package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts +0 -18
- package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts.map +0 -1
- package/dist/types/src/archive/components/Canvas/index.d.ts +0 -2
- package/dist/types/src/archive/components/Canvas/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts +0 -15
- package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts +0 -19
- package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/headers/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/index.d.ts +0 -6
- package/dist/types/src/archive/components/CellGrid/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/input/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts +0 -33
- package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts +0 -14
- package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/render/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts +0 -36
- package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts +0 -23
- package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/index.d.ts +0 -4
- package/dist/types/src/archive/components/CellGrid/state/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/types.d.ts +0 -39
- package/dist/types/src/archive/components/CellGrid/state/types.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts +0 -52
- package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts +0 -2
- package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts.map +0 -1
- package/dist/types/src/archive/components/FPS.d.ts +0 -9
- package/dist/types/src/archive/components/FPS.d.ts.map +0 -1
- package/dist/types/src/archive/components/Grid/Grid.d.ts +0 -6
- package/dist/types/src/archive/components/Grid/Grid.d.ts.map +0 -1
- package/dist/types/src/archive/components/Grid/index.d.ts +0 -2
- package/dist/types/src/archive/components/Grid/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/index.d.ts +0 -5
- package/dist/types/src/archive/components/index.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/index.d.ts +0 -5
- package/dist/types/src/archive/hooks/index.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/projection.d.ts +0 -60
- package/dist/types/src/archive/hooks/projection.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useCanvasContext.d.ts +0 -13
- package/dist/types/src/archive/hooks/useCanvasContext.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useDrag.d.ts +0 -6
- package/dist/types/src/archive/hooks/useDrag.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useWheel.d.ts +0 -8
- package/dist/types/src/archive/hooks/useWheel.d.ts.map +0 -1
- package/dist/types/src/archive/index.d.ts +0 -5
- package/dist/types/src/archive/index.d.ts.map +0 -1
- package/dist/types/src/archive/types.d.ts +0 -19
- package/dist/types/src/archive/types.d.ts.map +0 -1
- package/dist/types/src/archive/util/index.d.ts +0 -4
- package/dist/types/src/archive/util/index.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg-path.d.ts +0 -4
- package/dist/types/src/archive/util/svg-path.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg.d.ts +0 -32
- package/dist/types/src/archive/util/svg.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg.stories.d.ts +0 -15
- package/dist/types/src/archive/util/svg.stories.d.ts.map +0 -1
- package/dist/types/src/archive/util/util.d.ts +0 -17
- package/dist/types/src/archive/util/util.d.ts.map +0 -1
- package/dist/types/src/scene.d.ts +0 -5
- package/dist/types/src/scene.d.ts.map +0 -1
- package/src/archive/components/Canvas/Canvas.stories.tsx +0 -111
- package/src/archive/components/Canvas/Canvas.tsx +0 -87
- package/src/archive/components/Canvas/index.ts +0 -5
- package/src/archive/components/CellGrid/CellGrid.stories.tsx +0 -239
- package/src/archive/components/CellGrid/CellGrid.tsx +0 -270
- package/src/archive/components/CellGrid/headers/Ruler.tsx +0 -71
- package/src/archive/components/CellGrid/headers/TrackHeader.tsx +0 -58
- package/src/archive/components/CellGrid/headers/index.ts +0 -6
- package/src/archive/components/CellGrid/index.ts +0 -9
- package/src/archive/components/CellGrid/input/index.ts +0 -6
- package/src/archive/components/CellGrid/input/pointer.ts +0 -236
- package/src/archive/components/CellGrid/input/wheel.ts +0 -68
- package/src/archive/components/CellGrid/render/index.ts +0 -6
- package/src/archive/components/CellGrid/render/overlay-layer.ts +0 -66
- package/src/archive/components/CellGrid/render/static-layer.ts +0 -112
- package/src/archive/components/CellGrid/state/atoms.ts +0 -43
- package/src/archive/components/CellGrid/state/index.ts +0 -7
- package/src/archive/components/CellGrid/state/types.ts +0 -40
- package/src/archive/components/CellGrid/state/viewport.test.ts +0 -50
- package/src/archive/components/CellGrid/state/viewport.ts +0 -94
- package/src/archive/components/FPS.tsx +0 -98
- package/src/archive/components/Grid/Grid.tsx +0 -17
- package/src/archive/components/Grid/index.ts +0 -5
- package/src/archive/components/index.ts +0 -8
- package/src/archive/hooks/index.ts +0 -8
- package/src/archive/hooks/projection.tsx +0 -156
- package/src/archive/hooks/useCanvasContext.ts +0 -32
- package/src/archive/hooks/useDrag.tsx +0 -96
- package/src/archive/hooks/useWheel.tsx +0 -80
- package/src/archive/index.ts +0 -8
- package/src/archive/types.ts +0 -14
- package/src/archive/util/index.ts +0 -7
- package/src/archive/util/svg-path.ts +0 -13
- package/src/archive/util/svg.stories.tsx +0 -46
- package/src/archive/util/svg.tsx +0 -128
- package/src/archive/util/util.ts +0 -50
- package/src/scene.ts +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "A canvas component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -19,14 +19,9 @@
|
|
|
19
19
|
"import": "./dist/lib/index.mjs"
|
|
20
20
|
},
|
|
21
21
|
"./scene": {
|
|
22
|
-
"source": "./src/
|
|
23
|
-
"types": "./dist/types/src/
|
|
24
|
-
"import": "./dist/lib/
|
|
25
|
-
},
|
|
26
|
-
"./types": {
|
|
27
|
-
"source": "./src/archive/types.ts",
|
|
28
|
-
"types": "./dist/types/src/archive/types.d.ts",
|
|
29
|
-
"import": "./dist/lib/types.mjs"
|
|
22
|
+
"source": "./src/index.ts",
|
|
23
|
+
"types": "./dist/types/src/index.d.ts",
|
|
24
|
+
"import": "./dist/lib/index.mjs"
|
|
30
25
|
}
|
|
31
26
|
},
|
|
32
27
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -39,19 +34,16 @@
|
|
|
39
34
|
"@codemirror/state": "^6.7.6",
|
|
40
35
|
"@codemirror/view": "^6.43.13",
|
|
41
36
|
"@effect/atom-react": "4.0.0",
|
|
42
|
-
"bind-event-listener": "^3.0.0",
|
|
43
37
|
"d3": "^7.9.0",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/ui-editor": "0.13.0",
|
|
54
|
-
"@dxos/ui-types": "0.13.0"
|
|
38
|
+
"@dxos/diagram": "0.14.0",
|
|
39
|
+
"@dxos/echo": "0.14.0",
|
|
40
|
+
"@dxos/effect": "0.14.0",
|
|
41
|
+
"@dxos/react-hooks": "0.14.0",
|
|
42
|
+
"@dxos/react-ui-editor": "0.14.0",
|
|
43
|
+
"@dxos/react-ui-form": "0.14.0",
|
|
44
|
+
"@dxos/react-ui-list": "0.14.0",
|
|
45
|
+
"@dxos/ui-editor": "0.14.0",
|
|
46
|
+
"@dxos/ui-types": "0.14.0"
|
|
55
47
|
},
|
|
56
48
|
"devDependencies": {
|
|
57
49
|
"@playwright/test": "1.63.0",
|
|
@@ -63,16 +55,18 @@
|
|
|
63
55
|
"react": "~19.2.7",
|
|
64
56
|
"react-dom": "~19.2.7",
|
|
65
57
|
"vite": "^8.2.2",
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/ui-
|
|
58
|
+
"@dxos/log": "0.14.0",
|
|
59
|
+
"@dxos/react-ui": "0.14.0",
|
|
60
|
+
"@dxos/react-ui-syntax-highlighter": "0.14.0",
|
|
61
|
+
"@dxos/test-utils": "0.14.0",
|
|
62
|
+
"@dxos/ui-theme": "0.14.0"
|
|
69
63
|
},
|
|
70
64
|
"peerDependencies": {
|
|
71
65
|
"effect": "4.0.0",
|
|
72
66
|
"react": "~19.2.7",
|
|
73
67
|
"react-dom": "~19.2.7",
|
|
74
|
-
"@dxos/react-ui": "^0.
|
|
75
|
-
"@dxos/ui-theme": "^0.
|
|
68
|
+
"@dxos/react-ui": "^0.14.0",
|
|
69
|
+
"@dxos/ui-theme": "^0.14.0"
|
|
76
70
|
},
|
|
77
71
|
"publishConfig": {
|
|
78
72
|
"access": "public"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
|
|
7
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
8
|
+
import { translations as uiTranslations } from '@dxos/react-ui/translations';
|
|
9
|
+
|
|
10
|
+
import { About } from './About.tsx';
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof About> = {
|
|
13
|
+
title: 'ui/react-ui-canvas/About',
|
|
14
|
+
component: About,
|
|
15
|
+
decorators: [withTheme(), withLayout({ layout: 'centered' })],
|
|
16
|
+
parameters: { translations: uiTranslations },
|
|
17
|
+
args: {
|
|
18
|
+
classNames: 'w-80',
|
|
19
|
+
stats: [
|
|
20
|
+
{ id: 'nodes', label: 'Nodes', value: 12 },
|
|
21
|
+
{ id: 'links', label: 'Links', value: 9 },
|
|
22
|
+
{ id: 'layers', label: 'Layers', value: 2 },
|
|
23
|
+
{ id: 'zoom', label: 'Zoom', value: '100%' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
|
|
30
|
+
type Story = StoryObj<typeof meta>;
|
|
31
|
+
|
|
32
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { Fragment, type ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
import * as Layout from '@dxos/react-ui/Layout';
|
|
8
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
9
|
+
import * as Typography from '@dxos/react-ui/Typography';
|
|
10
|
+
import type * as Util from '@dxos/react-ui/Util';
|
|
11
|
+
|
|
12
|
+
export type AboutStat = { id: string; label: string; value: ReactNode };
|
|
13
|
+
|
|
14
|
+
export type AboutProps = Util.ThemedClassName<{
|
|
15
|
+
stats: readonly AboutStat[];
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
/** A two-column table of the drawing's figures, one per list-height row: a label and its value. */
|
|
19
|
+
export const About = ({ classNames, stats }: AboutProps) => (
|
|
20
|
+
<Panel.Root classNames={classNames} data-testid='about'>
|
|
21
|
+
<Panel.Body>
|
|
22
|
+
<Layout.Container>
|
|
23
|
+
<Layout.Grid cols={['fill', 'auto']}>
|
|
24
|
+
{stats.map(({ id, label, value }) => (
|
|
25
|
+
<Fragment key={id}>
|
|
26
|
+
<Typography.Text tone='muted' truncate>
|
|
27
|
+
{label}
|
|
28
|
+
</Typography.Text>
|
|
29
|
+
<Typography.Text classNames='text-end tabular-nums' data-testid={`about-${id}`}>
|
|
30
|
+
{value}
|
|
31
|
+
</Typography.Text>
|
|
32
|
+
</Fragment>
|
|
33
|
+
))}
|
|
34
|
+
</Layout.Grid>
|
|
35
|
+
</Layout.Container>
|
|
36
|
+
</Panel.Body>
|
|
37
|
+
</Panel.Root>
|
|
38
|
+
);
|
|
@@ -21,9 +21,23 @@ export const Breadcrumbs = ({ path, nameOf, onSelect }: BreadcrumbsProps) => (
|
|
|
21
21
|
{path.map((id, index) => (
|
|
22
22
|
<Fragment key={`${index}:${id}`}>
|
|
23
23
|
{index > 0 && <span className='text-fg-subtle'>›</span>}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
{/* The root is home whatever its scene is called. */}
|
|
25
|
+
{index === 0 ? (
|
|
26
|
+
<Button.Root
|
|
27
|
+
variant='ghost'
|
|
28
|
+
size='sm'
|
|
29
|
+
iconOnly
|
|
30
|
+
icon='ph--house--regular'
|
|
31
|
+
label={nameOf(id)}
|
|
32
|
+
disabled={index === path.length - 1}
|
|
33
|
+
data-testid='breadcrumb-root'
|
|
34
|
+
onClick={() => onSelect(index)}
|
|
35
|
+
/>
|
|
36
|
+
) : (
|
|
37
|
+
<Button.Root variant='ghost' size='sm' disabled={index === path.length - 1} onClick={() => onSelect(index)}>
|
|
38
|
+
{nameOf(id)}
|
|
39
|
+
</Button.Root>
|
|
40
|
+
)}
|
|
27
41
|
</Fragment>
|
|
28
42
|
))}
|
|
29
43
|
</nav>
|
|
@@ -12,7 +12,7 @@ import React, { memo } from 'react';
|
|
|
12
12
|
|
|
13
13
|
import { mx } from '@dxos/ui-theme';
|
|
14
14
|
|
|
15
|
-
import { type ControlPointRef, type Drag, type Handle } from '../../model/atoms.ts';
|
|
15
|
+
import { type ControlPointRef, type Drag, type Handle, isMoving } from '../../model/atoms.ts';
|
|
16
16
|
import { nodeDef } from '../../model/node-def.ts';
|
|
17
17
|
import { type NodeRegistry } from '../../model/registry.ts';
|
|
18
18
|
import {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ElementId,
|
|
22
22
|
type Endpoint,
|
|
23
23
|
type Link,
|
|
24
|
+
type LinkId,
|
|
24
25
|
type Node,
|
|
25
26
|
type Point,
|
|
26
27
|
type Port,
|
|
@@ -63,11 +64,17 @@ export type ControlFrameProps = {
|
|
|
63
64
|
capabilities: Capabilities;
|
|
64
65
|
selection: ReadonlySet<ElementId>;
|
|
65
66
|
hover?: ElementId;
|
|
67
|
+
/** The link under the pointer: it shows its end handles, as a selected link does. */
|
|
68
|
+
hoveredLink?: LinkId;
|
|
69
|
+
/** The pointer entered or left a hovered link's handles, which keeps the link hovered while on them. */
|
|
70
|
+
onLinkHover?: (id: LinkId | undefined) => void;
|
|
66
71
|
selectedPoint?: ControlPointRef;
|
|
67
72
|
zoom: number;
|
|
68
73
|
drag?: Drag;
|
|
69
74
|
/** The bounds a create gesture in flight would land, drawn as a provisional frame. */
|
|
70
75
|
createFrame?: Bounds;
|
|
76
|
+
/** Where the nodes of a move in flight will land, drawn under them as they follow the pointer. */
|
|
77
|
+
landing?: readonly Bounds[];
|
|
71
78
|
/** The gesture in flight would be refused (an overlap on the lattice): its outlines turn red. */
|
|
72
79
|
blocked?: boolean;
|
|
73
80
|
/** The scene's lattice, so a selected smart link's handles sit on its gutter route. */
|
|
@@ -97,10 +104,13 @@ export const ControlFrame = memo(
|
|
|
97
104
|
capabilities,
|
|
98
105
|
selection,
|
|
99
106
|
hover,
|
|
107
|
+
hoveredLink,
|
|
108
|
+
onLinkHover,
|
|
100
109
|
selectedPoint,
|
|
101
110
|
zoom,
|
|
102
111
|
drag,
|
|
103
112
|
createFrame,
|
|
113
|
+
landing,
|
|
104
114
|
blocked,
|
|
105
115
|
lattice,
|
|
106
116
|
onHandlePointerDown,
|
|
@@ -114,10 +124,14 @@ export const ControlFrame = memo(
|
|
|
114
124
|
const handleSize = 8 * unit;
|
|
115
125
|
const portRadius = 5 * unit;
|
|
116
126
|
const midpointRadius = 4 * unit;
|
|
117
|
-
|
|
118
|
-
const
|
|
127
|
+
// A move in flight shows the shapes themselves: no outlines or handles, just where they will land.
|
|
128
|
+
const chrome = isMoving(drag) ? [] : [...selection];
|
|
129
|
+
const selectedNodes = chrome.map((id) => scene.nodes[id]).filter((node) => node !== undefined);
|
|
130
|
+
const selectedLinks = chrome.map((id) => scene.links[id]).filter((link) => link !== undefined);
|
|
131
|
+
const linkUnder = hoveredLink && !selection.has(hoveredLink) ? scene.links[hoveredLink] : undefined;
|
|
132
|
+
// A hovered link shows the handles of a selected one; only its ends, its control points stay a selection's.
|
|
119
133
|
const lanes =
|
|
120
|
-
lattice && selectedLinks.length > 0
|
|
134
|
+
lattice && (selectedLinks.length > 0 || linkUnder !== undefined)
|
|
121
135
|
? new Map(
|
|
122
136
|
sceneLinkGeometry(scene, registry, Object.values(scene.links), lattice).map((geometry) => [
|
|
123
137
|
geometry.link.id,
|
|
@@ -141,6 +155,21 @@ export const ControlFrame = memo(
|
|
|
141
155
|
if (dropTarget) {
|
|
142
156
|
portNodes.add(dropTarget);
|
|
143
157
|
}
|
|
158
|
+
// The end a link drag holds still shows its ports too, a new link's source as an existing link's far end.
|
|
159
|
+
const draggedLink = drag?.kind === 'end' ? scene.links[drag.id] : undefined;
|
|
160
|
+
const fixedEnd =
|
|
161
|
+
drag?.kind === 'link'
|
|
162
|
+
? drag.source
|
|
163
|
+
: draggedLink && drag?.kind === 'end'
|
|
164
|
+
? drag.end === 'source'
|
|
165
|
+
? draggedLink.target
|
|
166
|
+
: draggedLink.source
|
|
167
|
+
: undefined;
|
|
168
|
+
const fixedNode = fixedEnd && endpointNode(fixedEnd);
|
|
169
|
+
const anchor = fixedNode ? scene.nodes[fixedNode] : undefined;
|
|
170
|
+
if (anchor) {
|
|
171
|
+
portNodes.add(anchor);
|
|
172
|
+
}
|
|
144
173
|
const marquee = drag?.kind === 'marquee' ? boundsFromPoints(drag.from, drag.to) : undefined;
|
|
145
174
|
// The layer draws every provisional link that will land (over a target, or free-ended); the band is only
|
|
146
175
|
// a port drag over free space, where dropping would create a node rather than a free end.
|
|
@@ -149,6 +178,74 @@ export const ControlFrame = memo(
|
|
|
149
178
|
? { from: { point: drag.from, side: drag.fromSide }, to: drag.to }
|
|
150
179
|
: undefined;
|
|
151
180
|
|
|
181
|
+
/** A link's end handles, and a selected spline's control points; a hovered link shows only its ends. */
|
|
182
|
+
const linkHandles = (link: Link, hoverOnly: boolean) => {
|
|
183
|
+
// On a lattice the handles sit on the link's lane, which depends on the links around it.
|
|
184
|
+
const geometry = capabilities.update
|
|
185
|
+
? (lanes?.get(link.id) ?? linkGeometry(scene, registry, link, lattice))
|
|
186
|
+
: undefined;
|
|
187
|
+
if (!geometry) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const points =
|
|
191
|
+
link.type === 'spline' ? (drag?.kind === 'point' && drag.id === link.id ? drag.points : link.points) : [];
|
|
192
|
+
const ends: [LinkEnd, Point][] = [
|
|
193
|
+
['source', geometry.source.point],
|
|
194
|
+
['target', geometry.target.point],
|
|
195
|
+
];
|
|
196
|
+
return (
|
|
197
|
+
<g key={link.id}>
|
|
198
|
+
{ends.map(([end, point]) => (
|
|
199
|
+
<circle
|
|
200
|
+
key={end}
|
|
201
|
+
cx={point.x}
|
|
202
|
+
cy={point.y}
|
|
203
|
+
r={portRadius}
|
|
204
|
+
className={mx(
|
|
205
|
+
'stroke-focus pointer-events-auto cursor-move hover:fill-focus',
|
|
206
|
+
drag?.kind === 'end' && drag.id === link.id && drag.end === end ? 'fill-focus' : 'fill-base-surface',
|
|
207
|
+
)}
|
|
208
|
+
strokeWidth={unit}
|
|
209
|
+
onPointerDown={(event) => onEndPointerDown?.(link, end, event)}
|
|
210
|
+
onPointerEnter={hoverOnly ? () => onLinkHover?.(link.id) : undefined}
|
|
211
|
+
onPointerLeave={hoverOnly ? () => onLinkHover?.(undefined) : undefined}
|
|
212
|
+
/>
|
|
213
|
+
))}
|
|
214
|
+
{/* Midpoints first, so a control point wins wherever the two land on each other. */}
|
|
215
|
+
{!hoverOnly &&
|
|
216
|
+
link.type === 'spline' &&
|
|
217
|
+
midpoints(geometry.source.point, points, geometry.target.point).map(({ index, point }) => (
|
|
218
|
+
<circle
|
|
219
|
+
key={`midpoint-${index}`}
|
|
220
|
+
cx={point.x}
|
|
221
|
+
cy={point.y}
|
|
222
|
+
r={midpointRadius}
|
|
223
|
+
className='fill-focus/40 pointer-events-auto cursor-copy hover:fill-focus'
|
|
224
|
+
onPointerDown={(event) => onMidpointPointerDown?.(link, index, point, event)}
|
|
225
|
+
/>
|
|
226
|
+
))}
|
|
227
|
+
{link.type === 'spline' &&
|
|
228
|
+
points.map((point, index) => (
|
|
229
|
+
<rect
|
|
230
|
+
key={index}
|
|
231
|
+
x={point.x - handleSize / 2}
|
|
232
|
+
y={point.y - handleSize / 2}
|
|
233
|
+
width={handleSize}
|
|
234
|
+
height={handleSize}
|
|
235
|
+
transform={`rotate(45 ${point.x} ${point.y})`}
|
|
236
|
+
className={mx(
|
|
237
|
+
'stroke-focus pointer-events-auto cursor-move hover:fill-focus',
|
|
238
|
+
selectedPoint?.link === link.id && selectedPoint.index === index ? 'fill-focus' : 'fill-base-surface',
|
|
239
|
+
)}
|
|
240
|
+
strokeWidth={unit}
|
|
241
|
+
onPointerDown={(event) => onPointPointerDown?.(link, index, event)}
|
|
242
|
+
onContextMenu={(event) => onPointContextMenu?.(link, index, event)}
|
|
243
|
+
/>
|
|
244
|
+
))}
|
|
245
|
+
</g>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
152
249
|
return (
|
|
153
250
|
<svg className='absolute overflow-visible pointer-events-none' width={1} height={1}>
|
|
154
251
|
{selectedNodes.map((node) => {
|
|
@@ -160,7 +257,7 @@ export const ControlFrame = memo(
|
|
|
160
257
|
y={bounds.y}
|
|
161
258
|
width={bounds.width}
|
|
162
259
|
height={bounds.height}
|
|
163
|
-
className={mx('fill-none', blocked ? 'stroke-error-border' : 'stroke-
|
|
260
|
+
className={mx('fill-none', blocked ? 'stroke-error-border' : 'stroke-focus')}
|
|
164
261
|
strokeWidth={unit}
|
|
165
262
|
data-blocked={blocked || undefined}
|
|
166
263
|
/>
|
|
@@ -173,9 +270,7 @@ export const ControlFrame = memo(
|
|
|
173
270
|
// Filled while it is an end of the drag in progress: the source, or the port it would drop on.
|
|
174
271
|
const isEnd = (end: Endpoint | undefined) =>
|
|
175
272
|
end !== undefined && !isPointEndpoint(end) && end.node === node.id && end.port === port.id;
|
|
176
|
-
const active =
|
|
177
|
-
(drag?.kind === 'link' && isEnd(drag.source)) ||
|
|
178
|
-
((drag?.kind === 'link' || drag?.kind === 'end') && isEnd(drag.target));
|
|
273
|
+
const active = isEnd(fixedEnd) || ((drag?.kind === 'link' || drag?.kind === 'end') && isEnd(drag.target));
|
|
179
274
|
return (
|
|
180
275
|
<circle
|
|
181
276
|
key={`${node.id}/${port.id}`}
|
|
@@ -183,8 +278,8 @@ export const ControlFrame = memo(
|
|
|
183
278
|
cy={point.y}
|
|
184
279
|
r={portRadius}
|
|
185
280
|
className={mx(
|
|
186
|
-
'stroke-
|
|
187
|
-
active ? 'fill-
|
|
281
|
+
'stroke-focus pointer-events-auto cursor-crosshair hover:fill-focus',
|
|
282
|
+
active ? 'fill-focus' : 'fill-base-surface',
|
|
188
283
|
)}
|
|
189
284
|
strokeWidth={unit}
|
|
190
285
|
onPointerDown={(event) => onPortPointerDown?.(node, port, event)}
|
|
@@ -192,6 +287,8 @@ export const ControlFrame = memo(
|
|
|
192
287
|
);
|
|
193
288
|
});
|
|
194
289
|
})}
|
|
290
|
+
{/* A hovered link's ends go under a node's handles, so a resize handle on the same point still wins. */}
|
|
291
|
+
{linkUnder && linkHandles(linkUnder, true)}
|
|
195
292
|
{/* Handles after the ports so a handle wins where a port sits on the same point (a side centre). */}
|
|
196
293
|
{single && capabilities.resize && nodeDef(registry, single)?.resizable && !single.locked && (
|
|
197
294
|
<g>
|
|
@@ -204,7 +301,7 @@ export const ControlFrame = memo(
|
|
|
204
301
|
y={point.y - handleSize / 2}
|
|
205
302
|
width={handleSize}
|
|
206
303
|
height={handleSize}
|
|
207
|
-
className='fill-base-surface stroke-
|
|
304
|
+
className='fill-base-surface stroke-focus pointer-events-auto'
|
|
208
305
|
strokeWidth={unit}
|
|
209
306
|
style={{ cursor: cursorFor(handle) }}
|
|
210
307
|
onPointerDown={(event) => onHandlePointerDown?.(single, handle, event)}
|
|
@@ -214,91 +311,50 @@ export const ControlFrame = memo(
|
|
|
214
311
|
</g>
|
|
215
312
|
)}
|
|
216
313
|
{/* A link's end and control-point handles all move it, so they follow the `update` capability together. */}
|
|
217
|
-
{selectedLinks.map((link) =>
|
|
218
|
-
// On a lattice the handles sit on the link's lane, which depends on the links around it.
|
|
219
|
-
const geometry = capabilities.update
|
|
220
|
-
? (lanes?.get(link.id) ?? linkGeometry(scene, registry, link, lattice))
|
|
221
|
-
: undefined;
|
|
222
|
-
if (!geometry) {
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
const points =
|
|
226
|
-
link.type === 'spline' ? (drag?.kind === 'point' && drag.id === link.id ? drag.points : link.points) : [];
|
|
227
|
-
const ends: [LinkEnd, Point][] = [
|
|
228
|
-
['source', geometry.source.point],
|
|
229
|
-
['target', geometry.target.point],
|
|
230
|
-
];
|
|
231
|
-
return (
|
|
232
|
-
<g key={link.id}>
|
|
233
|
-
{ends.map(([end, point]) => (
|
|
234
|
-
<circle
|
|
235
|
-
key={end}
|
|
236
|
-
cx={point.x}
|
|
237
|
-
cy={point.y}
|
|
238
|
-
r={portRadius}
|
|
239
|
-
className={mx(
|
|
240
|
-
'stroke-primary-500 pointer-events-auto cursor-move hover:fill-primary-500',
|
|
241
|
-
drag?.kind === 'end' && drag.id === link.id && drag.end === end
|
|
242
|
-
? 'fill-primary-500'
|
|
243
|
-
: 'fill-base-surface',
|
|
244
|
-
)}
|
|
245
|
-
strokeWidth={unit}
|
|
246
|
-
onPointerDown={(event) => onEndPointerDown?.(link, end, event)}
|
|
247
|
-
/>
|
|
248
|
-
))}
|
|
249
|
-
{/* Midpoints first, so a control point wins wherever the two land on each other. */}
|
|
250
|
-
{link.type === 'spline' &&
|
|
251
|
-
midpoints(geometry.source.point, points, geometry.target.point).map(({ index, point }) => (
|
|
252
|
-
<circle
|
|
253
|
-
key={`midpoint-${index}`}
|
|
254
|
-
cx={point.x}
|
|
255
|
-
cy={point.y}
|
|
256
|
-
r={midpointRadius}
|
|
257
|
-
className='fill-primary-500/40 pointer-events-auto cursor-copy hover:fill-primary-500'
|
|
258
|
-
onPointerDown={(event) => onMidpointPointerDown?.(link, index, point, event)}
|
|
259
|
-
/>
|
|
260
|
-
))}
|
|
261
|
-
{link.type === 'spline' &&
|
|
262
|
-
points.map((point, index) => (
|
|
263
|
-
<rect
|
|
264
|
-
key={index}
|
|
265
|
-
x={point.x - handleSize / 2}
|
|
266
|
-
y={point.y - handleSize / 2}
|
|
267
|
-
width={handleSize}
|
|
268
|
-
height={handleSize}
|
|
269
|
-
transform={`rotate(45 ${point.x} ${point.y})`}
|
|
270
|
-
className={mx(
|
|
271
|
-
'stroke-primary-500 pointer-events-auto cursor-move hover:fill-primary-500',
|
|
272
|
-
selectedPoint?.link === link.id && selectedPoint.index === index
|
|
273
|
-
? 'fill-primary-500'
|
|
274
|
-
: 'fill-base-surface',
|
|
275
|
-
)}
|
|
276
|
-
strokeWidth={unit}
|
|
277
|
-
onPointerDown={(event) => onPointPointerDown?.(link, index, event)}
|
|
278
|
-
onContextMenu={(event) => onPointContextMenu?.(link, index, event)}
|
|
279
|
-
/>
|
|
280
|
-
))}
|
|
281
|
-
</g>
|
|
282
|
-
);
|
|
283
|
-
})}
|
|
314
|
+
{selectedLinks.map((link) => linkHandles(link, false))}
|
|
284
315
|
{band && (
|
|
285
316
|
<path
|
|
286
|
-
d={curvePath(band.from, { point: band.to, side: oppositeSide(band.from.side) })}
|
|
287
|
-
className='fill-none stroke-
|
|
317
|
+
d={curvePath(band.from, { point: band.to, side: oppositeSide(band.from.side), free: true })}
|
|
318
|
+
className='fill-none stroke-focus'
|
|
288
319
|
strokeWidth={2 * unit}
|
|
289
320
|
strokeDasharray={`${6 * unit} ${4 * unit}`}
|
|
290
321
|
/>
|
|
291
322
|
)}
|
|
323
|
+
{/* A link end over empty canvas carries a connection circle, as an end on a port does. */}
|
|
324
|
+
{(drag?.kind === 'link' || drag?.kind === 'end') && !drag.target && (
|
|
325
|
+
<circle
|
|
326
|
+
data-testid='free-end'
|
|
327
|
+
cx={drag.to.x}
|
|
328
|
+
cy={drag.to.y}
|
|
329
|
+
r={portRadius}
|
|
330
|
+
className='fill-focus stroke-focus pointer-events-none'
|
|
331
|
+
strokeWidth={unit}
|
|
332
|
+
/>
|
|
333
|
+
)}
|
|
292
334
|
{marquee && (
|
|
293
335
|
<rect
|
|
294
336
|
x={marquee.x}
|
|
295
337
|
y={marquee.y}
|
|
296
338
|
width={marquee.width}
|
|
297
339
|
height={marquee.height}
|
|
298
|
-
className='fill-
|
|
340
|
+
className='fill-focus/10 stroke-focus'
|
|
299
341
|
strokeWidth={unit}
|
|
300
342
|
/>
|
|
301
343
|
)}
|
|
344
|
+
{landing?.map((frame, index) => (
|
|
345
|
+
<rect
|
|
346
|
+
key={index}
|
|
347
|
+
data-testid='landing-frame'
|
|
348
|
+
x={frame.x}
|
|
349
|
+
y={frame.y}
|
|
350
|
+
width={frame.width}
|
|
351
|
+
height={frame.height}
|
|
352
|
+
className={mx('fill-none', blocked ? 'stroke-error-border' : 'stroke-focus')}
|
|
353
|
+
data-blocked={blocked || undefined}
|
|
354
|
+
strokeWidth={unit}
|
|
355
|
+
strokeDasharray={`${4 * unit} ${4 * unit}`}
|
|
356
|
+
/>
|
|
357
|
+
))}
|
|
302
358
|
{createFrame && (
|
|
303
359
|
<rect
|
|
304
360
|
data-testid='create-frame'
|
|
@@ -306,7 +362,7 @@ export const ControlFrame = memo(
|
|
|
306
362
|
y={createFrame.y}
|
|
307
363
|
width={createFrame.width}
|
|
308
364
|
height={createFrame.height}
|
|
309
|
-
className={blocked ? 'fill-error-surface stroke-error-border' : 'fill-
|
|
365
|
+
className={blocked ? 'fill-error-surface stroke-error-border' : 'fill-focus/10 stroke-focus'}
|
|
310
366
|
data-blocked={blocked || undefined}
|
|
311
367
|
strokeWidth={unit}
|
|
312
368
|
/>
|
|
@@ -120,7 +120,7 @@ const DefaultStory = ({ size, showAxes }: GridProps) => {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
const meta: Meta<GridProps> = {
|
|
123
|
-
title: 'ui/react-ui-canvas/
|
|
123
|
+
title: 'ui/react-ui-canvas/Grid',
|
|
124
124
|
component: GridComponent,
|
|
125
125
|
render: DefaultStory,
|
|
126
126
|
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
8
|
+
|
|
9
|
+
import { translations as formTranslations } from '@dxos/react-ui-form/translations';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { translations as uiTranslations } from '@dxos/react-ui/translations';
|
|
12
|
+
|
|
13
|
+
import { type Layer, type LayerId } from '../../model/types.ts';
|
|
14
|
+
import { between } from '../../utils/order.ts';
|
|
15
|
+
import { LayersPanel } from './Layers.tsx';
|
|
16
|
+
|
|
17
|
+
/** Three layers, bottom first, the middle one hidden. */
|
|
18
|
+
const LAYERS: Layer[] = (() => {
|
|
19
|
+
const background: Layer = { id: 'background', name: 'Background', z: between() };
|
|
20
|
+
const notes: Layer = { id: 'notes', name: 'Notes', z: between(background.z), hidden: true };
|
|
21
|
+
const diagram: Layer = { id: 'diagram', name: 'Diagram', z: between(notes.z) };
|
|
22
|
+
return [background, notes, diagram];
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
/** The panel over local state, as a host keeps the scene's layers. */
|
|
26
|
+
const DefaultStory = () => {
|
|
27
|
+
const [layers, setLayers] = useState(LAYERS);
|
|
28
|
+
const [selected, setSelected] = useState<LayerId[]>(['diagram']);
|
|
29
|
+
const update = (id: LayerId, values: Partial<Layer>) =>
|
|
30
|
+
setLayers((layers) => layers.map((layer) => (layer.id === id ? { ...layer, ...values } : layer)));
|
|
31
|
+
const remove = (ids: LayerId[]) => setLayers((layers) => layers.filter((layer) => !ids.includes(layer.id)));
|
|
32
|
+
return (
|
|
33
|
+
<LayersPanel
|
|
34
|
+
classNames='w-72 border border-separator rounded-sm'
|
|
35
|
+
layers={layers}
|
|
36
|
+
selected={selected}
|
|
37
|
+
onSelectedChange={setSelected}
|
|
38
|
+
onToggle={(id) => update(id, { hidden: !layers.find((layer) => layer.id === id)?.hidden })}
|
|
39
|
+
onRename={(id, name) => update(id, { name })}
|
|
40
|
+
onMove={(id, index) =>
|
|
41
|
+
setLayers((layers) => {
|
|
42
|
+
const moved = layers.find((layer) => layer.id === id);
|
|
43
|
+
const others = layers.filter((layer) => layer.id !== id);
|
|
44
|
+
return moved ? [...others.slice(0, index), moved, ...others.slice(index)] : layers;
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
onCreate={() => {
|
|
48
|
+
const id = `layer-${layers.length + 1}`;
|
|
49
|
+
setLayers((layers) => [
|
|
50
|
+
{ id, name: `Layer ${layers.length + 1}`, z: between(undefined, layers[0]?.z) },
|
|
51
|
+
...layers,
|
|
52
|
+
]);
|
|
53
|
+
setSelected([id]);
|
|
54
|
+
return id;
|
|
55
|
+
}}
|
|
56
|
+
// In a scene, deleting takes the layer's shapes and merging moves them down; here there are none.
|
|
57
|
+
onDelete={(ids) => {
|
|
58
|
+
remove(ids);
|
|
59
|
+
setSelected([]);
|
|
60
|
+
}}
|
|
61
|
+
onMerge={(ids, into) => {
|
|
62
|
+
remove(ids.filter((id) => id !== into));
|
|
63
|
+
setSelected([into]);
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const meta: Meta<typeof DefaultStory> = {
|
|
70
|
+
title: 'ui/react-ui-canvas/Layers',
|
|
71
|
+
render: DefaultStory,
|
|
72
|
+
decorators: [withTheme(), withLayout({ layout: 'centered' })],
|
|
73
|
+
parameters: { translations: [...uiTranslations, ...formTranslations] },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default meta;
|
|
77
|
+
|
|
78
|
+
type Story = StoryObj<typeof meta>;
|
|
79
|
+
|
|
80
|
+
export const Default: Story = {};
|
|
81
|
+
|
|
82
|
+
/** Every toolbar action and an inline rename, against the panel's local state. */
|
|
83
|
+
export const Test: Story = {
|
|
84
|
+
play: async ({ canvasElement }) => {
|
|
85
|
+
const canvas = within(canvasElement);
|
|
86
|
+
// 1. The layers list top first, as they stack.
|
|
87
|
+
const names = () => canvas.getAllByTestId(/^layer-name-/).map((element) => element.textContent);
|
|
88
|
+
await expect(names()).toEqual(['Diagram', 'Notes', 'Background']);
|
|
89
|
+
|
|
90
|
+
// 2. A new layer goes at the end of the list (the bottom of the stack).
|
|
91
|
+
await userEvent.click(canvas.getByTestId('layers-create'));
|
|
92
|
+
await waitFor(() => expect(names().at(-1)).toBe('Layer 4'));
|
|
93
|
+
|
|
94
|
+
// 3. The new layer's name opens for editing; the green check saves it.
|
|
95
|
+
const input = await canvas.findByTestId('layer-input-layer-4');
|
|
96
|
+
await waitFor(() => expect(input).toHaveFocus());
|
|
97
|
+
await userEvent.clear(input);
|
|
98
|
+
await userEvent.type(input, 'Sketch');
|
|
99
|
+
await userEvent.click(within(canvas.getByTestId('layer-layer-4')).getByTestId('editable.save'));
|
|
100
|
+
await waitFor(() => expect(names().at(-1)).toBe('Sketch'));
|
|
101
|
+
|
|
102
|
+
// 4. The eye shows a hidden layer.
|
|
103
|
+
await userEvent.click(canvas.getByTestId('layer-toggle-notes'));
|
|
104
|
+
await expect(canvas.getByTestId('layer-toggle-notes')).toHaveAccessibleName('Hide layer');
|
|
105
|
+
|
|
106
|
+
// 5. Merge, in the toolbar's menu, needs two layers: a Shift-click extends the selection to the next one, and
|
|
107
|
+
// merging keeps the top-most. Shift rather than Cmd/Ctrl, whose key the listbox picks by platform. The menu is
|
|
108
|
+
// portalled, so its items are found in the document body.
|
|
109
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
110
|
+
await userEvent.click(canvas.getByTestId('layers-menu'));
|
|
111
|
+
await expect(await body.findByTestId('layers-merge')).toHaveAttribute('data-disabled');
|
|
112
|
+
await userEvent.keyboard('{Escape}');
|
|
113
|
+
await userEvent.click(canvas.getByTestId('layer-name-diagram'));
|
|
114
|
+
// One session, so the held key is still down for the click.
|
|
115
|
+
const user = userEvent.setup();
|
|
116
|
+
await user.keyboard('{Shift>}');
|
|
117
|
+
await user.click(canvas.getByTestId('layer-name-notes'));
|
|
118
|
+
await user.keyboard('{/Shift}');
|
|
119
|
+
await userEvent.click(canvas.getByTestId('layers-menu'));
|
|
120
|
+
const merge = await body.findByTestId('layers-merge');
|
|
121
|
+
await waitFor(() => expect(merge).not.toHaveAttribute('data-disabled'));
|
|
122
|
+
// Picked from the keyboard, as the react-ui Menu stories do: a synthetic click does not select a menu item.
|
|
123
|
+
await userEvent.keyboard('{Home}{Enter}');
|
|
124
|
+
await waitFor(() => expect(names()).toEqual(['Diagram', 'Background', 'Sketch']));
|
|
125
|
+
|
|
126
|
+
// 6. The arrows move along the list, and Enter opens the current row's name.
|
|
127
|
+
await userEvent.click(canvas.getByTestId('layer-name-diagram'));
|
|
128
|
+
await userEvent.keyboard('{ArrowDown}{Enter}');
|
|
129
|
+
await waitFor(() => expect(canvas.getByTestId('layer-input-background')).toHaveFocus());
|
|
130
|
+
},
|
|
131
|
+
};
|