@dschz/solid-flow 0.3.0-next.0 → 0.3.0-next.1

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.
@@ -1,7 +1,6 @@
1
- import { For, Show, createContext, createEffect, createMemo, createRenderEffect, createSignal, createStore, flush, mapArray, merge, omit, onCleanup, onSettled, snapshot, untrack, useContext } from "solid-js";
2
- import { ReactiveMap } from "@solid-primitives/map";
1
+ import { For, Show, createContext, createEffect, createMemo, createProjection, createSignal, createStore, flush, mapArray, merge, omit, onCleanup, onSettled, snapshot, untrack, useContext } from "solid-js";
3
2
  import { createMediaQuery } from "@solid-primitives/media";
4
- import { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, Position as Position$1, ResizeControlVariant, SelectionMode, SelectionMode as SelectionMode$1, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, addEdge as addEdge$1, areConnectionMapsEqual, calcAutoPan, calculateNodePosition, clampPosition, clampPositionToParent, createDevWarn, createMarkerIds, elementSelectionKeys, evaluateAbsolutePosition, fitViewport, getBezierEdgeCenter, getBezierPath, getBezierPath as getBezierPath$1, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getEdgeCenter, getEdgePosition, getEdgeToolbarTransform, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesBounds as getNodesBounds$1, getNodesInside, getOutgoers, getOverlappingArea, getSmoothStepPath, getSmoothStepPath as getSmoothStepPath$1, getStraightPath, getStraightPath as getStraightPath$1, getViewportForBounds, getViewportForBounds as getViewportForBounds$1, handleConnectionChange, infiniteExtent, initialConnection, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isMacOs, isNodeBase, isNumeric, isRectObject, mergeAriaLabelConfig, nodeHasDimensions, nodeToRect, panBy, pointToRendererPoint, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateNodeInternals } from "@xyflow/system";
3
+ import { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, Position as Position$1, ResizeControlVariant, SelectionMode, SelectionMode as SelectionMode$1, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, addEdge as addEdge$1, areConnectionMapsEqual, calcAutoPan, calculateNodePosition, clampPosition, clampPositionToParent, createDevWarn, createMarkerIds, elementSelectionKeys, evaluateAbsolutePosition, fitViewport, getBezierEdgeCenter, getBezierPath, getBezierPath as getBezierPath$1, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter, getEdgePosition, getEdgeToolbarTransform, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getHandleBounds, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesBounds as getNodesBounds$1, getNodesInside, getOutgoers, getOverlappingArea, getSmoothStepPath, getSmoothStepPath as getSmoothStepPath$1, getStraightPath, getStraightPath as getStraightPath$1, getViewportForBounds, getViewportForBounds as getViewportForBounds$1, handleConnectionChange, infiniteExtent, initialConnection, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isMacOs, isNodeBase, isNumeric, isRectObject, mergeAriaLabelConfig, nodeHasDimensions, nodeToRect, panBy, pointToRendererPoint, rendererPointToPoint, shallowNodeData, snapPosition } from "@xyflow/system";
5
4
  import { Dynamic, Portal, isServer } from "@solidjs/web";
6
5
  import { createEventListener, createEventListenerMap } from "@solid-primitives/event-listener";
7
6
  //#region src/components/contexts/edgeId.tsx
@@ -143,7 +142,7 @@ const EdgeLabelRenderer = (props) => {
143
142
  style: {}
144
143
  }), rest = omit(_props, "type", "class", "style", "position", "size", "reconnecting", "children"), { store, nodeLookup, edgeLookup, actions } = useInternalSolidFlow(), edgeId = useEdgeId(), [reconnecting, setReconnecting] = createSignal(!1);
145
144
  if (!edgeId()) throw Error("[solid-flow]: EdgeReconnectAnchor must be used within an Edge component");
146
- let edge = () => edgeLookup.get(edgeId()), onPointerDown = (event) => {
145
+ let edge = () => edgeLookup[edgeId()], onPointerDown = (event) => {
147
146
  if (event.button !== 0) return;
148
147
  setReconnecting(!0), store.onReconnectStart?.(event, edge(), _props.type);
149
148
  let opposite = _props.type === "target" ? {
@@ -362,533 +361,335 @@ const EdgeLabelRenderer = (props) => {
362
361
  };
363
362
  };
364
363
  return <BaseEdge path={pathData().path} labelX={pathData().labelX} labelY={pathData().labelY} label={props.label} labelStyle={props.labelStyle} markerStart={props.markerStart} markerEnd={props.markerEnd} interactionWidth={props.interactionWidth} style={props.style} />;
365
- }, getEdgeId = (connection) => {
366
- let { source, sourceHandle, target, targetHandle } = connection;
367
- return `xy-edge__${source}${sourceHandle || ""}-${target}${targetHandle || ""}`;
368
- }, NodeConnectableContext = createContext(null);
369
- function useNodeConnectable() {
370
- let ctx = useContext(NodeConnectableContext);
371
- if (!ctx) throw Error("solid-flow: Your application must be wrapped with <SolidFlow> in order to invoke useNodeConnectable");
372
- return ctx;
373
- }
364
+ }, getDefaultFlowStateProps = () => ({
365
+ id: "1",
366
+ nodes: [],
367
+ edges: [],
368
+ nodeOrigin: [0, 0],
369
+ nodeExtent: infiniteExtent,
370
+ defaultEdgeOptions: {},
371
+ colorMode: "system",
372
+ colorModeSSR: "light",
373
+ connectionMode: "strict",
374
+ connectionLineType: "default",
375
+ connectionRadius: 20,
376
+ nodeDragThreshold: 1,
377
+ minZoom: .5,
378
+ maxZoom: 2,
379
+ selectionMode: "partial",
380
+ fitView: !1,
381
+ noPanClass: "nopan",
382
+ noDragClass: "nodrag",
383
+ noWheelClass: "nowheel",
384
+ autoPanOnNodeDrag: !0,
385
+ autoPanOnConnect: !0,
386
+ autoPanOnNodeFocus: !0,
387
+ autoPanOnSelection: !0,
388
+ autoPanSpeed: 15,
389
+ elevateEdgesOnSelect: !0,
390
+ nodesDraggable: !0,
391
+ nodesConnectable: !0,
392
+ nodesFocusable: !0,
393
+ edgesFocusable: !0,
394
+ elementsSelectable: !0,
395
+ selectNodesOnDrag: !0,
396
+ elevateNodesOnSelect: !0,
397
+ zIndexMode: "basic",
398
+ onlyRenderVisibleElements: !1,
399
+ disableKeyboardA11y: !1,
400
+ defaultMarkerColor: "#b1b1b7",
401
+ ariaLiveMessage: "",
402
+ style: {},
403
+ isValidConnection: (() => !0),
404
+ onFlowError: createDevWarn("Solid Flow", "https://solidflow.dev/")
405
+ });
374
406
  //#endregion
375
- //#region src/components/graph/handle/Handle.tsx
376
- const Handle = (props) => {
377
- let _props = propDefaults(props, {
378
- type: "source",
379
- position: "top",
380
- isConnectableStart: !0,
381
- isConnectableEnd: !0
382
- }), { store, nodeLookup, connectionLookup, actions } = useInternalSolidFlow(), rest = omit(_props, "id", "type", "position", "isConnectable", "isConnectableStart", "isConnectableEnd", "isValidConnection", "onConnect", "onDisconnect", "children", "class", "style"), nodeId = useNodeId(), nodeConnectable = useNodeConnectable(), connectable = () => _props.isConnectable ?? nodeConnectable(), isTarget = () => _props.type === "target", handleId = () => _props.id ?? null, connectionInProcess = () => !!store.connection.fromHandle, connectingFrom = () => store.connection.fromHandle && store.connection.fromHandle.nodeId === nodeId() && store.connection.fromHandle.type === _props.type && store.connection.fromHandle.id === handleId(), connectingTo = () => store.connection.toHandle && store.connection.toHandle.nodeId === nodeId() && store.connection.toHandle.type === _props.type && store.connection.toHandle.id === handleId(), isPossibleTargetHandle = () => store.connectionMode === "strict" ? store.connection.fromHandle?.type !== _props.type : nodeId() !== store.connection.fromHandle?.nodeId || handleId() !== store.connection.fromHandle?.id, valid = () => !!(connectingTo() && store.connection.isValid), prevConnections = null;
383
- createEffect(() => {
384
- if (!_props.onConnect && !_props.onDisconnect) return null;
385
- let connectionKey = `${nodeId()}-${_props.type}${_props.id ? `-${_props.id}` : ""}`;
386
- return { connections: connectionLookup.get(connectionKey) };
387
- }, (current) => {
388
- if (!current) return;
389
- let { connections } = current;
390
- if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
391
- let _connections = connections ?? /* @__PURE__ */ new Map();
392
- handleConnectionChange(prevConnections, _connections, props.onDisconnect), handleConnectionChange(_connections, prevConnections, props.onConnect);
407
+ //#region src/core/facades.ts
408
+ /**
409
+ * A read-only `Map` view over an id-keyed record projection, for
410
+ * @xyflow/system interop: system helpers take `Map`s, our reactive lookups
411
+ * are records. Every read passes through to the record, so reads inside
412
+ * tracked scopes subscribe normally — `get`/`has` check with `in` first,
413
+ * which also subscribes while the key is still absent (the projection
414
+ * absent-key footgun), and `size`/iteration read the key set structurally.
415
+ *
416
+ * The mutating `Map` methods throw: writes belong to the roots the
417
+ * projection derives from.
418
+ */
419
+ var RecordMapFacade = class {
420
+ #record;
421
+ constructor(record) {
422
+ this.#record = record;
423
+ }
424
+ get(key) {
425
+ return key in this.#record ? this.#record[key] : void 0;
426
+ }
427
+ has(key) {
428
+ return key in this.#record;
429
+ }
430
+ get size() {
431
+ return Object.keys(this.#record).length;
432
+ }
433
+ *keys() {
434
+ yield* Object.keys(this.#record);
435
+ }
436
+ *values() {
437
+ for (let key of Object.keys(this.#record)) yield this.#record[key];
438
+ }
439
+ *entries() {
440
+ for (let key of Object.keys(this.#record)) yield [key, this.#record[key]];
441
+ }
442
+ [Symbol.iterator]() {
443
+ return this.entries();
444
+ }
445
+ forEach(callback, thisArg) {
446
+ for (let [key, value] of this.entries()) callback.call(thisArg, value, key, this);
447
+ }
448
+ [Symbol.toStringTag] = "RecordMapFacade";
449
+ set() {
450
+ throw Error("RecordMapFacade is read-only; write to the projection's source roots");
451
+ }
452
+ getOrInsert() {
453
+ throw Error("RecordMapFacade is read-only; write to the projection's source roots");
454
+ }
455
+ getOrInsertComputed() {
456
+ throw Error("RecordMapFacade is read-only; write to the projection's source roots");
457
+ }
458
+ delete() {
459
+ throw Error("RecordMapFacade is read-only; write to the projection's source roots");
460
+ }
461
+ clear() {
462
+ throw Error("RecordMapFacade is read-only; write to the projection's source roots");
463
+ }
464
+ };
465
+ //#endregion
466
+ //#region src/core/projections/connections.ts
467
+ /**
468
+ * Lookup keys for the connection index. Each edge is registered under six
469
+ * keys — for both of its endpoints: the node, the node+handle-type, and (when
470
+ * a handle id is present) the node+type+handle:
471
+ * `${nodeId}` · `${nodeId}-${type}` · `${nodeId}-${type}-${handleId}`
472
+ */
473
+ const connectionKey = (nodeId, type, handleId) => `${nodeId}${type ? handleId ? `-${type}-${handleId}` : `-${type}` : ""}`, pairKey = (aNode, aHandle, bNode, bHandle) => `${aNode}-${aHandle}--${bNode}-${bHandle}`, createConnections = (source) => createProjection(() => {
474
+ let out = {}, add = (key, entry, connection) => {
475
+ (out[key] ??= {})[entry] = connection;
476
+ };
477
+ for (let edge of source.edges) {
478
+ let sourceHandle = edge.sourceHandle ?? null, targetHandle = edge.targetHandle ?? null, connection = {
479
+ edgeId: edge.id,
480
+ source: edge.source,
481
+ target: edge.target,
482
+ sourceHandle,
483
+ targetHandle
484
+ }, sourceKey = pairKey(edge.source, sourceHandle, edge.target, targetHandle), targetKey = pairKey(edge.target, targetHandle, edge.source, sourceHandle);
485
+ add(edge.source, targetKey, connection), add(connectionKey(edge.source, "source"), targetKey, connection), sourceHandle && add(connectionKey(edge.source, "source", sourceHandle), targetKey, connection), add(edge.target, sourceKey, connection), add(connectionKey(edge.target, "target"), sourceKey, connection), targetHandle && add(connectionKey(edge.target, "target", targetHandle), sourceKey, connection);
486
+ }
487
+ return out;
488
+ }, {}, { key: "id" }), createEdgeLookup = (source) => createProjection(() => {
489
+ let out = {};
490
+ for (let edge of source.edges) out[edge.id] = edge;
491
+ return out;
492
+ }, {}, { key: "id" });
493
+ function isManualZIndexMode(zIndexMode) {
494
+ return zIndexMode === "manual";
495
+ }
496
+ function calculateZ(node, selectedNodeZ, zIndexMode) {
497
+ let zIndex = isNumeric(node.zIndex) ? node.zIndex : 0;
498
+ return isManualZIndexMode(zIndexMode) ? zIndex : zIndex + (node.selected ? selectedNodeZ : 0);
499
+ }
500
+ const EMPTY_AUTO_INDEX = /* @__PURE__ */ new Map(), createInternalNodes = (source) => {
501
+ let autoIndex = createMemo(() => {
502
+ if (source.zIndexMode !== "auto") return EMPTY_AUTO_INDEX;
503
+ let index = /* @__PURE__ */ new Map(), rootIds = /* @__PURE__ */ new Set();
504
+ for (let node of source.nodes) node.parentId ? rootIds.has(node.parentId) && !index.has(node.parentId) && index.set(node.parentId, index.size + 1) : rootIds.add(node.id);
505
+ return index;
506
+ }), entryById = /* @__PURE__ */ new Map(), rowStores = mapArray(() => source.nodes, (userNodeAccessor, index) => {
507
+ let id = userNodeAccessor().id, store = createProjection(() => {
508
+ let userNode = userNodeAccessor(), { nodeOrigin, nodeExtent, zIndexMode } = source, selectedNodeZ = source.elevateNodesOnSelect && !isManualZIndexMode(zIndexMode) ? 1e3 : 0, measurement = userNode.id in source.measurements ? source.measurements[userNode.id] : void 0, measured = {
509
+ width: userNode.measured?.width ?? measurement?.measured.width,
510
+ height: userNode.measured?.height ?? measurement?.measured.height
511
+ }, dimensions = getNodeDimensions({
512
+ measured,
513
+ width: userNode.width,
514
+ height: userNode.height,
515
+ initialWidth: userNode.initialWidth,
516
+ initialHeight: userNode.initialHeight
517
+ }), rootParentIndex = autoIndex().get(userNode.id), row = {
518
+ ...userNode,
519
+ measured,
520
+ internals: {
521
+ positionAbsolute: clampPosition(getNodePositionWithOrigin(userNode, nodeOrigin), isCoordinateExtent(userNode.extent) ? userNode.extent : nodeExtent, dimensions),
522
+ handleBounds: measurement?.handleBounds,
523
+ z: calculateZ(userNode, selectedNodeZ, zIndexMode) + (rootParentIndex === void 0 ? 0 : rootParentIndex * 10),
524
+ ...rootParentIndex === void 0 ? {} : { rootParentIndex },
525
+ userNode
526
+ }
527
+ };
528
+ if (userNode.parentId) {
529
+ let parentEntry = entryById.get(userNode.parentId);
530
+ if (parentEntry && parentEntry.index() < index()) {
531
+ let { x, y, z } = calculateChildXYZ(row, parentEntry.store.row, nodeOrigin, nodeExtent, selectedNodeZ, zIndexMode);
532
+ row.internals.positionAbsolute = {
533
+ x,
534
+ y
535
+ }, row.internals.z = z;
536
+ } else source.nodes.length;
537
+ }
538
+ if (userNode.parentId) {
539
+ let parent = entryById.get(userNode.parentId);
540
+ parent?.store.row.internals.positionAbsolute.x, parent?.store.row.internals.z;
541
+ }
542
+ return { row };
543
+ }, {}, { key: "id" }), entry = {
544
+ store,
545
+ index
546
+ };
547
+ return entryById.set(id, entry), onCleanup(() => {
548
+ entryById.get(id) === entry && entryById.delete(id);
549
+ }), {
550
+ id,
551
+ store
552
+ };
553
+ }, { keyed: (userNode) => userNode.id }), assigned = /* @__PURE__ */ new Map();
554
+ return createProjection((draft) => {
555
+ let seen = /* @__PURE__ */ new Set();
556
+ for (let { id, store } of rowStores()) {
557
+ seen.add(id);
558
+ let row = store.row;
559
+ assigned.get(id) !== row && (assigned.set(id, row), draft[id] = row);
393
560
  }
394
- prevConnections = connections ?? /* @__PURE__ */ new Map();
561
+ for (let id of assigned.keys()) seen.has(id) || (assigned.delete(id), delete draft[id]);
562
+ }, {}, {
563
+ key: null,
564
+ shallow: !0
395
565
  });
396
- let onConnectExtended = (connection) => {
397
- let handleConnection = {
398
- ...connection,
399
- id: getEdgeId(connection)
400
- }, edge = store.onBeforeConnect?.(handleConnection) ?? handleConnection;
401
- actions.addEdge(edge), store.onConnect?.(handleConnection);
402
- }, onPointerDown = (event) => {
403
- XYHandle.onPointerDown(event, {
404
- handleId: handleId(),
405
- nodeId: nodeId(),
406
- isTarget: isTarget(),
407
- connectionRadius: store.connectionRadius,
408
- domNode: store.domNode,
409
- nodeLookup,
410
- connectionMode: store.connectionMode,
411
- lib: store.lib,
412
- autoPanOnConnect: store.autoPanOnConnect,
413
- flowId: store.id,
414
- isValidConnection: _props.isValidConnection ?? store.isValidConnection,
415
- updateConnection: ((connection) => {
416
- actions.setConnection(connection), flush();
417
- }),
418
- cancelConnection: actions.cancelConnection,
419
- panBy: actions.panBy,
420
- onConnect: onConnectExtended,
421
- onConnectStart: (event, startParams) => {
422
- store.onConnectStart?.(event, {
423
- nodeId: startParams.nodeId,
424
- handleId: startParams.handleId,
425
- handleType: startParams.handleType
426
- });
427
- },
428
- onConnectEnd: store.onConnectEnd,
429
- getTransform: () => store.transform,
430
- getFromHandle: () => store.connection.fromHandle,
431
- autoPanSpeed: store.autoPanSpeed,
432
- dragThreshold: store.connectionDragThreshold,
433
- handleDomNode: event.currentTarget
434
- });
435
- }, onClick = (event) => {
436
- if (!nodeId() || !store.clickConnectStartHandle && !_props.isConnectableStart) return;
437
- if (!store.clickConnectStartHandle) {
438
- store.onClickConnectStart?.(event, {
439
- nodeId: nodeId(),
440
- handleId: handleId(),
441
- handleType: _props.type
442
- }), actions.setClickConnectStartHandle({
443
- nodeId: nodeId(),
444
- type: _props.type,
445
- id: handleId()
446
- });
447
- return;
448
- }
449
- let doc = getHostForElement(event.target), isValidConnectionHandler = _props.isValidConnection ?? store.isValidConnection, { connection, isValid } = XYHandle.isValid(event, {
450
- handle: {
451
- nodeId: nodeId(),
452
- id: handleId(),
453
- type: _props.type
454
- },
455
- connectionMode: store.connectionMode,
456
- fromNodeId: store.clickConnectStartHandle.nodeId,
457
- fromHandleId: store.clickConnectStartHandle.id ?? null,
458
- fromType: store.clickConnectStartHandle.type,
459
- isValidConnection: isValidConnectionHandler,
460
- flowId: store.id,
461
- doc,
462
- lib: store.lib,
463
- nodeLookup
464
- });
465
- isValid && connection && onConnectExtended(connection);
466
- let connectionClone = structuredClone(snapshot(store.connection));
467
- delete connectionClone.inProgress, connectionClone.toPosition = connectionClone.toHandle ? connectionClone.toHandle.position : null, store.onClickConnectEnd?.(event, connectionClone), actions.setClickConnectStartHandle(void 0);
468
- }, connectionIndicator = () => connectable() && (!connectionInProcess() || isPossibleTargetHandle()) && (connectionInProcess() || store.clickConnectStartHandle ? _props.isConnectableEnd : _props.isConnectableStart);
469
- return <div {...rest} role="button" aria-label={store.ariaLabelConfig["handle.ariaLabel"]} tabindex={-1} data-handleid={handleId()} data-nodeid={nodeId()} data-handlepos={_props.position} data-id={`${store.id}-${nodeId()}-${_props.id || null}-${_props.type}`} onClick={store.clickConnect ? onClick : void 0} onPointerDown={onPointerDown} style={_props.style} class={[
470
- "solid-flow__handle",
471
- `solid-flow__handle-${_props.position}`,
472
- store.noDragClass,
473
- store.noPanClass,
474
- _props.class,
475
- {
476
- valid: valid(),
477
- connectingto: !!connectingTo(),
478
- connectingfrom: !!connectingFrom(),
479
- source: !isTarget(),
480
- target: isTarget(),
481
- connectablestart: _props.isConnectableStart,
482
- connectableend: _props.isConnectableEnd,
483
- connectable: !!connectable(),
484
- connectionindicator: connectionIndicator()
566
+ };
567
+ function calculateChildXYZ(childNode, parentNode, nodeOrigin, nodeExtent, selectedNodeZ, zIndexMode) {
568
+ let { x: parentX, y: parentY } = parentNode.internals.positionAbsolute, childDimensions = getNodeDimensions(childNode), positionWithOrigin = getNodePositionWithOrigin(childNode, nodeOrigin), clampedPosition = isCoordinateExtent(childNode.extent) ? clampPosition(positionWithOrigin, childNode.extent, childDimensions) : positionWithOrigin, absolutePosition = clampPosition({
569
+ x: parentX + clampedPosition.x,
570
+ y: parentY + clampedPosition.y
571
+ }, nodeExtent, childDimensions);
572
+ childNode.extent === "parent" && (absolutePosition = clampPositionToParent(absolutePosition, childDimensions, parentNode));
573
+ let childZ = calculateZ(childNode, selectedNodeZ, zIndexMode), parentZ = parentNode.internals.z ?? 0;
574
+ return {
575
+ x: absolutePosition.x,
576
+ y: absolutePosition.y,
577
+ z: parentZ >= childZ ? parentZ + 1 : childZ
578
+ };
579
+ }
580
+ //#endregion
581
+ //#region src/core/projections/layoutedEdges.ts
582
+ /**
583
+ * Edge layout join: user edges × internal nodes → screen-space edge geometry,
584
+ * decomposed into SUB-STORES (spike 13): each edge is its own keyed
585
+ * projection holding `{ row }` — the layouted row, or null while the edge
586
+ * produces none (missing/unready endpoints, culled) — and the public record
587
+ * is a SHALLOW projection holding the PRESENT rows' proxies by reference.
588
+ *
589
+ * Reads chain: `record[id].sourceX` goes through the shallow slot into the
590
+ * edge's own store, so every materialized leaf signal hangs off its EDGE's
591
+ * computed (defeating rc.1's per-update companion walk — see
592
+ * internalNodes.ts). The edge projection tracks exactly what the join reads —
593
+ * the edge's props and its endpoints' geometry leaves through nodeLookup
594
+ * (which chains into the node row stores) — so one node move re-runs only
595
+ * the adjacent edges' projections; the record computed re-runs only when
596
+ * membership or presence changes.
597
+ *
598
+ * The viewport participates only while onlyRenderVisibleElements is active —
599
+ * panning must not touch edge rows otherwise.
600
+ *
601
+ * Rows whose endpoints are missing or unmeasured simply drop out of the
602
+ * record — the same "no entry" contract the ReactiveMap pipeline had.
603
+ */
604
+ const createLayoutedEdges = (source) => {
605
+ let rowStores = mapArray(() => source.edges, (edgeAccessor) => ({
606
+ id: edgeAccessor().id,
607
+ store: createProjection(() => {
608
+ let edge = edgeAccessor(), row = buildRow(source, edge);
609
+ return source.nodeLookup.get(edge.source)?.internals.handleBounds, source.nodeLookup.get(edge.target)?.internals.handleBounds, { row };
610
+ }, { row: null }, { key: "id" })
611
+ }), { keyed: (edge) => edge.id }), assigned = /* @__PURE__ */ new Map();
612
+ return createProjection((draft) => {
613
+ let seen = /* @__PURE__ */ new Set();
614
+ for (let { id, store } of rowStores()) {
615
+ let row = store.row;
616
+ row && (seen.add(id), assigned.get(id) !== row && (assigned.set(id, row), draft[id] = row));
485
617
  }
486
- ]}>
487
- {_props.children}
488
- </div>;
489
- }, DefaultNode = (props) => {
490
- let _props = propDefaults(props, {
491
- targetPosition: Position$1.Top,
492
- sourcePosition: Position$1.Bottom
618
+ for (let id of assigned.keys()) seen.has(id) || (assigned.delete(id), delete draft[id]);
619
+ }, {}, {
620
+ key: null,
621
+ shallow: !0
493
622
  });
494
- return <>
495
- <Handle type="target" position={_props.targetPosition} isConnectable={_props.isConnectable} />
496
- {props.data.label}
497
- <Handle type="source" position={_props.sourcePosition} isConnectable={_props.isConnectable} />
498
- </>;
499
- }, GroupNode = (props) => <div style={{
500
- position: "absolute",
501
- left: 0,
502
- top: 0,
503
- width: toPxString(props.width),
504
- height: toPxString(props.height)
505
- }} />, InputNode = (props) => {
506
- let _props = propDefaults(props, { sourcePosition: Position$1.Bottom });
507
- return <>
508
- {props.data?.label}
509
- <Handle type="source" position={_props.sourcePosition} isConnectable={_props.isConnectable} />
510
- </>;
511
- }, createDraggable = (elem, params) => {
512
- let { store, nodeLookup, actions } = useInternalSolidFlow(), [dragging, setDragging] = createSignal(!1);
513
- return createEffect(() => ({
514
- el: elem(),
515
- current: params()
516
- }), ({ el, current }) => {
517
- if (!el || current.disabled) return;
518
- let { onDrag, onDragStart, onDragStop, onNodeMouseDown } = current, dragInstance = XYDrag({
519
- onDrag,
520
- onDragStart: (event, dragItems, node, nodes) => {
521
- setDragging(!0), onDragStart?.(event, dragItems, node, nodes);
522
- },
523
- onDragStop: (event, dragItems, node, nodes) => {
524
- setDragging(!1), onDragStop?.(event, dragItems, node, nodes);
525
- },
526
- onNodeMouseDown,
527
- getStoreItems: () => ({
528
- nodes: store.nodes,
529
- nodeLookup,
530
- edges: store.edges,
531
- nodeExtent: store.nodeExtent,
532
- snapGrid: store.snapGrid ?? [0, 0],
533
- snapToGrid: !!store.snapGrid,
534
- autoPanSpeed: store.autoPanSpeed,
535
- nodeOrigin: store.nodeOrigin,
536
- multiSelectionActive: store.multiselectionKeyPressed,
537
- domNode: store.domNode,
538
- transform: store.transform,
539
- autoPanOnNodeDrag: store.autoPanOnNodeDrag,
540
- nodesDraggable: store.nodesDraggable,
541
- selectNodesOnDrag: store.selectNodesOnDrag,
542
- nodeDragThreshold: store.nodeDragThreshold,
543
- unselectNodesAndEdges: actions.unselectNodesAndEdges,
544
- updateNodePositions: actions.updateNodePositions,
545
- panBy: actions.panBy
546
- })
547
- });
548
- return dragInstance.update({
549
- domNode: el,
550
- nodeId: current.nodeId,
551
- noDragClassName: current.noDragClass,
552
- handleSelector: current.handleSelector,
553
- isSelectable: current.isSelectable,
554
- nodeClickDistance: current.nodeClickDistance
555
- }), () => {
556
- dragInstance.destroy();
557
- };
558
- }), dragging;
559
- }, NodeIdContext = createContext(null);
560
- function useNodeId() {
561
- let ctx = useContext(NodeIdContext);
562
- if (!ctx) throw Error("solid-flow: Your application must be wrapped with <SolidFlow> in order to invoke useNodeId");
563
- return ctx;
564
- }
565
- //#endregion
566
- //#region src/components/graph/node/NodeWrapper.tsx
567
- const NodeWrapper = (props) => {
568
- let { store, nodeLookup, parentLookup, actions } = useInternalSolidFlow(), [nodeRef, setNodeRef] = createSignal(), node = () => nodeLookup.get(props.nodeId), 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 = () => parentLookup.has(node().id), transform = () => {
569
- let { x, y } = node().internals.positionAbsolute;
570
- return `translate(${x}px, ${y}px)`;
571
- }, sizeStyle = () => {
572
- let w = node().width ?? node().initialWidth, h = node().height ?? node().initialHeight;
573
- return {
574
- ...node().style,
575
- ...w ? { width: toPxString(w) } : {},
576
- ...h ? { height: toPxString(h) } : {}
577
- };
578
- }, style = () => ({
579
- "z-index": node().internals.z,
580
- transform: transform(),
581
- visibility: nodeHasDimensions(node()) ? "visible" : "hidden",
582
- ...sizeStyle(),
583
- ...node().style ?? {}
584
- });
585
- createEffect(() => ({
586
- valid: nodeTypeValid(),
587
- nodeType: nodeType()
588
- }), ({ valid, nodeType }) => {}), createEffect(() => ({
589
- id: node().id,
590
- nodeElement: nodeRef(),
591
- nodeType: nodeType(),
592
- sourcePosition: node().sourcePosition,
593
- targetPosition: node().targetPosition
594
- }), (current, prev) => {
595
- current.nodeElement && (prev && prev.nodeElement === current.nodeElement && prev.sourcePosition === current.sourcePosition && prev.targetPosition === current.targetPosition && prev.nodeType === current.nodeType || actions.requestUpdateNodeInternals([[current.id, {
596
- id: current.id,
597
- nodeElement: current.nodeElement,
598
- force: !0
599
- }]]));
600
- }), createEffect(() => ({
601
- nodeElement: nodeRef(),
602
- hasDimensions: nodeHasDimensions(node()),
603
- resizeObserver: props.resizeObserver
604
- }), (current, prev) => {
605
- current.nodeElement === prev?.nodeElement && current.resizeObserver === prev?.resizeObserver && current.hasDimensions || (prev?.nodeElement && prev.resizeObserver?.unobserve(prev.nodeElement), current.nodeElement && current.resizeObserver?.observe(current.nodeElement));
623
+ }, buildRow = (source, edge) => {
624
+ let sourceNode = source.nodeLookup.get(edge.source), targetNode = source.nodeLookup.get(edge.target);
625
+ if (!sourceNode || !targetNode) return source.nodeLookup.size, null;
626
+ if (source.onlyRenderVisibleElements && !isEdgeVisible({
627
+ sourceNode,
628
+ targetNode,
629
+ width: source.width ?? 0,
630
+ height: source.height ?? 0,
631
+ transform: source.transform
632
+ })) return null;
633
+ let edgePosition = getEdgePosition({
634
+ id: edge.id,
635
+ sourceNode,
636
+ targetNode,
637
+ sourceHandle: edge.sourceHandle || null,
638
+ targetHandle: edge.targetHandle || null,
639
+ connectionMode: source.connectionMode,
640
+ onError: source.onError
606
641
  });
607
- let onSelectNodeHandler = (event) => {
608
- selectable() && (!store.selectNodesOnDrag || !draggable() || store.nodeDragThreshold > 0) && actions.handleNodeSelection(node().id), props.onNodeClick?.({
609
- node: userNode(),
610
- event
611
- });
612
- }, onKeyDown = (event) => {
613
- if (isInputDOMNode(event) || store.disableKeyboardA11y) return;
614
- if (elementSelectionKeys.includes(event.key) && selectable()) {
615
- actions.handleNodeSelection(node().id, event.key === "Escape", nodeRef());
616
- return;
617
- }
618
- let arrowKeyDiff = ARROW_KEY_DIFFS[event.key];
619
- draggable() && node().selected && arrowKeyDiff && (event.preventDefault(), actions.setAriaLiveMessage(store.ariaLabelConfig["node.a11yDescription.ariaLiveMessage"]({
620
- direction: event.key.replace("Arrow", "").toLowerCase(),
621
- x: ~~node().internals.positionAbsolute.x,
622
- y: ~~node().internals.positionAbsolute.y
623
- })), actions.moveSelectedNodes(arrowKeyDiff, event.shiftKey ? 4 : 1));
624
- }, onFocus = () => {
625
- if (store.disableKeyboardA11y || !store.autoPanOnNodeFocus || !nodeRef()?.matches(":focus-visible")) return;
626
- let { width, height, viewport } = store;
627
- getNodesInside(/* @__PURE__ */ new Map([[node().id, node()]]), {
628
- x: 0,
629
- y: 0,
630
- width,
631
- height
632
- }, [
633
- viewport.x,
634
- viewport.y,
635
- viewport.zoom
636
- ], !0).length > 0 || actions.setCenter(node().position.x + (node().measured.width ?? 0) / 2, node().position.y + (node().measured.height ?? 0) / 2, { zoom: viewport.zoom });
637
- }, dragging = createDraggable(nodeRef, () => ({
638
- nodeId: node().id,
639
- isSelectable: selectable(),
640
- disabled: !draggable(),
641
- handleSelector: node().dragHandle,
642
- noDragClass: store.noDragClass,
643
- nodeClickDistance: props.nodeClickDistance,
644
- onNodeMouseDown: actions.handleNodeSelection,
645
- onDrag: (event, _, targetNode, nodes) => {
646
- props.onNodeDrag?.({
647
- event,
648
- targetNode,
649
- nodes
650
- });
642
+ return edgePosition ? {
643
+ ...source.defaultEdgeOptions,
644
+ ...edge,
645
+ ...edgePosition,
646
+ zIndex: getElevatedEdgeZIndex({
647
+ selected: edge.selected,
648
+ zIndex: edge.zIndex ?? source.defaultEdgeOptions.zIndex,
649
+ sourceNode,
650
+ targetNode,
651
+ elevateOnSelect: source.elevateEdgesOnSelect,
652
+ zIndexMode: source.zIndexMode
653
+ }),
654
+ sourceNode,
655
+ targetNode,
656
+ edge
657
+ } : null;
658
+ }, createParentIds = (source) => createProjection(() => {
659
+ let out = {};
660
+ for (let node of source.nodes) node.parentId && (out[node.parentId] = !0);
661
+ return out;
662
+ }, {}, { key: "id" }), getInitialViewport = (fitView, initialViewport, width, height, nodeLookup) => {
663
+ if (fitView && !initialViewport && width && height) {
664
+ let bounds = getInternalNodesBounds(nodeLookup, { filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)) });
665
+ return getViewportForBounds$1(bounds, width, height, .5, 2, .1);
666
+ }
667
+ return initialViewport ?? {
668
+ x: 0,
669
+ y: 0,
670
+ zoom: 1
671
+ };
672
+ }, createFlowState = (props, injections = {}) => {
673
+ let _props = merge(getDefaultFlowStateProps(), props), initialNodeTypes = injections.initialNodeTypes ?? {}, initialEdgeTypes = injections.initialEdgeTypes ?? {}, prefersDark = injections.prefersDark ?? (() => _props.colorModeSSR === "dark"), [config, setConfig] = createSignal(_props), [ariaLabelConfig, setAriaLabelConfig] = createSignal(() => mergeAriaLabelConfig(config().ariaLabelConfig)), [ariaLiveMessage, setAriaLiveMessage] = createSignal(() => config().ariaLiveMessage), [clickConnectStartHandle, setClickConnectStartHandle] = createSignal(void 0), [connection, setConnection] = createSignal(initialConnection), [domNode, setDomNode] = createSignal(null), [dragging, setDragging] = createSignal(!1), [elementsSelectable, setElementsSelectable] = createSignal(() => config().elementsSelectable), [height, setHeight] = createSignal(() => config().height), [minZoom, _setMinZoom] = createSignal(() => config().minZoom), [maxZoom, _setMaxZoom] = createSignal(() => config().maxZoom), [nodesConnectable, setNodesConnectable] = createSignal(() => config().nodesDraggable), [nodesDraggable, setNodesDraggable] = createSignal(() => config().nodesDraggable), [panZoom, setPanZoom] = createSignal(null), [selectionRect, setSelectionRect] = createSignal(), [selectionRectMode, setSelectionRectMode] = createSignal(), [snapGrid, setSnapGrid] = createSignal(() => config().snapGrid), [translateExtent, _setTranslateExtent] = createSignal(() => config().translateExtent ?? infiniteExtent), [width, setWidth] = createSignal(() => config().width), [selectionKeyPressed, setSelectionKeyPressed] = createSignal(!1), [multiselectionKeyPressed, setMultiselectionKeyPressed] = createSignal(!1), [deleteKeyPressed, setDeleteKeyPressed] = createSignal(!1), [panActivationKeyPressed, setPanActivationKeyPressed] = createSignal(!1), [zoomActivationKeyPressed, setZoomActivationKeyPressed] = createSignal(!1), [nodesStore, setNodesStore] = createStore(_props.nodes), [edgesStore, setEdgesStore] = createStore(_props.edges), [measurementsStore, setMeasurementsStore] = createStore({}), internalNodes = createInternalNodes({
674
+ get nodes() {
675
+ return nodesStore;
651
676
  },
652
- onDragStart: (event, _, targetNode, nodes) => {
653
- props.onNodeDragStart?.({
654
- event,
655
- targetNode,
656
- nodes
657
- });
677
+ get measurements() {
678
+ return measurementsStore;
658
679
  },
659
- onDragStop: (event, _, targetNode, nodes) => {
660
- props.onNodeDragStop?.({
661
- event,
662
- targetNode,
663
- nodes
664
- });
665
- }
666
- }));
667
- return <Show when={!node().hidden}>
668
- <div ref={setNodeRef} data-id={node().id} class={[
669
- "solid-flow__node",
670
- `solid-flow__node-${nodeType()}`,
671
- {
672
- connectable: !!connectable(),
673
- draggable: !!draggable(),
674
- dragging: dragging(),
675
- nopan: !!draggable(),
676
- parent: isParentNode(),
677
- selectable: !!selectable(),
678
- selected: !!node().selected
680
+ get nodeOrigin() {
681
+ return config().nodeOrigin;
679
682
  },
680
- node().class
681
- ]} style={style()} onClick={onSelectNodeHandler} onPointerEnter={(event) => props.onNodePointerEnter?.({
682
- node: userNode(),
683
- event
684
- })} onPointerLeave={(event) => props.onNodePointerLeave?.({
685
- node: userNode(),
686
- event
687
- })} onPointerMove={(event) => props.onNodePointerMove?.({
688
- node: userNode(),
689
- event
690
- })} onContextMenu={(event) => props.onNodeContextMenu?.({
691
- node: userNode(),
692
- event
693
- })} onKeyDown={(e) => focusable() && onKeyDown(e)} onFocus={() => focusable() && onFocus()} tabindex={focusable() ? 0 : void 0} role={node().ariaRole ?? (focusable() ? "group" : void 0)} aria-roledescription="node" aria-describedby={store.disableKeyboardA11y ? void 0 : `${ARIA_NODE_DESC_KEY}-${store.id}`} {...node().domAttributes}>
694
- <NodeIdContext value={nodeId}>
695
- <NodeConnectableContext value={connectable}>
696
- <Dynamic component={nodeComponent()} data={node().data} id={node().id} selected={!!node().selected} selectable={selectable()} deletable={deletable()} sourcePosition={node().sourcePosition} targetPosition={node().targetPosition} zIndex={node().internals.z} dragging={dragging()} draggable={draggable()} dragHandle={node().dragHandle} parentId={node().parentId} type={nodeType()} isConnectable={connectable()} positionAbsoluteX={node().internals.positionAbsolute.x} positionAbsoluteY={node().internals.positionAbsolute.y} width={node().width} height={node().height} />
697
- </NodeConnectableContext>
698
- </NodeIdContext>
699
- </div>
700
- </Show>;
701
- }, OutputNode = (props) => {
702
- let _props = propDefaults(props, { targetPosition: Position$1.Top });
703
- return <>
704
- <Handle type="target" position={_props.targetPosition} isConnectable={_props.isConnectable} />
705
- {props.data?.label}
706
- </>;
707
- }, getDefaultFlowStateProps = () => ({
708
- id: "1",
709
- nodes: [],
710
- edges: [],
711
- nodeOrigin: [0, 0],
712
- nodeExtent: infiniteExtent,
713
- defaultEdgeOptions: {},
714
- colorMode: "system",
715
- colorModeSSR: "light",
716
- connectionMode: "strict",
717
- connectionLineType: "default",
718
- connectionRadius: 20,
719
- nodeDragThreshold: 1,
720
- minZoom: .5,
721
- maxZoom: 2,
722
- selectionMode: "partial",
723
- fitView: !1,
724
- noPanClass: "nopan",
725
- noDragClass: "nodrag",
726
- noWheelClass: "nowheel",
727
- autoPanOnNodeDrag: !0,
728
- autoPanOnConnect: !0,
729
- autoPanOnNodeFocus: !0,
730
- autoPanOnSelection: !0,
731
- autoPanSpeed: 15,
732
- elevateEdgesOnSelect: !0,
733
- nodesDraggable: !0,
734
- nodesConnectable: !0,
735
- nodesFocusable: !0,
736
- edgesFocusable: !0,
737
- elementsSelectable: !0,
738
- selectNodesOnDrag: !0,
739
- elevateNodesOnSelect: !0,
740
- zIndexMode: "basic",
741
- onlyRenderVisibleElements: !1,
742
- disableKeyboardA11y: !1,
743
- defaultMarkerColor: "#b1b1b7",
744
- ariaLiveMessage: "",
745
- style: {},
746
- isValidConnection: (() => !0),
747
- onFlowError: createDevWarn("Solid Flow", "https://solidflow.dev/")
748
- }), SELECTED_NODE_Z = 1e3, defaultOptions = {
749
- nodeOrigin: [0, 0],
750
- nodeExtent: infiniteExtent,
751
- elevateNodesOnSelect: !0,
752
- defaults: {},
753
- zIndexMode: "basic"
754
- };
755
- function isManualZIndexMode(zIndexMode) {
756
- return zIndexMode === "manual";
757
- }
758
- const adoptUserNodesDefaultOptions = {
759
- ...defaultOptions,
760
- checkEquality: !0
761
- };
762
- function mergeObjects(base, incoming) {
763
- let result = { ...base };
764
- for (let key in incoming) incoming[key] !== void 0 && (result[key] = incoming[key]);
765
- return result;
766
- }
767
- function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
768
- let _options = mergeObjects(adoptUserNodesDefaultOptions, options), nodesInitialized = nodes.length > 0, rootParentIndex = { i: 0 }, tmpLookup = new Map(nodeLookup), selectedNodeZ = _options?.elevateNodesOnSelect && !isManualZIndexMode(_options.zIndexMode) ? SELECTED_NODE_Z : 0;
769
- nodeLookup.clear(), parentLookup.clear();
770
- for (let userNode of nodes) {
771
- let internalNode = tmpLookup.get(userNode.id);
772
- if (_options.checkEquality && userNode === internalNode?.internals.userNode) nodeLookup.set(userNode.id, internalNode);
773
- else {
774
- let positionWithOrigin = getNodePositionWithOrigin(userNode, _options.nodeOrigin), extent = isCoordinateExtent(userNode.extent) ? userNode.extent : _options.nodeExtent, clampedPosition = clampPosition(positionWithOrigin, extent, getNodeDimensions(userNode));
775
- internalNode = {
776
- ..._options.defaults,
777
- ...userNode,
778
- measured: {
779
- width: userNode.measured?.width,
780
- height: userNode.measured?.height
781
- },
782
- internals: {
783
- positionAbsolute: clampedPosition,
784
- handleBounds: userNode.measured ? internalNode?.internals.handleBounds : void 0,
785
- z: calculateZ(userNode, selectedNodeZ, _options.zIndexMode),
786
- userNode
787
- }
788
- }, nodeLookup.set(userNode.id, internalNode);
789
- }
790
- (internalNode.measured === void 0 || internalNode.measured.width === void 0 || internalNode.measured.height === void 0) && !internalNode.hidden && (nodesInitialized = !1), userNode.parentId && updateChildNode(internalNode, nodeLookup, parentLookup, options, rootParentIndex);
791
- }
792
- return nodesInitialized;
793
- }
794
- function updateParentLookup(node, parentLookup) {
795
- if (!node.parentId) return;
796
- let childNodes = parentLookup.get(node.parentId);
797
- childNodes ? childNodes.set(node.id, node) : parentLookup.set(node.parentId, /* @__PURE__ */ new Map([[node.id, node]]));
798
- }
799
- /**
800
- * Updates positionAbsolute and zIndex of a child node and the parentLookup.
801
- */
802
- function updateChildNode(node, nodeLookup, parentLookup, options, rootParentIndex) {
803
- let { elevateNodesOnSelect, nodeOrigin, nodeExtent, zIndexMode } = mergeObjects(defaultOptions, options), parentId = node.parentId, parentNode = nodeLookup.get(parentId);
804
- if (!parentNode) return;
805
- updateParentLookup(node, parentLookup), rootParentIndex && !parentNode.parentId && parentNode.internals.rootParentIndex === void 0 && zIndexMode === "auto" && (parentNode.internals.rootParentIndex = ++rootParentIndex.i, parentNode.internals.z = parentNode.internals.z + rootParentIndex.i * 10), rootParentIndex && parentNode.internals.rootParentIndex !== void 0 && (rootParentIndex.i = parentNode.internals.rootParentIndex);
806
- let { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, elevateNodesOnSelect && !isManualZIndexMode(zIndexMode) ? SELECTED_NODE_Z : 0, zIndexMode), { positionAbsolute } = node.internals, positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
807
- (positionChanged || z !== node.internals.z) && nodeLookup.set(node.id, {
808
- ...node,
809
- internals: {
810
- ...node.internals,
811
- positionAbsolute: positionChanged ? {
812
- x,
813
- y
814
- } : positionAbsolute,
815
- z
683
+ get nodeExtent() {
684
+ return config().nodeExtent;
685
+ },
686
+ get elevateNodesOnSelect() {
687
+ return config().elevateNodesOnSelect;
688
+ },
689
+ get zIndexMode() {
690
+ return config().zIndexMode;
816
691
  }
817
- });
818
- }
819
- function calculateZ(node, selectedNodeZ, zIndexMode) {
820
- let zIndex = isNumeric(node.zIndex) ? node.zIndex : 0;
821
- return isManualZIndexMode(zIndexMode) ? zIndex : zIndex + (node.selected ? selectedNodeZ : 0);
822
- }
823
- function calculateChildXYZ(childNode, parentNode, nodeOrigin, nodeExtent, selectedNodeZ, zIndexMode) {
824
- let { x: parentX, y: parentY } = parentNode.internals.positionAbsolute, childDimensions = getNodeDimensions(childNode), positionWithOrigin = getNodePositionWithOrigin(childNode, nodeOrigin), clampedPosition = isCoordinateExtent(childNode.extent) ? clampPosition(positionWithOrigin, childNode.extent, childDimensions) : positionWithOrigin, absolutePosition = clampPosition({
825
- x: parentX + clampedPosition.x,
826
- y: parentY + clampedPosition.y
827
- }, nodeExtent, childDimensions);
828
- childNode.extent === "parent" && (absolutePosition = clampPositionToParent(absolutePosition, childDimensions, parentNode));
829
- let childZ = calculateZ(childNode, selectedNodeZ, zIndexMode), parentZ = parentNode.internals.z ?? 0;
830
- return {
831
- x: absolutePosition.x,
832
- y: absolutePosition.y,
833
- z: parentZ >= childZ ? parentZ + 1 : childZ
834
- };
835
- }
836
- /**
837
- * this function adds the connection to the connectionLookup
838
- * at the following keys: nodeId-type-handleId, nodeId-type and nodeId
839
- * @param type type of the connection
840
- * @param connection connection that should be added to the lookup
841
- * @param connectionKey at which key the connection should be added
842
- * @param connectionLookup reference to the connection lookup
843
- * @param nodeId nodeId of the connection
844
- * @param handleId handleId of the conneciton
845
- */
846
- function addConnectionToLookup(type, connection, connectionKey, connectionLookup, nodeId, handleId) {
847
- let add = (key) => {
848
- let next = new Map(untrack(() => connectionLookup.get(key)));
849
- next.set(connectionKey, connection), connectionLookup.set(key, next);
850
- };
851
- add(nodeId), add(`${nodeId}-${type}`), handleId && add(`${nodeId}-${type}-${handleId}`);
852
- }
853
- function removeConnectionFromLookup(type, connectionKey, connectionLookup, nodeId, handleId) {
854
- let remove = (key) => {
855
- let current = untrack(() => connectionLookup.get(key));
856
- if (!current) return;
857
- let next = new Map(current);
858
- next.delete(connectionKey), next.size === 0 ? connectionLookup.delete(key) : connectionLookup.set(key, next);
859
- };
860
- remove(nodeId), remove(`${nodeId}-${type}`), handleId && remove(`${nodeId}-${type}-${handleId}`);
861
- }
862
- //#endregion
863
- //#region src/data/createSolidFlow.tsx
864
- const InitialNodeTypesMap = {
865
- input: InputNode,
866
- output: OutputNode,
867
- default: DefaultNode,
868
- group: GroupNode
869
- }, InitialEdgeTypesMap = {
870
- straight: StraightEdgeInternal,
871
- smoothstep: SmoothStepEdgeInternal,
872
- default: BezierEdgeInternal,
873
- step: StepEdgeInternal
874
- }, getInitialViewport = (_nodesInitialized, fitView, initialViewport, width, height, nodeLookup) => {
875
- if (fitView && !initialViewport && width && height) {
876
- let bounds = getInternalNodesBounds(nodeLookup, { filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)) });
877
- return getViewportForBounds$1(bounds, width, height, .5, 2, .1);
878
- }
879
- return initialViewport ?? {
880
- x: 0,
881
- y: 0,
882
- zoom: 1
883
- };
884
- }, createSolidFlow = (props) => {
885
- let _props = merge(getDefaultFlowStateProps(), props), nodeLookup = new ReactiveMap(), parentLookup = new ReactiveMap(), edgeLookup = new ReactiveMap(), connectionLookup = new ReactiveMap(), layoutedEdgesMap = new ReactiveMap(), startNodesInitialized = untrack(() => adoptUserNodes(_props.nodes, nodeLookup, parentLookup, {
886
- nodeExtent: _props.nodeExtent,
887
- nodeOrigin: _props.nodeOrigin,
888
- elevateNodesOnSelect: _props.elevateNodesOnSelect,
889
- zIndexMode: _props.zIndexMode,
890
- checkEquality: !0
891
- })), initialViewport = getInitialViewport(startNodesInitialized, _props.fitView, _props.initialViewport, _props.width ?? 0, _props.height ?? 0, nodeLookup), mediaPrefersDark = createMediaQuery("(prefers-color-scheme: dark)", _props.colorModeSSR === "dark"), [config, setConfig] = createSignal(_props), [ariaLabelConfig, setAriaLabelConfig] = createSignal(() => mergeAriaLabelConfig(config().ariaLabelConfig)), [ariaLiveMessage, setAriaLiveMessage] = createSignal(() => config().ariaLiveMessage), [clickConnectStartHandle, setClickConnectStartHandle] = createSignal(void 0), [connection, setConnection] = createSignal(initialConnection), [domNode, setDomNode] = createSignal(null), [dragging, setDragging] = createSignal(!1), [elementsSelectable, setElementsSelectable] = createSignal(() => config().elementsSelectable), [height, setHeight] = createSignal(() => config().height), [minZoom, _setMinZoom] = createSignal(() => config().minZoom), [maxZoom, _setMaxZoom] = createSignal(() => config().maxZoom), [nodesConnectable, setNodesConnectable] = createSignal(() => config().nodesDraggable), [nodesDraggable, setNodesDraggable] = createSignal(() => config().nodesDraggable), [panZoom, setPanZoom] = createSignal(null), [selectionRect, setSelectionRect] = createSignal(), [selectionRectMode, setSelectionRectMode] = createSignal(), [snapGrid, setSnapGrid] = createSignal(() => config().snapGrid), [translateExtent, _setTranslateExtent] = createSignal(() => config().translateExtent ?? infiniteExtent), [width, setWidth] = createSignal(() => config().width), [selectionKeyPressed, setSelectionKeyPressed] = createSignal(!1), [multiselectionKeyPressed, setMultiselectionKeyPressed] = createSignal(!1), [deleteKeyPressed, setDeleteKeyPressed] = createSignal(!1), [panActivationKeyPressed, setPanActivationKeyPressed] = createSignal(!1), [zoomActivationKeyPressed, setZoomActivationKeyPressed] = createSignal(!1), [nodesStore, setNodesStore] = createStore(_props.nodes), [edgesStore, setEdgesStore] = createStore(_props.edges), [viewportStore, setViewportStore] = createStore(_props.viewport ?? initialViewport);
692
+ }), nodeLookup = new RecordMapFacade(internalNodes), initialViewport = getInitialViewport(_props.fitView, _props.initialViewport, _props.width ?? 0, _props.height ?? 0, nodeLookup), [viewportStore, setViewportStore] = createStore(_props.viewport ?? initialViewport);
892
693
  createEffect(() => config().nodes, (next) => {
893
694
  setNodesStore(() => next);
894
695
  }, { defer: !0 }), createEffect(() => config().edges, (next) => {
@@ -934,7 +735,7 @@ const InitialNodeTypesMap = {
934
735
  return clickConnectStartHandle();
935
736
  },
936
737
  get colorMode() {
937
- return this._colorMode === "system" ? mediaPrefersDark() ? "dark" : "light" : this._colorMode;
738
+ return this._colorMode === "system" ? prefersDark() ? "dark" : "light" : this._colorMode;
938
739
  },
939
740
  get connection() {
940
741
  let state = connection();
@@ -951,7 +752,7 @@ const InitialNodeTypesMap = {
951
752
  },
952
753
  get edgeTypes() {
953
754
  return {
954
- ...InitialEdgeTypesMap,
755
+ ...initialEdgeTypes,
955
756
  ...this._edgeTypes
956
757
  };
957
758
  },
@@ -987,7 +788,7 @@ const InitialNodeTypesMap = {
987
788
  },
988
789
  get nodeTypes() {
989
790
  return {
990
- ...InitialNodeTypesMap,
791
+ ...initialNodeTypes,
991
792
  ...this._nodeTypes
992
793
  };
993
794
  },
@@ -1051,7 +852,45 @@ const InitialNodeTypesMap = {
1051
852
  get zoomActivationKeyPressed() {
1052
853
  return zoomActivationKeyPressed();
1053
854
  }
1054
- }), visibleNodesMap = createMemo(() => nodeLookup), visibleNodeIds = createMemo(() => Array.from(visibleNodesMap().values()).map((edge) => edge.id)), visibleEdgeIds = createMemo(() => Array.from(layoutedEdgesMap.values()).map((edge) => edge.id)), getEdge = (id) => layoutedEdgesMap.get(id), fitView = async (options) => store.panZoom ? await fitViewport({
855
+ }), visibleNodesMap = createMemo(() => nodeLookup), visibleNodeIds = createMemo(() => Object.keys(internalNodes)), parentIds = createParentIds({ get nodes() {
856
+ return store.nodes;
857
+ } }), edgeLookup = createEdgeLookup({ get edges() {
858
+ return store.edges;
859
+ } }), connections = createConnections({ get edges() {
860
+ return store.edges;
861
+ } }), layoutedEdges = createLayoutedEdges({
862
+ get edges() {
863
+ return store.edges;
864
+ },
865
+ get connectionMode() {
866
+ return store.connectionMode;
867
+ },
868
+ get defaultEdgeOptions() {
869
+ return store.defaultEdgeOptions;
870
+ },
871
+ get elevateEdgesOnSelect() {
872
+ return store.elevateEdgesOnSelect;
873
+ },
874
+ get zIndexMode() {
875
+ return store.zIndexMode;
876
+ },
877
+ get onlyRenderVisibleElements() {
878
+ return store.onlyRenderVisibleElements;
879
+ },
880
+ get width() {
881
+ return store.width;
882
+ },
883
+ get height() {
884
+ return store.height;
885
+ },
886
+ get transform() {
887
+ return store.transform;
888
+ },
889
+ get onError() {
890
+ return store.onError;
891
+ },
892
+ nodeLookup
893
+ }), visibleEdgeIds = createMemo(() => Object.keys(layoutedEdges)), getEdge = (id) => layoutedEdges[id], fitView = async (options) => store.panZoom ? await fitViewport({
1055
894
  nodes: nodeLookup,
1056
895
  width: store.width,
1057
896
  height: store.height,
@@ -1077,42 +916,36 @@ const InitialNodeTypesMap = {
1077
916
  setNodesStore((nodes) => {
1078
917
  for (let node of nodes) nodeDragItems.has(node.id) && (node.dragging = dragging, node.position = nodeDragItems.get(node.id).position);
1079
918
  });
1080
- }, pendingEntries, requestUpdateNodeInternals = (updateEntries) => {
1081
- if (pendingEntries) {
1082
- pendingEntries.push(...updateEntries);
1083
- return;
1084
- }
1085
- pendingEntries = updateEntries, scheduleIdleCallback(() => {
1086
- {
1087
- let updates = new Map(pendingEntries);
1088
- pendingEntries = void 0;
1089
- let { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, parentLookup, store.domNode, store.nodeOrigin);
1090
- if (!updatedInternals) return;
1091
- updateAbsolutePositions(nodeLookup, parentLookup, {
1092
- nodeOrigin: store.nodeOrigin,
1093
- nodeExtent: store.nodeExtent,
1094
- zIndexMode: store.zIndexMode
1095
- });
1096
- let nodeToChange = changes.reduce((acc, change) => {
1097
- let node = nodeLookup.get(change.id)?.internals.userNode;
1098
- return node && acc.set(node.id, change), acc;
1099
- }, /* @__PURE__ */ new Map());
1100
- setNodesStore((nodes) => {
1101
- for (let node of nodes) {
1102
- let change = nodeToChange.get(node.id);
1103
- if (change) switch (change.type) {
1104
- case "dimensions":
1105
- change.setAttributes && (node.width = change.dimensions?.width ?? node.width, node.height = change.dimensions?.height ?? node.height), node.measured = {
1106
- ...node.measured,
1107
- ...change.dimensions
1108
- };
1109
- break;
1110
- case "position": node.position = change.position ?? node.position;
1111
- }
1112
- }
1113
- }), flush(), initialNodesMeasured = !0, tryInitialFitView();
919
+ }, applyMeasurementWrites = (writes) => {
920
+ setMeasurementsStore((draft) => {
921
+ for (let write of writes) if (write.hidden) {
922
+ let entry = draft[write.id];
923
+ entry && (entry.handleBounds = void 0);
924
+ } else draft[write.id] = {
925
+ measured: write.measured,
926
+ handleBounds: write.handleBounds
927
+ };
928
+ });
929
+ }, applyNodeChanges = (changes) => {
930
+ changes.length !== 0 && setNodesStore((nodes) => {
931
+ let nodeById = new Map(nodes.map((node) => [node.id, node]));
932
+ for (let change of changes) {
933
+ let node = nodeById.get(change.id);
934
+ if (node) switch (change.type) {
935
+ case "dimensions":
936
+ change.setAttributes && (node.width = change.dimensions?.width ?? node.width, node.height = change.dimensions?.height ?? node.height), node.measured = {
937
+ ...node.measured,
938
+ ...change.dimensions
939
+ };
940
+ break;
941
+ case "position": node.position = change.position ?? node.position;
942
+ }
1114
943
  }
1115
944
  });
945
+ }, markInitialNodesMeasured = () => {
946
+ initialNodesMeasured = !0, tryInitialFitView();
947
+ }, requestMeasure = () => {}, setMeasureRequester = (fn) => {
948
+ requestMeasure = fn;
1116
949
  }, zoomBy = async (factor, options) => store.panZoom ? store.panZoom.scaleBy(factor, options) : !1, zoomIn = (options) => zoomBy(1.2, options), zoomOut = (options) => zoomBy(1 / 1.2, options), setCenter = async (x, y, options) => {
1117
950
  let nextZoom = options?.zoom === void 0 ? store.maxZoom : options.zoom, currentPanZoom = store.panZoom;
1118
951
  return currentPanZoom ? (await currentPanZoom.setViewport({
@@ -1134,25 +967,292 @@ const InitialNodeTypesMap = {
1134
967
  let edgesToUnselect = new Set((edges ?? store.edges).map(({ id }) => id));
1135
968
  edgesToUnselect.size && setEdgesStore((edges) => {
1136
969
  for (let edge of edges) edgesToUnselect.has(edge.id) && (edge.selected = !1);
1137
- });
970
+ }), flush();
1138
971
  }, addSelectedNodes = (ids) => {
1139
- let isMultiSelection = store.multiselectionKeyPressed, selectState = /* @__PURE__ */ new Map();
972
+ let isMultiSelection = store.multiselectionKeyPressed;
1140
973
  setNodesStore((nodes) => {
1141
974
  for (let node of nodes) {
1142
975
  let nodeWillBeSelected = ids.includes(node.id), selected = isMultiSelection && node.selected || nodeWillBeSelected;
1143
- if (selectState.set(node.id, selected), node.selected === selected) continue;
1144
- let internalNode = nodeLookup.get(node.id);
1145
- internalNode && (internalNode.selected = selected), node.selected = selected;
976
+ node.selected !== selected && (node.selected = selected);
1146
977
  }
1147
- }), isMultiSelection || unselectNodesAndEdges({ nodes: [] });
978
+ }), isMultiSelection || unselectNodesAndEdges({ nodes: [] }), flush();
1148
979
  }, addSelectedEdges = (ids) => {
1149
- let isMultiSelection = store.multiselectionKeyPressed, edgeSelectState = /* @__PURE__ */ new Map();
980
+ let isMultiSelection = store.multiselectionKeyPressed;
1150
981
  setEdgesStore((edges) => {
1151
982
  for (let edge of edges) {
1152
983
  let edgeWillBeSelected = ids.includes(edge.id), selected = isMultiSelection && edge.selected || edgeWillBeSelected;
1153
- edgeSelectState.set(edge.id, selected), edge.selected !== selected && (edge.selected = selected);
984
+ edge.selected !== selected && (edge.selected = selected);
985
+ }
986
+ }), isMultiSelection || unselectNodesAndEdges({ edges: [] }), flush();
987
+ }, handleNodeSelection = (id, unselect, nodeRef) => {
988
+ let node = store.nodes.find((n) => n.id === id);
989
+ node && (setSelectionRect(void 0), setSelectionRectMode(void 0), node.selected ? (unselect || node.selected && store.multiselectionKeyPressed) && (unselectNodesAndEdges({
990
+ nodes: [node],
991
+ edges: []
992
+ }), requestAnimationFrame(() => nodeRef?.blur())) : addSelectedNodes([id]));
993
+ }, handleEdgeSelection = (id) => {
994
+ let edge = edgeLookup[id];
995
+ edge && (edge.selectable || store.elementsSelectable && edge.selectable === void 0) && (setSelectionRect(void 0), setSelectionRectMode(void 0), edge.selected ? edge.selected && store.multiselectionKeyPressed && unselectNodesAndEdges({
996
+ nodes: [],
997
+ edges: [edge]
998
+ }) : addSelectedEdges([id]));
999
+ }, moveSelectedNodes = (direction, factor) => {
1000
+ let nodeUpdates = /* @__PURE__ */ new Map(), xVelo = store.snapGrid?.[0] ?? 5, yVelo = store.snapGrid?.[1] ?? 5, xDiff = direction.x * xVelo * factor, yDiff = direction.y * yVelo * factor;
1001
+ for (let node of nodeLookup.values()) {
1002
+ if (!(node.selected && (node.draggable || store.nodesDraggable && node.draggable === void 0))) continue;
1003
+ let nextPosition = {
1004
+ x: node.internals.positionAbsolute.x + xDiff,
1005
+ y: node.internals.positionAbsolute.y + yDiff
1006
+ };
1007
+ store.snapGrid && (nextPosition = snapPosition(nextPosition, store.snapGrid));
1008
+ let { position } = calculateNodePosition({
1009
+ nodeId: node.id,
1010
+ nextPosition,
1011
+ nodeLookup,
1012
+ nodeExtent: store.nodeExtent,
1013
+ nodeOrigin: store.nodeOrigin,
1014
+ onError: store.onError
1015
+ });
1016
+ nodeUpdates.set(node.id, { position });
1017
+ }
1018
+ updateNodePositions(nodeUpdates);
1019
+ }, panBy$1 = (delta) => panBy({
1020
+ delta,
1021
+ panZoom: store.panZoom,
1022
+ transform: store.transform,
1023
+ translateExtent: store.translateExtent,
1024
+ width: store.width,
1025
+ height: store.height
1026
+ }), cancelConnection = () => {
1027
+ setConnection({ ...initialConnection });
1028
+ }, reset = () => {
1029
+ resetStoreValues(), unselectNodesAndEdges();
1030
+ }, flow = {
1031
+ get nodes() {
1032
+ return store.nodes;
1033
+ },
1034
+ get edges() {
1035
+ return store.edges;
1036
+ },
1037
+ get internalNodes() {
1038
+ return internalNodes;
1039
+ },
1040
+ get layoutedEdges() {
1041
+ return layoutedEdges;
1042
+ },
1043
+ get connections() {
1044
+ return connections;
1045
+ },
1046
+ selection: {
1047
+ get nodes() {
1048
+ return store.selectedNodes;
1049
+ },
1050
+ get edges() {
1051
+ return store.selectedEdges;
1052
+ }
1053
+ },
1054
+ get nodesInitialized() {
1055
+ return store.nodesInitialized;
1056
+ },
1057
+ get viewportInitialized() {
1058
+ return store.viewportInitialized;
1059
+ },
1060
+ get viewport() {
1061
+ return store.viewport;
1062
+ },
1063
+ get width() {
1064
+ return store.width;
1065
+ },
1066
+ get height() {
1067
+ return store.height;
1068
+ },
1069
+ get colorMode() {
1070
+ return store.colorMode;
1071
+ },
1072
+ get connection() {
1073
+ return store.connection;
1074
+ },
1075
+ get dragging() {
1076
+ return store.dragging;
1077
+ },
1078
+ get minZoom() {
1079
+ return store.minZoom;
1080
+ },
1081
+ get maxZoom() {
1082
+ return store.maxZoom;
1083
+ },
1084
+ get nodesDraggable() {
1085
+ return store.nodesDraggable;
1086
+ },
1087
+ get nodesConnectable() {
1088
+ return store.nodesConnectable;
1089
+ },
1090
+ get elementsSelectable() {
1091
+ return store.elementsSelectable;
1092
+ },
1093
+ get snapGrid() {
1094
+ return store.snapGrid;
1095
+ }
1096
+ }, getNodeRect = (node) => {
1097
+ let nodeToUse = isNode(node) ? node : nodeLookup.get(node.id), position = nodeToUse.parentId ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.measured, nodeToUse.parentId, nodeLookup, store.nodeOrigin) : nodeToUse.position, nodeWithPosition = {
1098
+ ...nodeToUse,
1099
+ position,
1100
+ width: nodeToUse.measured?.width ?? nodeToUse.width,
1101
+ height: nodeToUse.measured?.height ?? nodeToUse.height
1102
+ };
1103
+ return nodeToRect(nodeWithPosition);
1104
+ }, updateNode = (id, nodeUpdate, options = { replace: !1 }) => {
1105
+ setNodesStore((nodes) => {
1106
+ let index = nodes.findIndex((node) => node.id === id);
1107
+ if (index === -1) return;
1108
+ let node = nodes[index], nextNode = typeof nodeUpdate == "function" ? nodeUpdate(node) : nodeUpdate;
1109
+ nodes[index] = options?.replace && isNode(nextNode) ? nextNode : {
1110
+ ...node,
1111
+ ...nextNode
1112
+ };
1113
+ });
1114
+ }, commands = {
1115
+ fitView,
1116
+ fitBounds: async (bounds, options) => {
1117
+ if (!store.panZoom) return !1;
1118
+ let viewport = getViewportForBounds$1(bounds, store.width, store.height, store.minZoom, store.maxZoom, options?.padding ?? .1);
1119
+ return await store.panZoom.setViewport(viewport, {
1120
+ duration: options?.duration,
1121
+ ease: options?.ease,
1122
+ interpolate: options?.interpolate
1123
+ }), !0;
1124
+ },
1125
+ zoomIn,
1126
+ zoomOut,
1127
+ setZoom: (zoomLevel, options) => {
1128
+ let currentPanZoom = store.panZoom;
1129
+ return currentPanZoom ? currentPanZoom.scaleTo(zoomLevel, { duration: options?.duration }) : Promise.resolve(!1);
1130
+ },
1131
+ setCenter,
1132
+ setViewport: async (nextViewport, options) => {
1133
+ let currentViewport = store.viewport;
1134
+ return store.panZoom ? (await store.panZoom.setViewport({
1135
+ x: nextViewport.x ?? currentViewport.x,
1136
+ y: nextViewport.y ?? currentViewport.y,
1137
+ zoom: nextViewport.zoom ?? currentViewport.zoom
1138
+ }, options), !0) : !1;
1139
+ },
1140
+ panBy: panBy$1,
1141
+ screenToFlowPosition: (position, options = { snapToGrid: !0 }) => {
1142
+ if (!store.domNode) return position;
1143
+ let _snapGrid = options.snapToGrid ? store.snapGrid : !1, { x, y, zoom } = store.viewport, { x: domX, y: domY } = store.domNode.getBoundingClientRect(), correctedPosition = {
1144
+ x: position.x - domX,
1145
+ y: position.y - domY
1146
+ };
1147
+ return pointToRendererPoint(correctedPosition, [
1148
+ x,
1149
+ y,
1150
+ zoom
1151
+ ], _snapGrid !== null, _snapGrid || [1, 1]);
1152
+ },
1153
+ flowToScreenPosition: (position) => {
1154
+ if (!store.domNode) return position;
1155
+ let { x, y, zoom } = store.viewport, { x: domX, y: domY } = store.domNode.getBoundingClientRect(), rendererPosition = rendererPointToPoint(position, [
1156
+ x,
1157
+ y,
1158
+ zoom
1159
+ ]);
1160
+ return {
1161
+ x: rendererPosition.x + domX,
1162
+ y: rendererPosition.y + domY
1163
+ };
1164
+ },
1165
+ addNodes: (payload) => {
1166
+ let newNodes = Array.isArray(payload) ? payload : [payload];
1167
+ setNodesStore((nodes) => [...nodes, ...newNodes]);
1168
+ },
1169
+ addEdges: (payload) => {
1170
+ let newEdges = Array.isArray(payload) ? payload : [payload];
1171
+ setEdgesStore((edges) => [...edges, ...newEdges]);
1172
+ },
1173
+ setNodes: setNodesStore,
1174
+ setEdges: setEdgesStore,
1175
+ updateNode,
1176
+ updateNodeData: (id, dataUpdate, options) => {
1177
+ let node = nodeLookup.get(id)?.internals.userNode;
1178
+ if (!node) return;
1179
+ let nextData = typeof dataUpdate == "function" ? dataUpdate(node) : dataUpdate;
1180
+ updateNode(id, (current) => ({
1181
+ ...current,
1182
+ data: options?.replace ? nextData : {
1183
+ ...current.data,
1184
+ ...nextData
1185
+ }
1186
+ }));
1187
+ },
1188
+ updateEdge: (id, edgeUpdate, options = { replace: !1 }) => {
1189
+ setEdgesStore((edges) => {
1190
+ let index = edges.findIndex((edge) => edge.id === id);
1191
+ if (index === -1) return;
1192
+ let edge = edges[index], nextEdge = typeof edgeUpdate == "function" ? edgeUpdate(edge) : edgeUpdate;
1193
+ edges[index] = options.replace && isEdge(nextEdge) ? nextEdge : {
1194
+ ...edge,
1195
+ ...nextEdge
1196
+ };
1197
+ });
1198
+ },
1199
+ deleteElements: async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => {
1200
+ let { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
1201
+ nodesToRemove,
1202
+ edgesToRemove,
1203
+ nodes: store.nodes,
1204
+ edges: store.edges,
1205
+ onBeforeDelete: store.onBeforeDelete
1206
+ });
1207
+ if (matchingEdges) {
1208
+ let remainingEdges = store.edges.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id));
1209
+ store.onEdgesDelete?.(matchingEdges), setEdgesStore(() => remainingEdges);
1210
+ }
1211
+ if (matchingNodes) {
1212
+ let remainingNodes = store.nodes.filter((node) => !matchingNodes.some(({ id }) => id === node.id));
1213
+ store.onNodesDelete?.(matchingNodes), setNodesStore(() => remainingNodes);
1214
+ }
1215
+ return {
1216
+ deletedNodes: matchingNodes,
1217
+ deletedEdges: matchingEdges
1218
+ };
1219
+ },
1220
+ getIntersectingNodes: (nodeOrRect, partially = !0, nodesToIntersect) => {
1221
+ let isRect = isRectObject(nodeOrRect), nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
1222
+ return nodeRect ? (nodesToIntersect || store.nodes).filter((n) => {
1223
+ let internalNode = nodeLookup.get(n.id);
1224
+ if (!internalNode || !isRect && n.id === nodeOrRect.id) return !1;
1225
+ let currNodeRect = nodeToRect(internalNode), overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
1226
+ return partially && overlappingArea > 0 || overlappingArea >= nodeRect.width * nodeRect.height;
1227
+ }) : [];
1228
+ },
1229
+ isNodeIntersecting: (nodeOrRect, area, partially = !0) => {
1230
+ let nodeRect = isRectObject(nodeOrRect) ? nodeOrRect : getNodeRect(nodeOrRect);
1231
+ if (!nodeRect) return !1;
1232
+ let overlappingArea = getOverlappingArea(nodeRect, area);
1233
+ return partially && overlappingArea > 0 || overlappingArea >= nodeRect.width * nodeRect.height;
1234
+ },
1235
+ getNodesBounds: (nodesToMeasure) => getNodesBounds$1(nodesToMeasure, {
1236
+ nodeLookup,
1237
+ nodeOrigin: store.nodeOrigin
1238
+ }),
1239
+ updateNodeInternals: (id) => {
1240
+ let updateIds = Array.isArray(id) ? id : [id], updates = [];
1241
+ for (let updateId of updateIds) {
1242
+ let nodeElement = store.domNode?.querySelector(`.solid-flow__node[data-id="${updateId}"]`);
1243
+ nodeElement && updates.push([updateId, {
1244
+ id: updateId,
1245
+ nodeElement,
1246
+ force: !0
1247
+ }]);
1154
1248
  }
1155
- }), isMultiSelection || unselectNodesAndEdges({ edges: [] });
1249
+ requestMeasure(updates);
1250
+ },
1251
+ toObject: () => structuredClone({
1252
+ nodes: [...snapshot(store.nodes)],
1253
+ edges: [...snapshot(store.edges)],
1254
+ viewport: { ...snapshot(store.viewport) }
1255
+ })
1156
1256
  };
1157
1257
  return createEffect(() => !!(width() && height() && panZoom()), (ready) => {
1158
1258
  ready && tryInitialFitView();
@@ -1163,195 +1263,571 @@ const InitialNodeTypesMap = {
1163
1263
  y: store.viewport.y,
1164
1264
  zoom: store.viewport.zoom
1165
1265
  }
1166
- }), ({ panZoom, viewport }) => {
1167
- panZoom?.syncViewport(viewport);
1168
- }), createEffect(() => ({
1169
- panZoom: panZoom(),
1170
- extent: [store.minZoom, store.maxZoom]
1171
- }), ({ panZoom, extent }) => {
1172
- panZoom?.setScaleExtent(extent);
1173
- }), createEffect(() => ({
1174
- panZoom: panZoom(),
1175
- extent: store.translateExtent
1176
- }), ({ panZoom, extent }) => {
1177
- panZoom?.setTranslateExtent(extent);
1178
- }), createRenderEffect(mapArray(() => store.nodes, (userNode) => {
1179
- createRenderEffect(() => {
1180
- let internalNode = untrack(() => nodeLookup.get(userNode.id)), selectedNodeZ = store.elevateNodesOnSelect && store.zIndexMode !== "manual" ? 1e3 : 0, clampedPosition = clampPosition(getNodePositionWithOrigin(userNode, store.nodeOrigin), isCoordinateExtent(userNode.extent) ? userNode.extent : store.nodeExtent, getNodeDimensions(userNode)), preservedMeasured = {
1181
- width: userNode.measured?.width ?? internalNode?.measured?.width,
1182
- height: userNode.measured?.height ?? internalNode?.measured?.height
1183
- }, updatedNodeInternals = {
1184
- ...userNode,
1185
- measured: preservedMeasured,
1186
- internals: {
1187
- positionAbsolute: clampedPosition,
1188
- handleBounds: !userNode.measured && !internalNode?.measured ? void 0 : internalNode?.internals.handleBounds,
1189
- z: calculateZ(userNode, selectedNodeZ, store.zIndexMode),
1190
- userNode
1266
+ }), ({ panZoom, viewport }) => {
1267
+ panZoom?.syncViewport(viewport);
1268
+ }), createEffect(() => ({
1269
+ panZoom: panZoom(),
1270
+ extent: [store.minZoom, store.maxZoom]
1271
+ }), ({ panZoom, extent }) => {
1272
+ panZoom?.setScaleExtent(extent);
1273
+ }), createEffect(() => ({
1274
+ panZoom: panZoom(),
1275
+ extent: store.translateExtent
1276
+ }), ({ panZoom, extent }) => {
1277
+ panZoom?.setTranslateExtent(extent);
1278
+ }), createEffect(() => new Set(store.nodes.map((n) => n.id)), (currentIds) => {
1279
+ setMeasurementsStore((draft) => {
1280
+ for (let id of Object.keys(draft)) currentIds.has(id) || delete draft[id];
1281
+ });
1282
+ }), {
1283
+ store,
1284
+ flow,
1285
+ commands,
1286
+ internalNodes,
1287
+ layoutedEdges,
1288
+ nodeLookup,
1289
+ edgeLookup,
1290
+ parentIds,
1291
+ connections,
1292
+ actions: {
1293
+ getEdge,
1294
+ applyInitialFitView,
1295
+ applyMeasurementWrites,
1296
+ applyNodeChanges,
1297
+ markInitialNodesMeasured,
1298
+ setMeasureRequester,
1299
+ resetStoreValues,
1300
+ setAriaLabelConfig,
1301
+ setAriaLiveMessage,
1302
+ setClickConnectStartHandle,
1303
+ setConfig,
1304
+ setConnection,
1305
+ setDeleteKeyPressed,
1306
+ setDomNode,
1307
+ setDragging,
1308
+ get setEdges() {
1309
+ return setEdgesStore;
1310
+ },
1311
+ setElementsSelectable,
1312
+ setHeight,
1313
+ setMultiselectionKeyPressed,
1314
+ get setNodes() {
1315
+ return setNodesStore;
1316
+ },
1317
+ setNodesConnectable,
1318
+ setNodesDraggable,
1319
+ setPanActivationKeyPressed,
1320
+ setPanZoom,
1321
+ setSelectionKeyPressed,
1322
+ setSelectionRect,
1323
+ setSelectionRectMode,
1324
+ get setViewport() {
1325
+ return (viewport) => setViewportStore(() => viewport);
1326
+ },
1327
+ setWidth,
1328
+ setZoomActivationKeyPressed,
1329
+ addEdge,
1330
+ updateNodePositions,
1331
+ zoomIn,
1332
+ zoomOut,
1333
+ fitView,
1334
+ setCenter,
1335
+ setPaneClickDistance,
1336
+ unselectNodesAndEdges,
1337
+ addSelectedNodes,
1338
+ addSelectedEdges,
1339
+ handleNodeSelection,
1340
+ handleEdgeSelection,
1341
+ moveSelectedNodes,
1342
+ panBy: panBy$1,
1343
+ cancelConnection,
1344
+ reset
1345
+ }
1346
+ };
1347
+ }, getEdgeId = (connection) => {
1348
+ let { source, sourceHandle, target, targetHandle } = connection;
1349
+ return `xy-edge__${source}${sourceHandle || ""}-${target}${targetHandle || ""}`;
1350
+ }, NodeConnectableContext = createContext(null);
1351
+ function useNodeConnectable() {
1352
+ let ctx = useContext(NodeConnectableContext);
1353
+ if (!ctx) throw Error("solid-flow: Your application must be wrapped with <SolidFlow> in order to invoke useNodeConnectable");
1354
+ return ctx;
1355
+ }
1356
+ //#endregion
1357
+ //#region src/components/graph/handle/Handle.tsx
1358
+ const Handle = (props) => {
1359
+ let _props = propDefaults(props, {
1360
+ type: "source",
1361
+ position: "top",
1362
+ isConnectableStart: !0,
1363
+ isConnectableEnd: !0
1364
+ }), { store, nodeLookup, connections, actions } = useInternalSolidFlow(), rest = omit(_props, "id", "type", "position", "isConnectable", "isConnectableStart", "isConnectableEnd", "isValidConnection", "onConnect", "onDisconnect", "children", "class", "style"), nodeId = useNodeId(), nodeConnectable = useNodeConnectable(), connectable = () => _props.isConnectable ?? nodeConnectable(), isTarget = () => _props.type === "target", handleId = () => _props.id ?? null, connectionInProcess = () => !!store.connection.fromHandle, connectingFrom = () => store.connection.fromHandle && store.connection.fromHandle.nodeId === nodeId() && store.connection.fromHandle.type === _props.type && store.connection.fromHandle.id === handleId(), connectingTo = () => store.connection.toHandle && store.connection.toHandle.nodeId === nodeId() && store.connection.toHandle.type === _props.type && store.connection.toHandle.id === handleId(), isPossibleTargetHandle = () => store.connectionMode === "strict" ? store.connection.fromHandle?.type !== _props.type : nodeId() !== store.connection.fromHandle?.nodeId || handleId() !== store.connection.fromHandle?.id, valid = () => !!(connectingTo() && store.connection.isValid), prevConnections = null;
1365
+ createEffect(() => {
1366
+ if (!_props.onConnect && !_props.onDisconnect) return null;
1367
+ let rec = connections[connectionKey(nodeId(), _props.type, _props.id)], map = /* @__PURE__ */ new Map();
1368
+ for (let key of Object.keys(rec ?? {})) map.set(key, { ...rec[key] });
1369
+ return { connections: map };
1370
+ }, (current) => {
1371
+ if (!current) return;
1372
+ let { connections: next } = current;
1373
+ prevConnections && !areConnectionMapsEqual(next, prevConnections) && (handleConnectionChange(prevConnections, next, props.onDisconnect), handleConnectionChange(next, prevConnections, props.onConnect)), prevConnections = next;
1374
+ });
1375
+ let onConnectExtended = (connection) => {
1376
+ let handleConnection = {
1377
+ ...connection,
1378
+ id: getEdgeId(connection)
1379
+ }, edge = store.onBeforeConnect?.(handleConnection) ?? handleConnection;
1380
+ actions.addEdge(edge), store.onConnect?.(handleConnection);
1381
+ }, onPointerDown = (event) => {
1382
+ XYHandle.onPointerDown(event, {
1383
+ handleId: handleId(),
1384
+ nodeId: nodeId(),
1385
+ isTarget: isTarget(),
1386
+ connectionRadius: store.connectionRadius,
1387
+ domNode: store.domNode,
1388
+ nodeLookup,
1389
+ connectionMode: store.connectionMode,
1390
+ lib: store.lib,
1391
+ autoPanOnConnect: store.autoPanOnConnect,
1392
+ flowId: store.id,
1393
+ isValidConnection: _props.isValidConnection ?? store.isValidConnection,
1394
+ updateConnection: ((connection) => {
1395
+ actions.setConnection(connection), flush();
1396
+ }),
1397
+ cancelConnection: actions.cancelConnection,
1398
+ panBy: actions.panBy,
1399
+ onConnect: onConnectExtended,
1400
+ onConnectStart: (event, startParams) => {
1401
+ store.onConnectStart?.(event, {
1402
+ nodeId: startParams.nodeId,
1403
+ handleId: startParams.handleId,
1404
+ handleType: startParams.handleType
1405
+ });
1406
+ },
1407
+ onConnectEnd: store.onConnectEnd,
1408
+ getTransform: () => store.transform,
1409
+ getFromHandle: () => store.connection.fromHandle,
1410
+ autoPanSpeed: store.autoPanSpeed,
1411
+ dragThreshold: store.connectionDragThreshold,
1412
+ handleDomNode: event.currentTarget
1413
+ });
1414
+ }, onClick = (event) => {
1415
+ if (!nodeId() || !store.clickConnectStartHandle && !_props.isConnectableStart) return;
1416
+ if (!store.clickConnectStartHandle) {
1417
+ store.onClickConnectStart?.(event, {
1418
+ nodeId: nodeId(),
1419
+ handleId: handleId(),
1420
+ handleType: _props.type
1421
+ }), actions.setClickConnectStartHandle({
1422
+ nodeId: nodeId(),
1423
+ type: _props.type,
1424
+ id: handleId()
1425
+ });
1426
+ return;
1427
+ }
1428
+ let doc = getHostForElement(event.target), isValidConnectionHandler = _props.isValidConnection ?? store.isValidConnection, { connection, isValid } = XYHandle.isValid(event, {
1429
+ handle: {
1430
+ nodeId: nodeId(),
1431
+ id: handleId(),
1432
+ type: _props.type
1433
+ },
1434
+ connectionMode: store.connectionMode,
1435
+ fromNodeId: store.clickConnectStartHandle.nodeId,
1436
+ fromHandleId: store.clickConnectStartHandle.id ?? null,
1437
+ fromType: store.clickConnectStartHandle.type,
1438
+ isValidConnection: isValidConnectionHandler,
1439
+ flowId: store.id,
1440
+ doc,
1441
+ lib: store.lib,
1442
+ nodeLookup
1443
+ });
1444
+ isValid && connection && onConnectExtended(connection);
1445
+ let connectionClone = structuredClone(snapshot(store.connection));
1446
+ delete connectionClone.inProgress, connectionClone.toPosition = connectionClone.toHandle ? connectionClone.toHandle.position : null, store.onClickConnectEnd?.(event, connectionClone), actions.setClickConnectStartHandle(void 0);
1447
+ }, connectionIndicator = () => connectable() && (!connectionInProcess() || isPossibleTargetHandle()) && (connectionInProcess() || store.clickConnectStartHandle ? _props.isConnectableEnd : _props.isConnectableStart);
1448
+ return <div {...rest} role="button" aria-label={store.ariaLabelConfig["handle.ariaLabel"]} tabindex={-1} data-handleid={handleId()} data-nodeid={nodeId()} data-handlepos={_props.position} data-id={`${store.id}-${nodeId()}-${_props.id || null}-${_props.type}`} onClick={store.clickConnect ? onClick : void 0} onPointerDown={onPointerDown} style={_props.style} class={[
1449
+ "solid-flow__handle",
1450
+ `solid-flow__handle-${_props.position}`,
1451
+ store.noDragClass,
1452
+ store.noPanClass,
1453
+ _props.class,
1454
+ {
1455
+ valid: valid(),
1456
+ connectingto: !!connectingTo(),
1457
+ connectingfrom: !!connectingFrom(),
1458
+ source: !isTarget(),
1459
+ target: isTarget(),
1460
+ connectablestart: _props.isConnectableStart,
1461
+ connectableend: _props.isConnectableEnd,
1462
+ connectable: !!connectable(),
1463
+ connectionindicator: connectionIndicator()
1464
+ }
1465
+ ]}>
1466
+ {_props.children}
1467
+ </div>;
1468
+ }, DefaultNode = (props) => {
1469
+ let _props = propDefaults(props, {
1470
+ targetPosition: Position$1.Top,
1471
+ sourcePosition: Position$1.Bottom
1472
+ });
1473
+ return <>
1474
+ <Handle type="target" position={_props.targetPosition} isConnectable={_props.isConnectable} />
1475
+ {props.data.label}
1476
+ <Handle type="source" position={_props.sourcePosition} isConnectable={_props.isConnectable} />
1477
+ </>;
1478
+ }, GroupNode = (props) => <div style={{
1479
+ position: "absolute",
1480
+ left: 0,
1481
+ top: 0,
1482
+ width: toPxString(props.width),
1483
+ height: toPxString(props.height)
1484
+ }} />, InputNode = (props) => {
1485
+ let _props = propDefaults(props, { sourcePosition: Position$1.Bottom });
1486
+ return <>
1487
+ {props.data?.label}
1488
+ <Handle type="source" position={_props.sourcePosition} isConnectable={_props.isConnectable} />
1489
+ </>;
1490
+ }, createDraggable = (elem, params) => {
1491
+ let { store, nodeLookup, actions } = useInternalSolidFlow(), [dragging, setDragging] = createSignal(!1);
1492
+ return createEffect(() => ({
1493
+ el: elem(),
1494
+ current: params()
1495
+ }), ({ el, current }) => {
1496
+ if (!el || current.disabled) return;
1497
+ let { onDrag, onDragStart, onDragStop, onNodeMouseDown } = current, dragInstance = XYDrag({
1498
+ onDrag,
1499
+ onDragStart: (event, dragItems, node, nodes) => {
1500
+ setDragging(!0), onDragStart?.(event, dragItems, node, nodes);
1501
+ },
1502
+ onDragStop: (event, dragItems, node, nodes) => {
1503
+ setDragging(!1), onDragStop?.(event, dragItems, node, nodes);
1504
+ },
1505
+ onNodeMouseDown,
1506
+ getStoreItems: () => ({
1507
+ nodes: store.nodes,
1508
+ nodeLookup,
1509
+ edges: store.edges,
1510
+ nodeExtent: store.nodeExtent,
1511
+ snapGrid: store.snapGrid ?? [0, 0],
1512
+ snapToGrid: !!store.snapGrid,
1513
+ autoPanSpeed: store.autoPanSpeed,
1514
+ nodeOrigin: store.nodeOrigin,
1515
+ multiSelectionActive: store.multiselectionKeyPressed,
1516
+ domNode: store.domNode,
1517
+ transform: store.transform,
1518
+ autoPanOnNodeDrag: store.autoPanOnNodeDrag,
1519
+ nodesDraggable: store.nodesDraggable,
1520
+ selectNodesOnDrag: store.selectNodesOnDrag,
1521
+ nodeDragThreshold: store.nodeDragThreshold,
1522
+ unselectNodesAndEdges: actions.unselectNodesAndEdges,
1523
+ updateNodePositions: actions.updateNodePositions,
1524
+ panBy: actions.panBy
1525
+ })
1526
+ });
1527
+ return dragInstance.update({
1528
+ domNode: el,
1529
+ nodeId: current.nodeId,
1530
+ noDragClassName: current.noDragClass,
1531
+ handleSelector: current.handleSelector,
1532
+ isSelectable: current.isSelectable,
1533
+ nodeClickDistance: current.nodeClickDistance
1534
+ }), () => {
1535
+ dragInstance.destroy();
1536
+ };
1537
+ }), dragging;
1538
+ }, NodeIdContext = createContext(null);
1539
+ function useNodeId() {
1540
+ let ctx = useContext(NodeIdContext);
1541
+ if (!ctx) throw Error("solid-flow: Your application must be wrapped with <SolidFlow> in order to invoke useNodeId");
1542
+ return ctx;
1543
+ }
1544
+ //#endregion
1545
+ //#region src/components/graph/node/NodeWrapper.tsx
1546
+ const NodeWrapper = (props) => {
1547
+ let { store, nodeLookup, parentIds, actions } = useInternalSolidFlow(), [nodeRef, setNodeRef] = createSignal(), node = () => nodeLookup.get(props.nodeId), 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 = () => {
1548
+ let { x, y } = node().internals.positionAbsolute;
1549
+ return `translate(${x}px, ${y}px)`;
1550
+ }, sizeStyle = () => {
1551
+ let w = node().width ?? node().initialWidth, h = node().height ?? node().initialHeight;
1552
+ return {
1553
+ ...node().style,
1554
+ ...w ? { width: toPxString(w) } : {},
1555
+ ...h ? { height: toPxString(h) } : {}
1556
+ };
1557
+ }, style = () => ({
1558
+ "z-index": node().internals.z,
1559
+ transform: transform(),
1560
+ visibility: nodeHasDimensions(node()) ? "visible" : "hidden",
1561
+ ...sizeStyle(),
1562
+ ...node().style ?? {}
1563
+ });
1564
+ createEffect(() => ({
1565
+ valid: nodeTypeValid(),
1566
+ nodeType: nodeType()
1567
+ }), ({ valid, nodeType }) => {}), createEffect(() => ({
1568
+ id: node().id,
1569
+ nodeElement: nodeRef(),
1570
+ nodeType: nodeType(),
1571
+ sourcePosition: node().sourcePosition,
1572
+ targetPosition: node().targetPosition
1573
+ }), (current, prev) => {
1574
+ current.nodeElement && (prev && prev.nodeElement === current.nodeElement && prev.sourcePosition === current.sourcePosition && prev.targetPosition === current.targetPosition && prev.nodeType === current.nodeType || actions.requestUpdateNodeInternals([[current.id, {
1575
+ id: current.id,
1576
+ nodeElement: current.nodeElement,
1577
+ force: !0
1578
+ }]]));
1579
+ }), createEffect(() => ({
1580
+ nodeElement: nodeRef(),
1581
+ hasDimensions: nodeHasDimensions(node()),
1582
+ resizeObserver: props.resizeObserver
1583
+ }), (current, prev) => {
1584
+ current.nodeElement === prev?.nodeElement && current.resizeObserver === prev?.resizeObserver && current.hasDimensions || (prev?.nodeElement && prev.resizeObserver?.unobserve(prev.nodeElement), current.nodeElement && current.resizeObserver?.observe(current.nodeElement));
1585
+ });
1586
+ let onSelectNodeHandler = (event) => {
1587
+ selectable() && (!store.selectNodesOnDrag || !draggable() || store.nodeDragThreshold > 0) && actions.handleNodeSelection(node().id), props.onNodeClick?.({
1588
+ node: userNode(),
1589
+ event
1590
+ });
1591
+ }, onKeyDown = (event) => {
1592
+ if (isInputDOMNode(event) || store.disableKeyboardA11y) return;
1593
+ if (elementSelectionKeys.includes(event.key) && selectable()) {
1594
+ actions.handleNodeSelection(node().id, event.key === "Escape", nodeRef());
1595
+ return;
1596
+ }
1597
+ let arrowKeyDiff = ARROW_KEY_DIFFS[event.key];
1598
+ draggable() && node().selected && arrowKeyDiff && (event.preventDefault(), actions.setAriaLiveMessage(store.ariaLabelConfig["node.a11yDescription.ariaLiveMessage"]({
1599
+ direction: event.key.replace("Arrow", "").toLowerCase(),
1600
+ x: ~~node().internals.positionAbsolute.x,
1601
+ y: ~~node().internals.positionAbsolute.y
1602
+ })), actions.moveSelectedNodes(arrowKeyDiff, event.shiftKey ? 4 : 1));
1603
+ }, onFocus = () => {
1604
+ if (store.disableKeyboardA11y || !store.autoPanOnNodeFocus || !nodeRef()?.matches(":focus-visible")) return;
1605
+ let { width, height, viewport } = store;
1606
+ getNodesInside(/* @__PURE__ */ new Map([[node().id, node()]]), {
1607
+ x: 0,
1608
+ y: 0,
1609
+ width,
1610
+ height
1611
+ }, [
1612
+ viewport.x,
1613
+ viewport.y,
1614
+ viewport.zoom
1615
+ ], !0).length > 0 || actions.setCenter(node().position.x + (node().measured.width ?? 0) / 2, node().position.y + (node().measured.height ?? 0) / 2, { zoom: viewport.zoom });
1616
+ }, dragging = createDraggable(nodeRef, () => ({
1617
+ nodeId: node().id,
1618
+ isSelectable: selectable(),
1619
+ disabled: !draggable(),
1620
+ handleSelector: node().dragHandle,
1621
+ noDragClass: store.noDragClass,
1622
+ nodeClickDistance: props.nodeClickDistance,
1623
+ onNodeMouseDown: actions.handleNodeSelection,
1624
+ onDrag: (event, _, targetNode, nodes) => {
1625
+ props.onNodeDrag?.({
1626
+ event,
1627
+ targetNode,
1628
+ nodes
1629
+ });
1630
+ },
1631
+ onDragStart: (event, _, targetNode, nodes) => {
1632
+ props.onNodeDragStart?.({
1633
+ event,
1634
+ targetNode,
1635
+ nodes
1636
+ });
1637
+ },
1638
+ onDragStop: (event, _, targetNode, nodes) => {
1639
+ props.onNodeDragStop?.({
1640
+ event,
1641
+ targetNode,
1642
+ nodes
1643
+ });
1644
+ }
1645
+ }));
1646
+ return <Show when={!node().hidden}>
1647
+ <div ref={setNodeRef} data-id={node().id} class={[
1648
+ "solid-flow__node",
1649
+ `solid-flow__node-${nodeType()}`,
1650
+ {
1651
+ connectable: !!connectable(),
1652
+ draggable: !!draggable(),
1653
+ dragging: dragging(),
1654
+ nopan: !!draggable(),
1655
+ parent: isParentNode(),
1656
+ selectable: !!selectable(),
1657
+ selected: !!node().selected
1658
+ },
1659
+ node().class
1660
+ ]} style={style()} onClick={onSelectNodeHandler} onPointerEnter={(event) => props.onNodePointerEnter?.({
1661
+ node: userNode(),
1662
+ event
1663
+ })} onPointerLeave={(event) => props.onNodePointerLeave?.({
1664
+ node: userNode(),
1665
+ event
1666
+ })} onPointerMove={(event) => props.onNodePointerMove?.({
1667
+ node: userNode(),
1668
+ event
1669
+ })} onContextMenu={(event) => props.onNodeContextMenu?.({
1670
+ node: userNode(),
1671
+ event
1672
+ })} onKeyDown={(e) => focusable() && onKeyDown(e)} onFocus={() => focusable() && onFocus()} tabindex={focusable() ? 0 : void 0} role={node().ariaRole ?? (focusable() ? "group" : void 0)} aria-roledescription="node" aria-describedby={store.disableKeyboardA11y ? void 0 : `${ARIA_NODE_DESC_KEY}-${store.id}`} {...node().domAttributes}>
1673
+ <NodeIdContext value={nodeId}>
1674
+ <NodeConnectableContext value={connectable}>
1675
+ <Dynamic component={nodeComponent()} data={node().data} id={node().id} selected={!!node().selected} selectable={selectable()} deletable={deletable()} sourcePosition={node().sourcePosition} targetPosition={node().targetPosition} zIndex={node().internals.z} dragging={dragging()} draggable={draggable()} dragHandle={node().dragHandle} parentId={node().parentId} type={nodeType()} isConnectable={connectable()} positionAbsoluteX={node().internals.positionAbsolute.x} positionAbsoluteY={node().internals.positionAbsolute.y} width={node().width} height={node().height} />
1676
+ </NodeConnectableContext>
1677
+ </NodeIdContext>
1678
+ </div>
1679
+ </Show>;
1680
+ }, OutputNode = (props) => {
1681
+ let _props = propDefaults(props, { targetPosition: Position$1.Top });
1682
+ return <>
1683
+ <Handle type="target" position={_props.targetPosition} isConnectable={_props.isConnectable} />
1684
+ {props.data?.label}
1685
+ </>;
1686
+ };
1687
+ //#endregion
1688
+ //#region src/data/xyflow.ts
1689
+ /**
1690
+ * The DOM side of the measurement pipeline (fork of @xyflow/system's
1691
+ * updateNodeInternals): reads each updated node element's dimensions and
1692
+ * handle bounds from the DOM and reports them as data — measurement writes
1693
+ * for the measurements root plus user-facing dimension/position changes —
1694
+ * instead of writing into a node lookup. The internalNodes projection turns
1695
+ * the measurement writes back into internal-node state.
1696
+ *
1697
+ * `parentExpandChildren` must be turned into changes via
1698
+ * {@link handleExpandParent} only AFTER the measurement writes have been
1699
+ * flushed, so parent geometry reflects this measuring pass.
1700
+ */
1701
+ function measureNodeInternals(updates, nodeLookup, domNode, nodeExtent) {
1702
+ let measurementWrites = [], changes = [], parentExpandChildren = [], updatedInternals = !1, viewportNode = domNode?.querySelector(".xyflow__viewport");
1703
+ if (!viewportNode) return {
1704
+ updatedInternals,
1705
+ measurementWrites,
1706
+ changes,
1707
+ parentExpandChildren
1708
+ };
1709
+ let style = window.getComputedStyle(viewportNode), { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
1710
+ for (let update of updates.values()) {
1711
+ let node = nodeLookup.get(update.id);
1712
+ if (!node) continue;
1713
+ if (node.hidden) {
1714
+ measurementWrites.push({
1715
+ id: node.id,
1716
+ hidden: !0
1717
+ }), updatedInternals = !0;
1718
+ continue;
1719
+ }
1720
+ let dimensions = getDimensions(update.nodeElement), dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
1721
+ if (dimensions.width && dimensions.height && (dimensionChanged || !node.internals.handleBounds || update.force)) {
1722
+ let nodeBounds = update.nodeElement.getBoundingClientRect(), extent = isCoordinateExtent(node.extent) ? node.extent : nodeExtent, { positionAbsolute } = node.internals;
1723
+ node.parentId && node.extent === "parent" ? positionAbsolute = clampPositionToParent(positionAbsolute, dimensions, nodeLookup.get(node.parentId)) : extent && (positionAbsolute = clampPosition(positionAbsolute, extent, dimensions)), measurementWrites.push({
1724
+ id: node.id,
1725
+ measured: dimensions,
1726
+ handleBounds: {
1727
+ source: getHandleBounds("source", update.nodeElement, nodeBounds, zoom, node.id),
1728
+ target: getHandleBounds("target", update.nodeElement, nodeBounds, zoom, node.id)
1729
+ }
1730
+ }), updatedInternals = !0, dimensionChanged && (changes.push({
1731
+ id: node.id,
1732
+ type: "dimensions",
1733
+ dimensions
1734
+ }), node.expandParent && node.parentId && parentExpandChildren.push({
1735
+ id: node.id,
1736
+ parentId: node.parentId,
1737
+ rect: nodeToRect({
1738
+ ...node,
1739
+ measured: dimensions,
1740
+ internals: {
1741
+ ...node.internals,
1742
+ positionAbsolute
1743
+ }
1744
+ })
1745
+ }));
1746
+ }
1747
+ }
1748
+ return {
1749
+ updatedInternals,
1750
+ measurementWrites,
1751
+ changes,
1752
+ parentExpandChildren
1753
+ };
1754
+ }
1755
+ function handleExpandParent(children, nodeLookup, getChildNodes, nodeOrigin = [0, 0]) {
1756
+ let changes = [], parentExpansions = /* @__PURE__ */ new Map();
1757
+ for (let child of children) {
1758
+ let parent = nodeLookup.get(child.parentId);
1759
+ if (!parent) continue;
1760
+ let parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent), expandedRect = getBoundsOfRects(parentRect, child.rect);
1761
+ parentExpansions.set(child.parentId, {
1762
+ expandedRect,
1763
+ parent
1764
+ });
1765
+ }
1766
+ return parentExpansions.size > 0 && parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
1767
+ let positionAbsolute = parent.internals.positionAbsolute, dimensions = getNodeDimensions(parent), origin = parent.origin ?? nodeOrigin, xChange = expandedRect.x < positionAbsolute.x ? Math.round(Math.abs(positionAbsolute.x - expandedRect.x)) : 0, yChange = expandedRect.y < positionAbsolute.y ? Math.round(Math.abs(positionAbsolute.y - expandedRect.y)) : 0, newWidth = Math.max(dimensions.width, Math.round(expandedRect.width)), newHeight = Math.max(dimensions.height, Math.round(expandedRect.height)), widthChange = (newWidth - dimensions.width) * origin[0], heightChange = (newHeight - dimensions.height) * origin[1];
1768
+ if (xChange > 0 || yChange > 0 || widthChange || heightChange) {
1769
+ changes.push({
1770
+ id: parentId,
1771
+ type: "position",
1772
+ position: {
1773
+ x: parent.position.x - xChange + widthChange,
1774
+ y: parent.position.y - yChange + heightChange
1191
1775
  }
1192
- };
1193
- nodeLookup.set(userNode.id, updatedNodeInternals), userNode.parentId && updateChildNode(updatedNodeInternals, nodeLookup, parentLookup, {
1194
- nodeOrigin: store.nodeOrigin,
1195
- nodeExtent: store.nodeExtent,
1196
- elevateNodesOnSelect: store.elevateNodesOnSelect,
1197
- zIndexMode: store.zIndexMode,
1198
- checkEquality: !0
1199
- });
1200
- }, () => {}), onCleanup(() => {});
1201
- }), () => {}), createEffect(() => new Set(store.nodes.map((n) => n.id)), (currentIds) => {
1202
- for (let id of untrack(() => Array.from(nodeLookup.keys()))) currentIds.has(id) || nodeLookup.delete(id);
1203
- }), createRenderEffect(mapArray(() => store.edges, (edge) => {
1204
- let { source: sourceNodeId, target: targetNodeId, sourceHandle = null, targetHandle = null } = edge, connection = {
1205
- edgeId: edge.id,
1206
- source: sourceNodeId,
1207
- target: targetNodeId,
1208
- sourceHandle,
1209
- targetHandle
1210
- }, sourceKey = `${sourceNodeId}-${sourceHandle}--${targetNodeId}-${targetHandle}`, targetKey = `${targetNodeId}-${targetHandle}--${sourceNodeId}-${sourceHandle}`;
1211
- createRenderEffect(() => {
1212
- addConnectionToLookup("source", connection, targetKey, connectionLookup, sourceNodeId, sourceHandle), addConnectionToLookup("target", connection, sourceKey, connectionLookup, targetNodeId, targetHandle), edgeLookup.set(edge.id, edge);
1213
- }, () => {}), createRenderEffect(() => {
1214
- let sourceNode = nodeLookup.get(edge.source), targetNode = nodeLookup.get(edge.target);
1215
- if (!sourceNode || !targetNode) return;
1216
- if (store.onlyRenderVisibleElements) {
1217
- if (!isEdgeVisible({
1218
- sourceNode,
1219
- targetNode,
1220
- width: store.width ?? 0,
1221
- height: store.height ?? 0,
1222
- transform: store.transform
1223
- })) return;
1224
- store.visibleNodesMap.set(sourceNode.id, sourceNode), store.visibleNodesMap.set(targetNode.id, targetNode);
1225
- }
1226
- let edgePosition = getEdgePosition({
1227
- id: edge.id,
1228
- sourceNode,
1229
- targetNode,
1230
- sourceHandle: edge.sourceHandle || null,
1231
- targetHandle: edge.targetHandle || null,
1232
- connectionMode: store.connectionMode,
1233
- onError: store.onError
1234
1776
  });
1235
- edgePosition && layoutedEdgesMap.set(edge.id, {
1236
- ...store.defaultEdgeOptions,
1237
- ...edge,
1238
- ...edgePosition,
1239
- zIndex: getElevatedEdgeZIndex({
1240
- selected: edge.selected,
1241
- zIndex: edge.zIndex ?? store.defaultEdgeOptions.zIndex,
1242
- sourceNode,
1243
- targetNode,
1244
- elevateOnSelect: store.elevateEdgesOnSelect,
1245
- zIndexMode: store.zIndexMode
1246
- }),
1247
- sourceNode,
1248
- targetNode,
1249
- edge
1777
+ for (let childNode of getChildNodes(parentId)) children.some((child) => child.id === childNode.id) || changes.push({
1778
+ id: childNode.id,
1779
+ type: "position",
1780
+ position: {
1781
+ x: childNode.position.x + xChange,
1782
+ y: childNode.position.y + yChange
1783
+ }
1250
1784
  });
1251
- }, () => {}), onCleanup(() => {
1252
- edgeLookup.delete(edge.id), layoutedEdgesMap.delete(edge.id), removeConnectionFromLookup("source", targetKey, connectionLookup, sourceNodeId, sourceHandle), removeConnectionFromLookup("target", sourceKey, connectionLookup, targetNodeId, targetHandle);
1785
+ }
1786
+ (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height || xChange || yChange) && changes.push({
1787
+ id: parentId,
1788
+ type: "dimensions",
1789
+ setAttributes: !0,
1790
+ dimensions: {
1791
+ width: newWidth + (xChange ? origin[0] * xChange - widthChange : 0),
1792
+ height: newHeight + (yChange ? origin[1] * yChange - heightChange : 0)
1793
+ }
1253
1794
  });
1254
- }), () => {}), createEffect(() => new Set(store.edges.map((e) => e.id)), (currentIds) => {
1255
- for (let id of untrack(() => Array.from(edgeLookup.keys()))) currentIds.has(id) || (edgeLookup.delete(id), connectionLookup.delete(id), layoutedEdgesMap.delete(id));
1256
- }), {
1257
- store,
1258
- nodeLookup,
1259
- edgeLookup,
1260
- parentLookup,
1261
- connectionLookup,
1795
+ }), changes;
1796
+ }
1797
+ //#endregion
1798
+ //#region src/data/createSolidFlow.tsx
1799
+ const InitialNodeTypesMap = {
1800
+ input: InputNode,
1801
+ output: OutputNode,
1802
+ default: DefaultNode,
1803
+ group: GroupNode
1804
+ }, InitialEdgeTypesMap = {
1805
+ straight: StraightEdgeInternal,
1806
+ smoothstep: SmoothStepEdgeInternal,
1807
+ default: BezierEdgeInternal,
1808
+ step: StepEdgeInternal
1809
+ }, createSolidFlow = (props) => {
1810
+ let state = createFlowState(props, {
1811
+ prefersDark: createMediaQuery("(prefers-color-scheme: dark)", props.colorModeSSR === "dark"),
1812
+ initialNodeTypes: InitialNodeTypesMap,
1813
+ initialEdgeTypes: InitialEdgeTypesMap
1814
+ }), { store, nodeLookup, actions } = state, pendingEntries, requestUpdateNodeInternals = (updateEntries) => {
1815
+ if (pendingEntries) {
1816
+ pendingEntries.push(...updateEntries);
1817
+ return;
1818
+ }
1819
+ pendingEntries = updateEntries, scheduleIdleCallback(() => {
1820
+ let updates = new Map(pendingEntries);
1821
+ pendingEntries = void 0;
1822
+ let { updatedInternals, measurementWrites, changes, parentExpandChildren } = measureNodeInternals(updates, nodeLookup, store.domNode);
1823
+ updatedInternals && (actions.applyMeasurementWrites(measurementWrites), flush(), parentExpandChildren.length > 0 && changes.push(...handleExpandParent(parentExpandChildren, nodeLookup, (parentId) => store.nodes.filter((node) => node.parentId === parentId), store.nodeOrigin)), actions.applyNodeChanges(changes), flush(), actions.markInitialNodesMeasured());
1824
+ });
1825
+ };
1826
+ return actions.setMeasureRequester(requestUpdateNodeInternals), {
1827
+ ...state,
1262
1828
  actions: {
1263
- getEdge,
1264
- applyInitialFitView,
1265
- resetStoreValues,
1266
- requestUpdateNodeInternals,
1267
- setAriaLabelConfig,
1268
- setAriaLiveMessage,
1269
- setClickConnectStartHandle,
1270
- setConfig,
1271
- setConnection,
1272
- setDeleteKeyPressed,
1273
- setDomNode,
1274
- setDragging,
1275
- get setEdges() {
1276
- return setEdgesStore;
1277
- },
1278
- setElementsSelectable,
1279
- setHeight,
1280
- setMultiselectionKeyPressed,
1281
- get setNodes() {
1282
- return setNodesStore;
1283
- },
1284
- setNodesConnectable,
1285
- setNodesDraggable,
1286
- setPanActivationKeyPressed,
1287
- setPanZoom,
1288
- setSelectionKeyPressed,
1289
- setSelectionRect,
1290
- setSelectionRectMode,
1291
- get setViewport() {
1292
- return (viewport) => setViewportStore(() => viewport);
1293
- },
1294
- setWidth,
1295
- setZoomActivationKeyPressed,
1296
- addEdge,
1297
- updateNodePositions,
1298
- zoomIn,
1299
- zoomOut,
1300
- fitView,
1301
- setCenter,
1302
- setPaneClickDistance,
1303
- unselectNodesAndEdges,
1304
- addSelectedNodes,
1305
- addSelectedEdges,
1306
- handleNodeSelection: (id, unselect, nodeRef) => {
1307
- let node = store.nodes.find((n) => n.id === id);
1308
- node && (setSelectionRect(void 0), setSelectionRectMode(void 0), node.selected ? (unselect || node.selected && store.multiselectionKeyPressed) && (unselectNodesAndEdges({
1309
- nodes: [node],
1310
- edges: []
1311
- }), requestAnimationFrame(() => nodeRef?.blur())) : addSelectedNodes([id]));
1312
- },
1313
- handleEdgeSelection: (id) => {
1314
- let edge = edgeLookup.get(id);
1315
- edge && (edge.selectable || store.elementsSelectable && edge.selectable === void 0) && (setSelectionRect(void 0), setSelectionRectMode(void 0), edge.selected ? edge.selected && store.multiselectionKeyPressed && unselectNodesAndEdges({
1316
- nodes: [],
1317
- edges: [edge]
1318
- }) : addSelectedEdges([id]));
1319
- },
1320
- moveSelectedNodes: (direction, factor) => {
1321
- let nodeUpdates = /* @__PURE__ */ new Map(), xVelo = store.snapGrid?.[0] ?? 5, yVelo = store.snapGrid?.[1] ?? 5, xDiff = direction.x * xVelo * factor, yDiff = direction.y * yVelo * factor;
1322
- for (let node of nodeLookup.values()) {
1323
- if (!(node.selected && (node.draggable || store.nodesDraggable && node.draggable === void 0))) continue;
1324
- let nextPosition = {
1325
- x: node.internals.positionAbsolute.x + xDiff,
1326
- y: node.internals.positionAbsolute.y + yDiff
1327
- };
1328
- store.snapGrid && (nextPosition = snapPosition(nextPosition, store.snapGrid));
1329
- let { position, positionAbsolute } = calculateNodePosition({
1330
- nodeId: node.id,
1331
- nextPosition,
1332
- nodeLookup,
1333
- nodeExtent: store.nodeExtent,
1334
- nodeOrigin: store.nodeOrigin,
1335
- onError: store.onError
1336
- });
1337
- node.position = position, node.internals.positionAbsolute = positionAbsolute, nodeUpdates.set(node.id, node);
1338
- }
1339
- updateNodePositions(nodeUpdates);
1340
- },
1341
- panBy: (delta) => panBy({
1342
- delta,
1343
- panZoom: store.panZoom,
1344
- transform: store.transform,
1345
- translateExtent: store.translateExtent,
1346
- width: store.width,
1347
- height: store.height
1348
- }),
1349
- cancelConnection: () => {
1350
- setConnection({ ...initialConnection });
1351
- },
1352
- reset: () => {
1353
- resetStoreValues(), unselectNodesAndEdges();
1354
- }
1829
+ ...actions,
1830
+ requestUpdateNodeInternals
1355
1831
  }
1356
1832
  };
1357
1833
  }, SolidFlowContext = createContext(null);
@@ -1421,7 +1897,7 @@ const Marker = (props) => {
1421
1897
  for (let item of a) if (!b.has(item)) return !1;
1422
1898
  return !0;
1423
1899
  }, Pane = (props) => {
1424
- let { store, nodeLookup, edgeLookup, connectionLookup, actions } = useInternalSolidFlow(), [containerRef, setContainerRef] = createSignal(), container, containerBounds = null, connectionEndedOnPane = !1, selectionInProgress = !1, selectedNodeIds = /* @__PURE__ */ new Set(), selectedEdgeIds = /* @__PURE__ */ new Set(), autoPanId = 0, position = {
1900
+ let { store, nodeLookup, edgeLookup, connections, actions } = useInternalSolidFlow(), [containerRef, setContainerRef] = createSignal(), container, containerBounds = null, connectionEndedOnPane = !1, selectionInProgress = !1, selectedNodeIds = /* @__PURE__ */ new Set(), selectedEdgeIds = /* @__PURE__ */ new Set(), autoPanId = 0, position = {
1425
1901
  x: 0,
1426
1902
  y: 0
1427
1903
  }, autoPanStarted = !1, autoPanOnSelection = () => props.autoPanOnSelection ?? !0, paneClickDistance = () => props.paneClickDistance ?? 1, _panOnDrag = () => store.panActivationKeyPressed || props.panOnDrag, isSelecting = () => store.selectionKeyPressed || !!store.selectionRect || props.selectionOnDrag && _panOnDrag() !== !0, isSelectionEnabled = () => store.elementsSelectable && (isSelecting() || store.selectionRectMode === "user"), onClick = (event) => {
@@ -1467,9 +1943,9 @@ const Marker = (props) => {
1467
1943
  let edgesSelectable = store.defaultEdgeOptions.selectable ?? !0;
1468
1944
  selectedEdgeIds = /* @__PURE__ */ new Set();
1469
1945
  for (let nodeId of selectedNodeIds) {
1470
- let connections = connectionLookup.get(nodeId);
1471
- if (connections) for (let { edgeId } of connections.values()) {
1472
- let edge = edgeLookup.get(edgeId);
1946
+ let nodeConnections = connections[nodeId];
1947
+ if (nodeConnections) for (let { edgeId } of Object.values(nodeConnections)) {
1948
+ let edge = edgeLookup[edgeId];
1473
1949
  edge && (edge.selectable ?? edgesSelectable) && selectedEdgeIds.add(edgeId);
1474
1950
  }
1475
1951
  }
@@ -1800,7 +2276,7 @@ const Marker = (props) => {
1800
2276
  {_props.afterControls}
1801
2277
  </Panel>;
1802
2278
  }, EdgeToolbar = (props) => {
1803
- let rest = omit(props, "x", "y", "alignX", "alignY", "isVisible", "selectEdgeOnClick", "class", "children"), { store, edgeLookup } = useInternalSolidFlow(), edgeId = useEdgeId(), isActive = () => typeof props.isVisible == "boolean" ? props.isVisible : !!edgeLookup.get(edgeId())?.selected, transform = () => getEdgeToolbarTransform(props.x, props.y, store.viewport.zoom, props.alignX ?? "center", props.alignY ?? "center");
2279
+ let rest = omit(props, "x", "y", "alignX", "alignY", "isVisible", "selectEdgeOnClick", "class", "children"), { store, edgeLookup } = useInternalSolidFlow(), edgeId = useEdgeId(), isActive = () => typeof props.isVisible == "boolean" ? props.isVisible : !!edgeLookup[edgeId()]?.selected, transform = () => getEdgeToolbarTransform(props.x, props.y, store.viewport.zoom, props.alignX ?? "center", props.alignY ?? "center");
1804
2280
  return <Show when={isActive()}>
1805
2281
  <EdgeLabel selectEdgeOnClick={props.selectEdgeOnClick} transparent>
1806
2282
  <div class={["solid-flow__edge-toolbar", props.class]} style={{
@@ -2017,8 +2493,8 @@ const Marker = (props) => {
2017
2493
  * @returns an accessor for the current color mode class
2018
2494
  */
2019
2495
  function useColorMode() {
2020
- let { store } = useInternalSolidFlow();
2021
- return () => store.colorMode;
2496
+ let { flow } = useInternalSolidFlow();
2497
+ return () => flow.colorMode;
2022
2498
  }
2023
2499
  //#endregion
2024
2500
  //#region src/hooks/useConnection.tsx
@@ -2029,8 +2505,8 @@ function useColorMode() {
2029
2505
  * @returns current connection as a readable store
2030
2506
  */
2031
2507
  function useConnection() {
2032
- let { store } = useInternalSolidFlow();
2033
- return () => store.connection;
2508
+ let { flow } = useInternalSolidFlow();
2509
+ return () => flow.connection;
2034
2510
  }
2035
2511
  //#endregion
2036
2512
  //#region src/hooks/useGraph.ts
@@ -2061,15 +2537,15 @@ function useEdges() {
2061
2537
  * @returns store with the viewport object
2062
2538
  */
2063
2539
  function useViewport() {
2064
- let { store } = useInternalSolidFlow();
2065
- return () => store.viewport;
2540
+ let { flow } = useInternalSolidFlow();
2541
+ return () => flow.viewport;
2066
2542
  }
2067
2543
  //#endregion
2068
2544
  //#region src/hooks/useHandleEdgeSelect.tsx
2069
2545
  function useHandleEdgeSelect() {
2070
2546
  let { store, edgeLookup, actions } = useInternalSolidFlow();
2071
2547
  return (id) => {
2072
- let edge = edgeLookup.get(id);
2548
+ let edge = edgeLookup[id];
2073
2549
  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({
2074
2550
  nodes: [],
2075
2551
  edges: [edge]
@@ -2088,8 +2564,8 @@ function useHandleEdgeSelect() {
2088
2564
  * @returns an accessor that indicates whether the nodes are initialized
2089
2565
  */
2090
2566
  function useNodesInitialized() {
2091
- let { store } = useInternalSolidFlow();
2092
- return () => store.nodesInitialized;
2567
+ let { flow } = useInternalSolidFlow();
2568
+ return () => flow.nodesInitialized;
2093
2569
  }
2094
2570
  /**
2095
2571
  * Hook for seeing if the viewport is initialized.
@@ -2100,8 +2576,8 @@ function useNodesInitialized() {
2100
2576
  * @returns an accessor that indicates whether the viewport is initialized
2101
2577
  */
2102
2578
  function useViewportInitialized() {
2103
- let { store } = useInternalSolidFlow();
2104
- return () => store.viewportInitialized;
2579
+ let { flow } = useInternalSolidFlow();
2580
+ return () => flow.viewportInitialized;
2105
2581
  }
2106
2582
  //#endregion
2107
2583
  //#region src/hooks/useInternalNode.tsx
@@ -2113,8 +2589,8 @@ function useViewportInitialized() {
2113
2589
  * @returns an accessor with an internal node or undefined
2114
2590
  */
2115
2591
  function useInternalNode(id) {
2116
- let { nodeLookup } = useInternalSolidFlow();
2117
- return () => nodeLookup.get(id());
2592
+ let { flow } = useInternalSolidFlow();
2593
+ return () => flow.internalNodes[id()];
2118
2594
  }
2119
2595
  //#endregion
2120
2596
  //#region src/hooks/useNodeConnections.ts
@@ -2130,24 +2606,28 @@ function useInternalNode(id) {
2130
2606
  * @returns an array with connections
2131
2607
  */
2132
2608
  const useNodeConnections = (params) => {
2133
- let { connectionLookup } = useInternalSolidFlow(), ctxNodeId = () => {
2609
+ let { flow } = useInternalSolidFlow(), ctxNodeId = () => {
2134
2610
  let id = useContext(NodeIdContext);
2135
2611
  return id ? id() : "";
2136
- }, id = () => params().handleId, type = () => params().handleType, nodeId = () => params().id ?? ctxNodeId(), [connections, setConnections] = createSignal([]), lookupKey = () => `${nodeId()}${type() ? id() ? `-${type()}-${id()}` : `-${type()}` : ""}`, prevConnections;
2137
- return createEffect(() => ({ connections: connectionLookup.get(lookupKey()) }), ({ connections: nextConnections }) => {
2138
- areConnectionMapsEqual(nextConnections, prevConnections) || (prevConnections = nextConnections, setConnections(Array.from(nextConnections?.values() ?? [])));
2612
+ }, id = () => params().handleId, type = () => params().handleType, nodeId = () => params().id ?? ctxNodeId(), [connections, setConnections] = createSignal([]), prevConnections;
2613
+ return createEffect(() => {
2614
+ let rec = flow.connections[connectionKey(nodeId(), type(), id())], map = /* @__PURE__ */ new Map();
2615
+ for (let key of Object.keys(rec ?? {})) map.set(key, { ...rec[key] });
2616
+ return { connections: map };
2617
+ }, ({ connections: nextConnections }) => {
2618
+ areConnectionMapsEqual(nextConnections, prevConnections) || (prevConnections = nextConnections, setConnections(Array.from(nextConnections.values())));
2139
2619
  }), connections;
2140
2620
  };
2141
2621
  //#endregion
2142
2622
  //#region src/hooks/useNodesData.ts
2143
2623
  function useNodesData(nodeIds) {
2144
- let { nodeLookup } = useInternalSolidFlow(), prevNodesData = [];
2624
+ let { flow } = useInternalSolidFlow(), prevNodesData = [];
2145
2625
  return createMemo(() => {
2146
2626
  let nodesData = [], idValues = nodeIds();
2147
2627
  if (!idValues) return;
2148
2628
  let ids = Array.isArray(idValues) ? idValues : [idValues];
2149
2629
  for (let nodeId of ids) {
2150
- let node = nodeLookup.get(nodeId)?.internals.userNode;
2630
+ let node = flow.internalNodes[nodeId]?.internals.userNode;
2151
2631
  node && nodesData.push({
2152
2632
  id: node.id,
2153
2633
  type: node.type,
@@ -2160,206 +2640,45 @@ function useNodesData(nodeIds) {
2160
2640
  //#endregion
2161
2641
  //#region src/hooks/useSolidFlow.tsx
2162
2642
  /**
2163
- * Hook for accessing the SvelteFlow instance.
2643
+ * Hook for accessing the flow instance: `{ flow, commands }` plus the
2644
+ * commands spread at the top level for upstream familiarity.
2645
+ *
2646
+ * `flow` and `commands` are stable identities, so destructuring them is safe:
2647
+ * `const { flow, commands } = useSolidFlow()`.
2164
2648
  *
2165
2649
  * @public
2166
- * @returns A set of helper functions
2650
+ * @returns the flow's read struct, write surface, and deprecated aliases
2167
2651
  */
2168
2652
  function useSolidFlow() {
2169
- let { store, actions, nodeLookup, edgeLookup, connectionLookup } = useInternalSolidFlow(), getNodeRect = (node) => {
2170
- let nodeToUse = isNode(node) ? node : nodeLookup.get(node.id), position = nodeToUse.parentId ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.measured, nodeToUse.parentId, nodeLookup, store.nodeOrigin) : nodeToUse.position, nodeWithPosition = {
2171
- ...nodeToUse,
2172
- position,
2173
- width: nodeToUse.measured?.width ?? nodeToUse.width,
2174
- height: nodeToUse.measured?.height ?? nodeToUse.height
2175
- };
2176
- return nodeToRect(nodeWithPosition);
2177
- }, updateNode = (id, nodeUpdate, options = { replace: !1 }) => {
2178
- actions.setNodes((nodes) => {
2179
- let index = nodes.findIndex((node) => node.id === id);
2180
- if (index === -1) return;
2181
- let node = nodes[index], nextNode = typeof nodeUpdate == "function" ? nodeUpdate(node) : nodeUpdate;
2182
- nodes[index] = options?.replace && isNode(nextNode) ? nextNode : {
2183
- ...node,
2184
- ...nextNode
2185
- };
2186
- });
2187
- }, updateEdge = (id, edgeUpdate, options = { replace: !1 }) => {
2188
- actions.setEdges((edges) => {
2189
- let index = edges.findIndex((edge) => edge.id === id);
2190
- if (index === -1) return;
2191
- let edge = edges[index], nextEdge = typeof edgeUpdate == "function" ? edgeUpdate(edge) : edgeUpdate;
2192
- edges[index] = options.replace && isEdge(nextEdge) ? nextEdge : {
2193
- ...edge,
2194
- ...nextEdge
2195
- };
2196
- });
2197
- }, getInternalNode = (id) => nodeLookup.get(id);
2653
+ let { flow, commands, store, edgeLookup } = useInternalSolidFlow(), getInternalNode = (id) => flow.internalNodes[id];
2198
2654
  return {
2199
- zoomIn: actions.zoomIn,
2200
- zoomOut: actions.zoomOut,
2655
+ ...commands,
2656
+ flow,
2657
+ commands,
2201
2658
  getInternalNode,
2202
- setCenter: actions.setCenter,
2203
- fitView: actions.fitView,
2204
2659
  getNode: (id) => getInternalNode(id)?.internals.userNode,
2205
- getNodes: (ids) => ids ? getElements(nodeLookup, ids) : store.nodes,
2206
- getEdge: (id) => edgeLookup.get(id),
2207
- getEdges: (ids) => ids ? getElements(edgeLookup, ids) : store.edges,
2208
- addNodes: (payload) => {
2209
- let newNodes = Array.isArray(payload) ? payload : [payload];
2210
- actions.setNodes((nodes) => [...nodes, ...newNodes]);
2211
- },
2212
- addEdges: (payload) => {
2213
- let newEdges = Array.isArray(payload) ? payload : [payload];
2214
- actions.setEdges((edges) => [...edges, ...newEdges]);
2215
- },
2660
+ getNodes: (ids) => ids ? ids.flatMap((id) => {
2661
+ let userNode = flow.internalNodes[id]?.internals.userNode;
2662
+ return userNode ? [userNode] : [];
2663
+ }) : [...flow.nodes],
2664
+ getEdge: (id) => edgeLookup[id],
2665
+ getEdges: (ids) => ids ? ids.flatMap((id) => edgeLookup[id] ? [edgeLookup[id]] : []) : [...flow.edges],
2216
2666
  getViewport: () => snapshot(store.viewport),
2217
- setViewport: async (nextViewport, options) => {
2218
- let currentViewport = store.viewport;
2219
- return store.panZoom ? (await store.panZoom.setViewport({
2220
- x: nextViewport.x ?? currentViewport.x,
2221
- y: nextViewport.y ?? currentViewport.y,
2222
- zoom: nextViewport.zoom ?? currentViewport.zoom
2223
- }, options), Promise.resolve(!0)) : Promise.resolve(!1);
2224
- },
2225
- getZoom: () => store.viewport.zoom,
2226
- setZoom: (zoomLevel, options) => {
2227
- let panZoom = store.panZoom;
2228
- return panZoom ? panZoom.scaleTo(zoomLevel, { duration: options?.duration }) : Promise.resolve(!1);
2229
- },
2230
- fitBounds: async (bounds, options) => {
2231
- if (!store.panZoom) return Promise.resolve(!1);
2232
- let viewport = getViewportForBounds$1(bounds, store.width, store.height, store.minZoom, store.maxZoom, options?.padding ?? .1);
2233
- return await store.panZoom.setViewport(viewport, {
2234
- duration: options?.duration,
2235
- ease: options?.ease,
2236
- interpolate: options?.interpolate
2237
- }), Promise.resolve(!0);
2238
- },
2239
- getIntersectingNodes: (nodeOrRect, partially = !0, nodesToIntersect) => {
2240
- let isRect = isRectObject(nodeOrRect), nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
2241
- return nodeRect ? (nodesToIntersect || store.nodes).filter((n) => {
2242
- let internalNode = nodeLookup.get(n.id);
2243
- if (!internalNode || !isRect && n.id === nodeOrRect.id) return !1;
2244
- let currNodeRect = nodeToRect(internalNode), overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
2245
- return partially && overlappingArea > 0 || overlappingArea >= nodeRect.width * nodeRect.height;
2246
- }) : [];
2247
- },
2248
- isNodeIntersecting: (nodeOrRect, area, partially = !0) => {
2249
- let nodeRect = isRectObject(nodeOrRect) ? nodeOrRect : getNodeRect(nodeOrRect);
2250
- if (!nodeRect) return !1;
2251
- let overlappingArea = getOverlappingArea(nodeRect, area);
2252
- return partially && overlappingArea > 0 || overlappingArea >= nodeRect.width * nodeRect.height;
2253
- },
2254
- deleteElements: async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => {
2255
- let { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
2256
- nodesToRemove,
2257
- edgesToRemove,
2258
- nodes: store.nodes,
2259
- edges: store.edges,
2260
- onBeforeDelete: store.onBeforeDelete
2261
- });
2262
- if (matchingEdges) {
2263
- let remmainingEdges = store.edges.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id));
2264
- store.onEdgesDelete?.(matchingEdges), actions.setEdges(() => remmainingEdges);
2265
- }
2266
- if (matchingNodes) {
2267
- let remmainingNodes = store.nodes.filter((node) => !matchingNodes.some(({ id }) => id === node.id));
2268
- store.onNodesDelete?.(matchingNodes), actions.setNodes(() => remmainingNodes);
2269
- }
2270
- return {
2271
- deletedNodes: matchingNodes,
2272
- deletedEdges: matchingEdges
2273
- };
2274
- },
2275
- screenToFlowPosition: (position, options = { snapToGrid: !0 }) => {
2276
- if (!store.domNode) return position;
2277
- let _snapGrid = options.snapToGrid ? store.snapGrid : !1, { x, y, zoom } = store.viewport, { x: domX, y: domY } = store.domNode.getBoundingClientRect(), correctedPosition = {
2278
- x: position.x - domX,
2279
- y: position.y - domY
2280
- };
2281
- return pointToRendererPoint(correctedPosition, [
2282
- x,
2283
- y,
2284
- zoom
2285
- ], _snapGrid !== null, _snapGrid || [1, 1]);
2286
- },
2287
- /**
2288
- *
2289
- * @param position
2290
- * @returns
2291
- */
2292
- flowToScreenPosition: (position) => {
2293
- if (!store.domNode) return position;
2294
- let { x, y, zoom } = store.viewport, { x: domX, y: domY } = store.domNode.getBoundingClientRect(), rendererPosition = rendererPointToPoint(position, [
2295
- x,
2296
- y,
2297
- zoom
2298
- ]);
2299
- return {
2300
- x: rendererPosition.x + domX,
2301
- y: rendererPosition.y + domY
2302
- };
2303
- },
2304
- toObject: () => structuredClone({
2305
- nodes: [...snapshot(store.nodes)],
2306
- edges: [...snapshot(store.edges)],
2307
- viewport: { ...snapshot(store.viewport) }
2308
- }),
2309
- updateNode,
2310
- updateNodeData: (id, dataUpdate, options) => {
2311
- let node = nodeLookup.get(id)?.internals.userNode;
2312
- if (!node) return;
2313
- let nextData = typeof dataUpdate == "function" ? dataUpdate(node) : dataUpdate;
2314
- updateNode(id, (node) => ({
2315
- ...node,
2316
- data: options?.replace ? nextData : {
2317
- ...node.data,
2318
- ...nextData
2319
- }
2320
- }));
2321
- },
2322
- updateEdge,
2323
- getNodesBounds: (nodes) => getNodesBounds$1(nodes, {
2324
- nodeLookup,
2325
- nodeOrigin: store.nodeOrigin
2326
- }),
2327
- getHandleConnections: ({ type, id, nodeId }) => Array.from(connectionLookup.get(`${nodeId}-${type}-${id ?? null}`)?.values() ?? [])
2667
+ getZoom: () => flow.viewport.zoom,
2668
+ getHandleConnections: ({ type, id, nodeId }) => Object.values(flow.connections[connectionKey(nodeId, type, id ?? null)] ?? {})
2328
2669
  };
2329
2670
  }
2330
- function getElements(lookup, ids) {
2331
- let result = [];
2332
- for (let id of ids) {
2333
- let item = lookup.get(id);
2334
- if (item) {
2335
- let element = "internals" in item ? item.internals?.userNode : item;
2336
- result.push(element);
2337
- }
2338
- }
2339
- return result;
2340
- }
2341
2671
  //#endregion
2342
2672
  //#region src/hooks/useUpdateNodeInternals.ts
2343
2673
  /**
2344
- * Hook for updating node internals.
2674
+ * Hook for updating node internals. Sugar for `commands.updateNodeInternals`.
2345
2675
  *
2346
2676
  * @public
2347
2677
  * @returns function for updating node internals
2348
2678
  */
2349
2679
  function useUpdateNodeInternals() {
2350
- let { store, actions } = useInternalSolidFlow();
2351
- return (id) => {
2352
- let updateIds = Array.isArray(id) ? id : [id], updates = [];
2353
- for (let updateId of updateIds) {
2354
- let nodeElement = store.domNode?.querySelector(`.solid-flow__node[data-id="${updateId}"]`);
2355
- nodeElement && updates.push([updateId, {
2356
- id: updateId,
2357
- nodeElement,
2358
- force: !0
2359
- }]);
2360
- }
2361
- actions.requestUpdateNodeInternals(updates);
2362
- };
2680
+ let { commands } = useInternalSolidFlow();
2681
+ return commands.updateNodeInternals;
2363
2682
  }
2364
2683
  //#endregion
2365
2684
  //#region src/components/graph/plugins/NodeToolbar.tsx
@@ -2646,4 +2965,4 @@ const KeyHandler = (props) => {
2646
2965
  return [store, setStore];
2647
2966
  };
2648
2967
  //#endregion
2649
- 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, createEdgeStore, createNodeStore, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, useColorMode, useConnection, useEdges, useHandleEdgeSelect, useInternalNode, useNodeConnections, useNodes, useNodesData, useNodesInitialized, useSolidFlow, useUpdateNodeInternals, useViewport, useViewportInitialized };
2968
+ 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, useEdges, useHandleEdgeSelect, useInternalNode, useNodeConnections, useNodes, useNodesData, useNodesInitialized, useSolidFlow, useUpdateNodeInternals, useViewport, useViewportInitialized };