@dschz/solid-flow 0.3.0-next.3 → 0.3.0-next.5
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 +239 -209
- package/dist/index/index.d.ts +164 -59
- package/dist/index/index.js +238 -116
- package/dist/index/index.jsx +170 -73
- package/dist/styles/index.css +1 -1
- package/package.json +3 -1
package/dist/index/index.jsx
CHANGED
|
@@ -5,9 +5,18 @@ import { Dynamic, Portal, isServer } from "@solidjs/web";
|
|
|
5
5
|
import { createEventListener, createEventListenerMap } from "@solid-primitives/event-listener";
|
|
6
6
|
//#region src/contexts/edgeId.ts
|
|
7
7
|
const EdgeIdContext = createContext(null);
|
|
8
|
+
/**
|
|
9
|
+
* Returns the id of the edge this component is rendered inside. Available
|
|
10
|
+
* anywhere in a custom edge's subtree (provided by the edge wrapper), so
|
|
11
|
+
* nested components — like a custom edge label — can learn their host edge
|
|
12
|
+
* without prop drilling.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
* @returns a reactive accessor for the surrounding edge's id
|
|
16
|
+
*/
|
|
8
17
|
function useEdgeId() {
|
|
9
18
|
let ctx = useContext(EdgeIdContext);
|
|
10
|
-
if (!ctx) throw Error("solid-flow:
|
|
19
|
+
if (!ctx) throw Error("solid-flow: useEdgeId must be called inside an edge component (anywhere under a custom edge's subtree)");
|
|
11
20
|
return ctx;
|
|
12
21
|
}
|
|
13
22
|
//#endregion
|
|
@@ -141,11 +150,13 @@ const getEdgeId = (connection) => {
|
|
|
141
150
|
size: 25,
|
|
142
151
|
reconnecting: !1,
|
|
143
152
|
style: {}
|
|
144
|
-
}), rest = omit(_props, "type", "class", "style", "position", "size", "reconnecting", "children"), { store, nodeLookup, edgeLookup, actions } = useInternalSolidFlow(), edgeId = useEdgeId(), [reconnecting, setReconnecting] = createSignal(!1);
|
|
153
|
+
}), rest = omit(_props, "type", "class", "style", "position", "size", "reconnecting", "onReconnectingChange", "children"), { store, nodeLookup, edgeLookup, actions } = useInternalSolidFlow(), edgeId = useEdgeId(), [reconnecting, setReconnecting] = createSignal(!1);
|
|
145
154
|
if (!edgeId()) throw Error("[solid-flow]: EdgeReconnectAnchor must be used within an Edge component");
|
|
146
|
-
let edge = () => edgeLookup[edgeId()],
|
|
155
|
+
let edge = () => edgeLookup[edgeId()], isReconnecting = () => _props.reconnecting || reconnecting(), setReconnectingState = (next) => {
|
|
156
|
+
setReconnecting(next), _props.onReconnectingChange?.(next);
|
|
157
|
+
}, onPointerDown = (event) => {
|
|
147
158
|
if (event.button !== 0) return;
|
|
148
|
-
|
|
159
|
+
setReconnectingState(!0), store.onReconnectStart?.(event, edge(), _props.type);
|
|
149
160
|
let opposite = _props.type === "target" ? {
|
|
150
161
|
nodeId: edge().source,
|
|
151
162
|
handleId: edge().sourceHandle ?? null,
|
|
@@ -169,7 +180,9 @@ const getEdgeId = (connection) => {
|
|
|
169
180
|
edgeUpdaterType: opposite.type,
|
|
170
181
|
cancelConnection: actions.cancelConnection,
|
|
171
182
|
panBy: actions.panBy,
|
|
172
|
-
updateConnection:
|
|
183
|
+
updateConnection: (connection) => {
|
|
184
|
+
actions.setConnection(connection), flush();
|
|
185
|
+
},
|
|
173
186
|
isValidConnection: store.isValidConnection,
|
|
174
187
|
onConnectStart: store.onConnectStart,
|
|
175
188
|
onConnectEnd: store.onConnectEnd,
|
|
@@ -181,7 +194,7 @@ const getEdgeId = (connection) => {
|
|
|
181
194
|
newEdge = store.onBeforeReconnect?.(newEdge, edge()) ?? newEdge, newEdge && actions.setEdges((edges) => edges.map((e) => e.id === edge().id ? newEdge : e)), store.onReconnect?.(edge(), connection);
|
|
182
195
|
},
|
|
183
196
|
onReconnectEnd: (event, connectionState) => {
|
|
184
|
-
|
|
197
|
+
setReconnectingState(!1), store.onReconnectEnd?.(event, edge(), opposite.type, connectionState);
|
|
185
198
|
},
|
|
186
199
|
getTransform: () => store.transform,
|
|
187
200
|
getFromHandle: () => store.connection.fromHandle,
|
|
@@ -204,7 +217,7 @@ const getEdgeId = (connection) => {
|
|
|
204
217
|
cursor: "move",
|
|
205
218
|
..._props.style
|
|
206
219
|
}}>
|
|
207
|
-
<Show when={!
|
|
220
|
+
<Show when={!isReconnecting()}>{_props.children}</Show>
|
|
208
221
|
</div>
|
|
209
222
|
</EdgeLabel>;
|
|
210
223
|
}, ARIA_NODE_DESC_KEY = "solid-flow__node-desc", ARIA_EDGE_DESC_KEY = "solid-flow__edge-desc", A11yDescriptions = () => {
|
|
@@ -227,7 +240,6 @@ const getEdgeId = (connection) => {
|
|
|
227
240
|
let [store, setStore] = createStore(edges);
|
|
228
241
|
return [store, setStore];
|
|
229
242
|
}, STEP = .5 / 2, rectsEqual = (a, b) => a === b || !!a && !!b && a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height, rectsOverlap = (a, b) => a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y, createCullingViewport = (source) => createMemo(() => {
|
|
230
|
-
if (!source.onlyRenderVisibleElements) return null;
|
|
231
243
|
let { width, height } = source;
|
|
232
244
|
if (!width || !height) return null;
|
|
233
245
|
let [tx, ty, zoom] = source.transform, zoomBucket = 2 ** Math.floor(Math.log2(zoom)), bucketWidth = width / zoomBucket, bucketHeight = height / zoomBucket, stepX = bucketWidth * STEP, stepY = bucketHeight * STEP, centerX = (width / 2 - tx) / zoom, centerY = (height / 2 - ty) / zoom, quantizedX = Math.round(centerX / stepX) * stepX, quantizedY = Math.round(centerY / stepY) * stepY, halfWidth = bucketWidth * 1, halfHeight = bucketHeight * 1;
|
|
@@ -238,9 +250,9 @@ const getEdgeId = (connection) => {
|
|
|
238
250
|
height: 2 * halfHeight
|
|
239
251
|
};
|
|
240
252
|
}, { equals: rectsEqual }), isNodeCulled = (node, cullingViewport) => {
|
|
241
|
-
if (!cullingViewport || node.selected) return !1;
|
|
253
|
+
if (!cullingViewport || node.selected || node.cullable === !1) return !1;
|
|
242
254
|
let { width, height } = node.measured;
|
|
243
|
-
if (!width || !height) return !1;
|
|
255
|
+
if (!width || !height || !node.internals.handleBounds) return !1;
|
|
244
256
|
let { x, y } = node.internals.positionAbsolute;
|
|
245
257
|
return !rectsOverlap({
|
|
246
258
|
x,
|
|
@@ -249,7 +261,7 @@ const getEdgeId = (connection) => {
|
|
|
249
261
|
height
|
|
250
262
|
}, cullingViewport);
|
|
251
263
|
}, isEdgeCulled = (row, cullingViewport) => {
|
|
252
|
-
if (!cullingViewport || row.selected) return !1;
|
|
264
|
+
if (!cullingViewport || row.selected || row.cullable === !1) return !1;
|
|
253
265
|
let x = Math.min(row.sourceX, row.targetX), y = Math.min(row.sourceY, row.targetY);
|
|
254
266
|
return !rectsOverlap({
|
|
255
267
|
x,
|
|
@@ -291,7 +303,7 @@ const getEdgeId = (connection) => {
|
|
|
291
303
|
selectNodesOnDrag: !0,
|
|
292
304
|
elevateNodesOnSelect: !0,
|
|
293
305
|
zIndexMode: "basic",
|
|
294
|
-
onlyRenderVisibleElements: !
|
|
306
|
+
onlyRenderVisibleElements: !1,
|
|
295
307
|
disableKeyboardA11y: !1,
|
|
296
308
|
defaultMarkerColor: "#b1b1b7",
|
|
297
309
|
ariaLiveMessage: "",
|
|
@@ -581,9 +593,17 @@ const createLayoutedEdges = (source) => {
|
|
|
581
593
|
return config().zIndexMode;
|
|
582
594
|
}
|
|
583
595
|
}), nodeLookup = new RecordMapFacade(internalNodes), initialViewport = getInitialViewport(_props.fitView, _props.initialViewport, _props.width ?? 0, _props.height ?? 0, nodeLookup), [viewportStore, setViewportStore] = createStore(_props.viewport ?? initialViewport);
|
|
584
|
-
createEffect(() =>
|
|
596
|
+
createEffect(() => {
|
|
597
|
+
let next = config().nodes;
|
|
598
|
+
for (let node of next);
|
|
599
|
+
return { next };
|
|
600
|
+
}, ({ next }) => {
|
|
585
601
|
setNodesStore(() => next);
|
|
586
|
-
}, { defer: !0 }), createEffect(() =>
|
|
602
|
+
}, { defer: !0 }), createEffect(() => {
|
|
603
|
+
let next = config().edges;
|
|
604
|
+
for (let edge of next);
|
|
605
|
+
return { next };
|
|
606
|
+
}, ({ next }) => {
|
|
587
607
|
setEdgesStore(() => next);
|
|
588
608
|
}, { defer: !0 }), createEffect(() => config().viewport, (next) => {
|
|
589
609
|
next && setViewportStore(() => next);
|
|
@@ -744,9 +764,6 @@ const createLayoutedEdges = (source) => {
|
|
|
744
764
|
return zoomActivationKeyPressed();
|
|
745
765
|
}
|
|
746
766
|
}), cullingViewport = createCullingViewport({
|
|
747
|
-
get onlyRenderVisibleElements() {
|
|
748
|
-
return store.onlyRenderVisibleElements;
|
|
749
|
-
},
|
|
750
767
|
get width() {
|
|
751
768
|
return store.width;
|
|
752
769
|
},
|
|
@@ -1572,16 +1589,30 @@ const Handle = (props) => {
|
|
|
1572
1589
|
};
|
|
1573
1590
|
}), dragging;
|
|
1574
1591
|
}, NodeIdContext = createContext(null);
|
|
1592
|
+
/**
|
|
1593
|
+
* Returns the id of the node this component is rendered inside. Available
|
|
1594
|
+
* anywhere in a custom node's subtree (provided by the node wrapper), so
|
|
1595
|
+
* nested components — like a custom `Handle` — can learn their host node
|
|
1596
|
+
* without prop drilling.
|
|
1597
|
+
*
|
|
1598
|
+
* @public
|
|
1599
|
+
* @returns a reactive accessor for the surrounding node's id
|
|
1600
|
+
*/
|
|
1575
1601
|
function useNodeId() {
|
|
1576
1602
|
let ctx = useContext(NodeIdContext);
|
|
1577
|
-
if (!ctx) throw Error("solid-flow:
|
|
1603
|
+
if (!ctx) throw Error("solid-flow: useNodeId must be called inside a node component (anywhere under a custom node's subtree)");
|
|
1578
1604
|
return ctx;
|
|
1579
1605
|
}
|
|
1580
1606
|
//#endregion
|
|
1581
1607
|
//#region src/components/node/NodeWrapper.tsx
|
|
1582
1608
|
/** Internal per-node wrapper: dragging, selection, a11y, measurement, viewport culling, and the dynamic node component. */
|
|
1583
1609
|
const NodeWrapper = (props) => {
|
|
1584
|
-
let { store, nodeLookup, parentIds, actions } = useInternalSolidFlow(), [nodeRef, setNodeRef] = createSignal(), node = () => nodeLookup.get(props.nodeId)
|
|
1610
|
+
let { store, nodeLookup, parentIds, actions } = useInternalSolidFlow(), [nodeRef, setNodeRef] = createSignal(), node = () => nodeLookup.get(props.nodeId);
|
|
1611
|
+
onCleanup(() => {
|
|
1612
|
+
let el = nodeRef();
|
|
1613
|
+
el && props.resizeObserver?.unobserve(el);
|
|
1614
|
+
});
|
|
1615
|
+
let nodeId = () => node().id, nodeType = () => node().type ?? "default", deletable = () => node().deletable ?? !0, selectable = () => node().selectable ?? store.elementsSelectable, focusable = () => node().focusable ?? store.nodesFocusable, draggable = () => node().draggable ?? store.nodesDraggable, connectable = () => node().connectable ?? store.nodesConnectable, userNode = () => node().internals.userNode, nodeTypeValid = () => nodeType() in store.nodeTypes, nodeComponent = () => store.nodeTypes[nodeType()], isParentNode = () => !!parentIds[node().id], transform = () => {
|
|
1585
1616
|
let { x, y } = node().internals.positionAbsolute;
|
|
1586
1617
|
return `translate(${x}px, ${y}px)`;
|
|
1587
1618
|
}, sizeStyle = () => {
|
|
@@ -1950,16 +1981,28 @@ const ConnectionLine = (props) => {
|
|
|
1950
1981
|
</svg>
|
|
1951
1982
|
</Show>;
|
|
1952
1983
|
}, EdgeRenderer = (props) => {
|
|
1953
|
-
let { store } = useInternalSolidFlow()
|
|
1954
|
-
|
|
1984
|
+
let { store, actions } = useInternalSolidFlow(), [focusedEdgeId, setFocusedEdgeId] = createSignal(null), onFocusIn = (event) => {
|
|
1985
|
+
let edgeElement = event.target.closest("[data-id]");
|
|
1986
|
+
setFocusedEdgeId(edgeElement?.getAttribute("data-id") ?? null);
|
|
1987
|
+
};
|
|
1988
|
+
return <div class="solid-flow__edges" onFocusIn={onFocusIn} onFocusOut={() => setFocusedEdgeId(null)}>
|
|
1955
1989
|
<MarkerDefinition />
|
|
1956
1990
|
|
|
1957
1991
|
<For each={store.visibleEdgeIds}>
|
|
1958
|
-
{(edgeId) =>
|
|
1992
|
+
{(edgeId) => {
|
|
1993
|
+
let unmounted = createMemo(() => {
|
|
1994
|
+
if (!store.onlyRenderVisibleElements || focusedEdgeId() === edgeId) return !1;
|
|
1995
|
+
let edge = actions.getEdge(edgeId);
|
|
1996
|
+
return !!edge && isEdgeCulled(edge, store.cullingViewport);
|
|
1997
|
+
});
|
|
1998
|
+
return <Show when={!unmounted()}>
|
|
1999
|
+
<EdgeWrapper edgeId={edgeId} onEdgeClick={props.onEdgeClick} onEdgeContextMenu={props.onEdgeContextMenu} onEdgePointerEnter={props.onEdgePointerEnter} onEdgePointerLeave={props.onEdgePointerLeave} />
|
|
2000
|
+
</Show>;
|
|
2001
|
+
}}
|
|
1959
2002
|
</For>
|
|
1960
2003
|
</div>;
|
|
1961
2004
|
}, NodeRenderer = (props) => {
|
|
1962
|
-
let { actions, store } = useInternalSolidFlow(), resizeObserver = isServer ? void 0 : new ResizeObserver((entries) => {
|
|
2005
|
+
let { actions, store, nodeLookup } = useInternalSolidFlow(), resizeObserver = isServer ? void 0 : new ResizeObserver((entries) => {
|
|
1963
2006
|
actions.requestUpdateNodeInternals(entries.map((entry) => {
|
|
1964
2007
|
let id = entry.target.getAttribute("data-id");
|
|
1965
2008
|
return [id, {
|
|
@@ -1969,11 +2012,25 @@ const ConnectionLine = (props) => {
|
|
|
1969
2012
|
}];
|
|
1970
2013
|
}));
|
|
1971
2014
|
});
|
|
1972
|
-
|
|
2015
|
+
onCleanup(() => {
|
|
1973
2016
|
resizeObserver?.disconnect();
|
|
1974
|
-
})
|
|
2017
|
+
});
|
|
2018
|
+
let [focusedNodeId, setFocusedNodeId] = createSignal(null), onFocusIn = (event) => {
|
|
2019
|
+
let nodeElement = event.target.closest("[data-id]");
|
|
2020
|
+
setFocusedNodeId(nodeElement?.getAttribute("data-id") ?? null);
|
|
2021
|
+
};
|
|
2022
|
+
return <div class="solid-flow__container solid-flow__nodes" onFocusIn={onFocusIn} onFocusOut={() => setFocusedNodeId(null)}>
|
|
1975
2023
|
<For each={store.visibleNodeIds}>
|
|
1976
|
-
{(nodeId) =>
|
|
2024
|
+
{(nodeId) => {
|
|
2025
|
+
let unmounted = createMemo(() => {
|
|
2026
|
+
if (!store.onlyRenderVisibleElements || focusedNodeId() === nodeId) return !1;
|
|
2027
|
+
let node = nodeLookup.get(nodeId);
|
|
2028
|
+
return !!node && isNodeCulled(node, store.cullingViewport);
|
|
2029
|
+
});
|
|
2030
|
+
return <Show when={!unmounted()}>
|
|
2031
|
+
<NodeWrapper nodeId={nodeId} resizeObserver={resizeObserver} nodeClickDistance={props.nodeClickDistance} onNodeClick={props.onNodeClick} onNodePointerEnter={props.onNodePointerEnter} onNodePointerMove={props.onNodePointerMove} onNodePointerLeave={props.onNodePointerLeave} onNodeDrag={props.onNodeDrag} onNodeDragStart={props.onNodeDragStart} onNodeDragStop={props.onNodeDragStop} onNodeContextMenu={props.onNodeContextMenu} />
|
|
2032
|
+
</Show>;
|
|
2033
|
+
}}
|
|
1977
2034
|
</For>
|
|
1978
2035
|
</div>;
|
|
1979
2036
|
}, isSetEqual = (a, b) => {
|
|
@@ -2285,25 +2342,14 @@ const ConnectionLine = (props) => {
|
|
|
2285
2342
|
* `const { flow, commands } = useSolidFlow()`.
|
|
2286
2343
|
*
|
|
2287
2344
|
* @public
|
|
2288
|
-
* @returns the flow's read struct
|
|
2345
|
+
* @returns the flow's read struct and write surface
|
|
2289
2346
|
*/
|
|
2290
2347
|
function useSolidFlow() {
|
|
2291
|
-
let { flow, commands
|
|
2348
|
+
let { flow, commands } = useInternalSolidFlow();
|
|
2292
2349
|
return {
|
|
2293
2350
|
...commands,
|
|
2294
2351
|
flow,
|
|
2295
|
-
commands
|
|
2296
|
-
getInternalNode,
|
|
2297
|
-
getNode: (id) => getInternalNode(id)?.internals.userNode,
|
|
2298
|
-
getNodes: (ids) => ids ? ids.flatMap((id) => {
|
|
2299
|
-
let userNode = flow.internalNodes[id]?.internals.userNode;
|
|
2300
|
-
return userNode ? [userNode] : [];
|
|
2301
|
-
}) : [...flow.nodes],
|
|
2302
|
-
getEdge: (id) => edgeLookup[id],
|
|
2303
|
-
getEdges: (ids) => ids ? ids.flatMap((id) => edgeLookup[id] ? [edgeLookup[id]] : []) : [...flow.edges],
|
|
2304
|
-
getViewport: () => snapshot(store.viewport),
|
|
2305
|
-
getZoom: () => flow.viewport.zoom,
|
|
2306
|
-
getHandleConnections: ({ type, id, nodeId }) => Object.values(flow.connections[connectionKey(nodeId, type, id ?? null)] ?? {})
|
|
2352
|
+
commands
|
|
2307
2353
|
};
|
|
2308
2354
|
}
|
|
2309
2355
|
//#endregion
|
|
@@ -2339,6 +2385,34 @@ function matchesKey(event, keyDef) {
|
|
|
2339
2385
|
function matchesKeyArray(event, keyDefs) {
|
|
2340
2386
|
return keyDefs ? (Array.isArray(keyDefs) ? keyDefs : [keyDefs]).some((keyDef) => matchesKey(event, keyDef)) : !1;
|
|
2341
2387
|
}
|
|
2388
|
+
const MODIFIER_FLAG = {
|
|
2389
|
+
alt: "altKey",
|
|
2390
|
+
control: "ctrlKey",
|
|
2391
|
+
ctrl: "ctrlKey",
|
|
2392
|
+
meta: "metaKey",
|
|
2393
|
+
shift: "shiftKey"
|
|
2394
|
+
};
|
|
2395
|
+
/**
|
|
2396
|
+
* Whether an event's modifier flags PROVE this key definition cannot be held
|
|
2397
|
+
* right now: the key itself is a modifier whose flag is off, or one of its
|
|
2398
|
+
* required modifiers is off. Definitions built on non-modifier keys are never
|
|
2399
|
+
* contradicted (their state isn't derivable from flags).
|
|
2400
|
+
*/
|
|
2401
|
+
function isContradicted(flags, keyDef) {
|
|
2402
|
+
let keyFlag = MODIFIER_FLAG[getKeyString(keyDef).toLowerCase()];
|
|
2403
|
+
if (keyFlag && !flags[keyFlag]) return !0;
|
|
2404
|
+
let modifiers = getModifier(keyDef);
|
|
2405
|
+
return (Array.isArray(modifiers) ? modifiers.flatMap((mod) => mod) : [modifiers]).some((mod) => {
|
|
2406
|
+
let modFlag = MODIFIER_FLAG[mod.toLowerCase()];
|
|
2407
|
+
return !!modFlag && !flags[modFlag];
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
/** True only when EVERY definition for this key state is contradicted. */
|
|
2411
|
+
function allContradicted(flags, keyDefs) {
|
|
2412
|
+
if (!keyDefs) return !1;
|
|
2413
|
+
let keys = Array.isArray(keyDefs) ? keyDefs : [keyDefs];
|
|
2414
|
+
return keys.length > 0 && keys.every((keyDef) => isContradicted(flags, keyDef));
|
|
2415
|
+
}
|
|
2342
2416
|
const KeyHandler = (props) => {
|
|
2343
2417
|
let { store, actions } = useInternalSolidFlow(), { deleteElements } = useSolidFlow(), _props = {
|
|
2344
2418
|
get selectionKey() {
|
|
@@ -2358,6 +2432,20 @@ const KeyHandler = (props) => {
|
|
|
2358
2432
|
}
|
|
2359
2433
|
}, resetKeysAndSelection = () => {
|
|
2360
2434
|
actions.setSelectionRect(void 0), actions.setSelectionKeyPressed(!1), actions.setMultiselectionKeyPressed(!1), actions.setDeleteKeyPressed(!1), actions.setPanActivationKeyPressed(!1), actions.setZoomActivationKeyPressed(!1);
|
|
2435
|
+
}, reconcileModifiers = (event) => {
|
|
2436
|
+
let changed = !1;
|
|
2437
|
+
store.selectionKeyPressed && allContradicted(event, _props.selectionKey) && (actions.setSelectionKeyPressed(!1), changed = !0), store.multiselectionKeyPressed && allContradicted(event, _props.multiSelectionKey) && (actions.setMultiselectionKeyPressed(!1), changed = !0), store.panActivationKeyPressed && allContradicted(event, _props.panActivationKey) && (actions.setPanActivationKeyPressed(!1), changed = !0), store.zoomActivationKeyPressed && allContradicted(event, _props.zoomActivationKey) && (actions.setZoomActivationKeyPressed(!1), changed = !0), changed && flush();
|
|
2438
|
+
}, cancelPointerGestures = () => {
|
|
2439
|
+
let release = (Ctor, type) => {
|
|
2440
|
+
try {
|
|
2441
|
+
window.dispatchEvent(new Ctor(type, { view: window }));
|
|
2442
|
+
} catch {
|
|
2443
|
+
window.dispatchEvent(new Ctor(type));
|
|
2444
|
+
}
|
|
2445
|
+
};
|
|
2446
|
+
release(MouseEvent, "mouseup"), typeof PointerEvent < "u" && release(PointerEvent, "pointerup");
|
|
2447
|
+
}, handleWindowBlur = () => {
|
|
2448
|
+
resetKeysAndSelection(), cancelPointerGestures();
|
|
2361
2449
|
}, handleDelete = async () => {
|
|
2362
2450
|
let selectedNodes = store.nodes.filter((node) => node.selected), selectedEdges = store.edges.filter((edge) => edge.selected), { deletedNodes, deletedEdges } = await deleteElements({
|
|
2363
2451
|
nodes: selectedNodes,
|
|
@@ -2368,16 +2456,22 @@ const KeyHandler = (props) => {
|
|
|
2368
2456
|
edges: deletedEdges
|
|
2369
2457
|
});
|
|
2370
2458
|
};
|
|
2371
|
-
return isServer || createEventListenerMap(window, {
|
|
2459
|
+
return isServer || (createEventListenerMap(window, {
|
|
2372
2460
|
keydown: (event) => {
|
|
2373
|
-
matchesKeyArray(event, _props.selectionKey) && actions.setSelectionKeyPressed(!0), matchesKeyArray(event, _props.multiSelectionKey) && actions.setMultiselectionKeyPressed(!0), matchesKeyArray(event, _props.deleteKey) && !isInputDOMNode(event) && (event.ctrlKey || event.metaKey || event.shiftKey || (actions.setDeleteKeyPressed(!0), handleDelete())), matchesKeyArray(event, _props.panActivationKey) && actions.setPanActivationKeyPressed(!0), matchesKeyArray(event, _props.zoomActivationKey) && actions.setZoomActivationKeyPressed(!0), flush();
|
|
2461
|
+
reconcileModifiers(event), matchesKeyArray(event, _props.selectionKey) && actions.setSelectionKeyPressed(!0), matchesKeyArray(event, _props.multiSelectionKey) && actions.setMultiselectionKeyPressed(!0), matchesKeyArray(event, _props.deleteKey) && !isInputDOMNode(event) && (event.ctrlKey || event.metaKey || event.shiftKey || (actions.setDeleteKeyPressed(!0), handleDelete())), matchesKeyArray(event, _props.panActivationKey) && actions.setPanActivationKeyPressed(!0), matchesKeyArray(event, _props.zoomActivationKey) && actions.setZoomActivationKeyPressed(!0), flush();
|
|
2374
2462
|
},
|
|
2375
2463
|
keyup: (event) => {
|
|
2376
|
-
matchesKeyArray(event, _props.selectionKey) && actions.setSelectionKeyPressed(!1), matchesKeyArray(event, _props.multiSelectionKey) && actions.setMultiselectionKeyPressed(!1), matchesKeyArray(event, _props.deleteKey) && actions.setDeleteKeyPressed(!1), matchesKeyArray(event, _props.panActivationKey) && actions.setPanActivationKeyPressed(!1), matchesKeyArray(event, _props.zoomActivationKey) && actions.setZoomActivationKeyPressed(!1), flush();
|
|
2464
|
+
reconcileModifiers(event), matchesKeyArray(event, _props.selectionKey) && actions.setSelectionKeyPressed(!1), matchesKeyArray(event, _props.multiSelectionKey) && actions.setMultiselectionKeyPressed(!1), matchesKeyArray(event, _props.deleteKey) && actions.setDeleteKeyPressed(!1), matchesKeyArray(event, _props.panActivationKey) && actions.setPanActivationKeyPressed(!1), matchesKeyArray(event, _props.zoomActivationKey) && actions.setZoomActivationKeyPressed(!1), flush();
|
|
2377
2465
|
},
|
|
2378
|
-
blur:
|
|
2466
|
+
blur: handleWindowBlur,
|
|
2379
2467
|
contextmenu: resetKeysAndSelection
|
|
2380
|
-
}),
|
|
2468
|
+
}), createEventListenerMap(window, {
|
|
2469
|
+
pointerdown: reconcileModifiers,
|
|
2470
|
+
wheel: reconcileModifiers
|
|
2471
|
+
}, {
|
|
2472
|
+
capture: !0,
|
|
2473
|
+
passive: !0
|
|
2474
|
+
})), null;
|
|
2381
2475
|
}, SolidFlow = (props) => {
|
|
2382
2476
|
let [domNodeRef, setDomNodeRef] = createSignal(), domNode, _props = merge({
|
|
2383
2477
|
...getDefaultFlowStateProps(),
|
|
@@ -2534,19 +2628,6 @@ function useViewport() {
|
|
|
2534
2628
|
return () => flow.viewport;
|
|
2535
2629
|
}
|
|
2536
2630
|
//#endregion
|
|
2537
|
-
//#region src/hooks/useHandleEdgeSelect.ts
|
|
2538
|
-
/** Returns a callback applying edge-selection semantics for the given edge id. */
|
|
2539
|
-
function useHandleEdgeSelect() {
|
|
2540
|
-
let { store, edgeLookup, actions } = useInternalSolidFlow();
|
|
2541
|
-
return (id) => {
|
|
2542
|
-
let edge = edgeLookup[id];
|
|
2543
|
-
edge && (edge.selectable || store.elementsSelectable && edge.selectable === void 0) && (actions.setSelectionRect(void 0), actions.setSelectionRectMode(void 0), edge.selected ? edge.selected && store.multiselectionKeyPressed && actions.unselectNodesAndEdges({
|
|
2544
|
-
nodes: [],
|
|
2545
|
-
edges: [edge]
|
|
2546
|
-
}) : actions.addSelectedEdges([id]));
|
|
2547
|
-
};
|
|
2548
|
-
}
|
|
2549
|
-
//#endregion
|
|
2550
2631
|
//#region src/hooks/useInitialized.ts
|
|
2551
2632
|
/**
|
|
2552
2633
|
* Hook for seeing if all nodes have been measured.
|
|
@@ -2576,11 +2657,16 @@ function useViewportInitialized() {
|
|
|
2576
2657
|
//#endregion
|
|
2577
2658
|
//#region src/hooks/useInternalNode.ts
|
|
2578
2659
|
/**
|
|
2579
|
-
* Hook to get an internal node
|
|
2660
|
+
* Hook to get an internal node (the node enriched with measured dimensions,
|
|
2661
|
+
* absolute position, and z-order) by id.
|
|
2662
|
+
*
|
|
2663
|
+
* The id is an accessor (not a plain string) on purpose: passing a raw
|
|
2664
|
+
* reactive read like `props.nodeId` would capture the value once and silently
|
|
2665
|
+
* lose reactivity — `useInternalNode(() => props.nodeId)` keeps it live.
|
|
2580
2666
|
*
|
|
2581
2667
|
* @public
|
|
2582
|
-
* @param id - the node id
|
|
2583
|
-
* @returns an accessor with
|
|
2668
|
+
* @param id - a reactive accessor for the node id
|
|
2669
|
+
* @returns an accessor with the internal node, or undefined while absent
|
|
2584
2670
|
*/
|
|
2585
2671
|
function useInternalNode(id) {
|
|
2586
2672
|
let { flow } = useInternalSolidFlow();
|
|
@@ -2667,7 +2753,6 @@ const DotPattern = (props) => {
|
|
|
2667
2753
|
let _props = propDefaults(props, {
|
|
2668
2754
|
variant: "dots",
|
|
2669
2755
|
gap: 20,
|
|
2670
|
-
size: 1,
|
|
2671
2756
|
lineWidth: 1,
|
|
2672
2757
|
style: {}
|
|
2673
2758
|
}), { store } = useInternalSolidFlow(), isDots = () => _props.variant === "dots", isCross = () => _props.variant === "cross", patternId = () => `background-pattern-${store.id}-${_props.id ?? ""}`, scaledSize = () => (_props.size ?? DEFAULT_SIZE[_props.variant]) * store.viewport.zoom, gapXY = () => Array.isArray(_props.gap) ? _props.gap : [_props.gap, _props.gap], scaledGap = () => [gapXY()[0] * store.viewport.zoom || 1, gapXY()[1] * store.viewport.zoom || 1], patternDimensions = () => isCross() ? [scaledSize(), scaledSize()] : scaledGap(), patternOffset = () => isDots() ? [scaledSize() / 2, scaledSize() / 2] : [patternDimensions()[0] / 2, patternDimensions()[1] / 2];
|
|
@@ -2763,8 +2848,8 @@ const DotPattern = (props) => {
|
|
|
2763
2848
|
borderRadius: 5,
|
|
2764
2849
|
width: 0,
|
|
2765
2850
|
height: 0
|
|
2766
|
-
}), style = () => Object.entries({
|
|
2767
|
-
fill:
|
|
2851
|
+
}), fill = () => _props.color ?? _props.style?.background ?? _props.style?.["background-color"], style = () => Object.entries({
|
|
2852
|
+
fill: fill(),
|
|
2768
2853
|
stroke: _props.strokeColor,
|
|
2769
2854
|
"stroke-width": _props.strokeWidth
|
|
2770
2855
|
}).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => (acc[key] = value, acc), {});
|
|
@@ -2772,7 +2857,7 @@ const DotPattern = (props) => {
|
|
|
2772
2857
|
"solid-flow__minimap-node",
|
|
2773
2858
|
{ selected: !!_props.selected },
|
|
2774
2859
|
_props.class
|
|
2775
|
-
]} x={_props.x} y={_props.y} rx={_props.borderRadius} ry={_props.borderRadius} width={_props.width} height={_props.height} shape-rendering={_props.shapeRendering} style={style()} />;
|
|
2860
|
+
]} x={_props.x} y={_props.y} rx={_props.borderRadius} ry={_props.borderRadius} width={_props.width} height={_props.height} shape-rendering={_props.shapeRendering} style={style()} onClick={_props.onClick ? (event) => _props.onClick(event, _props.id) : void 0} />;
|
|
2776
2861
|
}, getAttrFunction = (func) => func instanceof Function ? func : () => func, MiniMap = (props) => {
|
|
2777
2862
|
let { store, nodeLookup } = useInternalSolidFlow(), _props = propDefaults(props, {
|
|
2778
2863
|
position: "bottom-right",
|
|
@@ -2785,7 +2870,7 @@ const DotPattern = (props) => {
|
|
|
2785
2870
|
nodeBorderRadius: 5,
|
|
2786
2871
|
nodeStrokeWidth: 2,
|
|
2787
2872
|
style: {}
|
|
2788
|
-
}), paneProps = omit(_props, "class", "style", "position", "nodeClass", "nodeStrokeColor", "nodeColor", "pannable", "zoomable", "inversePan", "zoomStep", "bgColor", "width", "height", "maskColor", "maskStrokeColor", "maskStrokeWidth", "nodeBorderRadius", "nodeStrokeWidth"), nodeColorFunc = () => _props.nodeColor === void 0 ? void 0 : getAttrFunction(_props.nodeColor), nodeStrokeColorFunc = () => getAttrFunction(_props.nodeStrokeColor), nodeClassFunc = () => getAttrFunction(_props.nodeClass), shapeRendering = () => typeof window > "u" || window.chrome ? "crispEdges" : "geometricPrecision", labelledBy = createMemo(() => `solid-flow__minimap-desc-${store.id}`), getViewBB = () => ({
|
|
2873
|
+
}), paneProps = omit(_props, "class", "style", "position", "nodeClass", "nodeStrokeColor", "nodeColor", "pannable", "zoomable", "inversePan", "zoomStep", "bgColor", "width", "height", "maskColor", "maskStrokeColor", "maskStrokeWidth", "nodeBorderRadius", "nodeStrokeWidth", "nodeComponent", "onClick", "onNodeClick"), nodeColorFunc = () => _props.nodeColor === void 0 ? void 0 : getAttrFunction(_props.nodeColor), nodeStrokeColorFunc = () => getAttrFunction(_props.nodeStrokeColor), nodeClassFunc = () => getAttrFunction(_props.nodeClass), shapeRendering = () => typeof window > "u" || window.chrome ? "crispEdges" : "geometricPrecision", labelledBy = createMemo(() => `solid-flow__minimap-desc-${store.id}`), getViewBB = () => ({
|
|
2789
2874
|
x: -store.viewport.x / store.viewport.zoom,
|
|
2790
2875
|
y: -store.viewport.y / store.viewport.zoom,
|
|
2791
2876
|
width: store.width / store.viewport.zoom,
|
|
@@ -2810,7 +2895,7 @@ const DotPattern = (props) => {
|
|
|
2810
2895
|
<Show when={store.panZoom}>
|
|
2811
2896
|
{(panZoom) => {
|
|
2812
2897
|
let [ref, setRef] = createSignal(), [minimap, setMinimap] = createSignal();
|
|
2813
|
-
|
|
2898
|
+
createEffect(() => ({
|
|
2814
2899
|
el: ref(),
|
|
2815
2900
|
panZoom: panZoom()
|
|
2816
2901
|
}), ({ el, panZoom }) => {
|
|
@@ -2837,7 +2922,19 @@ const DotPattern = (props) => {
|
|
|
2837
2922
|
}
|
|
2838
2923
|
}), ({ instance, options }) => {
|
|
2839
2924
|
instance?.update(options);
|
|
2840
|
-
})
|
|
2925
|
+
});
|
|
2926
|
+
let onSvgClick = (event) => {
|
|
2927
|
+
if (!_props.onClick) return;
|
|
2928
|
+
let [x, y] = minimap()?.pointer(event) ?? [0, 0];
|
|
2929
|
+
_props.onClick(event, {
|
|
2930
|
+
x,
|
|
2931
|
+
y
|
|
2932
|
+
});
|
|
2933
|
+
}, onSvgNodeClick = (event, nodeId) => {
|
|
2934
|
+
let node = nodeLookup.get(nodeId)?.internals.userNode;
|
|
2935
|
+
node && _props.onNodeClick?.(event, node);
|
|
2936
|
+
};
|
|
2937
|
+
return <svg ref={setRef} width={_props.width} height={_props.height} viewBox={`${getX()} ${getY()} ${getViewboxWidth()} ${getViewboxHeight()}`} class="solid-flow__minimap-svg" role="img" aria-labelledby={labelledBy()} onClick={_props.onClick ? onSvgClick : void 0} style={{
|
|
2841
2938
|
"--xy-minimap-mask-background-color-props": _props.maskColor,
|
|
2842
2939
|
"--xy-minimap-mask-stroke-color-props": _props.maskStrokeColor,
|
|
2843
2940
|
"--xy-minimap-mask-stroke-width-props": strokeWidth()
|
|
@@ -2847,7 +2944,7 @@ const DotPattern = (props) => {
|
|
|
2847
2944
|
{(nodeId) => {
|
|
2848
2945
|
let node = createMemo(() => nodeLookup.get(nodeId())), nodeVisible = () => !!(node() && nodeHasDimensions(node()) && !node().hidden);
|
|
2849
2946
|
return <Show when={nodeVisible() && getNodeDimensions(node())}>
|
|
2850
|
-
{(nodeDimensions) => <MiniMapNode x={node().internals.positionAbsolute.x} y={node().internals.positionAbsolute.y} borderRadius={_props.nodeBorderRadius} strokeWidth={_props.nodeStrokeWidth} shapeRendering={shapeRendering()} width={nodeDimensions().width} height={nodeDimensions().height} selected={node().selected} color={nodeColorFunc()?.call(null, node())} strokeColor={nodeStrokeColorFunc().call(null, node())} class={nodeClassFunc().call(null, node())} />}
|
|
2947
|
+
{(nodeDimensions) => <Dynamic component={_props.nodeComponent ?? MiniMapNode} id={nodeId()} x={node().internals.positionAbsolute.x} y={node().internals.positionAbsolute.y} borderRadius={_props.nodeBorderRadius} strokeWidth={_props.nodeStrokeWidth} shapeRendering={shapeRendering()} width={nodeDimensions().width} height={nodeDimensions().height} selected={node().selected} color={nodeColorFunc()?.call(null, node())} strokeColor={nodeStrokeColorFunc().call(null, node())} class={nodeClassFunc().call(null, node())} style={node().style} onClick={_props.onNodeClick ? onSvgNodeClick : void 0} />}
|
|
2851
2948
|
</Show>;
|
|
2852
2949
|
}}
|
|
2853
2950
|
</For>
|
|
@@ -2964,7 +3061,7 @@ const DotPattern = (props) => {
|
|
|
2964
3061
|
</EdgeLabel>
|
|
2965
3062
|
</Show>;
|
|
2966
3063
|
}, NodeToolbar = (props) => {
|
|
2967
|
-
let { store } = useInternalSolidFlow(), {
|
|
3064
|
+
let { store } = useInternalSolidFlow(), { flow, getNodesBounds } = useSolidFlow(), _props = propDefaults(props, {
|
|
2968
3065
|
offset: 10,
|
|
2969
3066
|
position: "top",
|
|
2970
3067
|
align: "center",
|
|
@@ -2973,7 +3070,7 @@ const DotPattern = (props) => {
|
|
|
2973
3070
|
let id = useContext(NodeIdContext);
|
|
2974
3071
|
return id ? id() : "";
|
|
2975
3072
|
}, toolbarNodes = () => (Array.isArray(_props.nodeId) ? _props.nodeId : [_props.nodeId ?? ctxNodeId()]).reduce((res, nodeId) => {
|
|
2976
|
-
let node =
|
|
3073
|
+
let node = flow.internalNodes[nodeId];
|
|
2977
3074
|
return node && res.push(node), res;
|
|
2978
3075
|
}, []), transform = () => {
|
|
2979
3076
|
let nodeRect = getNodesBounds(toolbarNodes());
|
|
@@ -2981,7 +3078,7 @@ const DotPattern = (props) => {
|
|
|
2981
3078
|
}, zIndex = () => {
|
|
2982
3079
|
let nodes = toolbarNodes();
|
|
2983
3080
|
return nodes.length === 0 ? 1 : Math.max(...nodes.map((node) => (node.internals.z || 5) + 1));
|
|
2984
|
-
}, selectedNodesCount = () =>
|
|
3081
|
+
}, selectedNodesCount = () => flow.selection.nodes.length, isActive = () => {
|
|
2985
3082
|
let nodes = toolbarNodes();
|
|
2986
3083
|
return typeof _props.isVisible == "boolean" ? _props.isVisible : nodes.length === 1 && !!nodes[0].selected && selectedNodesCount() === 1;
|
|
2987
3084
|
}, showPortal = () => !!(store.domNode && isActive() && toolbarNodes().length > 0);
|
|
@@ -2999,4 +3096,4 @@ const DotPattern = (props) => {
|
|
|
2999
3096
|
</Show>;
|
|
3000
3097
|
}, SelectionMode = SelectionMode$1, getEdgeCenter = getEdgeCenter$1, getBezierEdgeCenter = getBezierEdgeCenter$1;
|
|
3001
3098
|
//#endregion
|
|
3002
|
-
export { Background, BaseEdge, BezierEdge, BezierEdgeInternal, ConnectionLine, ConnectionLineType, ConnectionMode, ControlButton, Controls, DefaultNode, EdgeLabel, EdgeLabelRenderer, EdgeReconnectAnchor, EdgeRenderer, EdgeToolbar, EdgeWrapper, GroupNode, Handle, InputNode, Marker, MarkerDefinition, MarkerType, MiniMap, NodeRenderer, NodeResizer, NodeSelection, NodeToolbar, NodeWrapper, OutputNode, PanOnScrollMode, Pane, Panel, Position, ResizeControl, ResizeControlVariant, Selection, SelectionMode, SmoothStepEdge, SmoothStepEdgeInternal, SolidFlow, SolidFlowProvider, StepEdge, StepEdgeInternal, StraightEdge, StraightEdgeInternal, Viewport, ViewportPortal, Zoom, addEdge, connectionKey, createEdgeStore, createNodeStore, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, useColorMode, useConnection,
|
|
3099
|
+
export { Background, BaseEdge, BezierEdge, BezierEdgeInternal, ConnectionLine, ConnectionLineType, ConnectionMode, ControlButton, Controls, DefaultNode, EdgeLabel, EdgeLabelRenderer, EdgeReconnectAnchor, EdgeRenderer, EdgeToolbar, EdgeWrapper, GroupNode, Handle, InputNode, Marker, MarkerDefinition, MarkerType, MiniMap, MiniMapNode, NodeRenderer, NodeResizer, NodeSelection, NodeToolbar, NodeWrapper, OutputNode, PanOnScrollMode, Pane, Panel, Position, ResizeControl, ResizeControlVariant, Selection, SelectionMode, SmoothStepEdge, SmoothStepEdgeInternal, SolidFlow, SolidFlowProvider, StepEdge, StepEdgeInternal, StraightEdge, StraightEdgeInternal, Viewport, ViewportPortal, Zoom, addEdge, connectionKey, createEdgeStore, createNodeStore, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, useColorMode, useConnection, useEdgeId, useEdges, useInternalNode, useNodeConnections, useNodeId, useNodes, useNodesData, useNodesInitialized, useSolidFlow, useUpdateNodeInternals, useViewport, useViewportInitialized };
|
package/dist/styles/index.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dschz/solid-flow",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.5",
|
|
4
4
|
"description": "Solid Flow - A highly customizable Solid library for building node-based editors, workflow systems, diagrams and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai workflows",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"start": "vite",
|
|
88
88
|
"test": "vitest run",
|
|
89
89
|
"test:cov": "vitest run --coverage",
|
|
90
|
+
"test:e2e": "playwright test",
|
|
90
91
|
"typecheck": "tsc --noEmit",
|
|
91
92
|
"test:ssr": "vitest run --config vite.config.ssr.ts"
|
|
92
93
|
},
|
|
@@ -100,6 +101,7 @@
|
|
|
100
101
|
"@changesets/cli": "2.31.1",
|
|
101
102
|
"@dagrejs/dagre": "^1.1.5",
|
|
102
103
|
"@dom-expressions/compiler": "0.50.0-next.43",
|
|
104
|
+
"@playwright/test": "1.62.1",
|
|
103
105
|
"@solidjs/testing-library": "1.0.0-beta.2",
|
|
104
106
|
"@solidjs/vite-plugin": "3.0.0-next.28",
|
|
105
107
|
"@testing-library/jest-dom": "7.0.0",
|