@xyflow/vue 2.0.0-next.3 → 2.0.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,12 +22,14 @@ let ErrorCode = /* @__PURE__ */ function(ErrorCode) {
22
22
  ErrorCode["NODE_MISSING_PARENT"] = "NODE_MISSING_PARENT";
23
23
  ErrorCode["NODE_TYPE_MISSING"] = "NODE_TYPE_MISSING";
24
24
  ErrorCode["NODE_EXTENT_INVALID"] = "NODE_EXTENT_INVALID";
25
+ ErrorCode["NODE_DUPLICATE_ID"] = "NODE_DUPLICATE_ID";
25
26
  ErrorCode["EDGE_INVALID"] = "EDGE_INVALID";
26
27
  ErrorCode["EDGE_NOT_FOUND"] = "EDGE_NOT_FOUND";
27
28
  ErrorCode["EDGE_SOURCE_MISSING"] = "EDGE_SOURCE_MISSING";
28
29
  ErrorCode["EDGE_TARGET_MISSING"] = "EDGE_TARGET_MISSING";
29
30
  ErrorCode["EDGE_TYPE_MISSING"] = "EDGE_TYPE_MISSING";
30
31
  ErrorCode["EDGE_SOURCE_TARGET_MISSING"] = "EDGE_SOURCE_TARGET_MISSING";
32
+ ErrorCode["EDGE_DUPLICATE_ID"] = "EDGE_DUPLICATE_ID";
31
33
  ErrorCode["USE_VUE_FLOW_OUTSIDE_PROVIDER"] = "USE_VUE_FLOW_OUTSIDE_PROVIDER";
32
34
  return ErrorCode;
33
35
  }({});
@@ -39,11 +41,13 @@ const messages = {
39
41
  ["NODE_MISSING_PARENT"]: (id, parentId) => `Node is missing a parent\nNode id: ${id}\nParent id: ${parentId}`,
40
42
  ["NODE_TYPE_MISSING"]: (type) => `Node type is missing\nType: ${type}`,
41
43
  ["NODE_EXTENT_INVALID"]: (id) => `Only child nodes can use a parent extent\nNode id: ${id}`,
44
+ ["NODE_DUPLICATE_ID"]: (id) => `Node id is not unique — a later node overwrites the earlier one in the lookup\nNode id: ${id}`,
42
45
  ["EDGE_INVALID"]: (id) => `An edge needs a source and a target\nEdge id: ${id}`,
43
46
  ["EDGE_SOURCE_MISSING"]: (id, source) => `Edge source is missing\nEdge id: ${id} \nSource id: ${source}`,
44
47
  ["EDGE_TARGET_MISSING"]: (id, target) => `Edge target is missing\nEdge id: ${id} \nTarget id: ${target}`,
45
48
  ["EDGE_TYPE_MISSING"]: (type) => `Edge type is missing\nType: ${type}`,
46
49
  ["EDGE_SOURCE_TARGET_MISSING"]: (id, source, target) => `Edge source or target is missing\nEdge id: ${id} \nSource id: ${source} \nTarget id: ${target}`,
50
+ ["EDGE_DUPLICATE_ID"]: (id) => `Edge id is not unique — a later edge overwrites the earlier one in the lookup\nEdge id: ${id}`,
47
51
  ["EDGE_NOT_FOUND"]: (id) => `Edge not found\nEdge id: ${id}`,
48
52
  ["USE_VUE_FLOW_OUTSIDE_PROVIDER"]: () => `useVueFlow() was called without a <VueFlow> or <VueFlowProvider> ancestor (or outside a component setup). Render one of them above the call, or wrap your components in <VueFlowProvider> to share a store.`
49
53
  };
@@ -826,12 +830,15 @@ function reconnectEdgeAction(edge, newConnection, prevEdge, shouldReplaceId, tri
826
830
  */
827
831
  function adoptNodes(nodes, nodeLookup, parentLookup, triggerError, options) {
828
832
  const validNodes = [];
833
+ const seenNodeIds = /* @__PURE__ */ new Set();
829
834
  for (let i = 0; i < nodes.length; ++i) {
830
835
  const node = nodes[i];
831
836
  if (!isNode(node)) {
832
837
  triggerError(new VueFlowError("NODE_INVALID", node?.id ?? `[ID UNKNOWN|INDEX ${i}]`));
833
838
  continue;
834
839
  }
840
+ if (seenNodeIds.has(node.id)) triggerError(new VueFlowError("NODE_DUPLICATE_ID", node.id));
841
+ else seenNodeIds.add(node.id);
835
842
  validNodes.push((0, vue.markRaw)((0, vue.toRaw)(node)));
836
843
  }
837
844
  const priorInternals = /* @__PURE__ */ new Map();
@@ -1554,9 +1561,10 @@ function useViewportHelper(state, nodeLookup) {
1554
1561
  /**
1555
1562
  * Two-way bind a `v-model` array ref to the store, identity-in / snapshot-out, with native `watch`.
1556
1563
  *
1557
- * Used only when `<VueFlow>` does NOT own its store (it reuses a `<VueFlowProvider>`'s), so the model
1558
- * refs can't back the store directly. The owned-store path is single-source instead the model refs
1559
- * ARE the store's nodes/edges (see `createStore`'s `StoreSignals` binding), needing no sync here.
1564
+ * Runs for BOTH the owned- and reused-store paths: the store's canonical nodes/edges are always an
1565
+ * internal `shallowRef` (see `createStore`), never the v-model ref, so this is the single place the model
1566
+ * ref is bridged to the store. Both directions flush synchronously so the model ref and the store's
1567
+ * synchronous reads (`getNodes`/`state.nodes`) never disagree within a tick.
1560
1568
  *
1561
1569
  * - **out** (store → model): snapshot on every membership change; element refs are shared, so per-node
1562
1570
  * field mutations surface without a copy.
@@ -1569,24 +1577,29 @@ function syncModelArray(model, storeItems, setItems) {
1569
1577
  (0, vue.watch)([storeItems, () => storeItems.value.length], () => {
1570
1578
  lastSnapshot = [...storeItems.value];
1571
1579
  model.value = lastSnapshot;
1572
- }, { immediate: storeItems.value.length > 0 });
1580
+ }, {
1581
+ immediate: storeItems.value.length > 0,
1582
+ flush: "sync"
1583
+ });
1573
1584
  (0, vue.watch)([model, () => model.value?.length], ([next]) => {
1574
1585
  if (!Array.isArray(next)) return;
1575
1586
  const nextRaw = (0, vue.toRaw)(next);
1576
1587
  if (nextRaw === lastSnapshot) return;
1577
1588
  setItems(nextRaw);
1578
- }, { immediate: true });
1589
+ }, {
1590
+ immediate: true,
1591
+ flush: "sync"
1592
+ });
1579
1593
  }
1580
1594
  /**
1581
1595
  * Watches props and updates the store accordingly
1582
1596
  *
1583
1597
  * @internal
1584
- * @param models v-model refs for nodes/edges (bound only when `ownsStore` is false see {@link syncModelArray})
1598
+ * @param models v-model refs for nodes/edges bridged to the store here (see {@link syncModelArray})
1585
1599
  * @param props the `<VueFlow>` props
1586
1600
  * @param handle the created store handle ({@link VueFlowStoreHandle}) — instance (actions) + reactive state
1587
- * @param ownsStore whether this `<VueFlow>` created the store (then nodes/edges are signal-backed and skipped here)
1588
1601
  */
1589
- function useWatchProps(models, props, handle, ownsStore = false) {
1602
+ function useWatchProps(models, props, handle) {
1590
1603
  const { instance, state } = handle;
1591
1604
  const storeRefs = storeToRefs(state);
1592
1605
  const scope = (0, vue.effectScope)(true);
@@ -1690,10 +1703,8 @@ function useWatchProps(models, props, handle, ownsStore = false) {
1690
1703
  }
1691
1704
  };
1692
1705
  const runAll = () => {
1693
- if (!ownsStore) {
1694
- watchNodesValue();
1695
- watchEdgesValue();
1696
- }
1706
+ watchNodesValue();
1707
+ watchEdgesValue();
1697
1708
  watchMinZoom();
1698
1709
  watchMaxZoom();
1699
1710
  watchTranslateExtent();
@@ -1715,7 +1726,8 @@ const _sfc_main$22 = /* @__PURE__ */ (0, vue.defineComponent)({
1715
1726
  props: {
1716
1727
  position: {
1717
1728
  type: String,
1718
- required: true
1729
+ required: false,
1730
+ default: "top-left"
1719
1731
  },
1720
1732
  label: {
1721
1733
  type: [String, null],
@@ -3307,14 +3319,16 @@ const _sfc_main$14 = /* @__PURE__ */ (0, vue.defineComponent)({
3307
3319
  const { nodeLookup, parentLookup } = useStore();
3308
3320
  const { transform, nodeOrigin, snapGrid, snapToGrid, vueFlowRef, noDragClassName } = storeToRefs(useStore());
3309
3321
  const resizeControlRef = (0, vue.shallowRef)();
3322
+ const contextNodeId = (0, vue.inject)(NodeId, null);
3323
+ const nodeId = (0, vue.toRef)(() => typeof props.nodeId === "string" ? props.nodeId : contextNodeId ?? void 0);
3310
3324
  const controlPosition = (0, vue.toRef)(() => props.position ?? DefaultPositions[props.variant]);
3311
3325
  const positionClassNames = (0, vue.computed)(() => controlPosition.value.split("-"));
3312
3326
  const controlStyle = (0, vue.toRef)(() => props.color ? { [StylingProperty[props.variant]]: props.color } : {});
3313
3327
  (0, vue.watchEffect)((onCleanup) => {
3314
- if (!resizeControlRef.value || !props.nodeId) return;
3328
+ if (!resizeControlRef.value || !nodeId.value) return;
3315
3329
  const resizerInstance = (0, _xyflow_system.XYResizer)({
3316
3330
  domNode: resizeControlRef.value,
3317
- nodeId: props.nodeId,
3331
+ nodeId: nodeId.value,
3318
3332
  getStoreItems: () => ({
3319
3333
  nodeLookup,
3320
3334
  transform: transform.value,
@@ -3325,7 +3339,7 @@ const _sfc_main$14 = /* @__PURE__ */ (0, vue.defineComponent)({
3325
3339
  }),
3326
3340
  onChange: (changes, childChanges) => {
3327
3341
  const nodeChanges = [];
3328
- const node = nodeLookup.get(props.nodeId);
3342
+ const node = nodeLookup.get(nodeId.value);
3329
3343
  let nextX = changes.x;
3330
3344
  let nextY = changes.y;
3331
3345
  if (node?.expandParent && node.parentId) {
@@ -3357,14 +3371,14 @@ const _sfc_main$14 = /* @__PURE__ */ (0, vue.defineComponent)({
3357
3371
  y: nextY ?? node?.position.y ?? 0
3358
3372
  };
3359
3373
  nodeChanges.push({
3360
- id: props.nodeId,
3374
+ id: nodeId.value,
3361
3375
  type: "position",
3362
3376
  position,
3363
3377
  positionAbsolute: position
3364
3378
  });
3365
3379
  }
3366
3380
  if (typeof changes.width !== "undefined" || typeof changes.height !== "undefined") nodeChanges.push({
3367
- id: props.nodeId,
3381
+ id: nodeId.value,
3368
3382
  type: "dimensions",
3369
3383
  setAttributes: true,
3370
3384
  resizing: true,
@@ -3383,7 +3397,7 @@ const _sfc_main$14 = /* @__PURE__ */ (0, vue.defineComponent)({
3383
3397
  },
3384
3398
  onEnd: ({ width, height }) => {
3385
3399
  triggerEmits.nodesChange([{
3386
- id: props.nodeId,
3400
+ id: nodeId.value,
3387
3401
  type: "dimensions",
3388
3402
  resizing: false,
3389
3403
  dimensions: {
@@ -3434,6 +3448,8 @@ const _sfc_main$14 = /* @__PURE__ */ (0, vue.defineComponent)({
3434
3448
  vueFlowRef,
3435
3449
  noDragClassName,
3436
3450
  resizeControlRef,
3451
+ contextNodeId,
3452
+ nodeId,
3437
3453
  controlPosition,
3438
3454
  positionClassNames,
3439
3455
  controlStyle,
@@ -4277,8 +4293,11 @@ function useActions(state, nodeLookup, parentLookup, edgeLookup) {
4277
4293
  */
4278
4294
  function commitEdges(next) {
4279
4295
  const rawEdgeLookup = (0, vue.toRaw)(edgeLookup);
4296
+ const seenEdgeIds = /* @__PURE__ */ new Set();
4280
4297
  for (let i = 0; i < next.length; i++) {
4281
4298
  const edge = next[i] = (0, vue.markRaw)((0, vue.toRaw)(next[i]));
4299
+ if (seenEdgeIds.has(edge.id)) state.hooks.error.trigger(new VueFlowError("EDGE_DUPLICATE_ID", edge.id));
4300
+ else seenEdgeIds.add(edge.id);
4282
4301
  if (rawEdgeLookup.get(edge.id) !== edge) edgeLookup.set(edge.id, edge);
4283
4302
  }
4284
4303
  if (rawEdgeLookup.size !== next.length) {
@@ -4962,17 +4981,14 @@ function useGetters(state, nodeLookup) {
4962
4981
  * @internal
4963
4982
  */
4964
4983
  function createVueFlowStore(id, preloadedState, onDestroy, signals) {
4965
- const nodesSignal = signals?.nodes ?? (0, vue.shallowRef)([]);
4966
- const edgesSignal = signals?.edges ?? (0, vue.shallowRef)([]);
4967
- let lastWriteNodes;
4968
- let lastWriteEdges;
4984
+ const nodesSignal = (0, vue.shallowRef)(signals?.nodes?.value ?? []);
4985
+ const edgesSignal = (0, vue.shallowRef)(signals?.edges?.value ?? []);
4969
4986
  const emptyNodes = [];
4970
4987
  const emptyEdges = [];
4971
4988
  const state = useState();
4972
4989
  Object.defineProperty(state, "nodes", {
4973
4990
  get: () => nodesSignal.value ?? emptyNodes,
4974
4991
  set: (value) => {
4975
- lastWriteNodes = (0, vue.toRaw)(value);
4976
4992
  nodesSignal.value = value;
4977
4993
  },
4978
4994
  enumerable: true,
@@ -4981,7 +4997,6 @@ function createVueFlowStore(id, preloadedState, onDestroy, signals) {
4981
4997
  Object.defineProperty(state, "edges", {
4982
4998
  get: () => edgesSignal.value ?? emptyEdges,
4983
4999
  set: (value) => {
4984
- lastWriteEdges = (0, vue.toRaw)(value);
4985
5000
  edgesSignal.value = value;
4986
5001
  },
4987
5002
  enumerable: true,
@@ -5004,14 +5019,6 @@ function createVueFlowStore(id, preloadedState, onDestroy, signals) {
5004
5019
  ...reactiveState,
5005
5020
  ...preloadedState
5006
5021
  });
5007
- if (signals?.nodes) (0, vue.watch)(nodesSignal, (next) => {
5008
- const nextRaw = next && (0, vue.toRaw)(next);
5009
- if (nextRaw && nextRaw !== lastWriteNodes) actions.setNodes(nextRaw);
5010
- });
5011
- if (signals?.edges) (0, vue.watch)(edgesSignal, (next) => {
5012
- const nextRaw = next && (0, vue.toRaw)(next);
5013
- if (nextRaw && nextRaw !== lastWriteEdges) actions.setEdges(nextRaw);
5014
- });
5015
5022
  return {
5016
5023
  instance: {
5017
5024
  ...hooksOn,
@@ -5162,7 +5169,7 @@ function useViewportSync(model, state = useStore()) {
5162
5169
  };
5163
5170
  if (sameViewport(viewport, model.value)) return;
5164
5171
  model.value = viewport;
5165
- });
5172
+ }, { flush: "sync" });
5166
5173
  }
5167
5174
  //#endregion
5168
5175
  //#region src/components/ConnectionLine/index.ts
@@ -5195,13 +5202,13 @@ const ConnectionLine = (0, vue.defineComponent)({
5195
5202
  }
5196
5203
  const fromHandle = (startHandleId ? handleBounds.find((d) => d.id === startHandleId) : handleBounds[0]) ?? null;
5197
5204
  const fromPosition = fromHandle?.position ?? _xyflow_system.Position.Top;
5198
- const { x: fromX, y: fromY } = (0, _xyflow_system.getHandlePosition)(fromNode.value, fromHandle, fromPosition);
5205
+ const { x: fromX, y: fromY } = (0, _xyflow_system.getHandlePosition)(fromNode.value, fromHandle, fromPosition, true);
5199
5206
  let toHandle = null;
5200
5207
  if (toNode.value) if (connectionMode.value === _xyflow_system.ConnectionMode.Strict) toHandle = toNode.value.internals.handleBounds?.[handleType === "source" ? "target" : "source"]?.find((d) => d.id === connectionEndHandle.value?.id) || null;
5201
5208
  else toHandle = [...toNode.value.internals.handleBounds?.source ?? [], ...toNode.value.internals.handleBounds?.target ?? []].find((d) => d.id === connectionEndHandle.value?.id) || null;
5202
5209
  const toPosition = connectionEndHandle.value?.position ?? (fromPosition ? _xyflow_system.oppositePosition[fromPosition] : null);
5203
5210
  if (!fromPosition || !toPosition) return null;
5204
- const { x: toX, y: toY } = toHandle && toNode.value ? (0, _xyflow_system.getHandlePosition)(toNode.value, toHandle, toPosition) : pointer.value;
5211
+ const { x: toX, y: toY } = toHandle && toNode.value ? (0, _xyflow_system.getHandlePosition)(toNode.value, toHandle, toPosition, true) : pointer.value;
5205
5212
  const type = connectionLineOptions.value.type ?? _xyflow_system.ConnectionLineType.Bezier;
5206
5213
  let dAttr = "";
5207
5214
  const pathParams = {
@@ -5942,6 +5949,8 @@ const NodeWrapper = (0, vue.defineComponent)({
5942
5949
  const getStyle = (0, vue.computed)(() => {
5943
5950
  const node = nodeRef.value;
5944
5951
  const styles = { ...node?.style };
5952
+ if (typeof styles.width === "number") styles.width = `${styles.width}px`;
5953
+ if (typeof styles.height === "number") styles.height = `${styles.height}px`;
5945
5954
  const isMeasured = !!node?.internals.handleBounds;
5946
5955
  const width = node?.width ?? (isMeasured ? void 0 : node?.initialWidth);
5947
5956
  const height = node?.height ?? (isMeasured ? void 0 : node?.initialHeight);
@@ -7310,7 +7319,7 @@ const _sfc_main$1 = /* @__PURE__ */ (0, vue.defineComponent)({
7310
7319
  }, props, {
7311
7320
  instance,
7312
7321
  state
7313
- }, ownsStore);
7322
+ });
7314
7323
  useHooks(emit, state.hooks);
7315
7324
  useOnInitHandler(instance);
7316
7325
  useSelectionChange(instance);
@@ -7383,6 +7392,89 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
7383
7392
  }
7384
7393
  var VueFlowProvider_default = /* @__PURE__ */ export_helper_default(_sfc_main, [["render", _sfc_render], ["__file", "/Users/moritz/xyflow/xyflow/packages/vue/src/container/VueFlowProvider/VueFlowProvider.vue"]]);
7385
7394
  //#endregion
7395
+ //#region src/utils/prop-of.ts
7396
+ function propOf(required = false) {
7397
+ return {
7398
+ type: null,
7399
+ required
7400
+ };
7401
+ }
7402
+ //#endregion
7403
+ //#region src/props-objects.gen.ts
7404
+ function nodeProps() {
7405
+ return {
7406
+ data: propOf(true),
7407
+ deletable: propOf(true),
7408
+ dragHandle: propOf(),
7409
+ draggable: propOf(true),
7410
+ dragging: propOf(true),
7411
+ height: propOf(),
7412
+ id: propOf(true),
7413
+ isConnectable: propOf(true),
7414
+ parentId: propOf(),
7415
+ positionAbsoluteX: propOf(true),
7416
+ positionAbsoluteY: propOf(true),
7417
+ selectable: propOf(true),
7418
+ selected: propOf(true),
7419
+ sourcePosition: propOf(),
7420
+ targetPosition: propOf(),
7421
+ type: propOf(true),
7422
+ width: propOf(),
7423
+ zIndex: propOf(true)
7424
+ };
7425
+ }
7426
+ function edgeProps() {
7427
+ return {
7428
+ animated: propOf(),
7429
+ curvature: propOf(),
7430
+ data: propOf(),
7431
+ deletable: propOf(),
7432
+ id: propOf(true),
7433
+ interactionWidth: propOf(),
7434
+ label: propOf(),
7435
+ labelBgBorderRadius: propOf(),
7436
+ labelBgPadding: propOf(),
7437
+ labelBgStyle: propOf(),
7438
+ labelShowBg: propOf(),
7439
+ labelStyle: propOf(),
7440
+ markerEnd: propOf(),
7441
+ markerStart: propOf(),
7442
+ reconnectable: propOf(),
7443
+ selectable: propOf(),
7444
+ selected: propOf(),
7445
+ source: propOf(true),
7446
+ sourceHandleId: propOf(),
7447
+ sourcePosition: propOf(true),
7448
+ sourceX: propOf(true),
7449
+ sourceY: propOf(true),
7450
+ style: propOf(),
7451
+ target: propOf(true),
7452
+ targetHandleId: propOf(),
7453
+ targetPosition: propOf(true),
7454
+ targetX: propOf(true),
7455
+ targetY: propOf(true),
7456
+ type: propOf()
7457
+ };
7458
+ }
7459
+ function connectionLineProps() {
7460
+ return {
7461
+ connectionStatus: propOf(true),
7462
+ fromHandle: propOf(true),
7463
+ fromNode: propOf(true),
7464
+ fromPosition: propOf(true),
7465
+ fromX: propOf(true),
7466
+ fromY: propOf(true),
7467
+ markerEnd: propOf(),
7468
+ markerStart: propOf(),
7469
+ pointer: propOf(true),
7470
+ toHandle: propOf(true),
7471
+ toNode: propOf(true),
7472
+ toPosition: propOf(true),
7473
+ toX: propOf(true),
7474
+ toY: propOf(true)
7475
+ };
7476
+ }
7477
+ //#endregion
7386
7478
  exports.Background = Background_default;
7387
7479
  exports.BaseEdge = BaseEdge_default;
7388
7480
  exports.BezierEdge = BezierEdge;
@@ -7455,8 +7547,10 @@ Object.defineProperty(exports, "clamp", {
7455
7547
  }
7456
7548
  });
7457
7549
  exports.connectionExists = connectionExists;
7550
+ exports.connectionLineProps = connectionLineProps;
7458
7551
  exports.defaultEdgeTypes = defaultEdgeTypes;
7459
7552
  exports.defaultNodeTypes = defaultNodeTypes;
7553
+ exports.edgeProps = edgeProps;
7460
7554
  Object.defineProperty(exports, "getBezierEdgeCenter", {
7461
7555
  enumerable: true,
7462
7556
  get: function() {
@@ -7570,6 +7664,7 @@ Object.defineProperty(exports, "isNodeBase", {
7570
7664
  return _xyflow_system.isNodeBase;
7571
7665
  }
7572
7666
  });
7667
+ exports.nodeProps = nodeProps;
7573
7668
  Object.defineProperty(exports, "pointToRendererPoint", {
7574
7669
  enumerable: true,
7575
7670
  get: function() {