@dschz/solid-flow 1.0.0-next.7 → 1.0.0-next.8

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,4 +1,4 @@
1
- import { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, Position as Position$1, ResizeControlVariant, 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 as getBezierEdgeCenter$1, getBezierPath, getBezierPath as getBezierPath$1, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter as getEdgeCenter$1, 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, isInputDOMNode, isMacOs, isNodeBase, isNumeric, isRectObject, mergeAriaLabelConfig, nodeHasDimensions, nodeToRect, panBy, pointToRendererPoint, rendererPointToPoint, shallowNodeData, snapPosition } from "@xyflow/system";
1
+ import { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, Position as Position$1, ResizeControlVariant, 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, errorMessages, evaluateAbsolutePosition, fitViewport, getBezierEdgeCenter as getBezierEdgeCenter$1, getBezierPath, getBezierPath as getBezierPath$1, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter as getEdgeCenter$1, 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, isInputDOMNode, isMacOs, isNodeBase, isNumeric, isRectObject, mergeAriaLabelConfig, nodeHasDimensions, nodeToRect, panBy, pointToRendererPoint, rendererPointToPoint, shallowNodeData, snapPosition } from "@xyflow/system";
2
2
  import { For, Show, createContext, createEffect, createMemo, createProjection, createSignal, createStore, flush, mapArray, merge, omit, onCleanup, onSettled, snapshot, untrack, useContext } from "solid-js";
3
3
  import { createMediaQuery } from "@solid-primitives/media";
4
4
  import { Dynamic, Portal, isServer } from "@solidjs/web";
@@ -65,7 +65,9 @@ function propDefaults(props, defaults) {
65
65
  const getEdgeId = (connection) => {
66
66
  let { source, sourceHandle, target, targetHandle } = connection;
67
67
  return `xy-edge__${source}${sourceHandle || ""}-${target}${targetHandle || ""}`;
68
- }, isEdgeSelectable = (edge, store) => edge.selectable ?? store.defaultEdgeOptions.selectable ?? store.elementsSelectable, EdgeLabelRenderer = (props) => {
68
+ }, isEdgeSelectable = (edge, store) => edge.selectable ?? store.defaultEdgeOptions.selectable ?? store.elementsSelectable, emitFlowError = (onError, id, message) => {
69
+ onError && onError(id, message);
70
+ }, EdgeLabelRenderer = (props) => {
69
71
  let { store } = useInternalSolidFlow(), labelNode = () => store.domNode?.querySelector(".solid-flow__edge-labels");
70
72
  return <Show when={labelNode()}>{(root) => <Portal mount={root()}>{props.children}</Portal>}</Show>;
71
73
  }, EdgeLabel = (props) => {
@@ -75,7 +77,7 @@ const getEdgeId = (connection) => {
75
77
  selectEdgeOnClick: !1,
76
78
  transparent: !1,
77
79
  style: {}
78
- }), rest = omit(_props, "x", "y", "width", "height", "selectEdgeOnClick", "transparent", "children", "class", "style"), { actions } = useInternalSolidFlow(), id = useEdgeId(), zIndex = () => actions.getEdge(id())?.zIndex;
80
+ }), rest = omit(_props, "x", "y", "width", "height", "selectEdgeOnClick", "transparent", "children", "class", "style"), { actions } = useInternalSolidFlow(), id = useEdgeId(), zIndex = () => actions.getLayoutedEdge(id())?.zIndex;
79
81
  return <EdgeLabelRenderer>
80
82
  <div role="button" tabindex={-1} class={[
81
83
  "solid-flow__edge-label",
@@ -320,6 +322,30 @@ const armConnectionGestureLookup = (options) => {
320
322
  doc.removeEventListener("mousemove", update, !0), doc.removeEventListener("touchmove", update, !0), doc.removeEventListener("mouseup", dispose, !0), doc.removeEventListener("touchend", dispose, !0), lookup.disarm();
321
323
  };
322
324
  return doc.addEventListener("mousemove", update, !0), doc.addEventListener("touchmove", update, !0), doc.addEventListener("mouseup", dispose, !0), doc.addEventListener("touchend", dispose, !0), lookup;
325
+ }, buildConnectionGestureParams = (options) => {
326
+ let { event, store, actions, gestureLookup } = options;
327
+ return {
328
+ lib: store.lib,
329
+ flowId: store.id,
330
+ domNode: store.domNode,
331
+ autoPanOnConnect: store.autoPanOnConnect,
332
+ autoPanSpeed: store.autoPanSpeed,
333
+ connectionMode: store.connectionMode,
334
+ connectionRadius: store.connectionRadius,
335
+ nodeLookup: gestureLookup,
336
+ cancelConnection: actions.cancelConnection,
337
+ panBy: actions.panBy,
338
+ updateConnection: ((connection) => {
339
+ actions.setConnection(connection), flush();
340
+ }),
341
+ isValidConnection: store.isValidConnection,
342
+ onConnectStart: store.onConnectStart,
343
+ onConnectEnd: store.onConnectEnd,
344
+ getTransform: () => store.transform,
345
+ getFromHandle: () => store.connection.fromHandle,
346
+ dragThreshold: store.connectionDragThreshold,
347
+ handleDomNode: event.currentTarget
348
+ };
323
349
  }, EdgeReconnectAnchor = (props) => {
324
350
  let _props = propDefaults(props, {
325
351
  size: 25,
@@ -348,25 +374,16 @@ const armConnectionGestureLookup = (options) => {
348
374
  connectionRadius: store.connectionRadius
349
375
  });
350
376
  XYHandle.onPointerDown(event, {
351
- lib: store.lib,
352
- flowId: store.id,
353
- domNode: store.domNode,
377
+ ...buildConnectionGestureParams({
378
+ event,
379
+ store,
380
+ actions,
381
+ gestureLookup
382
+ }),
354
383
  nodeId: opposite.nodeId,
355
384
  handleId: opposite.handleId,
356
- autoPanOnConnect: store.autoPanOnConnect,
357
- connectionMode: store.connectionMode,
358
- connectionRadius: store.connectionRadius,
359
- nodeLookup: gestureLookup,
360
385
  isTarget: opposite.type === "target",
361
386
  edgeUpdaterType: opposite.type,
362
- cancelConnection: actions.cancelConnection,
363
- panBy: actions.panBy,
364
- updateConnection: (connection) => {
365
- actions.setConnection(connection), flush();
366
- },
367
- isValidConnection: store.isValidConnection,
368
- onConnectStart: store.onConnectStart,
369
- onConnectEnd: store.onConnectEnd,
370
387
  onConnect: (connection) => {
371
388
  let newEdge = {
372
389
  ...edge(),
@@ -376,12 +393,7 @@ const armConnectionGestureLookup = (options) => {
376
393
  },
377
394
  onReconnectEnd: (event, connectionState) => {
378
395
  setReconnectingState(!1), store.onReconnectEnd?.(event, edge(), opposite.type, connectionState);
379
- },
380
- getTransform: () => store.transform,
381
- getFromHandle: () => store.connection.fromHandle,
382
- autoPanSpeed: store.autoPanSpeed,
383
- dragThreshold: store.connectionDragThreshold,
384
- handleDomNode: event.currentTarget
396
+ }
385
397
  });
386
398
  };
387
399
  return <EdgeLabel x={_props.position?.x} y={_props.position?.y} style={_props.style} {...rest}>
@@ -450,14 +462,22 @@ const armConnectionGestureLookup = (options) => {
450
462
  addSelectedEdges,
451
463
  handleNodeSelection: (id, unselect, nodeRef) => {
452
464
  let node = store.nodes.find((n) => n.id === id);
453
- node && (setSelectionRect(void 0), setSelectionRectMode(void 0), node.selected ? (unselect || node.selected && store.multiselectionKeyPressed) && (unselectNodesAndEdges({
465
+ if (!node) {
466
+ emitFlowError(store.onError, "012", errorMessages.error012(id));
467
+ return;
468
+ }
469
+ setSelectionRect(void 0), setSelectionRectMode(void 0), node.selected ? (unselect || node.selected && store.multiselectionKeyPressed) && (unselectNodesAndEdges({
454
470
  nodes: [node],
455
471
  edges: []
456
- }), requestAnimationFrame(() => nodeRef?.blur())) : addSelectedNodes([id]));
472
+ }), requestAnimationFrame(() => nodeRef?.blur())) : addSelectedNodes([id]);
457
473
  },
458
474
  handleEdgeSelection: (id) => {
459
475
  let edge = edgeLookup[id];
460
- edge && isEdgeSelectable(edge, store) && (setSelectionRect(void 0), setSelectionRectMode(void 0), edge.selected ? edge.selected && store.multiselectionKeyPressed && unselectNodesAndEdges({
476
+ if (!edge) {
477
+ emitFlowError(store.onError, "012", errorMessages.error012(id));
478
+ return;
479
+ }
480
+ isEdgeSelectable(edge, store) && (setSelectionRect(void 0), setSelectionRectMode(void 0), edge.selected ? edge.selected && store.multiselectionKeyPressed && unselectNodesAndEdges({
461
481
  nodes: [],
462
482
  edges: [edge]
463
483
  }) : addSelectedEdges([id]));
@@ -674,7 +694,20 @@ const createMeasurementIngest = ({ setMeasurementsStore, setNodesStore, nodes })
674
694
  let out = {};
675
695
  for (let edge of source.edges) out[edge.id] = edge;
676
696
  return out;
677
- }, {}, { key: "id" });
697
+ }, {}, { key: "id" }), createRowRecordProjection = (rowStores) => {
698
+ let assigned = /* @__PURE__ */ new Map();
699
+ return createProjection((draft) => {
700
+ let seen = /* @__PURE__ */ new Set();
701
+ for (let { id, store } of rowStores()) {
702
+ let row = store.row;
703
+ row && (seen.add(id), assigned.get(id) !== row && (assigned.set(id, row), draft[id] = row));
704
+ }
705
+ for (let id of assigned.keys()) seen.has(id) || (assigned.delete(id), delete draft[id]);
706
+ }, {}, {
707
+ key: null,
708
+ shallow: !0
709
+ });
710
+ };
678
711
  function isManualZIndexMode(zIndexMode) {
679
712
  return zIndexMode === "manual";
680
713
  }
@@ -718,7 +751,7 @@ const EMPTY_AUTO_INDEX = /* @__PURE__ */ new Map(), createInternalNodes = (sourc
718
751
  x,
719
752
  y
720
753
  }, row.internals.z = z;
721
- } else source.nodes.length;
754
+ } else source.nodes.length, emitFlowError(source.onError, "parent-missing", `Parent node ${userNode.parentId} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);
722
755
  }
723
756
  return { row };
724
757
  }, {}, { key: "id" }), entry = {
@@ -731,19 +764,8 @@ const EMPTY_AUTO_INDEX = /* @__PURE__ */ new Map(), createInternalNodes = (sourc
731
764
  id,
732
765
  store
733
766
  };
734
- }, { keyed: (userNode) => userNode.id }), assigned = /* @__PURE__ */ new Map();
735
- return createProjection((draft) => {
736
- let seen = /* @__PURE__ */ new Set();
737
- for (let { id, store } of rowStores()) {
738
- seen.add(id);
739
- let row = store.row;
740
- assigned.get(id) !== row && (assigned.set(id, row), draft[id] = row);
741
- }
742
- for (let id of assigned.keys()) seen.has(id) || (assigned.delete(id), delete draft[id]);
743
- }, {}, {
744
- key: null,
745
- shallow: !0
746
- });
767
+ }, { keyed: (userNode) => userNode.id });
768
+ return createRowRecordProjection(rowStores);
747
769
  };
748
770
  function calculateChildXYZ(childNode, parentNode, nodeOrigin, nodeExtent, selectedNodeZ, zIndexMode) {
749
771
  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({
@@ -791,18 +813,8 @@ const createLayoutedEdges = (source) => {
791
813
  let edge = edgeAccessor();
792
814
  return { row: buildRow(source, edge) };
793
815
  }, { row: null }, { key: "id" })
794
- }), { keyed: (edge) => edge.id }), assigned = /* @__PURE__ */ new Map();
795
- return createProjection((draft) => {
796
- let seen = /* @__PURE__ */ new Set();
797
- for (let { id, store } of rowStores()) {
798
- let row = store.row;
799
- row && (seen.add(id), assigned.get(id) !== row && (assigned.set(id, row), draft[id] = row));
800
- }
801
- for (let id of assigned.keys()) seen.has(id) || (assigned.delete(id), delete draft[id]);
802
- }, {}, {
803
- key: null,
804
- shallow: !0
805
- });
816
+ }), { keyed: (edge) => edge.id });
817
+ return createRowRecordProjection(rowStores);
806
818
  }, buildRow = (source, edge) => {
807
819
  let sourceNode = source.nodeLookup.get(edge.source), targetNode = source.nodeLookup.get(edge.target);
808
820
  if (!sourceNode || !targetNode) return null;
@@ -890,6 +902,9 @@ const createLayoutedEdges = (source) => {
890
902
  },
891
903
  get zIndexMode() {
892
904
  return config().zIndexMode;
905
+ },
906
+ get onError() {
907
+ return config().onFlowError;
893
908
  }
894
909
  }), nodeLookup = new RecordMapFacade(internalNodes), initialViewport = getInitialViewport(_props.fitView, _props.initialViewport, _props.width ?? 0, _props.height ?? 0, nodeLookup), [viewportStore, setViewportStore] = createStore(_props.viewport ?? initialViewport);
895
910
  createEffect(() => config().viewport, (next) => {
@@ -917,6 +932,10 @@ const createLayoutedEdges = (source) => {
917
932
  let state = connection(), toHandle = state.inProgress ? state.toHandle : null, key = toHandle ? connectionKey(toHandle.nodeId, toHandle.type, toHandle.id ?? null) : null;
918
933
  for (let existing of Object.keys(draft)) existing !== key && delete draft[existing];
919
934
  key && (draft[key] = state.isValid ? "valid" : "invalid");
935
+ }, {}, { key: null }), connectionOriginByHandle = createProjection((draft) => {
936
+ let fromHandle = connection().fromHandle ?? clickConnectStartHandle(), fromKey = fromHandle ? connectionKey(fromHandle.nodeId, fromHandle.type, fromHandle.id ?? null) : null, siblingKey = fromHandle ? connectionKey(fromHandle.nodeId, fromHandle.type === "source" ? "target" : "source", fromHandle.id ?? null) : null;
937
+ for (let existing of Object.keys(draft)) existing !== fromKey && existing !== siblingKey && delete draft[existing];
938
+ fromKey && (draft[fromKey] = "from"), siblingKey && (draft[siblingKey] = "excluded");
920
939
  }, {}, { key: null }), mergedNodeTypes = createMemo(() => ({
921
940
  ...initialNodeTypes,
922
941
  ...config().nodeTypes
@@ -948,6 +967,9 @@ const createLayoutedEdges = (source) => {
948
967
  get connectionTargetByHandle() {
949
968
  return connectionTargetByHandle;
950
969
  },
970
+ get connectionOriginByHandle() {
971
+ return connectionOriginByHandle;
972
+ },
951
973
  get domNode() {
952
974
  return domNode();
953
975
  },
@@ -1086,7 +1108,7 @@ const createLayoutedEdges = (source) => {
1086
1108
  return store.onError;
1087
1109
  },
1088
1110
  nodeLookup
1089
- }), visibleEdgeIds = createMemo(() => Object.keys(layoutedEdges)), getEdge = (id) => layoutedEdges[id], fitView = async (options) => store.panZoom ? await fitViewport({
1111
+ }), visibleEdgeIds = createMemo(() => Object.keys(layoutedEdges)), getLayoutedEdge = (id) => layoutedEdges[id], fitView = async (options) => store.panZoom ? await fitViewport({
1090
1112
  nodes: nodeLookup,
1091
1113
  width: store.width,
1092
1114
  height: store.height,
@@ -1433,7 +1455,7 @@ const createLayoutedEdges = (source) => {
1433
1455
  parentIds,
1434
1456
  connections,
1435
1457
  actions: {
1436
- getEdge,
1458
+ getLayoutedEdge,
1437
1459
  applyInitialFitView,
1438
1460
  applyMeasurementWrites,
1439
1461
  applyNodeChanges,
@@ -1485,7 +1507,14 @@ const createLayoutedEdges = (source) => {
1485
1507
  let [store, setStore] = typeof nodes == "function" ? createStore(nodes, []) : createStore(nodes);
1486
1508
  return [store, setStore];
1487
1509
  }, EdgeWrapper = (props) => {
1488
- let edgeRef, { store, actions } = useInternalSolidFlow(), edgeId = () => props.edgeId, edge = () => actions.getEdge(edgeId()), edgeType = () => edge().type ?? "default", selectable = () => isEdgeSelectable(edge(), store), focusable = () => edge().focusable ?? store.edgesFocusable, edgeComponent = () => store.edgeTypes[edgeType()], markerStartUrl = () => edge().markerStart ? `url('#${getMarkerId(edge().markerStart, store.id)}')` : void 0, markerEndUrl = () => edge().markerEnd ? `url('#${getMarkerId(edge().markerEnd, store.id)}')` : void 0, onClick = (event) => {
1510
+ let edgeRef, { store, actions } = useInternalSolidFlow(), edgeId = () => props.edgeId, edge = () => actions.getLayoutedEdge(edgeId()), edgeType = () => edge().type ?? "default", selectable = () => isEdgeSelectable(edge(), store), focusable = () => edge().focusable ?? store.edgesFocusable, edgeTypeValid = () => edgeType() in store.edgeTypes, edgeComponent = () => store.edgeTypes[edgeTypeValid() ? edgeType() : "default"];
1511
+ createEffect(() => ({
1512
+ valid: edgeTypeValid(),
1513
+ edgeType: edgeType()
1514
+ }), ({ valid, edgeType }) => {
1515
+ valid || emitFlowError(store.onError, "011", errorMessages.error011(edgeType));
1516
+ });
1517
+ let markerStartUrl = () => edge().markerStart ? `url('#${getMarkerId(edge().markerStart, store.id)}')` : void 0, markerEndUrl = () => edge().markerEnd ? `url('#${getMarkerId(edge().markerEnd, store.id)}')` : void 0, onClick = (event) => {
1489
1518
  selectable() && actions.handleEdgeSelection(edgeId()), props.onEdgeClick?.({
1490
1519
  edge: edge(),
1491
1520
  event
@@ -1642,10 +1671,7 @@ const Handle = (props) => {
1642
1671
  position: "top",
1643
1672
  isConnectableStart: !0,
1644
1673
  isConnectableEnd: !0
1645
- }), { 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.connectionFromHandle, connectingFrom = () => {
1646
- let fromHandle = store.connectionFromHandle;
1647
- return fromHandle && fromHandle.nodeId === nodeId() && fromHandle.type === _props.type && fromHandle.id === handleId();
1648
- }, targetState = () => store.connectionTargetByHandle[connectionKey(nodeId(), _props.type, handleId())], connectingTo = () => targetState() !== void 0, isPossibleTargetHandle = () => store.connectionMode === "strict" ? store.connectionFromHandle?.type !== _props.type : nodeId() !== store.connectionFromHandle?.nodeId || handleId() !== store.connectionFromHandle?.id, valid = () => targetState() === "valid", prevConnections = null;
1674
+ }), { 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, originState = () => store.connectionOriginByHandle[connectionKey(nodeId(), _props.type, handleId())], connectingFrom = () => originState() === "from", targetState = () => store.connectionTargetByHandle[connectionKey(nodeId(), _props.type, handleId())], connectingTo = () => targetState() !== void 0, valid = () => targetState() === "valid", prevConnections = null;
1649
1675
  createEffect(() => {
1650
1676
  if (!_props.onConnect && !_props.onDisconnect) return null;
1651
1677
  let rec = connections[connectionKey(nodeId(), _props.type, _props.id)], map = /* @__PURE__ */ new Map();
@@ -1671,36 +1697,17 @@ const Handle = (props) => {
1671
1697
  connectionRadius: store.connectionRadius
1672
1698
  });
1673
1699
  XYHandle.onPointerDown(event, {
1700
+ ...buildConnectionGestureParams({
1701
+ event,
1702
+ store,
1703
+ actions,
1704
+ gestureLookup
1705
+ }),
1674
1706
  handleId: handleId(),
1675
1707
  nodeId: nodeId(),
1676
1708
  isTarget: isTarget(),
1677
- connectionRadius: store.connectionRadius,
1678
- domNode: store.domNode,
1679
- nodeLookup: gestureLookup,
1680
- connectionMode: store.connectionMode,
1681
- lib: store.lib,
1682
- autoPanOnConnect: store.autoPanOnConnect,
1683
- flowId: store.id,
1684
1709
  isValidConnection: _props.isValidConnection ?? store.isValidConnection,
1685
- updateConnection: ((connection) => {
1686
- actions.setConnection(connection), flush();
1687
- }),
1688
- cancelConnection: actions.cancelConnection,
1689
- panBy: actions.panBy,
1690
- onConnect: onConnectExtended,
1691
- onConnectStart: (event, startParams) => {
1692
- store.onConnectStart?.(event, {
1693
- nodeId: startParams.nodeId,
1694
- handleId: startParams.handleId,
1695
- handleType: startParams.handleType
1696
- });
1697
- },
1698
- onConnectEnd: store.onConnectEnd,
1699
- getTransform: () => store.transform,
1700
- getFromHandle: () => store.connection.fromHandle,
1701
- autoPanSpeed: store.autoPanSpeed,
1702
- dragThreshold: store.connectionDragThreshold,
1703
- handleDomNode: event.currentTarget
1710
+ onConnect: onConnectExtended
1704
1711
  });
1705
1712
  }, onClick = (event) => {
1706
1713
  if (!nodeId() || !store.clickConnectStartHandle && !_props.isConnectableStart) return;
@@ -1735,7 +1742,7 @@ const Handle = (props) => {
1735
1742
  isValid && connection && onConnectExtended(connection);
1736
1743
  let connectionClone = structuredClone(snapshot(store.connection));
1737
1744
  delete connectionClone.inProgress, connectionClone.toPosition = connectionClone.toHandle ? connectionClone.toHandle.position : null, store.onClickConnectEnd?.(event, connectionClone), actions.setClickConnectStartHandle(void 0);
1738
- }, connectionIndicator = () => connectable() && (!connectionInProcess() || isPossibleTargetHandle()) && (connectionInProcess() || store.clickConnectStartHandle ? _props.isConnectableEnd : _props.isConnectableStart);
1745
+ };
1739
1746
  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={[
1740
1747
  "solid-flow__handle",
1741
1748
  `solid-flow__handle-${_props.position}`,
@@ -1751,7 +1758,7 @@ const Handle = (props) => {
1751
1758
  connectablestart: _props.isConnectableStart,
1752
1759
  connectableend: _props.isConnectableEnd,
1753
1760
  connectable: !!connectable(),
1754
- connectionindicator: connectionIndicator()
1761
+ excluded: !!originState()
1755
1762
  }
1756
1763
  ]}>
1757
1764
  {_props.children}
@@ -1850,7 +1857,7 @@ const NodeWrapper = (props) => {
1850
1857
  let el = nodeRef();
1851
1858
  el && props.resizeObserver?.unobserve(el);
1852
1859
  });
1853
- let nodeId = () => node().id, nodeType = () => node().type ?? "default", deletable = () => node().deletable ?? !0, selectable = () => node().selectable ?? store.elementsSelectable, focusable = () => node().focusable ?? store.nodesFocusable, draggable = () => node().draggable ?? store.nodesDraggable, connectable = () => node().connectable ?? store.nodesConnectable, userNode = () => node().internals.userNode, nodeTypeValid = () => nodeType() in store.nodeTypes, nodeComponent = () => store.nodeTypes[nodeType()], isParentNode = () => !!parentIds[node().id], transform = () => {
1860
+ let nodeId = () => node().id, nodeType = () => node().type ?? "default", deletable = () => node().deletable ?? !0, selectable = () => node().selectable ?? store.elementsSelectable, focusable = () => node().focusable ?? store.nodesFocusable, draggable = () => node().draggable ?? store.nodesDraggable, connectable = () => node().connectable ?? store.nodesConnectable, userNode = () => node().internals.userNode, nodeTypeValid = () => nodeType() in store.nodeTypes, nodeComponent = () => store.nodeTypes[nodeTypeValid() ? nodeType() : "default"], isParentNode = () => !!parentIds[node().id], transform = () => {
1854
1861
  let { x, y } = node().internals.positionAbsolute;
1855
1862
  return `translate(${x}px, ${y}px)`;
1856
1863
  }, sizeStyle = () => {
@@ -1870,7 +1877,9 @@ const NodeWrapper = (props) => {
1870
1877
  createEffect(() => ({
1871
1878
  valid: nodeTypeValid(),
1872
1879
  nodeType: nodeType()
1873
- }), ({ valid, nodeType }) => {}), createEffect(() => ({
1880
+ }), ({ valid, nodeType }) => {
1881
+ valid || emitFlowError(store.onError, "003", errorMessages.error003(nodeType));
1882
+ }), createEffect(() => ({
1874
1883
  id: node().id,
1875
1884
  nodeElement: nodeRef(),
1876
1885
  nodeType: nodeType(),
@@ -2146,29 +2155,31 @@ function useInternalSolidFlow() {
2146
2155
  //#region src/components/connection/ConnectionLine.tsx
2147
2156
  /** Internal component rendering the in-progress connection line. */
2148
2157
  const ConnectionLine = (props) => {
2149
- let { store } = useInternalSolidFlow(), connectionStatus = () => getConnectionStatus(store.connection.isValid);
2150
- return <Show when={store.connection.inProgress}>
2151
- <svg class="solid-flow__container solid-flow__connectionline" width={store.width} height={store.height} style={props.containerStyle}>
2152
- <g class={["solid-flow__connection", connectionStatus()]}>
2153
- <Show when={props.component} fallback={<InternalConnectionLine style={props.style} />}>
2154
- {(CustomComponent) => {
2158
+ let { store } = useInternalSolidFlow(), connectionStatus = () => getConnectionStatus(store.connection.isValid), inProgress = createMemo(() => {
2159
+ let state = store.connection;
2160
+ return state.inProgress ? state : null;
2161
+ });
2162
+ return <Show when={inProgress()}>
2163
+ {(connection) => <svg class="solid-flow__container solid-flow__connectionline" width={store.width} height={store.height} style={props.containerStyle}>
2164
+ <g class={["solid-flow__connection", connectionStatus()]}>
2165
+ <Show when={props.component} fallback={<InternalConnectionLine style={props.style} connection={connection()} />}>
2166
+ {(CustomComponent) => {
2155
2167
  let UserConnectionLine = CustomComponent();
2156
- return <UserConnectionLine connectionLineType={store.connectionLineType} connectionLineStyle={props.style} fromNode={store.connection.fromNode} fromHandle={store.connection.fromHandle} fromX={store.connection.from.x} fromY={store.connection.from.y} toX={store.connection.to.x} toY={store.connection.to.y} fromPosition={store.connection.fromPosition} toPosition={store.connection.toPosition} connectionStatus={connectionStatus()} toNode={store.connection.toNode} toHandle={store.connection.toHandle} />;
2168
+ return <UserConnectionLine connectionLineType={store.connectionLineType} connectionLineStyle={props.style} fromNode={connection().fromNode} fromHandle={connection().fromHandle} fromX={connection().from.x} fromY={connection().from.y} toX={connection().to.x} toY={connection().to.y} fromPosition={connection().fromPosition} toPosition={connection().toPosition} connectionStatus={connectionStatus()} toNode={connection().toNode} toHandle={connection().toHandle} />;
2157
2169
  }}
2158
- </Show>
2159
- </g>
2160
- </svg>
2170
+ </Show>
2171
+ </g>
2172
+ </svg>}
2161
2173
  </Show>;
2162
2174
  }, InternalConnectionLine = (props) => {
2163
2175
  let { store } = useInternalSolidFlow(), path = () => {
2164
- if (!store.connection.inProgress) return;
2165
2176
  let pathParams = {
2166
- sourceX: store.connection.from.x,
2167
- sourceY: store.connection.from.y,
2168
- sourcePosition: store.connection.fromPosition,
2169
- targetX: store.connection.to.x,
2170
- targetY: store.connection.to.y,
2171
- targetPosition: store.connection.toPosition
2177
+ sourceX: props.connection.from.x,
2178
+ sourceY: props.connection.from.y,
2179
+ sourcePosition: props.connection.fromPosition,
2180
+ targetX: props.connection.to.x,
2181
+ targetY: props.connection.to.y,
2182
+ targetPosition: props.connection.toPosition
2172
2183
  };
2173
2184
  switch (store.connectionLineType) {
2174
2185
  case "default": {
@@ -2217,19 +2228,26 @@ const ConnectionLine = (props) => {
2217
2228
  </defs>
2218
2229
  </svg>
2219
2230
  </Show>;
2220
- }, EdgeRenderer = (props) => {
2221
- let { store, actions } = useInternalSolidFlow(), [focusedEdgeId, setFocusedEdgeId] = createSignal(null), onFocusIn = (event) => {
2222
- let edgeElement = event.target.closest("[data-id]");
2223
- setFocusedEdgeId(edgeElement?.getAttribute("data-id") ?? null);
2231
+ }, createFocusedIdTracker = () => {
2232
+ let [focusedId, setFocusedId] = createSignal(null);
2233
+ return {
2234
+ focusedId,
2235
+ onFocusIn: (event) => {
2236
+ let element = event.target.closest("[data-id]");
2237
+ setFocusedId(element?.getAttribute("data-id") ?? null);
2238
+ },
2239
+ onFocusOut: () => setFocusedId(null)
2224
2240
  };
2225
- return <div class="solid-flow__edges" onFocusIn={onFocusIn} onFocusOut={() => setFocusedEdgeId(null)}>
2241
+ }, EdgeRenderer = (props) => {
2242
+ let { store, actions } = useInternalSolidFlow(), { focusedId: focusedEdgeId, onFocusIn, onFocusOut } = createFocusedIdTracker();
2243
+ return <div class="solid-flow__edges" onFocusIn={onFocusIn} onFocusOut={onFocusOut}>
2226
2244
  <MarkerDefinition />
2227
2245
 
2228
2246
  <For each={store.visibleEdgeIds}>
2229
2247
  {(edgeId) => {
2230
2248
  let unmounted = createMemo(() => {
2231
2249
  if (!store.onlyRenderVisibleElements || focusedEdgeId() === edgeId) return !1;
2232
- let edge = actions.getEdge(edgeId);
2250
+ let edge = actions.getLayoutedEdge(edgeId);
2233
2251
  return !!edge && isEdgeCulled(edge, store.cullingViewport);
2234
2252
  });
2235
2253
  return <Show when={!unmounted()}>
@@ -2252,11 +2270,8 @@ const ConnectionLine = (props) => {
2252
2270
  onCleanup(() => {
2253
2271
  resizeObserver?.disconnect();
2254
2272
  });
2255
- let [focusedNodeId, setFocusedNodeId] = createSignal(null), onFocusIn = (event) => {
2256
- let nodeElement = event.target.closest("[data-id]");
2257
- setFocusedNodeId(nodeElement?.getAttribute("data-id") ?? null);
2258
- };
2259
- return <div class="solid-flow__container solid-flow__nodes" onFocusIn={onFocusIn} onFocusOut={() => setFocusedNodeId(null)}>
2273
+ let { focusedId: focusedNodeId, onFocusIn, onFocusOut } = createFocusedIdTracker();
2274
+ return <div class="solid-flow__container solid-flow__nodes" onFocusIn={onFocusIn} onFocusOut={onFocusOut}>
2260
2275
  <For each={store.visibleNodeIds}>
2261
2276
  {(nodeId) => {
2262
2277
  let unmounted = createMemo(() => {
@@ -2278,7 +2293,7 @@ const ConnectionLine = (props) => {
2278
2293
  let { store, nodeLookup, edgeLookup, connections, actions } = useInternalSolidFlow(), [containerRef, setContainerRef] = createSignal(), container, containerBounds = null, connectionEndedOnPane = !1, selectionInProgress = !1, selectionSpatialLookup = new GestureSpatialLookup(nodeLookup, 400), selectedNodeIds = /* @__PURE__ */ new Set(), selectedEdgeIds = /* @__PURE__ */ new Set(), autoPanId = 0, position = {
2279
2294
  x: 0,
2280
2295
  y: 0
2281
- }, 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) => {
2296
+ }, autoPanStarted = !1, autoPanOnSelection = () => props.autoPanOnSelection ?? !0, paneClickDistance = () => props.paneClickDistance ?? 0, _panOnDrag = () => store.panActivationKeyPressed || props.panOnDrag, isSelecting = () => store.selectionKeyPressed || !!store.selectionRect || props.selectionOnDrag && _panOnDrag() !== !0, isSelectionEnabled = () => store.elementsSelectable && (isSelecting() || store.selectionRectMode === "user"), onClick = (event) => {
2282
2297
  if (event.target === container) {
2283
2298
  if (selectionInProgress || store.connection.inProgress || connectionEndedOnPane) {
2284
2299
  selectionInProgress = !1, connectionEndedOnPane = !1;
@@ -2575,29 +2590,13 @@ const ConnectionLine = (props) => {
2575
2590
  Solid Flow
2576
2591
  </a>
2577
2592
  </Panel>
2578
- </Show>;
2579
- //#endregion
2580
- //#region src/hooks/useSolidFlow.ts
2581
- /**
2582
- * Hook for accessing the flow instance: `{ flow, commands }` plus the
2583
- * commands spread at the top level for upstream familiarity.
2584
- *
2585
- * `flow` and `commands` are stable identities, so destructuring them is safe:
2586
- * `const { flow, commands } = useSolidFlow()`.
2587
- *
2588
- * @public
2589
- * @returns the flow's read struct and write surface
2590
- */
2591
- function useSolidFlow() {
2592
- let { flow, commands } = useInternalSolidFlow();
2593
- return {
2594
- ...commands,
2595
- flow,
2596
- commands
2597
- };
2598
- }
2599
- //#endregion
2600
- //#region src/components/utility/KeyHandler.ts
2593
+ </Show>, MODIFIER_FLAG = {
2594
+ alt: "altKey",
2595
+ control: "ctrlKey",
2596
+ ctrl: "ctrlKey",
2597
+ meta: "metaKey",
2598
+ shift: "shiftKey"
2599
+ };
2601
2600
  function isKeyObject(key) {
2602
2601
  return typeof key == "object" && !!key;
2603
2602
  }
@@ -2629,18 +2628,13 @@ function matchesKey(event, keyDef) {
2629
2628
  function matchesKeyArray(event, keyDefs) {
2630
2629
  return keyDefs ? (Array.isArray(keyDefs) ? keyDefs : [keyDefs]).some((keyDef) => matchesKey(event, keyDef)) : !1;
2631
2630
  }
2632
- const MODIFIER_FLAG = {
2633
- alt: "altKey",
2634
- control: "ctrlKey",
2635
- ctrl: "ctrlKey",
2636
- meta: "metaKey",
2637
- shift: "shiftKey"
2638
- };
2639
2631
  /**
2640
2632
  * Whether an event's modifier flags PROVE this key definition cannot be held
2641
2633
  * right now: the key itself is a modifier whose flag is off, or one of its
2642
2634
  * required modifiers is off. Definitions built on non-modifier keys are never
2643
- * contradicted (their state isn't derivable from flags).
2635
+ * contradicted (their state isn't derivable from flags). This is the
2636
+ * self-heal for OS overlays that swallow keyups without blurring the window
2637
+ * (upstream xyflow#5679).
2644
2638
  */
2645
2639
  function isContradicted(flags, keyDef) {
2646
2640
  let keyFlag = MODIFIER_FLAG[getKeyString(keyDef).toLowerCase()];
@@ -2657,6 +2651,28 @@ function allContradicted(flags, keyDefs) {
2657
2651
  let keys = Array.isArray(keyDefs) ? keyDefs : [keyDefs];
2658
2652
  return keys.length > 0 && keys.every((keyDef) => isContradicted(flags, keyDef));
2659
2653
  }
2654
+ //#endregion
2655
+ //#region src/hooks/useSolidFlow.ts
2656
+ /**
2657
+ * Hook for accessing the flow instance: `{ flow, commands }` plus the
2658
+ * commands spread at the top level for upstream familiarity.
2659
+ *
2660
+ * `flow` and `commands` are stable identities, so destructuring them is safe:
2661
+ * `const { flow, commands } = useSolidFlow()`.
2662
+ *
2663
+ * @public
2664
+ * @returns the flow's read struct and write surface
2665
+ */
2666
+ function useSolidFlow() {
2667
+ let { flow, commands } = useInternalSolidFlow();
2668
+ return {
2669
+ ...commands,
2670
+ flow,
2671
+ commands
2672
+ };
2673
+ }
2674
+ //#endregion
2675
+ //#region src/components/utility/KeyHandler.ts
2660
2676
  const KeyHandler = (props) => {
2661
2677
  let { store, actions } = useInternalSolidFlow(), { deleteElements } = useSolidFlow(), _props = {
2662
2678
  get selectionKey() {
@@ -2757,7 +2773,13 @@ const KeyHandler = (props) => {
2757
2773
  "solid-flow",
2758
2774
  "solid-flow__container",
2759
2775
  _props.class,
2760
- store.colorMode
2776
+ store.colorMode,
2777
+ {
2778
+ connecting: !!store.connectionFromHandle || !!store.clickConnectStartHandle,
2779
+ "connecting-from-source": (store.connectionFromHandle ?? store.clickConnectStartHandle)?.type === "source",
2780
+ "connecting-from-target": (store.connectionFromHandle ?? store.clickConnectStartHandle)?.type === "target",
2781
+ "connection-strict": store.connectionMode === "strict"
2782
+ }
2761
2783
  ]} style={rootStyle()} onScroll={(e) => {
2762
2784
  e.currentTarget.scrollTo({
2763
2785
  top: 0,
@@ -2830,8 +2852,8 @@ function useConnection() {
2830
2852
  * @returns store with an array of nodes
2831
2853
  */
2832
2854
  function useNodes() {
2833
- let { store } = useInternalSolidFlow();
2834
- return () => store.nodes;
2855
+ let { flow } = useInternalSolidFlow();
2856
+ return () => flow.nodes;
2835
2857
  }
2836
2858
  /**
2837
2859
  * Hook for getting the current edges from the store.
@@ -2840,8 +2862,8 @@ function useNodes() {
2840
2862
  * @returns store with an array of edges
2841
2863
  */
2842
2864
  function useEdges() {
2843
- let { store } = useInternalSolidFlow();
2844
- return () => store.edges;
2865
+ let { flow } = useInternalSolidFlow();
2866
+ return () => flow.edges;
2845
2867
  }
2846
2868
  /**
2847
2869
  * Hook for getting the current viewport from the store.
@@ -2899,6 +2921,52 @@ function useInternalNode(id) {
2899
2921
  return () => flow.internalNodes[id()];
2900
2922
  }
2901
2923
  //#endregion
2924
+ //#region src/hooks/useKeyPress.ts
2925
+ /**
2926
+ * Reactive "is this key (combo) held right now?" — the Solid Flow
2927
+ * counterpart of React Flow's `useKeyPress`, usable anywhere (no flow
2928
+ * context required).
2929
+ *
2930
+ * The definition is an Accessor per house convention — `useKeyPress(() =>
2931
+ * "a")`, `useKeyPress(() => ["a", "d"])`, or `useKeyPress(() => ({ key:
2932
+ * "s", modifier: ["meta"] }))`; swapping the definition resets the state.
2933
+ *
2934
+ * Hardened beyond upstream:
2935
+ * - Combos re-activate when the base key is re-pressed while the modifier
2936
+ * stays held (upstream's oldest open key bug, xyflow#2248).
2937
+ * - Stuck modifiers self-heal from the flags later keyboard/pointer/wheel
2938
+ * events carry (OS overlays swallow keyups without blurring — the macOS
2939
+ * screenshot HUD; xyflow#5679), and window blur resets.
2940
+ */
2941
+ function useKeyPress(keys) {
2942
+ let [pressed, setPressed] = createSignal(!1);
2943
+ if (createEffect(() => keys(), () => {
2944
+ setPressed(!1);
2945
+ }, { defer: !0 }), !isServer) {
2946
+ let reconcile = (event) => {
2947
+ pressed() && allContradicted(event, keys()) && (setPressed(!1), flush());
2948
+ };
2949
+ createEventListenerMap(window, {
2950
+ keydown: (event) => {
2951
+ reconcile(event), matchesKeyArray(event, keys()) && (setPressed(!0), flush());
2952
+ },
2953
+ keyup: (event) => {
2954
+ reconcile(event), matchesKeyArray(event, keys()) && (setPressed(!1), flush());
2955
+ },
2956
+ blur: () => {
2957
+ setPressed(!1), flush();
2958
+ }
2959
+ }), createEventListenerMap(window, {
2960
+ pointerdown: reconcile,
2961
+ wheel: reconcile
2962
+ }, {
2963
+ capture: !0,
2964
+ passive: !0
2965
+ });
2966
+ }
2967
+ return pressed;
2968
+ }
2969
+ //#endregion
2902
2970
  //#region src/hooks/useNodeConnections.ts
2903
2971
  /**
2904
2972
  * Hook to retrieve all edges connected to a node. Can be filtered by handle type and id.
@@ -3084,7 +3152,7 @@ const DotPattern = (props) => {
3084
3152
  { selected: !!_props.selected },
3085
3153
  _props.class
3086
3154
  ]} x={_props.x} y={_props.y} rx={_props.borderRadius} ry={_props.borderRadius} width={_props.width} height={_props.height} shape-rendering={_props.shapeRendering} style={style()} onClick={_props.onClick ? (event) => _props.onClick(event, _props.id) : void 0} />;
3087
- }, getAttrFunction = (func) => func instanceof Function ? func : () => func, MiniMap = (props) => {
3155
+ }, getAttrFunction = (value) => value instanceof Function ? value : () => value, MiniMap = (props) => {
3088
3156
  let { store, nodeLookup } = useInternalSolidFlow(), _props = propDefaults(props, {
3089
3157
  position: "bottom-right",
3090
3158
  nodeClass: "",
@@ -3094,19 +3162,41 @@ const DotPattern = (props) => {
3094
3162
  width: 200,
3095
3163
  height: 150,
3096
3164
  nodeBorderRadius: 5,
3165
+ offsetScale: 5,
3097
3166
  nodeStrokeWidth: 2,
3098
3167
  style: {}
3099
- }), paneProps = omit(_props, "class", "style", "position", "nodeClass", "nodeStrokeColor", "nodeColor", "pannable", "zoomable", "inversePan", "zoomStep", "bgColor", "width", "height", "maskColor", "maskStrokeColor", "maskStrokeWidth", "nodeBorderRadius", "nodeStrokeWidth", "nodeComponent", "onClick", "onNodeClick"), nodeColorFunc = () => _props.nodeColor === void 0 ? void 0 : getAttrFunction(_props.nodeColor), nodeStrokeColorFunc = () => getAttrFunction(_props.nodeStrokeColor), nodeClassFunc = () => getAttrFunction(_props.nodeClass), shapeRendering = typeof window > "u" || window.chrome ? "crispEdges" : "geometricPrecision", labelledBy = createMemo(() => `solid-flow__minimap-desc-${store.id}`), viewBB = createMemo(() => ({
3168
+ }), paneProps = omit(_props, "class", "style", "position", "nodeClass", "nodeStrokeColor", "nodeColor", "pannable", "zoomable", "inversePan", "zoomStep", "offsetScale", "bgColor", "width", "height", "maskColor", "maskStrokeColor", "maskStrokeWidth", "nodeBorderRadius", "nodeStrokeWidth", "nodeComponent", "onClick", "onNodeClick"), nodeColorFunc = () => _props.nodeColor === void 0 ? void 0 : getAttrFunction(_props.nodeColor), nodeStrokeColorFunc = () => getAttrFunction(_props.nodeStrokeColor), nodeClassFunc = () => getAttrFunction(_props.nodeClass), shapeRendering = typeof window > "u" || window.chrome ? "crispEdges" : "geometricPrecision", labelledBy = createMemo(() => `solid-flow__minimap-desc-${store.id}`), viewBB = createMemo(() => ({
3100
3169
  x: -store.viewport.x / store.viewport.zoom,
3101
3170
  y: -store.viewport.y / store.viewport.zoom,
3102
3171
  width: store.width / store.viewport.zoom,
3103
3172
  height: store.height / store.viewport.zoom
3104
- })), boundingRect = createMemo(() => {
3105
- let view = viewBB();
3106
- if (nodeLookup.size === 0) return view;
3173
+ })), rectsEqual = (a, b) => a === b || !!a && !!b && a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height, sampleBounds = () => untrack(() => {
3174
+ if (nodeLookup.size === 0) return null;
3107
3175
  let bounds = getInternalNodesBounds(nodeLookup);
3108
- return !Number.isFinite(bounds.x) || !Number.isFinite(bounds.width) ? view : getBoundsOfRects(bounds, view);
3109
- }), viewScale = createMemo(() => Math.max(boundingRect().width / _props.width, boundingRect().height / _props.height)), getViewWidth = () => viewScale() * _props.width, getViewHeight = () => viewScale() * _props.height, getOffset = () => 5 * viewScale(), getX = () => {
3176
+ return Number.isFinite(bounds.x) && Number.isFinite(bounds.width) ? bounds : null;
3177
+ }), [graphBounds, setGraphBounds] = createSignal(sampleBounds(), { equals: rectsEqual });
3178
+ createEffect(() => store.dragging, (dragging) => {
3179
+ if (!dragging) {
3180
+ setGraphBounds(sampleBounds());
3181
+ return;
3182
+ }
3183
+ let raf = requestAnimationFrame(function tick() {
3184
+ setGraphBounds(sampleBounds()), raf = requestAnimationFrame(tick);
3185
+ });
3186
+ return () => cancelAnimationFrame(raf);
3187
+ }), createEffect(() => ({
3188
+ count: store.nodes.length,
3189
+ initialized: store.nodesInitialized
3190
+ }), () => {
3191
+ setGraphBounds(sampleBounds());
3192
+ }), createEffect(() => null, () => {
3193
+ let interval = setInterval(() => setGraphBounds(sampleBounds()), 500);
3194
+ return () => clearInterval(interval);
3195
+ });
3196
+ let boundingRect = createMemo(() => {
3197
+ let view = viewBB(), bounds = graphBounds();
3198
+ return bounds ? getBoundsOfRects(bounds, view) : view;
3199
+ }), viewScale = createMemo(() => Math.max(boundingRect().width / _props.width, boundingRect().height / _props.height)), getViewWidth = () => viewScale() * _props.width, getViewHeight = () => viewScale() * _props.height, getOffset = () => _props.offsetScale * viewScale(), getX = () => {
3110
3200
  let rect = boundingRect();
3111
3201
  return rect.x - (getViewWidth() - rect.width) / 2 - getOffset();
3112
3202
  }, getY = () => {
@@ -3167,9 +3257,15 @@ const DotPattern = (props) => {
3167
3257
  <title id={labelledBy()}>{store.ariaLabelConfig["minimap.ariaLabel"]}</title>
3168
3258
  <For keyed={!1} each={nodeIds()}>
3169
3259
  {(nodeId) => {
3170
- let node = createMemo(() => nodeLookup.get(nodeId())), nodeVisible = () => !!(node() && nodeHasDimensions(node()) && !node().hidden);
3171
- return <Show when={nodeVisible() && getNodeDimensions(node())}>
3172
- {(nodeDimensions) => <Dynamic component={_props.nodeComponent ?? MiniMapNode} id={nodeId()} x={node().internals.positionAbsolute.x} y={node().internals.positionAbsolute.y} borderRadius={_props.nodeBorderRadius} strokeWidth={_props.nodeStrokeWidth} shapeRendering={shapeRendering} width={nodeDimensions().width} height={nodeDimensions().height} selected={node().selected} color={nodeColorFunc()?.call(null, node())} strokeColor={nodeStrokeColorFunc().call(null, node())} class={nodeClassFunc().call(null, node())} style={node().style} onClick={_props.onNodeClick ? onSvgNodeClick : void 0} />}
3260
+ let visibleNode = createMemo(() => {
3261
+ let row = nodeLookup.get(nodeId());
3262
+ return row && nodeHasDimensions(row) && !row.hidden ? row : null;
3263
+ });
3264
+ return <Show when={visibleNode()}>
3265
+ {(node) => {
3266
+ let dimensions = () => getNodeDimensions(node()), userNode = () => node().internals.userNode;
3267
+ return <Dynamic component={_props.nodeComponent ?? MiniMapNode} id={nodeId()} x={node().internals.positionAbsolute.x} y={node().internals.positionAbsolute.y} borderRadius={_props.nodeBorderRadius} strokeWidth={_props.nodeStrokeWidth} shapeRendering={shapeRendering} width={dimensions().width} height={dimensions().height} selected={node().selected} color={nodeColorFunc()?.call(null, userNode())} strokeColor={nodeStrokeColorFunc().call(null, userNode())} class={nodeClassFunc().call(null, userNode())} style={node().style} onClick={_props.onNodeClick ? onSvgNodeClick : void 0} />;
3268
+ }}
3173
3269
  </Show>;
3174
3270
  }}
3175
3271
  </For>
@@ -3286,7 +3382,7 @@ const DotPattern = (props) => {
3286
3382
  </EdgeLabel>
3287
3383
  </Show>;
3288
3384
  }, NodeToolbar = (props) => {
3289
- let { store } = useInternalSolidFlow(), { flow, getNodesBounds } = useSolidFlow(), _props = propDefaults(props, {
3385
+ let { store, flow, commands } = useInternalSolidFlow(), _props = propDefaults(props, {
3290
3386
  offset: 10,
3291
3387
  position: "top",
3292
3388
  align: "center",
@@ -3298,7 +3394,7 @@ const DotPattern = (props) => {
3298
3394
  let node = flow.internalNodes[nodeId];
3299
3395
  return node && res.push(node), res;
3300
3396
  }, []), transform = () => {
3301
- let nodeRect = getNodesBounds(toolbarNodes());
3397
+ let nodeRect = commands.getNodesBounds(toolbarNodes());
3302
3398
  return nodeRect ? getNodeToolbarTransform(nodeRect, store.viewport, _props.position, _props.offset, _props.align) : "";
3303
3399
  }, zIndex = () => {
3304
3400
  let nodes = toolbarNodes();
@@ -3321,4 +3417,4 @@ const DotPattern = (props) => {
3321
3417
  </Show>;
3322
3418
  }, SelectionMode = SelectionMode$1, getEdgeCenter = getEdgeCenter$1, getBezierEdgeCenter = getBezierEdgeCenter$1;
3323
3419
  //#endregion
3324
- export { Background, BaseEdge, BezierEdge, BezierEdgeInternal, ConnectionLine, ConnectionLineType, ConnectionMode, ControlButton, Controls, DefaultNode, EdgeLabel, EdgeLabelRenderer, EdgeReconnectAnchor, EdgeRenderer, EdgeToolbar, EdgeWrapper, GroupNode, Handle, InputNode, Marker, MarkerDefinition, MarkerType, MiniMap, MiniMapNode, NodeRenderer, NodeResizer, NodeSelection, NodeToolbar, NodeWrapper, OutputNode, PanOnScrollMode, Pane, Panel, Position, ResizeControl, ResizeControlVariant, Selection, SelectionMode, SmoothStepEdge, SmoothStepEdgeInternal, SolidFlow, SolidFlowProvider, StepEdge, StepEdgeInternal, StraightEdge, StraightEdgeInternal, Viewport, ViewportPortal, Zoom, addEdge, connectionKey, createEdgeStore, createNodeStore, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, useColorMode, useConnection, useEdgeId, useEdges, useInternalNode, useNodeConnections, useNodeId, useNodes, useNodesData, useNodesInitialized, useSolidFlow, useUpdateNodeInternals, useViewport, useViewportInitialized };
3420
+ export { Background, BaseEdge, BezierEdge, BezierEdgeInternal, ConnectionLine, ConnectionLineType, ConnectionMode, ControlButton, Controls, DefaultNode, EdgeLabel, EdgeLabelRenderer, EdgeReconnectAnchor, EdgeRenderer, EdgeToolbar, EdgeWrapper, GroupNode, Handle, InputNode, Marker, MarkerDefinition, MarkerType, MiniMap, MiniMapNode, NodeRenderer, NodeResizer, NodeSelection, NodeToolbar, NodeWrapper, OutputNode, PanOnScrollMode, Pane, Panel, Position, ResizeControl, ResizeControlVariant, Selection, SelectionMode, SmoothStepEdge, SmoothStepEdgeInternal, SolidFlow, SolidFlowProvider, StepEdge, StepEdgeInternal, StraightEdge, StraightEdgeInternal, Viewport, ViewportPortal, Zoom, addEdge, connectionKey, createEdgeStore, createNodeStore, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, useColorMode, useConnection, useEdgeId, useEdges, useInternalNode, useKeyPress, useNodeConnections, useNodeId, useNodes, useNodesData, useNodesInitialized, useSolidFlow, useUpdateNodeInternals, useViewport, useViewportInitialized };