@luciole-sh/flow-graph 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +671 -2
- package/dist/Flow.d.ts +41 -0
- package/dist/Flow.d.ts.map +1 -0
- package/dist/Flow.js +489 -0
- package/dist/Flow.js.map +1 -0
- package/dist/components.d.ts +28 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +100 -0
- package/dist/components.js.map +1 -0
- package/dist/frame.d.ts +68 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +136 -0
- package/dist/frame.js.map +1 -0
- package/dist/geometry.d.ts +60 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +205 -0
- package/dist/geometry.js.map +1 -0
- package/dist/hooks.d.ts +56 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +65 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap.d.ts +11 -0
- package/dist/keymap.d.ts.map +1 -0
- package/dist/keymap.js +42 -0
- package/dist/keymap.js.map +1 -0
- package/dist/navigation.d.ts +32 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +68 -0
- package/dist/navigation.js.map +1 -0
- package/dist/nodes.d.ts +18 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +46 -0
- package/dist/nodes.js.map +1 -0
- package/dist/raster.d.ts +112 -0
- package/dist/raster.d.ts.map +1 -0
- package/dist/raster.js +370 -0
- package/dist/raster.js.map +1 -0
- package/dist/store.d.ts +104 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +186 -0
- package/dist/store.js.map +1 -0
- package/dist/theme.d.ts +25 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +39 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vendor/xyflow/LICENSE +21 -0
- package/dist/vendor/xyflow/bezier.d.ts +26 -0
- package/dist/vendor/xyflow/bezier.d.ts.map +1 -0
- package/dist/vendor/xyflow/bezier.js +47 -0
- package/dist/vendor/xyflow/bezier.js.map +1 -0
- package/dist/vendor/xyflow/changes.d.ts +9 -0
- package/dist/vendor/xyflow/changes.d.ts.map +1 -0
- package/dist/vendor/xyflow/changes.js +133 -0
- package/dist/vendor/xyflow/changes.js.map +1 -0
- package/dist/vendor/xyflow/smoothstep.d.ts +17 -0
- package/dist/vendor/xyflow/smoothstep.d.ts.map +1 -0
- package/dist/vendor/xyflow/smoothstep.js +166 -0
- package/dist/vendor/xyflow/smoothstep.js.map +1 -0
- package/package.json +61 -4
- package/src/Flow.tsx +684 -0
- package/src/components.tsx +180 -0
- package/src/frame.ts +203 -0
- package/src/geometry.ts +274 -0
- package/src/hooks.tsx +121 -0
- package/src/index.ts +21 -0
- package/src/keymap.tsx +45 -0
- package/src/navigation.ts +86 -0
- package/src/nodes.tsx +86 -0
- package/src/raster.ts +453 -0
- package/src/store.ts +240 -0
- package/src/theme.ts +61 -0
- package/src/types.ts +130 -0
- package/src/vendor/xyflow/LICENSE +21 -0
- package/src/vendor/xyflow/README.md +20 -0
- package/src/vendor/xyflow/bezier.ts +102 -0
- package/src/vendor/xyflow/changes.ts +177 -0
- package/src/vendor/xyflow/smoothstep.ts +219 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Adapted from xyflow, packages/system/src/utils/edges/smoothstep-edge.ts (MIT, see LICENSE
|
|
2
|
+
// and README.md in this directory): `getPoints` only, returning the orthogonal path's
|
|
3
|
+
// corner points instead of an SVG path.
|
|
4
|
+
import type { Position, XY } from "../../types.ts";
|
|
5
|
+
import { getEdgeCenter } from "./bezier.ts";
|
|
6
|
+
|
|
7
|
+
const HALF = 0.5;
|
|
8
|
+
|
|
9
|
+
const handleDirections: Record<Position, XY> = {
|
|
10
|
+
left: { x: -1, y: 0 },
|
|
11
|
+
right: { x: 1, y: 0 },
|
|
12
|
+
top: { x: 0, y: -1 },
|
|
13
|
+
bottom: { x: 0, y: 1 },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getDirection = ({
|
|
17
|
+
source,
|
|
18
|
+
sourcePosition,
|
|
19
|
+
target,
|
|
20
|
+
}: {
|
|
21
|
+
source: XY;
|
|
22
|
+
sourcePosition: Position;
|
|
23
|
+
target: XY;
|
|
24
|
+
}): XY => {
|
|
25
|
+
if (sourcePosition === "left" || sourcePosition === "right") {
|
|
26
|
+
return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
|
|
27
|
+
}
|
|
28
|
+
return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type StepPoints = {
|
|
32
|
+
/** From the source to the target, both included; consecutive points share an axis. */
|
|
33
|
+
points: XY[];
|
|
34
|
+
labelX: number;
|
|
35
|
+
labelY: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* With this function we try to mimic an orthogonal edge routing behaviour
|
|
40
|
+
* It's not as good as a real orthogonal edge routing, but it's faster and good enough as a default for step and smooth step edges
|
|
41
|
+
*/
|
|
42
|
+
export function getStepPoints({
|
|
43
|
+
source,
|
|
44
|
+
sourcePosition = "bottom",
|
|
45
|
+
target,
|
|
46
|
+
targetPosition = "top",
|
|
47
|
+
center = {},
|
|
48
|
+
offset,
|
|
49
|
+
stepPosition = HALF,
|
|
50
|
+
}: {
|
|
51
|
+
source: XY;
|
|
52
|
+
sourcePosition?: Position;
|
|
53
|
+
target: XY;
|
|
54
|
+
targetPosition?: Position;
|
|
55
|
+
center?: Partial<XY>;
|
|
56
|
+
offset: number;
|
|
57
|
+
stepPosition?: number;
|
|
58
|
+
}): StepPoints {
|
|
59
|
+
const sourceDir = handleDirections[sourcePosition];
|
|
60
|
+
const targetDir = handleDirections[targetPosition];
|
|
61
|
+
const sourceGapped: XY = {
|
|
62
|
+
x: source.x + sourceDir.x * offset,
|
|
63
|
+
y: source.y + sourceDir.y * offset,
|
|
64
|
+
};
|
|
65
|
+
const targetGapped: XY = {
|
|
66
|
+
x: target.x + targetDir.x * offset,
|
|
67
|
+
y: target.y + targetDir.y * offset,
|
|
68
|
+
};
|
|
69
|
+
const dir = getDirection({ source: sourceGapped, sourcePosition, target: targetGapped });
|
|
70
|
+
const dirAccessor = dir.x !== 0 ? "x" : "y";
|
|
71
|
+
const currDir = dir[dirAccessor];
|
|
72
|
+
|
|
73
|
+
let points: XY[];
|
|
74
|
+
let centerX: number;
|
|
75
|
+
let centerY: number;
|
|
76
|
+
const sourceGapOffset = { x: 0, y: 0 };
|
|
77
|
+
const targetGapOffset = { x: 0, y: 0 };
|
|
78
|
+
|
|
79
|
+
// opposite handle positions, default case
|
|
80
|
+
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
|
81
|
+
if (dirAccessor === "x") {
|
|
82
|
+
// Primary direction is horizontal, so stepPosition affects X coordinate
|
|
83
|
+
centerX = center.x ?? sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition;
|
|
84
|
+
centerY = center.y ?? (sourceGapped.y + targetGapped.y) / 2;
|
|
85
|
+
} else {
|
|
86
|
+
// Primary direction is vertical, so stepPosition affects Y coordinate
|
|
87
|
+
centerX = center.x ?? (sourceGapped.x + targetGapped.x) / 2;
|
|
88
|
+
centerY = center.y ?? sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* --->
|
|
93
|
+
* |
|
|
94
|
+
* >---
|
|
95
|
+
*/
|
|
96
|
+
const verticalSplit: XY[] = [
|
|
97
|
+
{ x: centerX, y: sourceGapped.y },
|
|
98
|
+
{ x: centerX, y: targetGapped.y },
|
|
99
|
+
];
|
|
100
|
+
/*
|
|
101
|
+
* |
|
|
102
|
+
* ---
|
|
103
|
+
* |
|
|
104
|
+
*/
|
|
105
|
+
const horizontalSplit: XY[] = [
|
|
106
|
+
{ x: sourceGapped.x, y: centerY },
|
|
107
|
+
{ x: targetGapped.x, y: centerY },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
111
|
+
points = dirAccessor === "x" ? verticalSplit : horizontalSplit;
|
|
112
|
+
} else {
|
|
113
|
+
points = dirAccessor === "x" ? horizontalSplit : verticalSplit;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// sourceTarget means we take x from source and y from target, targetSource is the opposite
|
|
117
|
+
const sourceTarget: XY[] = [{ x: sourceGapped.x, y: targetGapped.y }];
|
|
118
|
+
const targetSource: XY[] = [{ x: targetGapped.x, y: sourceGapped.y }];
|
|
119
|
+
// this handles edges with same handle positions
|
|
120
|
+
if (dirAccessor === "x") {
|
|
121
|
+
points = sourceDir.x === currDir ? targetSource : sourceTarget;
|
|
122
|
+
} else {
|
|
123
|
+
points = sourceDir.y === currDir ? sourceTarget : targetSource;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (sourcePosition === targetPosition) {
|
|
127
|
+
const diff = Math.abs(source[dirAccessor] - target[dirAccessor]);
|
|
128
|
+
|
|
129
|
+
// if an edge goes from right to right for example (sourcePosition === targetPosition) and the distance between source.x and target.x is less than the offset, the added point and the gapped source/target will overlap. This leads to a weird edge path. To avoid this we add a gapOffset to the source/target
|
|
130
|
+
if (diff <= offset) {
|
|
131
|
+
const gapOffset = Math.min(offset - 1, offset - diff);
|
|
132
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
133
|
+
sourceGapOffset[dirAccessor] =
|
|
134
|
+
(sourceGapped[dirAccessor] > source[dirAccessor] ? -1 : 1) * gapOffset;
|
|
135
|
+
} else {
|
|
136
|
+
targetGapOffset[dirAccessor] =
|
|
137
|
+
(targetGapped[dirAccessor] > target[dirAccessor] ? -1 : 1) * gapOffset;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// these are conditions for handling mixed handle positions like Right -> Bottom for example
|
|
143
|
+
if (sourcePosition !== targetPosition) {
|
|
144
|
+
const dirAccessorOpposite = dirAccessor === "x" ? "y" : "x";
|
|
145
|
+
const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
|
|
146
|
+
const sourceGtTargetOppo =
|
|
147
|
+
sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
|
|
148
|
+
const sourceLtTargetOppo =
|
|
149
|
+
sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
|
|
150
|
+
const flipSourceTarget =
|
|
151
|
+
(sourceDir[dirAccessor] === 1 &&
|
|
152
|
+
((!isSameDir && sourceGtTargetOppo) || (isSameDir && sourceLtTargetOppo))) ||
|
|
153
|
+
(sourceDir[dirAccessor] !== 1 &&
|
|
154
|
+
((!isSameDir && sourceLtTargetOppo) || (isSameDir && sourceGtTargetOppo)));
|
|
155
|
+
|
|
156
|
+
if (flipSourceTarget) {
|
|
157
|
+
points = dirAccessor === "x" ? sourceTarget : targetSource;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const sourceGapPoint = {
|
|
162
|
+
x: sourceGapped.x + sourceGapOffset.x,
|
|
163
|
+
y: sourceGapped.y + sourceGapOffset.y,
|
|
164
|
+
};
|
|
165
|
+
const targetGapPoint = {
|
|
166
|
+
x: targetGapped.x + targetGapOffset.x,
|
|
167
|
+
y: targetGapped.y + targetGapOffset.y,
|
|
168
|
+
};
|
|
169
|
+
const corner = points[0] ?? sourceGapPoint;
|
|
170
|
+
const maxXDistance = Math.max(
|
|
171
|
+
Math.abs(sourceGapPoint.x - corner.x),
|
|
172
|
+
Math.abs(targetGapPoint.x - corner.x),
|
|
173
|
+
);
|
|
174
|
+
const maxYDistance = Math.max(
|
|
175
|
+
Math.abs(sourceGapPoint.y - corner.y),
|
|
176
|
+
Math.abs(targetGapPoint.y - corner.y),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// we want to place the label on the longest segment of the edge
|
|
180
|
+
if (maxXDistance >= maxYDistance) {
|
|
181
|
+
centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
|
|
182
|
+
centerY = corner.y;
|
|
183
|
+
} else {
|
|
184
|
+
centerX = corner.x;
|
|
185
|
+
centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const gappedSource = {
|
|
190
|
+
x: sourceGapped.x + sourceGapOffset.x,
|
|
191
|
+
y: sourceGapped.y + sourceGapOffset.y,
|
|
192
|
+
};
|
|
193
|
+
const gappedTarget = {
|
|
194
|
+
x: targetGapped.x + targetGapOffset.x,
|
|
195
|
+
y: targetGapped.y + targetGapOffset.y,
|
|
196
|
+
};
|
|
197
|
+
const first = points[0] ?? gappedSource;
|
|
198
|
+
const last = points.at(-1) ?? gappedTarget;
|
|
199
|
+
|
|
200
|
+
// Unlike the original, the default label center (getEdgeCenter) is only a fallback.
|
|
201
|
+
const [defaultX, defaultY] = getEdgeCenter({
|
|
202
|
+
sourceX: source.x,
|
|
203
|
+
sourceY: source.y,
|
|
204
|
+
targetX: target.x,
|
|
205
|
+
targetY: target.y,
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
points: [
|
|
209
|
+
source,
|
|
210
|
+
// we only want to add the gapped source/target if they are different from the first/last point to avoid duplicates which can cause issues with the bends
|
|
211
|
+
...(gappedSource.x !== first.x || gappedSource.y !== first.y ? [gappedSource] : []),
|
|
212
|
+
...points,
|
|
213
|
+
...(gappedTarget.x !== last.x || gappedTarget.y !== last.y ? [gappedTarget] : []),
|
|
214
|
+
target,
|
|
215
|
+
],
|
|
216
|
+
labelX: Number.isFinite(centerX) ? centerX : defaultX,
|
|
217
|
+
labelY: Number.isFinite(centerY) ? centerY : defaultY,
|
|
218
|
+
};
|
|
219
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Standalone (no `@luciole-sh/core/tsconfig`): the package ships its sources, and Bun reads this
|
|
3
|
+
// file to compile them (JSX for OpenTUI) wherever the package is installed.
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"jsxImportSource": "@opentui/react",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"erasableSyntaxOnly": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"allowImportingTsExtensions": true,
|
|
16
|
+
"types": [],
|
|
17
|
+
"moduleDetection": "force",
|
|
18
|
+
"forceConsistentCasingInFileNames": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"],
|
|
21
|
+
"exclude": ["**/node_modules", "dist"]
|
|
22
|
+
}
|