@elabs-ai/components-flow 4.1.0 → 5.0.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 +2 -1
- package/dist/canvas-shell/canvas-shell.js +13 -0
- package/dist/canvas-shell/canvas-shell.js.map +1 -0
- package/dist/canvas-shell/clamped-fit-offset.js +8 -0
- package/dist/canvas-shell/clamped-fit-offset.js.map +1 -0
- package/dist/canvas-shell/index.js +14 -0
- package/dist/canvas-shell/index.js.map +1 -0
- package/dist/canvas-shell/use-measured-nodes.js +8 -0
- package/dist/canvas-shell/use-measured-nodes.js.map +1 -0
- package/dist/chunk-3BTU2VWZ.js +42 -0
- package/dist/chunk-3BTU2VWZ.js.map +1 -0
- package/dist/chunk-3LK6OOXE.js +2 -0
- package/dist/chunk-3LK6OOXE.js.map +1 -0
- package/dist/chunk-3M3FQAD4.js +105 -0
- package/dist/chunk-3M3FQAD4.js.map +1 -0
- package/dist/chunk-3Y5TCGC3.js +2 -0
- package/dist/chunk-3Y5TCGC3.js.map +1 -0
- package/dist/chunk-4VYHZM3S.js +108 -0
- package/dist/chunk-4VYHZM3S.js.map +1 -0
- package/dist/chunk-5TVVM4GR.js +45 -0
- package/dist/chunk-5TVVM4GR.js.map +1 -0
- package/dist/chunk-5XT73MQQ.js +2 -0
- package/dist/chunk-5XT73MQQ.js.map +1 -0
- package/dist/chunk-6LQD5Y5R.js +24 -0
- package/dist/chunk-6LQD5Y5R.js.map +1 -0
- package/dist/chunk-6SR26QY2.js +18 -0
- package/dist/chunk-6SR26QY2.js.map +1 -0
- package/dist/chunk-75JJK6BH.js +157 -0
- package/dist/chunk-75JJK6BH.js.map +1 -0
- package/dist/chunk-A66JBW4X.js +2 -0
- package/dist/chunk-A66JBW4X.js.map +1 -0
- package/dist/chunk-A7AFM3LI.js +72 -0
- package/dist/chunk-A7AFM3LI.js.map +1 -0
- package/dist/chunk-ADHLQF56.js +2 -0
- package/dist/chunk-ADHLQF56.js.map +1 -0
- package/dist/chunk-ATO4LXJF.js +75 -0
- package/dist/chunk-ATO4LXJF.js.map +1 -0
- package/dist/chunk-ATXVXTMZ.js +227 -0
- package/dist/chunk-ATXVXTMZ.js.map +1 -0
- package/dist/chunk-B6IUPZU7.js +2 -0
- package/dist/chunk-B6IUPZU7.js.map +1 -0
- package/dist/chunk-BJ7UMCD3.js +53 -0
- package/dist/chunk-BJ7UMCD3.js.map +1 -0
- package/dist/chunk-BWGYV4JZ.js +2 -0
- package/dist/chunk-BWGYV4JZ.js.map +1 -0
- package/dist/chunk-CWNO63LQ.js +109 -0
- package/dist/chunk-CWNO63LQ.js.map +1 -0
- package/dist/chunk-DHPUXKCZ.js +76 -0
- package/dist/chunk-DHPUXKCZ.js.map +1 -0
- package/dist/chunk-DNX7G3KA.js +185 -0
- package/dist/chunk-DNX7G3KA.js.map +1 -0
- package/dist/chunk-DNZRAJAQ.js +41 -0
- package/dist/chunk-DNZRAJAQ.js.map +1 -0
- package/dist/chunk-ELCLRVD4.js +2 -0
- package/dist/chunk-ELCLRVD4.js.map +1 -0
- package/dist/chunk-ELOADZSX.js +2 -0
- package/dist/chunk-ELOADZSX.js.map +1 -0
- package/dist/chunk-EUNV2MR5.js +21 -0
- package/dist/chunk-EUNV2MR5.js.map +1 -0
- package/dist/chunk-FAFQ5KLB.js +43 -0
- package/dist/chunk-FAFQ5KLB.js.map +1 -0
- package/dist/chunk-FIRO6Y3R.js +128 -0
- package/dist/chunk-FIRO6Y3R.js.map +1 -0
- package/dist/chunk-GE73WQUK.js +2 -0
- package/dist/chunk-GE73WQUK.js.map +1 -0
- package/dist/chunk-GIARAOU6.js +39 -0
- package/dist/chunk-GIARAOU6.js.map +1 -0
- package/dist/chunk-HTLTCCFA.js +73 -0
- package/dist/chunk-HTLTCCFA.js.map +1 -0
- package/dist/chunk-I53W3CFN.js +56 -0
- package/dist/chunk-I53W3CFN.js.map +1 -0
- package/dist/chunk-IJVFPUXO.js +2 -0
- package/dist/chunk-IJVFPUXO.js.map +1 -0
- package/dist/chunk-JUPMOISE.js +51 -0
- package/dist/chunk-JUPMOISE.js.map +1 -0
- package/dist/chunk-K5RPSVO6.js +167 -0
- package/dist/chunk-K5RPSVO6.js.map +1 -0
- package/dist/chunk-K6U27YJ6.js +78 -0
- package/dist/chunk-K6U27YJ6.js.map +1 -0
- package/dist/chunk-KDVMGMAK.js +2 -0
- package/dist/chunk-KDVMGMAK.js.map +1 -0
- package/dist/chunk-LQOZCOK3.js +74 -0
- package/dist/chunk-LQOZCOK3.js.map +1 -0
- package/dist/chunk-NAQWNX2A.js +102 -0
- package/dist/chunk-NAQWNX2A.js.map +1 -0
- package/dist/chunk-NVJVWGUS.js +152 -0
- package/dist/chunk-NVJVWGUS.js.map +1 -0
- package/dist/chunk-O2F3R3PV.js +2 -0
- package/dist/chunk-O2F3R3PV.js.map +1 -0
- package/dist/chunk-OMFQHMA6.js +2 -0
- package/dist/chunk-OMFQHMA6.js.map +1 -0
- package/dist/chunk-PE6JXVOX.js +2 -0
- package/dist/chunk-PE6JXVOX.js.map +1 -0
- package/dist/chunk-ROJEIIA4.js +52 -0
- package/dist/chunk-ROJEIIA4.js.map +1 -0
- package/dist/chunk-ROYAQKJA.js +9 -0
- package/dist/chunk-ROYAQKJA.js.map +1 -0
- package/dist/chunk-SHCZK4DO.js +45 -0
- package/dist/chunk-SHCZK4DO.js.map +1 -0
- package/dist/chunk-SXXJV6YJ.js +79 -0
- package/dist/chunk-SXXJV6YJ.js.map +1 -0
- package/dist/chunk-T7S7NJFD.js +287 -0
- package/dist/chunk-T7S7NJFD.js.map +1 -0
- package/dist/chunk-TEZ3OXAY.js +63 -0
- package/dist/chunk-TEZ3OXAY.js.map +1 -0
- package/dist/chunk-TGWPM2CG.js +199 -0
- package/dist/chunk-TGWPM2CG.js.map +1 -0
- package/dist/chunk-TSB6RE7L.js +2 -0
- package/dist/chunk-TSB6RE7L.js.map +1 -0
- package/dist/chunk-UPV2MT4O.js +2 -0
- package/dist/chunk-UPV2MT4O.js.map +1 -0
- package/dist/chunk-UZJZY4A5.js +2 -0
- package/dist/chunk-UZJZY4A5.js.map +1 -0
- package/dist/chunk-W3TQMUGX.js +2 -0
- package/dist/chunk-W3TQMUGX.js.map +1 -0
- package/dist/chunk-WN46RIOC.js +29 -0
- package/dist/chunk-WN46RIOC.js.map +1 -0
- package/dist/chunk-XZFE2YYH.js +70 -0
- package/dist/chunk-XZFE2YYH.js.map +1 -0
- package/dist/chunk-YG3KXTZW.js +2 -0
- package/dist/chunk-YG3KXTZW.js.map +1 -0
- package/dist/chunk-ZATVF42E.js +65 -0
- package/dist/chunk-ZATVF42E.js.map +1 -0
- package/dist/chunk-ZXZTLQIX.js +19 -0
- package/dist/chunk-ZXZTLQIX.js.map +1 -0
- package/dist/flow-button-edge/flow-button-edge.js +10 -0
- package/dist/flow-button-edge/flow-button-edge.js.map +1 -0
- package/dist/flow-button-edge/index.js +11 -0
- package/dist/flow-button-edge/index.js.map +1 -0
- package/dist/flow-edge/flow-edge.js +10 -0
- package/dist/flow-edge/flow-edge.js.map +1 -0
- package/dist/flow-edge/index.js +11 -0
- package/dist/flow-edge/index.js.map +1 -0
- package/dist/flow-edge-path/flow-edge-path.js +12 -0
- package/dist/flow-edge-path/flow-edge-path.js.map +1 -0
- package/dist/flow-edge-path/index.js +13 -0
- package/dist/flow-edge-path/index.js.map +1 -0
- package/dist/flow-edge-tokens/edge-path-sampler.js +10 -0
- package/dist/flow-edge-tokens/edge-path-sampler.js.map +1 -0
- package/dist/flow-edge-tokens/flow-edge-tokens.js +12 -0
- package/dist/flow-edge-tokens/flow-edge-tokens.js.map +1 -0
- package/dist/flow-edge-tokens/index.js +17 -0
- package/dist/flow-edge-tokens/index.js.map +1 -0
- package/dist/flow-floating-edge/floating-edge-geometry.js +8 -0
- package/dist/flow-floating-edge/floating-edge-geometry.js.map +1 -0
- package/dist/flow-floating-edge/flow-floating-edge.js +11 -0
- package/dist/flow-floating-edge/flow-floating-edge.js.map +1 -0
- package/dist/flow-floating-edge/index.js +15 -0
- package/dist/flow-floating-edge/index.js.map +1 -0
- package/dist/flow-group-node/flow-group-node.js +12 -0
- package/dist/flow-group-node/flow-group-node.js.map +1 -0
- package/dist/flow-group-node/index.js +13 -0
- package/dist/flow-group-node/index.js.map +1 -0
- package/dist/flow-handle/flow-handle-anchor.js +8 -0
- package/dist/flow-handle/flow-handle-anchor.js.map +1 -0
- package/dist/flow-handle/index.js +9 -0
- package/dist/flow-handle/index.js.map +1 -0
- package/dist/flow-layout/backbone.js +8 -0
- package/dist/flow-layout/backbone.js.map +1 -0
- package/dist/flow-layout/flow-layout.js +10 -0
- package/dist/flow-layout/flow-layout.js.map +1 -0
- package/dist/flow-layout/index.js +21 -0
- package/dist/flow-layout/index.js.map +1 -0
- package/dist/flow-layout/layout-flow-elk.js +9 -0
- package/dist/flow-layout/layout-flow-elk.js.map +1 -0
- package/dist/flow-layout/layout-graph.js +9 -0
- package/dist/flow-layout/layout-graph.js.map +1 -0
- package/dist/flow-layout/use-auto-layout.js +10 -0
- package/dist/flow-layout/use-auto-layout.js.map +1 -0
- package/dist/flow-layout/use-flow-layout.js +9 -0
- package/dist/flow-layout/use-flow-layout.js.map +1 -0
- package/dist/flow-mini-map/flow-mini-map.js +8 -0
- package/dist/flow-mini-map/flow-mini-map.js.map +1 -0
- package/dist/flow-mini-map/index.js +9 -0
- package/dist/flow-mini-map/index.js.map +1 -0
- package/dist/flow-node/flow-node.js +11 -0
- package/dist/flow-node/flow-node.js.map +1 -0
- package/dist/flow-node/index.js +12 -0
- package/dist/flow-node/index.js.map +1 -0
- package/dist/flow-placeholder-node/flow-placeholder-node.js +9 -0
- package/dist/flow-placeholder-node/flow-placeholder-node.js.map +1 -0
- package/dist/flow-placeholder-node/index.js +10 -0
- package/dist/flow-placeholder-node/index.js.map +1 -0
- package/dist/flow-self-loop-edge/flow-self-loop-edge.js +20 -0
- package/dist/flow-self-loop-edge/flow-self-loop-edge.js.map +1 -0
- package/dist/flow-self-loop-edge/index.js +28 -0
- package/dist/flow-self-loop-edge/index.js.map +1 -0
- package/dist/flow-self-loop-edge/self-loop-geometry.js +12 -0
- package/dist/flow-self-loop-edge/self-loop-geometry.js.map +1 -0
- package/dist/flow-smart-edge/flow-smart-edge.js +11 -0
- package/dist/flow-smart-edge/flow-smart-edge.js.map +1 -0
- package/dist/flow-smart-edge/index.js +25 -0
- package/dist/flow-smart-edge/index.js.map +1 -0
- package/dist/flow-smart-edge/smart-edge-geometry.js +18 -0
- package/dist/flow-smart-edge/smart-edge-geometry.js.map +1 -0
- package/dist/flow-weighted-edge/back-edge-geometry.js +8 -0
- package/dist/flow-weighted-edge/back-edge-geometry.js.map +1 -0
- package/dist/flow-weighted-edge/edge-aria.js +10 -0
- package/dist/flow-weighted-edge/edge-aria.js.map +1 -0
- package/dist/flow-weighted-edge/edge-label-pill.js +8 -0
- package/dist/flow-weighted-edge/edge-label-pill.js.map +1 -0
- package/dist/flow-weighted-edge/flow-weighted-edge.js +16 -0
- package/dist/flow-weighted-edge/flow-weighted-edge.js.map +1 -0
- package/dist/flow-weighted-edge/index.js +34 -0
- package/dist/flow-weighted-edge/index.js.map +1 -0
- package/dist/flow-weighted-edge/weight-scale.js +10 -0
- package/dist/flow-weighted-edge/weight-scale.js.map +1 -0
- package/dist/helper-lines/get-helper-lines.js +8 -0
- package/dist/helper-lines/get-helper-lines.js.map +1 -0
- package/dist/helper-lines/helper-lines.js +9 -0
- package/dist/helper-lines/helper-lines.js.map +1 -0
- package/dist/helper-lines/index.js +17 -0
- package/dist/helper-lines/index.js.map +1 -0
- package/dist/helper-lines/use-helper-lines.js +10 -0
- package/dist/helper-lines/use-helper-lines.js.map +1 -0
- package/dist/index.d.ts +241 -2
- package/dist/index.js +167 -2306
- package/dist/index.js.map +1 -1
- package/dist/inspector-panel/index.js +9 -0
- package/dist/inspector-panel/index.js.map +1 -0
- package/dist/inspector-panel/inspector-panel.js +8 -0
- package/dist/inspector-panel/inspector-panel.js.map +1 -0
- package/dist/legend/index.js +10 -0
- package/dist/legend/index.js.map +1 -0
- package/dist/legend/legend.js +9 -0
- package/dist/legend/legend.js.map +1 -0
- package/dist/use-flow-groups/group-operations.js +20 -0
- package/dist/use-flow-groups/group-operations.js.map +1 -0
- package/dist/use-flow-groups/index.js +25 -0
- package/dist/use-flow-groups/index.js.map +1 -0
- package/dist/use-flow-groups/use-flow-groups.js +9 -0
- package/dist/use-flow-groups/use-flow-groups.js.map +1 -0
- package/dist/zoom-controls/index.js +9 -0
- package/dist/zoom-controls/index.js.map +1 -0
- package/dist/zoom-controls/zoom-controls.js +8 -0
- package/dist/zoom-controls/zoom-controls.js.map +1 -0
- package/package.json +14 -7
- package/src/__contract__/inspector-panel.contract.test.tsx +49 -0
- package/src/__contract__/legend.contract.test.tsx +49 -0
- package/src/canvas-shell/canvas-shell.stories.tsx +1 -0
- package/src/canvas-shell/canvas-shell.tsx +51 -4
- package/src/canvas-shell/clamped-fit-offset.test.ts +25 -0
- package/src/canvas-shell/clamped-fit-offset.ts +30 -0
- package/src/custom-nodes/custom-nodes.stories.tsx +718 -0
- package/src/flow-edge-tokens/edge-path-sampler.test.ts +108 -0
- package/src/flow-edge-tokens/edge-path-sampler.ts +362 -0
- package/src/flow-edge-tokens/flow-edge-tokens.stories.tsx +312 -0
- package/src/flow-edge-tokens/flow-edge-tokens.test.tsx +115 -0
- package/src/flow-edge-tokens/flow-edge-tokens.tsx +94 -0
- package/src/flow-edge-tokens/index.ts +12 -0
- package/src/flow-layout/backbone.test.ts +88 -0
- package/src/flow-layout/backbone.ts +58 -0
- package/src/flow-layout/layout-flow-elk.stories.tsx +154 -0
- package/src/flow-layout/layout-flow-elk.test.ts +152 -0
- package/src/flow-layout/layout-flow-elk.ts +399 -0
- package/src/flow-mini-map/flow-mini-map.stories.tsx +20 -0
- package/src/flow-node/flow-node.stories.tsx +40 -1
- package/src/flow-self-loop-edge/flow-self-loop-edge.test.tsx +38 -0
- package/src/flow-self-loop-edge/flow-self-loop-edge.tsx +7 -0
- package/src/flow-weighted-edge/edge-label-pill.test.tsx +20 -0
- package/src/flow-weighted-edge/flow-weighted-edge.stories.tsx +1 -0
- package/src/flow-weighted-edge/flow-weighted-edge.test.tsx +40 -0
- package/src/flow-weighted-edge/flow-weighted-edge.tsx +7 -0
- package/src/index.ts +13 -0
- package/src/inspector-panel/inspector-panel.tsx +9 -6
- package/src/legend/legend.test.tsx +11 -0
- package/src/legend/legend.tsx +15 -7
- package/src/templates-flow-workspace.stories.tsx +7 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
FLOW_GROUP_NODE_TYPE,
|
|
4
|
+
collapseGroup,
|
|
5
|
+
expandGroup,
|
|
6
|
+
groupNodes,
|
|
7
|
+
isFlowGroupProxyEdge,
|
|
8
|
+
toggleGroupCollapsed,
|
|
9
|
+
ungroup
|
|
10
|
+
} from "../chunk-TGWPM2CG.js";
|
|
11
|
+
export {
|
|
12
|
+
FLOW_GROUP_NODE_TYPE,
|
|
13
|
+
collapseGroup,
|
|
14
|
+
expandGroup,
|
|
15
|
+
groupNodes,
|
|
16
|
+
isFlowGroupProxyEdge,
|
|
17
|
+
toggleGroupCollapsed,
|
|
18
|
+
ungroup
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=group-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ELOADZSX.js";
|
|
3
|
+
import {
|
|
4
|
+
useFlowGroups
|
|
5
|
+
} from "../chunk-LQOZCOK3.js";
|
|
6
|
+
import {
|
|
7
|
+
FLOW_GROUP_NODE_TYPE,
|
|
8
|
+
collapseGroup,
|
|
9
|
+
expandGroup,
|
|
10
|
+
groupNodes,
|
|
11
|
+
isFlowGroupProxyEdge,
|
|
12
|
+
toggleGroupCollapsed,
|
|
13
|
+
ungroup
|
|
14
|
+
} from "../chunk-TGWPM2CG.js";
|
|
15
|
+
export {
|
|
16
|
+
FLOW_GROUP_NODE_TYPE,
|
|
17
|
+
collapseGroup,
|
|
18
|
+
expandGroup,
|
|
19
|
+
groupNodes,
|
|
20
|
+
isFlowGroupProxyEdge,
|
|
21
|
+
toggleGroupCollapsed,
|
|
22
|
+
ungroup,
|
|
23
|
+
useFlowGroups
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elabs-ai/components-flow",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,10 +30,16 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@xyflow/react": "^12.11.1",
|
|
33
|
+
"elkjs": "^0.12.0",
|
|
33
34
|
"react": "^18.2.0 || ^19.0.0",
|
|
34
35
|
"react-dom": "^18.2.0 || ^19.0.0",
|
|
35
|
-
"@elabs-ai/components-tokens": "
|
|
36
|
-
"@elabs-ai/components-ui": "
|
|
36
|
+
"@elabs-ai/components-tokens": "5.0.0",
|
|
37
|
+
"@elabs-ai/components-ui": "5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"elkjs": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
37
43
|
},
|
|
38
44
|
"devDependencies": {
|
|
39
45
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -44,6 +50,7 @@
|
|
|
44
50
|
"@types/react-dom": "^19.0.2",
|
|
45
51
|
"@vitejs/plugin-react": "^4.3.4",
|
|
46
52
|
"@xyflow/react": "^12.11.1",
|
|
53
|
+
"elkjs": "^0.12.0",
|
|
47
54
|
"eslint": "^9.17.0",
|
|
48
55
|
"jsdom": "^25.0.1",
|
|
49
56
|
"react": "^19.0.0",
|
|
@@ -51,11 +58,11 @@
|
|
|
51
58
|
"tsup": "^8.3.5",
|
|
52
59
|
"typescript": "^5.7.3",
|
|
53
60
|
"vitest": "^3.0.2",
|
|
54
|
-
"@elabs-ai/components-tokens": "4.1.0",
|
|
55
|
-
"@elabs-ai/components-icons": "4.1.0",
|
|
56
61
|
"@elabs-ai/components-eslint-config": "0.1.0",
|
|
57
|
-
"@elabs-ai/components-
|
|
58
|
-
"@elabs-ai/components-
|
|
62
|
+
"@elabs-ai/components-icons": "5.0.0",
|
|
63
|
+
"@elabs-ai/components-tokens": "5.0.0",
|
|
64
|
+
"@elabs-ai/components-typescript-config": "0.1.0",
|
|
65
|
+
"@elabs-ai/components-ui": "5.0.0"
|
|
59
66
|
},
|
|
60
67
|
"scripts": {
|
|
61
68
|
"build": "tsup",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-contract-tests.mjs — do not edit; re-run the generator instead.
|
|
2
|
+
/**
|
|
3
|
+
* Contract probe for InspectorPanel (`packages/flow/src/inspector-panel/inspector-panel.tsx`), derived from its
|
|
4
|
+
* `Default` story (packages/flow/src/inspector-panel/inspector-panel.stories.tsx). See scripts/gen-contract-tests.mjs.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
7
|
+
import { render, cleanup } from "@testing-library/react";
|
|
8
|
+
import { createRef } from "react";
|
|
9
|
+
import * as stories from "../inspector-panel/inspector-panel.stories";
|
|
10
|
+
import knownFailuresJson from "../../../../scripts/check/contract-known-failures.json";
|
|
11
|
+
|
|
12
|
+
afterEach(cleanup);
|
|
13
|
+
|
|
14
|
+
// Every component here has its own prop/ref/element shape; a generated probe
|
|
15
|
+
// stays generic on purpose (loosely typed, not untyped — see
|
|
16
|
+
// scripts/gen-contract-tests.mjs) rather than re-deriving each one.
|
|
17
|
+
const KNOWN_FAILURES: Record<string, string> = knownFailuresJson;
|
|
18
|
+
const meta = stories.default as { component?: unknown; args?: Record<string, unknown> };
|
|
19
|
+
const Default = (stories as { Default?: { args?: Record<string, unknown> } }).Default;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- probe target; see comment above
|
|
21
|
+
const Component = meta.component as any;
|
|
22
|
+
const args = { ...(meta.args ?? {}), ...(Default?.args ?? {}) };
|
|
23
|
+
const isForwardRefComponent = Component?.["$$typeof"] === Symbol.for("react.forward_ref");
|
|
24
|
+
|
|
25
|
+
/** Wrap a known, tracked failure in `it.fails` so fixing it forces the key's removal. */
|
|
26
|
+
function contractIt(assertion: string, name: string, fn: () => void) {
|
|
27
|
+
const key = `flow-inspectorpanel--default|jsdom|jsdom|${assertion}`;
|
|
28
|
+
const reason = KNOWN_FAILURES[key];
|
|
29
|
+
if (reason) return it.fails(`${name} (known failure: ${reason})`, fn);
|
|
30
|
+
return it(name, fn);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("InspectorPanel contract", () => {
|
|
34
|
+
it.skipIf(!isForwardRefComponent)("forwards a ref to a DOM element", () => {
|
|
35
|
+
const ref = createRef<Element>();
|
|
36
|
+
render(<Component {...args} ref={ref} />);
|
|
37
|
+
expect(ref.current).toBeInstanceOf(Element);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
contractIt("className", "merges a caller className onto the root", () => {
|
|
41
|
+
const { container } = render(<Component {...args} className="contract-probe" />);
|
|
42
|
+
expect(container.querySelector(".contract-probe")).not.toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
contractIt("data-slot", 'exposes data-slot="inspector-panel" on its root', () => {
|
|
46
|
+
const { container } = render(<Component {...args} />);
|
|
47
|
+
expect(container.querySelector('[data-slot="inspector-panel"]')).not.toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-contract-tests.mjs — do not edit; re-run the generator instead.
|
|
2
|
+
/**
|
|
3
|
+
* Contract probe for Legend (`packages/flow/src/legend/legend.tsx`), derived from its
|
|
4
|
+
* `Default` story (packages/flow/src/legend/legend.stories.tsx). See scripts/gen-contract-tests.mjs.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
7
|
+
import { render, cleanup } from "@testing-library/react";
|
|
8
|
+
import { createRef } from "react";
|
|
9
|
+
import * as stories from "../legend/legend.stories";
|
|
10
|
+
import knownFailuresJson from "../../../../scripts/check/contract-known-failures.json";
|
|
11
|
+
|
|
12
|
+
afterEach(cleanup);
|
|
13
|
+
|
|
14
|
+
// Every component here has its own prop/ref/element shape; a generated probe
|
|
15
|
+
// stays generic on purpose (loosely typed, not untyped — see
|
|
16
|
+
// scripts/gen-contract-tests.mjs) rather than re-deriving each one.
|
|
17
|
+
const KNOWN_FAILURES: Record<string, string> = knownFailuresJson;
|
|
18
|
+
const meta = stories.default as { component?: unknown; args?: Record<string, unknown> };
|
|
19
|
+
const Default = (stories as { Default?: { args?: Record<string, unknown> } }).Default;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- probe target; see comment above
|
|
21
|
+
const Component = meta.component as any;
|
|
22
|
+
const args = { ...(meta.args ?? {}), ...(Default?.args ?? {}) };
|
|
23
|
+
const isForwardRefComponent = Component?.["$$typeof"] === Symbol.for("react.forward_ref");
|
|
24
|
+
|
|
25
|
+
/** Wrap a known, tracked failure in `it.fails` so fixing it forces the key's removal. */
|
|
26
|
+
function contractIt(assertion: string, name: string, fn: () => void) {
|
|
27
|
+
const key = `flow-legend--default|jsdom|jsdom|${assertion}`;
|
|
28
|
+
const reason = KNOWN_FAILURES[key];
|
|
29
|
+
if (reason) return it.fails(`${name} (known failure: ${reason})`, fn);
|
|
30
|
+
return it(name, fn);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("Legend contract", () => {
|
|
34
|
+
it.skipIf(!isForwardRefComponent)("forwards a ref to a DOM element", () => {
|
|
35
|
+
const ref = createRef<Element>();
|
|
36
|
+
render(<Component {...args} ref={ref} />);
|
|
37
|
+
expect(ref.current).toBeInstanceOf(Element);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
contractIt("className", "merges a caller className onto the root", () => {
|
|
41
|
+
const { container } = render(<Component {...args} className="contract-probe" />);
|
|
42
|
+
expect(container.querySelector(".contract-probe")).not.toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
contractIt("data-slot", 'exposes data-slot="legend" on its root', () => {
|
|
46
|
+
const { container } = render(<Component {...args} />);
|
|
47
|
+
expect(container.querySelector('[data-slot="legend"]')).not.toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
16
16
|
import { HelperLines } from "../helper-lines/helper-lines";
|
|
17
17
|
import { useHelperLines } from "../helper-lines/use-helper-lines";
|
|
18
|
+
import { clampedFitOffset } from "./clamped-fit-offset";
|
|
18
19
|
import { useMeasuredNodes } from "./use-measured-nodes";
|
|
19
20
|
|
|
20
21
|
export interface CanvasShellProps<
|
|
@@ -52,6 +53,15 @@ export interface CanvasShellProps<
|
|
|
52
53
|
fitViewKey?: string | number;
|
|
53
54
|
/** Options for the {@link fitViewKey} re-fit. Ignored without one. */
|
|
54
55
|
fitViewKeyOptions?: FitViewOptions;
|
|
56
|
+
/**
|
|
57
|
+
* Which nodes a CLAMPED {@link fitViewKey} re-fit keeps in view — typically where the
|
|
58
|
+
* diagram begins. A fit that cannot shrink far enough (a `minZoom` floor) is pinned to the
|
|
59
|
+
* content's top-left corner by default, which is where a top-down layout starts; a
|
|
60
|
+
* left-to-right layout starts at the left edge but at mid-height, so pinning to the top
|
|
61
|
+
* opens on an empty corner. With anchors, each overflowing axis centres the anchor nodes
|
|
62
|
+
* instead, without ever scrolling past the content's own edge. Unset: today's corner pin.
|
|
63
|
+
*/
|
|
64
|
+
fitViewAnchorNodeIds?: readonly string[];
|
|
55
65
|
/** Overlays rendered inside the flow (ZoomControls, Legend, Panels). */
|
|
56
66
|
children?: ReactNode;
|
|
57
67
|
className?: string;
|
|
@@ -120,6 +130,7 @@ function CanvasShellBase<NodeType extends Node, EdgeType extends Edge>({
|
|
|
120
130
|
onNodesChange,
|
|
121
131
|
fitViewKey,
|
|
122
132
|
fitViewKeyOptions,
|
|
133
|
+
fitViewAnchorNodeIds,
|
|
123
134
|
...props
|
|
124
135
|
}: CanvasShellInnerProps<NodeType, EdgeType>) {
|
|
125
136
|
const measured = useMeasuredNodes<NodeType>(nodes, onNodesChange);
|
|
@@ -135,7 +146,11 @@ function CanvasShellBase<NodeType extends Node, EdgeType extends Edge>({
|
|
|
135
146
|
>
|
|
136
147
|
{background ? <Background gap={20} size={1} color="var(--canvas-grid)" /> : null}
|
|
137
148
|
{fitViewKey === undefined ? null : (
|
|
138
|
-
<FitViewOnKey
|
|
149
|
+
<FitViewOnKey
|
|
150
|
+
fitViewKey={fitViewKey}
|
|
151
|
+
options={fitViewKeyOptions}
|
|
152
|
+
anchorNodeIds={fitViewAnchorNodeIds}
|
|
153
|
+
/>
|
|
139
154
|
)}
|
|
140
155
|
{children}
|
|
141
156
|
</ReactFlow>
|
|
@@ -152,9 +167,11 @@ function CanvasShellBase<NodeType extends Node, EdgeType extends Edge>({
|
|
|
152
167
|
function FitViewOnKey({
|
|
153
168
|
fitViewKey,
|
|
154
169
|
options,
|
|
170
|
+
anchorNodeIds,
|
|
155
171
|
}: {
|
|
156
172
|
fitViewKey: string | number;
|
|
157
173
|
options?: FitViewOptions;
|
|
174
|
+
anchorNodeIds?: readonly string[];
|
|
158
175
|
}) {
|
|
159
176
|
const { fitView, getNodes, getNodesBounds, setViewport } = useReactFlow();
|
|
160
177
|
const store = useStoreApi();
|
|
@@ -162,7 +179,14 @@ function FitViewOnKey({
|
|
|
162
179
|
useEffect(() => {
|
|
163
180
|
if (!nodesInitialized) return;
|
|
164
181
|
void fitView(options).then(() =>
|
|
165
|
-
anchorToStartWhenClamped(
|
|
182
|
+
anchorToStartWhenClamped(
|
|
183
|
+
store,
|
|
184
|
+
setViewport,
|
|
185
|
+
getNodesBounds,
|
|
186
|
+
getNodes(),
|
|
187
|
+
options,
|
|
188
|
+
anchorNodeIds,
|
|
189
|
+
),
|
|
166
190
|
);
|
|
167
191
|
// `options` is deliberately absent: an inline object literal would re-fit on every
|
|
168
192
|
// render, which is a viewport jump under the reader's cursor. The key is the trigger.
|
|
@@ -196,6 +220,7 @@ function anchorToStartWhenClamped(
|
|
|
196
220
|
getNodesBounds: ReturnType<typeof useReactFlow>["getNodesBounds"],
|
|
197
221
|
nodes: Node[],
|
|
198
222
|
options: FitViewOptions | undefined,
|
|
223
|
+
anchorNodeIds?: readonly string[],
|
|
199
224
|
): void {
|
|
200
225
|
if (nodes.length === 0) return;
|
|
201
226
|
const { width, height, transform, panZoom } = store.getState();
|
|
@@ -213,9 +238,31 @@ function anchorToStartWhenClamped(
|
|
|
213
238
|
const overflowsY = bounds.height * zoom > height - padY * 2 + 1;
|
|
214
239
|
if (!overflowsX && !overflowsY) return;
|
|
215
240
|
|
|
241
|
+
// With anchors: centre them on an overflowing axis, but never scroll past the content's
|
|
242
|
+
// own start or end. On the axis the diagram runs along, the anchors sit at the content's
|
|
243
|
+
// start, so this lands exactly where the corner pin does; on the cross axis it keeps the
|
|
244
|
+
// first rank in view instead of opening on whatever happens to be top-most.
|
|
245
|
+
const anchors = anchorNodeIds?.length
|
|
246
|
+
? nodes.filter((node) => anchorNodeIds.includes(node.id))
|
|
247
|
+
: [];
|
|
248
|
+
const anchor = anchors.length > 0 ? getNodesBounds(anchors) : null;
|
|
249
|
+
const along = (size: number, pad: number, start: number, extent: number, centre: number) =>
|
|
250
|
+
clampedFitOffset({
|
|
251
|
+
size,
|
|
252
|
+
pad,
|
|
253
|
+
start,
|
|
254
|
+
extent,
|
|
255
|
+
zoom,
|
|
256
|
+
anchorCentre: anchor === null ? undefined : centre,
|
|
257
|
+
});
|
|
258
|
+
|
|
216
259
|
const next = {
|
|
217
|
-
x: overflowsX
|
|
218
|
-
|
|
260
|
+
x: overflowsX
|
|
261
|
+
? along(width, padX, bounds.x, bounds.width, (anchor?.x ?? 0) + (anchor?.width ?? 0) / 2)
|
|
262
|
+
: x,
|
|
263
|
+
y: overflowsY
|
|
264
|
+
? along(height, padY, bounds.y, bounds.height, (anchor?.y ?? 0) + (anchor?.height ?? 0) / 2)
|
|
265
|
+
: y,
|
|
219
266
|
zoom,
|
|
220
267
|
};
|
|
221
268
|
// `setViewport` is a no-op before the pan/zoom instance exists (the very first commit).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { clampedFitOffset } from "./clamped-fit-offset";
|
|
3
|
+
|
|
4
|
+
describe("clampedFitOffset", () => {
|
|
5
|
+
const axis = { size: 1000, pad: 100, start: 0, extent: 4000, zoom: 0.75 };
|
|
6
|
+
|
|
7
|
+
it("pins the content's start at the padding when there is no anchor", () => {
|
|
8
|
+
expect(clampedFitOffset(axis)).toBe(100);
|
|
9
|
+
expect(clampedFitOffset({ ...axis, start: 200 })).toBe(100 - 200 * 0.75);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("an anchor at the content's start lands exactly where the corner pin does", () => {
|
|
13
|
+
expect(clampedFitOffset({ ...axis, anchorCentre: 90 })).toBe(100);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("centres an anchor that sits in the middle of the overflowing axis", () => {
|
|
17
|
+
// Anchor centre at 2000 → 1500 px at zoom 0.75 → offset 500 - 1500.
|
|
18
|
+
expect(clampedFitOffset({ ...axis, anchorCentre: 2000 })).toBe(-1000);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("never scrolls past the content's end", () => {
|
|
22
|
+
// End pinned: 1000 - 100 - 4000 * 0.75 = -2100; an anchor at the very end asks for less.
|
|
23
|
+
expect(clampedFitOffset({ ...axis, anchorCentre: 3990 })).toBe(-2100);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The viewport offset for ONE overflowing axis of a clamped fit (pure; exported for its test).
|
|
3
|
+
* Without an anchor the content's start is pinned at the padding. With one, the anchor is
|
|
4
|
+
* centred — but never so far that the pane shows empty space before the content's start or
|
|
5
|
+
* after its end.
|
|
6
|
+
*/
|
|
7
|
+
export function clampedFitOffset({
|
|
8
|
+
size,
|
|
9
|
+
pad,
|
|
10
|
+
start,
|
|
11
|
+
extent,
|
|
12
|
+
zoom,
|
|
13
|
+
anchorCentre,
|
|
14
|
+
}: {
|
|
15
|
+
/** Pane size on this axis, px. */
|
|
16
|
+
size: number;
|
|
17
|
+
/** Padding the fit keeps on this axis, px. */
|
|
18
|
+
pad: number;
|
|
19
|
+
/** Content start and extent on this axis, in flow coordinates. */
|
|
20
|
+
start: number;
|
|
21
|
+
extent: number;
|
|
22
|
+
zoom: number;
|
|
23
|
+
/** Centre of the anchor nodes on this axis, in flow coordinates. */
|
|
24
|
+
anchorCentre?: number;
|
|
25
|
+
}): number {
|
|
26
|
+
const startPinned = pad - start * zoom;
|
|
27
|
+
if (anchorCentre === undefined) return startPinned;
|
|
28
|
+
const endPinned = size - pad - (start + extent) * zoom;
|
|
29
|
+
return Math.min(startPinned, Math.max(endPinned, size / 2 - anchorCentre * zoom));
|
|
30
|
+
}
|