@yh-ui/flow 1.0.52 → 1.0.53

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/Flow.vue CHANGED
@@ -135,7 +135,8 @@ import {
135
135
  createControlsPlugin,
136
136
  createSnapPlugin,
137
137
  createExportPlugin,
138
- createLayoutPlugin
138
+ createLayoutPlugin,
139
+ createHistoryPlugin
139
140
  } from "./plugins/plugins";
140
141
  import { useViewport } from "./core/useViewport";
141
142
  import { useNodes } from "./core/useNodes";
@@ -243,8 +244,10 @@ const usePlugin = (plugin) => {
243
244
  registeredPlugins.value = pluginManager.getPlugins();
244
245
  };
245
246
  const removePlugin = (pluginId) => {
246
- pluginManager.unregister(pluginId);
247
- registeredPlugins.value = pluginManager.getPlugins();
247
+ if (pluginManager.hasPlugin(pluginId)) {
248
+ pluginManager.unregister(pluginId);
249
+ registeredPlugins.value = pluginManager.getPlugins();
250
+ }
248
251
  };
249
252
  const nodesManager = useNodes(viewportRef, {
250
253
  nodes: nodesRef,
@@ -259,12 +262,51 @@ const selectionManager = useSelection({
259
262
  nodes: nodesRef,
260
263
  edges: edgesRef
261
264
  });
262
- const historyManager = useHistory(nodesRef, edgesRef, {
265
+ const localHistoryManager = useHistory(nodesRef, edgesRef, {
263
266
  maxHistory: props.maxHistory || 50,
264
267
  onHistoryChange: (canUndo, canRedo) => {
265
268
  emit("historyChange", { canUndo, canRedo });
266
269
  }
267
270
  });
271
+ const getHistoryPlugin = () => {
272
+ return pluginManager.getPlugin("history");
273
+ };
274
+ const historyManager = {
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ push: (state) => {
277
+ const plugin = getHistoryPlugin();
278
+ if (plugin) {
279
+ plugin.saveSnapshot();
280
+ } else {
281
+ localHistoryManager.push(state);
282
+ }
283
+ },
284
+ undo: () => {
285
+ const plugin = getHistoryPlugin();
286
+ if (plugin) {
287
+ plugin.undo();
288
+ } else {
289
+ localHistoryManager.undo();
290
+ }
291
+ },
292
+ redo: () => {
293
+ const plugin = getHistoryPlugin();
294
+ if (plugin) {
295
+ plugin.redo();
296
+ } else {
297
+ localHistoryManager.redo();
298
+ }
299
+ },
300
+ clear: () => {
301
+ const plugin = getHistoryPlugin();
302
+ if (plugin) {
303
+ plugin.clearHistory();
304
+ } else {
305
+ localHistoryManager.clear();
306
+ }
307
+ }
308
+ };
309
+ historyManager.push({ nodes: nodesRef.value, edges: edgesRef.value });
268
310
  const alignmentManager = useAlignment({
269
311
  nodes: nodesRef,
270
312
  snapThreshold: snapThreshold.value
@@ -392,6 +434,63 @@ const handleMouseMove = (event) => {
392
434
  }
393
435
  }
394
436
  };
437
+ const getNodeHandles = (node, type) => {
438
+ if (node.handleBounds) {
439
+ const handles = [];
440
+ if (node.handleBounds.top) handles.push(...node.handleBounds.top);
441
+ if (node.handleBounds.right) handles.push(...node.handleBounds.right);
442
+ if (node.handleBounds.bottom) handles.push(...node.handleBounds.bottom);
443
+ if (node.handleBounds.left) handles.push(...node.handleBounds.left);
444
+ return handles.filter((h) => h.type === type);
445
+ }
446
+ if (node.type === "group") return [];
447
+ if (node.type === "input") {
448
+ return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
449
+ }
450
+ if (node.type === "output") {
451
+ return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
452
+ }
453
+ if (node.type === "bpmn-start") {
454
+ return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
455
+ }
456
+ if (node.type === "bpmn-end") {
457
+ return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
458
+ }
459
+ if (node.type === "bpmn-task" || node.type === "bpmn-service-task" || node.type === "bpmn-user-task") {
460
+ if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
461
+ return [{ id: void 0, type: "target", position: "left" }];
462
+ }
463
+ if (node.type === "bpmn-exclusive-gateway" || node.type === "bpmn-parallel-gateway" || node.type === "bpmn-inclusive-gateway") {
464
+ if (type === "source") {
465
+ return [
466
+ { id: void 0, type: "source", position: "right" },
467
+ { id: void 0, type: "source", position: "bottom" }
468
+ ];
469
+ }
470
+ return [{ id: void 0, type: "target", position: "left" }];
471
+ }
472
+ if (node.type === "ai-start") {
473
+ return type === "source" ? [{ id: void 0, type: "source", position: "right" }] : [];
474
+ }
475
+ if (node.type === "ai-end") {
476
+ return type === "target" ? [{ id: void 0, type: "target", position: "left" }] : [];
477
+ }
478
+ if (node.type === "ai-llm" || node.type === "ai-prompt" || node.type === "ai-agent" || node.type === "ai-tool" || node.type === "ai-memory") {
479
+ if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
480
+ return [{ id: void 0, type: "target", position: "left" }];
481
+ }
482
+ if (node.type === "ai-condition") {
483
+ if (type === "source") {
484
+ return [
485
+ { id: void 0, type: "source", position: "right" },
486
+ { id: void 0, type: "source", position: "bottom" }
487
+ ];
488
+ }
489
+ return [{ id: void 0, type: "target", position: "left" }];
490
+ }
491
+ if (type === "source") return [{ id: void 0, type: "source", position: "right" }];
492
+ return [{ id: void 0, type: "target", position: "left" }];
493
+ };
395
494
  const handleMouseUp = (event) => {
396
495
  isPanning.value = false;
397
496
  if (isSelecting.value) {
@@ -411,17 +510,37 @@ const handleMouseUp = (event) => {
411
510
  if (targetNode) {
412
511
  const sourceNodeId = updatingEdge.value ? updatingEdge.value.handleType === "source" ? targetNode.id : updatingEdge.value.edge.source : connectionStart.value.nodeId;
413
512
  const targetNodeId = updatingEdge.value ? updatingEdge.value.handleType === "target" ? targetNode.id : updatingEdge.value.edge.target : targetNode.id;
414
- const sourceNode = nodesRef.value.find((n) => n.id === sourceNodeId);
415
- const validationResult = validateConnection(
416
- sourceNode,
417
- nodesRef.value.find((n) => n.id === targetNodeId),
418
- {
419
- source: sourceNodeId,
420
- target: targetNodeId,
421
- sourceHandle: updatingEdge.value && updatingEdge.value.handleType === "source" ? void 0 : updatingEdge.value?.edge.sourceHandle || connectionStart.value?.handleId,
422
- targetHandle: updatingEdge.value && updatingEdge.value.handleType === "target" ? void 0 : updatingEdge.value?.edge.targetHandle
513
+ let dropHandleId = void 0;
514
+ let dropHandleType = void 0;
515
+ const element = document.elementFromPoint(event.clientX, event.clientY);
516
+ const handleEl = element?.closest(".yh-flow-handle");
517
+ if (handleEl) {
518
+ dropHandleId = handleEl.getAttribute("data-handle-id") || void 0;
519
+ dropHandleType = handleEl.getAttribute("data-handle-type") || void 0;
520
+ }
521
+ let finalSourceHandle = void 0;
522
+ let finalTargetHandle = void 0;
523
+ if (updatingEdge.value) {
524
+ const { edge, handleType } = updatingEdge.value;
525
+ if (handleType === "source") {
526
+ finalSourceHandle = dropHandleType === "source" ? dropHandleId : getNodeHandles(targetNode, "source")[0]?.id || void 0;
527
+ finalTargetHandle = edge.targetHandle || void 0;
528
+ } else {
529
+ finalSourceHandle = edge.sourceHandle || void 0;
530
+ finalTargetHandle = dropHandleType === "target" ? dropHandleId : getNodeHandles(targetNode, "target")[0]?.id || void 0;
423
531
  }
424
- );
532
+ } else {
533
+ finalSourceHandle = connectionStart.value?.handleId || void 0;
534
+ finalTargetHandle = dropHandleType === "target" ? dropHandleId : getNodeHandles(targetNode, "target")[0]?.id || void 0;
535
+ }
536
+ const sourceNode = nodesRef.value.find((n) => n.id === sourceNodeId);
537
+ const targetNodeObj = nodesRef.value.find((n) => n.id === targetNodeId);
538
+ const validationResult = validateConnection(sourceNode, targetNodeObj, {
539
+ source: sourceNodeId,
540
+ target: targetNodeId,
541
+ sourceHandle: finalSourceHandle,
542
+ targetHandle: finalTargetHandle
543
+ });
425
544
  if (!validationResult.isValid) {
426
545
  console.warn("Invalid connection:", validationResult.message);
427
546
  isConnecting.value = false;
@@ -430,12 +549,12 @@ const handleMouseUp = (event) => {
430
549
  return;
431
550
  }
432
551
  if (updatingEdge.value) {
433
- const { edge, handleType } = updatingEdge.value;
552
+ const { edge } = updatingEdge.value;
434
553
  const connection = {
435
- source: handleType === "source" ? targetNode.id : edge.source,
436
- target: handleType === "target" ? targetNode.id : edge.target,
437
- sourceHandle: handleType === "source" ? void 0 : edge.sourceHandle,
438
- targetHandle: handleType === "target" ? void 0 : edge.targetHandle
554
+ source: sourceNodeId,
555
+ target: targetNodeId,
556
+ sourceHandle: finalSourceHandle,
557
+ targetHandle: finalTargetHandle
439
558
  };
440
559
  edgesManager.updateEdge(edge.id, connection);
441
560
  emit("edgeUpdate", { edge, connection });
@@ -443,10 +562,10 @@ const handleMouseUp = (event) => {
443
562
  } else {
444
563
  const newEdge = {
445
564
  id: `edge-${Date.now()}`,
446
- source: connectionStart.value.nodeId,
447
- target: targetNode.id,
448
- sourceHandle: connectionStart.value.handleId || void 0,
449
- targetHandle: void 0,
565
+ source: sourceNodeId,
566
+ target: targetNodeId,
567
+ sourceHandle: finalSourceHandle,
568
+ targetHandle: finalTargetHandle,
450
569
  type: "bezier"
451
570
  };
452
571
  edgesManager.addEdge(newEdge);
@@ -806,13 +925,14 @@ watch(
806
925
  { deep: true }
807
926
  );
808
927
  let handleKeyDown;
928
+ let resizeObserver = null;
809
929
  onMounted(() => {
810
930
  document.addEventListener("mousemove", handleMouseMove);
811
931
  document.addEventListener("mouseup", handleMouseUp);
812
932
  if (containerRef.value) {
813
933
  containerWidth.value = containerRef.value.clientWidth;
814
934
  containerHeight.value = containerRef.value.clientHeight;
815
- const resizeObserver = new ResizeObserver((entries) => {
935
+ resizeObserver = new ResizeObserver((entries) => {
816
936
  for (const entry of entries) {
817
937
  containerWidth.value = entry.contentRect.width;
818
938
  containerHeight.value = entry.contentRect.height;
@@ -955,16 +1075,38 @@ onMounted(() => {
955
1075
  }
956
1076
  }
957
1077
  );
1078
+ watch(
1079
+ () => [props.history, props.maxHistory],
1080
+ ([history, maxHistory]) => {
1081
+ removePlugin("history");
1082
+ if (history) {
1083
+ usePlugin(
1084
+ createHistoryPlugin({
1085
+ enabled: true,
1086
+ maxHistory: maxHistory || 50,
1087
+ enableKeyboard: false,
1088
+ onHistoryChange: (canUndo, canRedo) => {
1089
+ emit("historyChange", { canUndo, canRedo });
1090
+ }
1091
+ })
1092
+ );
1093
+ }
1094
+ },
1095
+ { immediate: true }
1096
+ );
958
1097
  if (props.keyboardShortcuts) {
959
1098
  const keyboard = useKeyboard({
960
1099
  enabled: true,
961
1100
  onDelete: () => {
962
1101
  const selectedNodes = nodesRef.value.filter((n) => n.selected);
963
1102
  const selectedEdges = edgesRef.value.filter((e) => e.selected);
964
- selectedNodes.forEach((node) => nodesManager.removeNode(node.id));
965
- selectedEdges.forEach((edge) => edgesManager.removeEdge(edge.id));
966
- emit("update:nodes", nodesRef.value);
967
- emit("update:edges", edgesRef.value);
1103
+ if (selectedNodes.length > 0 || selectedEdges.length > 0) {
1104
+ historyManager.push({ nodes: nodesRef.value, edges: edgesRef.value });
1105
+ selectedNodes.forEach((node) => nodesManager.removeNode(node.id));
1106
+ selectedEdges.forEach((edge) => edgesManager.removeEdge(edge.id));
1107
+ emit("update:nodes", nodesRef.value);
1108
+ emit("update:edges", edgesRef.value);
1109
+ }
968
1110
  },
969
1111
  onUndo: () => historyManager.undo(),
970
1112
  onRedo: () => historyManager.redo(),
@@ -983,6 +1125,10 @@ onMounted(() => {
983
1125
  }
984
1126
  });
985
1127
  onBeforeUnmount(() => {
1128
+ if (resizeObserver) {
1129
+ resizeObserver.disconnect();
1130
+ resizeObserver = null;
1131
+ }
986
1132
  if (props.keyboardShortcuts && handleKeyDown) {
987
1133
  document.removeEventListener("keydown", handleKeyDown);
988
1134
  }
@@ -19,7 +19,9 @@ function useKeyboard(options = {}) {
19
19
  if (!enabled) return;
20
20
  const key = event.key;
21
21
  const ctrl = event.ctrlKey || event.metaKey;
22
- if ((key === "Delete" || key === "Backspace") && !event.target?.toString().includes("Input")) {
22
+ const target = event.target;
23
+ const isEditable = target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable === true);
24
+ if ((key === "Delete" || key === "Backspace") && !isEditable) {
23
25
  event.preventDefault();
24
26
  onDelete?.();
25
27
  }
@@ -13,7 +13,9 @@ export function useKeyboard(options = {}) {
13
13
  if (!enabled) return;
14
14
  const key = event.key;
15
15
  const ctrl = event.ctrlKey || event.metaKey;
16
- if ((key === "Delete" || key === "Backspace") && !event.target?.toString().includes("Input")) {
16
+ const target = event.target;
17
+ const isEditable = target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable === true);
18
+ if ((key === "Delete" || key === "Backspace") && !isEditable) {
17
19
  event.preventDefault();
18
20
  onDelete?.();
19
21
  }
@@ -51,6 +51,44 @@ function getHandlePosition(node, handlePosition = "right", _handleId) {
51
51
  }
52
52
  width = width || 150;
53
53
  height = height || 40;
54
+ if (node.handleBounds && node.handleBounds[handlePosition]) {
55
+ const handles = node.handleBounds[handlePosition] || [];
56
+ if (handles.length > 0) {
57
+ let handleIndex = -1;
58
+ if (_handleId) {
59
+ handleIndex = handles.findIndex(h => h.id === _handleId);
60
+ }
61
+ if (handleIndex === -1) {
62
+ handleIndex = 0;
63
+ }
64
+ const handle = handles[handleIndex];
65
+ if (handle) {
66
+ if (handle.x !== void 0 && handle.y !== void 0) {
67
+ return {
68
+ x: x + handle.x,
69
+ y: y + handle.y
70
+ };
71
+ }
72
+ const N = handles.length;
73
+ const i = handleIndex;
74
+ if (handlePosition === "left" || handlePosition === "right") {
75
+ const hX = handlePosition === "left" ? 0 : width;
76
+ const hY = height / (N + 1) * (i + 1);
77
+ return {
78
+ x: x + hX,
79
+ y: y + hY
80
+ };
81
+ } else {
82
+ const hX = width / (N + 1) * (i + 1);
83
+ const hY = handlePosition === "top" ? 0 : height;
84
+ return {
85
+ x: x + hX,
86
+ y: y + hY
87
+ };
88
+ }
89
+ }
90
+ }
91
+ }
54
92
  switch (handlePosition) {
55
93
  case "top":
56
94
  return {
@@ -1,4 +1,4 @@
1
- import type { Position, EdgeType, NodeStyle } from '../types';
1
+ import type { Position, EdgeType, NodeStyle, NodeHandle } from '../types';
2
2
  export interface EdgePathParams {
3
3
  sourceX: number;
4
4
  sourceY: number;
@@ -24,6 +24,12 @@ export declare function getHandlePosition(node: {
24
24
  width: number;
25
25
  height: number;
26
26
  };
27
+ handleBounds?: {
28
+ top?: NodeHandle[];
29
+ right?: NodeHandle[];
30
+ bottom?: NodeHandle[];
31
+ left?: NodeHandle[];
32
+ };
27
33
  }, handlePosition?: Position, _handleId?: string | null): {
28
34
  x: number;
29
35
  y: number;
@@ -25,6 +25,35 @@ export function getHandlePosition(node, handlePosition = "right", _handleId) {
25
25
  }
26
26
  width = width || 150;
27
27
  height = height || 40;
28
+ if (node.handleBounds && node.handleBounds[handlePosition]) {
29
+ const handles = node.handleBounds[handlePosition] || [];
30
+ if (handles.length > 0) {
31
+ let handleIndex = -1;
32
+ if (_handleId) {
33
+ handleIndex = handles.findIndex((h) => h.id === _handleId);
34
+ }
35
+ if (handleIndex === -1) {
36
+ handleIndex = 0;
37
+ }
38
+ const handle = handles[handleIndex];
39
+ if (handle) {
40
+ if (handle.x !== void 0 && handle.y !== void 0) {
41
+ return { x: x + handle.x, y: y + handle.y };
42
+ }
43
+ const N = handles.length;
44
+ const i = handleIndex;
45
+ if (handlePosition === "left" || handlePosition === "right") {
46
+ const hX = handlePosition === "left" ? 0 : width;
47
+ const hY = height / (N + 1) * (i + 1);
48
+ return { x: x + hX, y: y + hY };
49
+ } else {
50
+ const hX = width / (N + 1) * (i + 1);
51
+ const hY = handlePosition === "top" ? 0 : height;
52
+ return { x: x + hX, y: y + hY };
53
+ }
54
+ }
55
+ }
56
+ }
28
57
  switch (handlePosition) {
29
58
  case "top":
30
59
  return { x: x + width / 2, y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yh-ui/flow",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "description": "YH-UI High-performance Flow Chart Component",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -32,8 +32,8 @@
32
32
  "lint": "eslint ."
33
33
  },
34
34
  "dependencies": {
35
- "@yh-ui/utils": "^1.0.52",
36
- "@yh-ui/hooks": "^1.0.52"
35
+ "@yh-ui/utils": "^1.0.53",
36
+ "@yh-ui/hooks": "^1.0.53"
37
37
  },
38
38
  "devDependencies": {
39
39
  "vue": "^3.5.35",