@likec4/diagram 1.7.1 → 1.7.3

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.
Files changed (69) hide show
  1. package/dist/LikeC4Diagram.d.ts +1 -1
  2. package/dist/LikeC4Diagram.props.d.ts +38 -22
  3. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  4. package/dist/globals.css.d.ts +0 -1
  5. package/dist/hooks/index.d.ts +2 -0
  6. package/dist/{state/useDiagramStore.d.ts → hooks/useDiagramState.d.ts} +2 -3
  7. package/dist/{xyflow/hooks → hooks}/useXYFlow.d.ts +10 -10
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.mjs +755 -401
  10. package/dist/state/DiagramContext.d.ts +74 -30
  11. package/dist/state/diagram-to-xyflow.d.ts +2 -0
  12. package/dist/state/diagramStore.d.ts +71 -22
  13. package/dist/style.css +655 -35
  14. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +3 -0
  15. package/dist/ui/top-left/TopLeftPanel.d.ts +4 -0
  16. package/dist/ui/top-left/_shared.d.ts +5 -0
  17. package/dist/ui/top-left/styles.css.d.ts +3 -0
  18. package/dist/xyflow/XYFlowInner.d.ts +0 -1
  19. package/dist/xyflow/edges/edges.css.d.ts +1 -1
  20. package/dist/xyflow/{hooks/useLayoutConstraints.d.ts → useLayoutConstraints.d.ts} +1 -1
  21. package/dist/xyflow/utils.d.ts +11 -1
  22. package/package.json +6 -6
  23. package/src/LikeC4Diagram.props.ts +66 -32
  24. package/src/LikeC4Diagram.tsx +19 -12
  25. package/src/StaticLikeC4Diagram.tsx +2 -0
  26. package/src/globals.css.ts +0 -2
  27. package/src/hooks/index.ts +2 -0
  28. package/src/{state/useDiagramStore.ts → hooks/useDiagramState.ts} +3 -4
  29. package/src/{xyflow/hooks → hooks}/useXYFlow.ts +1 -1
  30. package/src/index.ts +4 -2
  31. package/src/state/DiagramContext.tsx +15 -7
  32. package/src/state/diagram-to-xyflow.ts +17 -3
  33. package/src/state/diagramStore.ts +73 -38
  34. package/src/ui/DiagramTitlePanel.tsx +1 -1
  35. package/src/ui/DynamicViewWalkthrough.tsx +1 -2
  36. package/src/ui/OptionsPanel.tsx +1 -1
  37. package/src/ui/options/EdgeOptions.tsx +1 -16
  38. package/src/ui/options/NodeOptions.tsx +38 -13
  39. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +131 -0
  40. package/src/ui/top-left/TopLeftPanel.tsx +213 -0
  41. package/src/ui/top-left/_shared.ts +19 -0
  42. package/src/ui/top-left/styles.css.ts +38 -0
  43. package/src/xyflow/FitviewOnDiagramChange.tsx +2 -2
  44. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +87 -59
  45. package/src/xyflow/XYFlow.tsx +7 -8
  46. package/src/xyflow/XYFlowEvents.tsx +39 -19
  47. package/src/xyflow/XYFlowInner.tsx +5 -7
  48. package/src/xyflow/edges/RelationshipEdge.tsx +60 -27
  49. package/src/xyflow/edges/edges.css.ts +29 -25
  50. package/src/xyflow/nodes/compound/CompoundNode.tsx +1 -1
  51. package/src/xyflow/nodes/element/ElementLink.tsx +1 -2
  52. package/src/xyflow/nodes/element/ElementNode.tsx +7 -5
  53. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  54. package/src/xyflow/{hooks/useLayoutConstraints.ts → useLayoutConstraints.ts} +3 -3
  55. package/src/xyflow/utils.ts +25 -1
  56. package/dist/state/hooks.d.ts +0 -4
  57. package/dist/state/index.d.ts +0 -2
  58. package/dist/state/useNavigationHistory.d.ts +0 -5
  59. package/dist/types.d.ts +0 -20
  60. package/dist/ui/BackwardForwardPanel.css.d.ts +0 -1
  61. package/dist/ui/BackwardForwardPanel.d.ts +0 -1
  62. package/dist/xyflow/hooks/index.d.ts +0 -2
  63. package/src/state/hooks.ts +0 -5
  64. package/src/state/index.ts +0 -2
  65. package/src/state/useNavigationHistory.ts +0 -39
  66. package/src/types.ts +0 -22
  67. package/src/ui/BackwardForwardPanel.css.ts +0 -22
  68. package/src/ui/BackwardForwardPanel.tsx +0 -76
  69. package/src/xyflow/hooks/index.ts +0 -3
package/dist/index.mjs CHANGED
@@ -6,41 +6,23 @@ import { useReactFlow, useStoreApi, useNodesData, useStore, applyNodeChanges, ap
6
6
  import clsx from "clsx";
7
7
  import { shallowEqual, deepEqual } from "fast-equals";
8
8
  import { m, AnimatePresence, LazyMotion, domAnimation } from "framer-motion";
9
- import { useContext, useState, useCallback, createContext, useRef, useEffect, memo, useMemo, useId } from "react";
10
- import { MantineContext, Box, ActionIcon, Text as Text$1, HoverCard, HoverCardTarget, UnstyledButton, HoverCardDropdown, Stack, Group, Anchor, CopyButton, Button, Card, CardSection, Spoiler, ButtonGroup, rem, Divider, Space, TooltipGroup, Flex, Tooltip, ColorSwatch, CheckIcon, Select, Slider, SegmentedControl } from "@mantine/core";
11
- import { invariant, hasAtLeast, extractStep, nonNullable, StepEdgeId, getBBoxCenter, nonexhaustive, isAncestor, defaultTheme, ElementShapes } from "@likec4/core";
12
- import { getNodeDimensions, getBoundsOfRects, boxToRect, nodeToRect, getViewportForBounds as getViewportForBounds$1 } from "@xyflow/system";
9
+ import { createContext, useRef, useCallback, useContext, useState, memo, useEffect, useId, useMemo } from "react";
10
+ import { createWithEqualityFn, useStoreWithEqualityFn } from "zustand/traditional";
11
+ import { invariant, hasAtLeast, whereOperatorAsPredicate, extractStep, nonNullable, StepEdgeId, getBBoxCenter, nonexhaustive, isAncestor, defaultTheme, ElementShapes } from "@likec4/core";
12
+ import { useFirstMountState, useCustomCompareEffect, useDebouncedEffect } from "@react-hookz/web";
13
+ import { getNodeDimensions, getBoundsOfRects, boxToRect, getViewportForBounds as getViewportForBounds$1 } from "@xyflow/system";
13
14
  import { DEV } from "esm-env";
14
15
  import { isArray, hasAtLeast as hasAtLeast$1, isNullish, entries, reduce, isTruthy, first, last, clamp, isNumber, isNonNullish, keys, takeWhile } from "remeda";
15
16
  import { subscribeWithSelector, devtools } from "zustand/middleware";
16
17
  import { shallow } from "zustand/shallow";
17
- import { createWithEqualityFn, useStoreWithEqualityFn } from "zustand/traditional";
18
18
  import { Bezier } from "bezier-js";
19
- import { useFirstMountState, useCustomCompareEffect, useDebouncedEffect } from "@react-hookz/web";
19
+ import { MantineContext, Box, ActionIcon as ActionIcon$2, Text as Text$1, HoverCard, HoverCardTarget, UnstyledButton, HoverCardDropdown, Stack, Group, Anchor, CopyButton, Button, Card, CardSection, Spoiler, ButtonGroup, rem, Divider, Space, TooltipGroup, Flex, Tooltip as Tooltip$2, ColorSwatch, CheckIcon, Select, Slider, SegmentedControl, Popover, PopoverTarget, PopoverDropdown, FloatingIndicator, Notification, Code } from "@mantine/core";
20
+ import { useHotkeys, useLocalStorage } from "@mantine/hooks";
20
21
  import { assignInlineVars } from "@vanilla-extract/dynamic";
21
22
  import { line, curveCatmullRomOpen } from "d3-shape";
22
23
  import { Solver, Operator, Expression, Strength, Variable } from "@lume/kiwi";
23
- import { IconZoomScan, IconLink, IconChevronLeft, IconChevronRight, IconMenu, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled } from "@tabler/icons-react";
24
- import { useLocalStorage, useHotkeys } from "@mantine/hooks";
25
- var rootClassName = "likec4-diagram-root", cssReactFlow = "_2h8g8h0", cssDisablePan = "_2h8g8h1", cssTransparentBg = "_2h8g8h2", cssNoControls = "_2h8g8h3";
26
- function EnsureMantine({ children }) {
27
- if (!useContext(MantineContext))
28
- throw new Error("LikeC4Diagram must be a child of MantineProvider");
29
- return /* @__PURE__ */ jsx(Fragment, { children });
30
- }
31
- function useSetState(initialState) {
32
- const [state, _setState] = useState(initialState), setState = useCallback(
33
- (statePartial) => _setState((current) => {
34
- const next = {
35
- ...current,
36
- ...typeof statePartial == "function" ? statePartial(current) : statePartial
37
- };
38
- return shallowEqual(current, next) ? current : next;
39
- }),
40
- []
41
- );
42
- return [state, setState];
43
- }
24
+ import { IconZoomScan, IconLink, IconMenu, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled, IconFileSymlink, IconLayoutDashboard, IconArrowBigDownLines, IconFocusCentered, IconMenu2, IconChevronLeft, IconChevronRight, IconAlertTriangle } from "@tabler/icons-react";
25
+ var rootClassName = "likec4-diagram-root";
44
26
  const noop = () => {
45
27
  }, depsShallowEqual = (d1, d2) => {
46
28
  if (d1 === d2)
@@ -77,6 +59,12 @@ const useXYStoreApi = useStoreApi, ZIndexes = {
77
59
  function toDomPrecision(v) {
78
60
  return v === null ? 0.01 : Math.round(v * 100) / 100;
79
61
  }
62
+ const nodeToRect = (nd) => ({
63
+ x: nd.internals.positionAbsolute.x,
64
+ y: nd.internals.positionAbsolute.y,
65
+ width: nd.width ?? nd.data.element.width,
66
+ height: nd.height ?? nd.data.element.height
67
+ }), isInside = (test, target) => test.x >= target.x && test.y >= target.y && test.x + test.width <= target.x + target.width && test.y + test.height <= target.y + target.height;
80
68
  function bezierControlPoints(diagramEdge) {
81
69
  let [start, ...bezierPoints] = diagramEdge.points;
82
70
  invariant(start, "start should be defined");
@@ -104,7 +92,15 @@ function diagramViewToXYFlowData(view, opts) {
104
92
  const isDynamicView = view.__ === "dynamic", xynodes = [], xyedges = [], nodeLookup = /* @__PURE__ */ new Map(), traverse = view.nodes.reduce(
105
93
  (acc, node) => (nodeLookup.set(node.id, node), node.parent || acc.push({ node, parent: null }), acc),
106
94
  new Array()
107
- ), ns = "", nodeById = (id) => nonNullable(nodeLookup.get(id), `Node not found: ${id}`);
95
+ );
96
+ let visiblePredicate = (_nodeOrEdge) => !0;
97
+ if (opts.where)
98
+ try {
99
+ visiblePredicate = whereOperatorAsPredicate(opts.where);
100
+ } catch (e) {
101
+ console.error("Error in where filter:", e);
102
+ }
103
+ const ns = "", nodeById = (id) => nonNullable(nodeLookup.get(id), `Node not found: ${id}`);
108
104
  let next;
109
105
  for (; next = traverse.shift(); ) {
110
106
  const { node, parent } = next;
@@ -127,9 +123,9 @@ function diagramViewToXYFlowData(view, opts) {
127
123
  deletable: !1,
128
124
  position,
129
125
  zIndex: isCompound ? ZIndexes.Compound : ZIndexes.Element,
130
- hidden: !1,
131
126
  width: node.width,
132
127
  height: node.height,
128
+ hidden: !visiblePredicate(node),
133
129
  // parentId: parent ? ns + parent.id : null,
134
130
  ...parent && {
135
131
  parentId: ns + parent.id
@@ -148,6 +144,7 @@ function diagramViewToXYFlowData(view, opts) {
148
144
  target: ns + target,
149
145
  zIndex: ZIndexes.Edge,
150
146
  selectable: opts.selectable,
147
+ hidden: !visiblePredicate(edge),
151
148
  deletable: !1,
152
149
  data: {
153
150
  edge,
@@ -190,6 +187,7 @@ function createDiagramStore(props) {
190
187
  xynodes,
191
188
  xyedges
192
189
  } = diagramViewToXYFlowData(props.view, {
190
+ where: props.whereFilter,
193
191
  draggable: props.nodesDraggable,
194
192
  selectable: props.nodesSelectable
195
193
  });
@@ -224,6 +222,7 @@ function createDiagramStore(props) {
224
222
  xyflow,
225
223
  xystore,
226
224
  dimmed: dimmed2,
225
+ whereFilter,
227
226
  view: current,
228
227
  lastOnNavigate,
229
228
  navigationHistory,
@@ -239,10 +238,6 @@ function createDiagramStore(props) {
239
238
  xyedges: xyedges2,
240
239
  xynodes: xynodes2
241
240
  } = get();
242
- if (shallowEqual(current, nextView)) {
243
- DEV && console.debug("store: skip updateView");
244
- return;
245
- }
246
241
  viewSyncDebounceTimeout !== null && (clearTimeout(viewSyncDebounceTimeout), viewSyncDebounceTimeout = null);
247
242
  const isSameView = current.id === nextView.id;
248
243
  if (isSameView) {
@@ -289,24 +284,27 @@ function createDiagramStore(props) {
289
284
  lastClickedEdgeId = null, lastClickedNodeId = null, hoveredEdgeId = null, hoveredNodeId = null, focusedNodeId = null, activeDynamicViewStep = null, dimmed2 = EmptyStringSet;
290
285
  }
291
286
  const update = diagramViewToXYFlowData(nextView, {
287
+ where: whereFilter,
292
288
  draggable: nodesDraggable,
293
289
  selectable: nodesSelectable
294
290
  });
295
291
  update.xynodes = update.xynodes.map((update2) => {
296
292
  const existing = xynodes2.find((n) => n.id === update2.id);
297
- return existing && existing.type === update2.type && deepEqual(existing.parentId ?? null, update2.parentId ?? null) ? deepEqual(existing.data.element, update2.data.element) ? existing : {
293
+ return existing && existing.type === update2.type && deepEqual(existing.parentId ?? null, update2.parentId ?? null) ? existing.hidden === update2.hidden && existing.width === update2.width && existing.height === update2.height && deepEqual(existing.position, update2.position) && deepEqual(existing.data.element, update2.data.element) ? existing : {
298
294
  ...existing,
299
295
  ...update2
300
296
  } : update2;
301
297
  }), isSameView && !nextView.hasLayoutDrift && (update.xyedges = update.xyedges.map((update2) => {
302
298
  const existing = xyedges2.find((n) => n.id === update2.id);
303
- return existing ? deepEqual(existing.data, update2.data) ? existing : {
299
+ return existing ? existing.hidden === update2.hidden && deepEqual(existing.data.label, update2.data.label) && deepEqual(existing.data.controlPoints, update2.data.controlPoints) && deepEqual(existing.data.edge, update2.data.edge) ? existing : {
304
300
  ...existing,
305
- ...update2
301
+ ...update2,
302
+ data: {
303
+ ...existing.data,
304
+ ...update2.data
305
+ }
306
306
  } : update2;
307
- }));
308
- const hasChanges = !isSameView || !shallowEqual(update.xynodes, xynodes2) || !shallowEqual(update.xyedges, xyedges2);
309
- set(
307
+ })), set(
310
308
  {
311
309
  isDynamicView: nextView.__ === "dynamic",
312
310
  viewSyncDebounceTimeout,
@@ -321,8 +319,8 @@ function createDiagramStore(props) {
321
319
  navigationHistory,
322
320
  navigationHistoryIndex,
323
321
  dimmed: dimmed2,
324
- xynodes: hasChanges ? update.xynodes : xynodes2,
325
- xyedges: hasChanges ? update.xyedges : xyedges2
322
+ xynodes: !isSameView || !shallowEqual(update.xynodes, xynodes2) ? update.xynodes : xynodes2,
323
+ xyedges: !isSameView || !shallowEqual(update.xyedges, xyedges2) ? update.xyedges : xyedges2
326
324
  },
327
325
  noReplace,
328
326
  isSameView ? "update-view [same]" : "update-view [another]"
@@ -439,10 +437,7 @@ function createDiagramStore(props) {
439
437
  },
440
438
  triggerSaveManualLayout: () => {
441
439
  const { xyflow, view, onChange, xystore, viewSyncDebounceTimeout } = get();
442
- if (viewSyncDebounceTimeout !== null && (clearTimeout(viewSyncDebounceTimeout), set({ viewSyncDebounceTimeout: null })), !onChange) {
443
- DEV && console.debug("ignore triggerSaveManualLayout, as no onChange handler is set");
444
- return;
445
- }
440
+ viewSyncDebounceTimeout !== null && (clearTimeout(viewSyncDebounceTimeout), set({ viewSyncDebounceTimeout: null }));
446
441
  const { nodeLookup } = xystore.getState(), movedNodes = new StringSet();
447
442
  let bounds = {
448
443
  x: 0,
@@ -471,7 +466,15 @@ function createDiagramStore(props) {
471
466
  points: data.edge.points
472
467
  };
473
468
  (_a = data.label) != null && _a.bbox && (_updated.labelBBox = (_b = data.label) == null ? void 0 : _b.bbox), data.edge.labelBBox && (_updated.labelBBox ?? (_updated.labelBBox = data.edge.labelBBox)), controlPoints && hasAtLeast$1(controlPoints, 1) && (_updated.controlPoints = controlPoints), !sourceOrTargetMoved && data.edge.dotpos && (_updated.dotpos = data.edge.dotpos);
474
- const allX = [...data.edge.points.map((p) => p[0]), ...(controlPoints ?? []).map((p) => p.x)], allY = [...data.edge.points.map((p) => p[1]), ...(controlPoints ?? []).map((p) => p.y)], rect = boxToRect({
469
+ const allX = [
470
+ ...data.edge.points.map((p) => p[0]),
471
+ ...(controlPoints ?? []).map((p) => p.x),
472
+ ..._updated.labelBBox ? [_updated.labelBBox.x, _updated.labelBBox.x + _updated.labelBBox.width] : []
473
+ ], allY = [
474
+ ...data.edge.points.map((p) => p[1]),
475
+ ...(controlPoints ?? []).map((p) => p.y),
476
+ ..._updated.labelBBox ? [_updated.labelBBox.y, _updated.labelBBox.y + _updated.labelBBox.height] : []
477
+ ], rect = boxToRect({
475
478
  x: Math.floor(Math.min(...allX)),
476
479
  y: Math.floor(Math.min(...allY)),
477
480
  x2: Math.ceil(Math.max(...allX)),
@@ -488,14 +491,19 @@ function createDiagramStore(props) {
488
491
  ...bounds
489
492
  }
490
493
  };
491
- DEV && console.debug("triggerSaveManualLayout", change), onChange({ change });
494
+ DEV && console.debug("triggerSaveManualLayout", change), isInside(bounds, view.bounds) || set(
495
+ {
496
+ view: {
497
+ ...view,
498
+ bounds
499
+ }
500
+ },
501
+ noReplace,
502
+ "update view bounds"
503
+ ), onChange == null || onChange({ change });
492
504
  },
493
505
  scheduleSaveManualLayout: () => {
494
- let { viewSyncDebounceTimeout, onChange } = get();
495
- if (!onChange) {
496
- DEV && console.debug("ignore triggerSaveManualLayout, as no onChange handler is set");
497
- return;
498
- }
506
+ let { viewSyncDebounceTimeout } = get();
499
507
  viewSyncDebounceTimeout && clearTimeout(viewSyncDebounceTimeout), set(
500
508
  {
501
509
  viewSyncDebounceTimeout: setTimeout(() => {
@@ -508,13 +516,13 @@ function createDiagramStore(props) {
508
516
  );
509
517
  },
510
518
  triggerOnNavigateTo: (xynodeId, event) => {
511
- const { view, xynodes: xynodes2, onNavigateTo } = get();
519
+ const { view, xynodes: xynodes2, onNavigateTo, cancelSaveManualLayout } = get();
512
520
  if (!onNavigateTo)
513
521
  return;
514
522
  const xynode = xynodes2.find(({ id }) => id === xynodeId);
515
523
  invariant(xynode, `xynode not found: ${xynodeId}`);
516
524
  const element = view.nodes.find(({ id }) => id === xynodeId);
517
- invariant(element == null ? void 0 : element.navigateTo, `node is not navigable: ${xynodeId}`), set(
525
+ invariant(element == null ? void 0 : element.navigateTo, `node is not navigable: ${xynodeId}`), cancelSaveManualLayout(), set(
518
526
  {
519
527
  lastClickedNodeId: xynodeId,
520
528
  lastOnNavigate: {
@@ -659,6 +667,7 @@ function DiagramContextProvider({
659
667
  view,
660
668
  className,
661
669
  keepAspectRatio,
670
+ whereFilter,
662
671
  ...props
663
672
  }) {
664
673
  const containerRef = useRef(null), xystore = useXYStoreApi(), xyflow = useXYFlow(), store = useRef(), getContainer = useCallback(() => containerRef.current, [containerRef]);
@@ -667,6 +676,7 @@ function DiagramContextProvider({
667
676
  xyflow,
668
677
  view,
669
678
  getContainer,
679
+ whereFilter: structuredClone(whereFilter),
670
680
  ...props
671
681
  })), useUpdateEffect(
672
682
  () => {
@@ -682,10 +692,11 @@ function DiagramContextProvider({
682
692
  [props]
683
693
  ), useUpdateEffect(
684
694
  () => {
685
- var _a;
686
- (_a = store.current) == null || _a.getState().updateView(view);
695
+ const current = store.current;
696
+ invariant(current, "DiagramContext.store.current is not defined"), deepEqual(whereFilter, current.getState().whereFilter) || current.setState({ whereFilter: structuredClone(whereFilter) }, !1, "update where filter"), current.getState().updateView(view);
687
697
  },
688
- [view]
698
+ [view, whereFilter],
699
+ (a, b) => shallowEqual(a[0], b[0]) && deepEqual(a[1], b[1])
689
700
  ), /* @__PURE__ */ jsx(
690
701
  "div",
691
702
  {
@@ -713,6 +724,12 @@ function useDiagramStoreApi() {
713
724
  throw new Error("useDiagramStoreApi could be used only inside DiagramContext");
714
725
  return store;
715
726
  }
727
+ var cssReactFlow = "_2h8g8h0", cssDisablePan = "_2h8g8h1", cssTransparentBg = "_2h8g8h2", cssNoControls = "_2h8g8h3";
728
+ function EnsureMantine({ children }) {
729
+ if (!useContext(MantineContext))
730
+ throw new Error("LikeC4Diagram must be a child of MantineProvider");
731
+ return /* @__PURE__ */ jsx(Fragment, { children });
732
+ }
716
733
  function selectXYFlowSize(state) {
717
734
  return `${Math.round(state.width)}:${Math.round(state.height)}`;
718
735
  }
@@ -756,51 +773,84 @@ function FitViewOnDiagramChange() {
756
773
  50
757
774
  ), viewportNotMoved && !requiresFit ? /* @__PURE__ */ jsx(FitViewOnViewportResize, { diagramApi }) : null;
758
775
  }
776
+ function useSetState(initialState) {
777
+ const [state, _setState] = useState(initialState), setState = useCallback(
778
+ (statePartial) => _setState((current) => {
779
+ const next = {
780
+ ...current,
781
+ ...typeof statePartial == "function" ? statePartial(current) : statePartial
782
+ };
783
+ return shallowEqual(current, next) ? current : next;
784
+ }),
785
+ []
786
+ );
787
+ return [state, setState];
788
+ }
759
789
  function SelectEdgesOnNodeFocus() {
760
- const diagramStore = useDiagramStoreApi();
761
- return useEffect(
762
- () => diagramStore.subscribe((s) => s.focusedNodeId, (focusedNodeId) => {
763
- if (!focusedNodeId)
764
- return;
765
- const edgeChanges = [], nodeChanges = [], {
766
- edgeLookup,
767
- nodeLookup,
768
- width,
769
- height,
770
- panZoom,
771
- transform,
772
- triggerNodeChanges,
773
- triggerEdgeChanges
774
- } = diagramStore.getState().xystore.getState();
775
- let focusBounds = nodeToRect(nonNullable(nodeLookup.get(focusedNodeId)));
776
- for (const [, edge] of edgeLookup) {
777
- if (edge.source === focusedNodeId || edge.target === focusedNodeId) {
778
- const anotherNodeId = edge.source === focusedNodeId ? edge.target : edge.source, nd = nodeToRect(nonNullable(nodeLookup.get(anotherNodeId)));
779
- focusBounds = getBoundsOfRects(focusBounds, nd);
780
- }
781
- edge.selected && edgeChanges.push({
782
- id: edge.id,
783
- type: "select",
784
- selected: !1
785
- });
790
+ const diagramStore = useDiagramStoreApi(), focusedNodeId = useDiagramState((s) => s.focusedNodeId);
791
+ return useUpdateEffect(() => {
792
+ if (!focusedNodeId) {
793
+ diagramStore.getState().xystore.getState().resetSelectedElements();
794
+ return;
795
+ }
796
+ const container2 = diagramStore.getState().getContainer();
797
+ if (!container2)
798
+ return;
799
+ const { width, height } = container2.getBoundingClientRect(), edgeChanges = [], nodeChanges = [], {
800
+ edgeLookup,
801
+ nodeLookup,
802
+ panZoom,
803
+ transform,
804
+ triggerNodeChanges,
805
+ triggerEdgeChanges
806
+ } = diagramStore.getState().xystore.getState();
807
+ let focusBounds = nodeToRect(nonNullable(nodeLookup.get(focusedNodeId)));
808
+ for (const [, edge] of edgeLookup) {
809
+ if (edge.source === focusedNodeId || edge.target === focusedNodeId) {
810
+ const anotherNodeId = edge.source === focusedNodeId ? edge.target : edge.source, nd = nonNullable(nodeLookup.get(anotherNodeId));
811
+ focusBounds = getBoundsOfRects(focusBounds, nodeToRect(nd));
786
812
  }
787
- for (const [, node] of nodeLookup)
788
- node.selected && node.id !== focusedNodeId && nodeChanges.push({
789
- id: node.id,
790
- type: "select",
791
- selected: !1
792
- });
793
- nodeChanges.push({
794
- id: focusedNodeId,
813
+ edge.selected && edgeChanges.push({
814
+ id: edge.id,
795
815
  type: "select",
796
- selected: !0
797
- }), edgeChanges.length > 0 && triggerEdgeChanges(edgeChanges), triggerNodeChanges(nodeChanges);
798
- const maxZoom = Math.max(1, transform[2]), viewport = getViewportForBounds$1(focusBounds, width, height, MinZoom, maxZoom, 0.2);
799
- panZoom == null || panZoom.setViewport(viewport, {
800
- duration: 350
816
+ selected: !1
801
817
  });
802
- }),
803
- [diagramStore]
818
+ }
819
+ for (const [, node] of nodeLookup)
820
+ node.selected && node.id !== focusedNodeId && nodeChanges.push({
821
+ id: node.id,
822
+ type: "select",
823
+ selected: !1
824
+ });
825
+ nodeChanges.push({
826
+ id: focusedNodeId,
827
+ type: "select",
828
+ selected: !0
829
+ }), edgeChanges.length > 0 && triggerEdgeChanges(edgeChanges), triggerNodeChanges(nodeChanges);
830
+ const maxZoom = Math.max(1, transform[2]), viewport = getViewportForBounds$1(
831
+ {
832
+ x: focusBounds.x - 16,
833
+ y: focusBounds.y - 16,
834
+ width: focusBounds.width + 32,
835
+ height: focusBounds.height + 32
836
+ },
837
+ width,
838
+ height,
839
+ 0.1,
840
+ maxZoom,
841
+ 0
842
+ );
843
+ panZoom == null || panZoom.setViewport(viewport, {
844
+ duration: 350
845
+ });
846
+ }, [focusedNodeId]), useHotkeys(
847
+ focusedNodeId !== null ? [
848
+ ["Escape", (e) => {
849
+ e.stopImmediatePropagation();
850
+ const { xystore, fitDiagram } = diagramStore.getState();
851
+ fitDiagram(), xystore.getState().resetSelectedElements();
852
+ }, { preventDefault: !0 }]
853
+ ] : []
804
854
  ), null;
805
855
  }
806
856
  const Open = (props) => /* @__PURE__ */ jsx(
@@ -962,7 +1012,7 @@ const Open = (props) => /* @__PURE__ */ jsx(
962
1012
  Dot,
963
1013
  ODot
964
1014
  };
965
- var container$4 = "bexn7q1", dimmed$2 = "bexn7q2", edgePathBg = "bexn7q3", markerContext = "bexn7q4", controlPoint = "bexn7q5", controlDragging = "bexn7q6", cssEdgePath = "bexn7q8", varLabelX = "var(--bexn7q9)", varLabelY = "var(--bexn7qa)", edgeLabel = "bexn7qc", stepEdgeNumber = "bexn7qd", edgeLabelText = "bexn7qe";
1015
+ var container$4 = "bexn7q1", dimmed$2 = "bexn7q2", edgePathBg = "bexn7q3", markerContext = "bexn7q4", controlPoint = "bexn7q5", controlDragging = "bexn7q6", cssEdgePath = "bexn7q8", stepEdgeNumber = "bexn7q9", varLabelX = "var(--bexn7qa)", varLabelY = "var(--bexn7qb)", edgeLabel = "bexn7qd", edgeLabelText = "bexn7qe";
966
1016
  function getNodeIntersectionFromCenterToPoint(intersectionNode, { x: x1, y: y1 }) {
967
1017
  const {
968
1018
  width: intersectionNodeWidth,
@@ -1003,7 +1053,7 @@ function bezierPath(bezierSpline) {
1003
1053
  }
1004
1054
  return invariant(points.length === 0, "all points should be consumed"), path;
1005
1055
  }
1006
- const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1056
+ const isSame = (a, b) => Math.abs(a - b) < 2.5, isSamePoint = (a, b) => {
1007
1057
  const [ax, ay] = isArray(a) ? a : [a.x, a.y], [bx, by] = isArray(b) ? b : [b.x, b.y];
1008
1058
  return isSame(ax, bx) && isSame(ay, by);
1009
1059
  }, sameControlPoints = (a, b) => a === b ? !0 : !a || !b || a.length !== b.length ? !1 : a.every((ap, i) => isSamePoint(ap, b[i])), isEqualProps$2 = (prev, next) => prev.id === next.id && deepEqual(prev.source, next.source) && deepEqual(prev.target, next.target) && deepEqual(prev.selected ?? !1, next.selected ?? !1) && isSame(prev.sourceX, next.sourceX) && isSame(prev.sourceY, next.sourceY) && isSame(prev.targetX, next.targetX) && isSame(prev.targetY, next.targetY) && deepEqual(prev.data.stepNum, next.data.stepNum) && deepEqual(prev.data.label, next.data.label) && sameControlPoints(prev.data.controlPoints, next.data.controlPoints) && deepEqual(prev.data.edge, next.data.edge), curve = line().curve(curveCatmullRomOpen).x((d) => d.x).y((d) => d.y), RelationshipEdge = /* @__PURE__ */ memo(function({
@@ -1019,11 +1069,12 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1019
1069
  target,
1020
1070
  interactionWidth
1021
1071
  }) {
1022
- const [isControlPointDragging, setIsControlPointDragging] = useState(!1), diagramStore = useDiagramStoreApi(), xyflowStore = useXYStoreApi(), { isActive, isEdgePathEditable, isHovered, isDimmed } = useDiagramState((s) => ({
1023
- isEdgePathEditable: s.readonly !== !0 && s.experimentalEdgeEditing === !0,
1072
+ const [isControlPointDragging, setIsControlPointDragging] = useState(!1), diagramStore = useDiagramStoreApi(), xyflowStore = useXYStoreApi(), { isActive, isEdgePathEditable, isHovered, isDimmed, hasOpenSourceRelation } = useDiagramState((s) => ({
1073
+ isEdgePathEditable: s.readonly !== !0 && s.experimentalEdgeEditing === !0 && s.focusedNodeId === null && s.activeDynamicViewStep === null,
1024
1074
  isActive: s.focusedNodeId === source || s.focusedNodeId === target || s.activeDynamicViewStep !== null && s.activeDynamicViewStep === data.stepNum,
1025
1075
  isHovered: s.hoveredEdgeId === id,
1026
- isDimmed: s.dimmed.has(id)
1076
+ isDimmed: s.dimmed.has(id),
1077
+ hasOpenSourceRelation: !!s.onOpenSourceRelation
1027
1078
  })), { nodeLookup, edgeLookup } = xyflowStore.getState(), sourceNode = nonNullable(nodeLookup.get(source), `source node ${source} not found`), targetNode = nonNullable(nodeLookup.get(target), `target node ${target} not found`), isModified = isTruthy(data.controlPoints) || !isSamePoint(sourceNode.internals.positionAbsolute, sourceNode.data.element.position) || !isSamePoint(targetNode.internals.positionAbsolute, targetNode.data.element.position), {
1028
1079
  label,
1029
1080
  edge: {
@@ -1045,7 +1096,6 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1045
1096
  });
1046
1097
  let edgePath;
1047
1098
  if (isModified) {
1048
- labelX = labelPos.x, labelY = labelPos.y;
1049
1099
  const sourceCenterPos = { x: sourceX, y: sourceY }, targetCenterPos = { x: targetX, y: targetY }, points = diagramEdge.dir === "back" ? [
1050
1100
  targetCenterPos,
1051
1101
  getNodeIntersectionFromCenterToPoint(targetNode, first(controlPoints) ?? sourceCenterPos),
@@ -1084,9 +1134,9 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1084
1134
  });
1085
1135
  },
1086
1136
  [labelPos],
1087
- 100,
1137
+ 50,
1088
1138
  300
1089
- );
1139
+ ), (isModified || isControlPointDragging) && (labelX = labelPos.x, labelY = labelPos.y);
1090
1140
  const onControlPointerDown = (index, e) => {
1091
1141
  const { domNode } = xyflowStore.getState();
1092
1142
  if (!domNode || e.pointerType !== "mouse")
@@ -1105,22 +1155,35 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1105
1155
  e.stopPropagation();
1106
1156
  let hasMoved = !1, pointer = { x: e.clientX, y: e.clientY };
1107
1157
  const onPointerMove = (e2) => {
1108
- if (!isSamePoint(pointer, [e2.clientX, e2.clientY])) {
1158
+ if (hasMoved || !isSamePoint(pointer, [e2.clientX, e2.clientY])) {
1109
1159
  setIsControlPointDragging(!0), hasMoved = !0, pointer = { x: e2.clientX, y: e2.clientY };
1110
- const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: !1 }), newControlPoints = controlPoints.slice();
1111
- newControlPoints[index] = {
1112
- x: Math.round(x),
1113
- y: Math.round(y)
1114
- }, xyflow.updateEdgeData(id, { controlPoints: newControlPoints });
1160
+ const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: !1 });
1161
+ xyflow.updateEdgeData(id, (xyedge) => {
1162
+ const cp = (xyedge.data.controlPoints ?? controlPoints).slice();
1163
+ return cp[index] = {
1164
+ x: Math.round(x),
1165
+ y: Math.round(y)
1166
+ }, {
1167
+ controlPoints: cp
1168
+ };
1169
+ });
1115
1170
  }
1116
- }, onPointerUp = () => {
1117
- domNode.removeEventListener("pointermove", onPointerMove), domNode.removeEventListener("pointerup", onPointerUp), (hasMoved || wasCanceled) && diagramStore.getState().scheduleSaveManualLayout(), setIsControlPointDragging(!1);
1171
+ e2.stopPropagation();
1172
+ }, onPointerUp = (e2) => {
1173
+ domNode.removeEventListener("pointermove", onPointerMove, {
1174
+ capture: !0
1175
+ }), hasMoved && e2.stopPropagation(), (hasMoved || wasCanceled) && diagramStore.getState().scheduleSaveManualLayout(), setIsControlPointDragging(!1);
1118
1176
  };
1119
- domNode.addEventListener("pointermove", onPointerMove), domNode.addEventListener("pointerup", onPointerUp, { once: !0 });
1177
+ domNode.addEventListener("pointermove", onPointerMove, {
1178
+ capture: !0
1179
+ }), domNode.addEventListener("pointerup", onPointerUp, {
1180
+ once: !0,
1181
+ capture: !0
1182
+ });
1120
1183
  };
1121
1184
  let markerStartName = toMarker(diagramEdge.tail), markerEndName = toMarker(diagramEdge.head ?? "normal");
1122
1185
  diagramEdge.dir === "back" && ([markerStartName, markerEndName] = [markerEndName, markerStartName]);
1123
- const MarkerStart = markerStartName ? EdgeMarkers[markerStartName] : null, MarkerEnd = markerEndName ? EdgeMarkers[markerEndName] : null, labelZIndex = 1 + (isHovered ? ZIndexes.Element : edgeLookup.get(id).zIndex ?? ZIndexes.Edge);
1186
+ const MarkerStart = markerStartName ? EdgeMarkers[markerStartName] : null, MarkerEnd = markerEndName ? EdgeMarkers[markerEndName] : null, labelZIndex = 1 + (isHovered ? ZIndexes.Element : edgeLookup.get(id).zIndex ?? ZIndexes.Edge), relationId = first(data.edge.relations), showOpenSourceRelation = hasOpenSourceRelation && relationId !== void 0;
1124
1187
  return /* @__PURE__ */ jsxs(
1125
1188
  "g",
1126
1189
  {
@@ -1206,13 +1269,17 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1206
1269
  zIndex: labelZIndex,
1207
1270
  isHovered,
1208
1271
  isActive,
1209
- isStepEdge
1272
+ isStepEdge,
1273
+ onClick: showOpenSourceRelation && ((e) => {
1274
+ var _a, _b;
1275
+ e.stopPropagation(), (_b = (_a = diagramStore.getState()).onOpenSourceRelation) == null || _b.call(_a, relationId);
1276
+ })
1210
1277
  }
1211
1278
  )
1212
1279
  ]
1213
1280
  }
1214
1281
  );
1215
- }, isEqualProps$2), EdgeLabel = memo(({
1282
+ }, isEqualProps$2), EdgeLabel = ({
1216
1283
  isDimmed,
1217
1284
  color,
1218
1285
  isModified,
@@ -1224,7 +1291,8 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1224
1291
  stepNum,
1225
1292
  isHovered,
1226
1293
  isActive,
1227
- zIndex
1294
+ zIndex,
1295
+ onClick
1228
1296
  }) => /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsxs(
1229
1297
  Box,
1230
1298
  {
@@ -1252,162 +1320,17 @@ const isSame = (a, b) => Math.abs(a - b) < 3.1, isSamePoint = (a, b) => {
1252
1320
  "data-edge-hovered": isHovered,
1253
1321
  "data-edge-active": isActive
1254
1322
  },
1323
+ onClick: onClick || void 0,
1255
1324
  children: [
1256
1325
  stepNum !== null && /* @__PURE__ */ jsx(Box, { className: stepEdgeNumber, children: stepNum }),
1257
1326
  isTruthy(label == null ? void 0 : label.text) && /* @__PURE__ */ jsx(Box, { className: edgeLabelText, children: label.text })
1258
1327
  ]
1259
1328
  }
1260
- ) }), deepEqual);
1261
- class Rect {
1262
- constructor() {
1263
- __publicField(this, "id");
1264
- __publicField(this, "minX", new Variable());
1265
- __publicField(this, "minY", new Variable());
1266
- __publicField(this, "maxX", new Variable());
1267
- __publicField(this, "maxY", new Variable());
1268
- }
1269
- get x() {
1270
- return this.minX;
1271
- }
1272
- get y() {
1273
- return this.minY;
1274
- }
1275
- get positionAbsolute() {
1276
- return {
1277
- x: this.x.value(),
1278
- y: this.y.value()
1279
- };
1280
- }
1281
- get dimensions() {
1282
- return {
1283
- width: this.maxX.value() - this.minX.value(),
1284
- height: this.maxY.value() - this.minY.value()
1285
- };
1286
- }
1287
- // Position relative to parent
1288
- get position() {
1289
- const positionAbsolute = this.positionAbsolute;
1290
- if (!this.parent)
1291
- return positionAbsolute;
1292
- const parentPosition = this.parent.positionAbsolute;
1293
- return {
1294
- x: positionAbsolute.x - parentPosition.x,
1295
- y: positionAbsolute.y - parentPosition.y
1296
- };
1297
- }
1298
- }
1299
- class Compound extends Rect {
1300
- constructor(solver, xynode, parent = null) {
1301
- super();
1302
- __publicField(this, "children", []);
1303
- this.solver = solver, this.parent = parent, this.id = xynode.id, solver.createConstraint(this.maxX, Operator.Ge, this.minX), solver.createConstraint(this.maxY, Operator.Ge, this.minY), parent && parent.addChild(this);
1304
- }
1305
- addChild(rect) {
1306
- this.children.push(rect);
1307
- const leftPadding = new Expression(
1308
- rect.minX,
1309
- [-1, this.minX]
1310
- ), { weak } = Strength;
1311
- this.solver.createConstraint(leftPadding, Operator.Ge, 40), this.solver.createConstraint(leftPadding, Operator.Eq, 41, weak);
1312
- const topPadding = new Expression(
1313
- rect.minY,
1314
- [-1, this.minY]
1315
- );
1316
- this.solver.createConstraint(topPadding, Operator.Ge, 54), this.solver.createConstraint(topPadding, Operator.Eq, 55, weak);
1317
- const rightPadding = new Expression(
1318
- this.maxX,
1319
- [-1, rect.maxX]
1320
- );
1321
- this.solver.createConstraint(rightPadding, Operator.Ge, 40), this.solver.createConstraint(rightPadding, Operator.Eq, 41, weak);
1322
- const bottomPadding = new Expression(
1323
- this.maxY,
1324
- [-1, rect.maxY]
1325
- );
1326
- this.solver.createConstraint(bottomPadding, Operator.Ge, 40), this.solver.createConstraint(bottomPadding, Operator.Eq, 41, weak);
1327
- }
1328
- }
1329
- class Leaf extends Rect {
1330
- constructor(solver, xynode, parent = null, isEditing = !1) {
1331
- super(), this.parent = parent, this.isEditing = isEditing, this.id = xynode.id;
1332
- const x = Math.ceil(xynode.internals.positionAbsolute.x), y = Math.ceil(xynode.internals.positionAbsolute.y), { width, height } = getNodeDimensions(xynode);
1333
- if (this.maxX = new Expression(this.minX, Math.ceil(width)), this.maxY = new Expression(this.minY, Math.ceil(height)), isEditing) {
1334
- const superStrong = Strength.create(100, 0, 0);
1335
- solver.addEditVariable(this.minX, superStrong), solver.addEditVariable(this.minY, superStrong), solver.suggestValue(this.minX, x), solver.suggestValue(this.minY, y);
1336
- } else {
1337
- const halfMedium = Strength.create(0, 1, 0, 0.5);
1338
- solver.createConstraint(this.minX, Operator.Eq, x, halfMedium), solver.createConstraint(this.minY, Operator.Eq, y, halfMedium);
1339
- }
1340
- parent && parent.addChild(this);
1341
- }
1342
- }
1343
- function createLayoutConstraints(xyflowApi, draggingNodeId) {
1344
- var _a;
1345
- const { parentLookup, nodeLookup } = xyflowApi.getState(), solver = new Solver(), rects = /* @__PURE__ */ new Map(), traverse = new Array();
1346
- for (const [, xynode] of nodeLookup)
1347
- isNullish(xynode.parentId) && traverse.push({
1348
- xynode,
1349
- parent: null
1350
- });
1351
- for (; traverse.length > 0; ) {
1352
- const { xynode, parent } = traverse.shift(), isDragging = xynode.dragging === !0 || xynode.id === draggingNodeId, shouldTraverse = !isDragging && xynode.type === "compound" && isAncestor(xynode.id, draggingNodeId), rect = shouldTraverse ? new Compound(solver, xynode, parent) : new Leaf(solver, xynode, parent, isDragging);
1353
- rects.set(xynode.id, rect), shouldTraverse && ((_a = parentLookup.get(xynode.id)) == null || _a.forEach((child) => {
1354
- traverse.push({
1355
- xynode: child,
1356
- parent: rect
1357
- });
1358
- }));
1359
- }
1360
- solver.updateVariables(), solver.maxIterations = 1e3;
1361
- const rectsToUpdate = [...rects.values()].filter((r) => r.id !== draggingNodeId);
1362
- function updateXYFlowNodes() {
1363
- solver.updateVariables(), xyflowApi.getState().triggerNodeChanges(
1364
- rectsToUpdate.reduce((acc, r) => (acc.push({
1365
- id: r.id,
1366
- type: "position",
1367
- dragging: !1,
1368
- position: r.position,
1369
- positionAbsolute: r.positionAbsolute
1370
- }), r instanceof Compound && acc.push({
1371
- id: r.id,
1372
- type: "dimensions",
1373
- setAttributes: !0,
1374
- dimensions: r.dimensions
1375
- }), acc), [])
1376
- );
1377
- }
1378
- let animationFrameId = null;
1379
- function onNodeDrag(xynode) {
1380
- rectsToUpdate.length !== 0 && (animationFrameId ?? (animationFrameId = requestAnimationFrame(() => {
1381
- animationFrameId = null;
1382
- const pos = nodeLookup.get(xynode.id).internals.positionAbsolute, rect = nonNullable(rects.get(xynode.id));
1383
- solver.suggestValue(rect.minX, Math.ceil(pos.x)), solver.suggestValue(rect.minY, Math.ceil(pos.y)), updateXYFlowNodes();
1384
- })));
1385
- }
1386
- return {
1387
- onNodeDrag,
1388
- updateXYFlowNodes
1389
- };
1390
- }
1391
- function useLayoutConstraints() {
1392
- const diagramApi = useDiagramStoreApi(), solverRef = useRef();
1393
- return useMemo(() => ({
1394
- onNodeDragStart: (_event, xynode) => {
1395
- const { cancelSaveManualLayout, xystore } = diagramApi.getState();
1396
- cancelSaveManualLayout(), solverRef.current = createLayoutConstraints(xystore, xynode.id);
1397
- },
1398
- onNodeDrag: (_event, xynode) => {
1399
- var _a;
1400
- invariant(solverRef.current, "solverRef.current should be defined"), (_a = solverRef.current) == null || _a.onNodeDrag(xynode);
1401
- },
1402
- onNodeDragStop: (_event, _xynode) => {
1403
- diagramApi.getState().scheduleSaveManualLayout(), solverRef.current = void 0;
1404
- }
1405
- }), [diagramApi]);
1406
- }
1329
+ ) });
1407
1330
  function NavigateToBtn({ xynodeId, className }) {
1408
1331
  const diagramApi = useDiagramStoreApi();
1409
1332
  return /* @__PURE__ */ jsx(
1410
- ActionIcon,
1333
+ ActionIcon$2,
1411
1334
  {
1412
1335
  className: clsx("nodrag nopan", className),
1413
1336
  radius: "md",
@@ -1797,12 +1720,13 @@ const Text = Text$1.withProps({
1797
1720
  width,
1798
1721
  height
1799
1722
  }) {
1800
- const store = useDiagramStoreApi(), { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState((s) => ({
1723
+ const store = useDiagramStoreApi(), { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive, hasGoToSource } = useDiagramState((s) => ({
1801
1724
  isHovered: s.hoveredNodeId === id,
1802
1725
  isDimmed: s.dimmed.has(id),
1803
1726
  isInteractive: s.nodesDraggable || s.nodesSelectable || !!s.onNavigateTo,
1804
1727
  isHovercards: s.showElementLinks,
1805
- hasOnNavigateTo: !!s.onNavigateTo
1728
+ hasOnNavigateTo: !!s.onNavigateTo,
1729
+ hasGoToSource: !!s.onOpenSourceElement
1806
1730
  })), isNavigable = hasOnNavigateTo && !!element.navigateTo, w = toDomPrecision(width ?? element.width), h = toDomPrecision(height ?? element.height);
1807
1731
  let animate = "idle";
1808
1732
  switch (!0) {
@@ -1936,33 +1860,179 @@ const Text = Text$1.withProps({
1936
1860
  const RenderIcon = store.getState().renderIcon, icon = RenderIcon ? /* @__PURE__ */ jsx(RenderIcon, { node }) : null;
1937
1861
  return icon ? /* @__PURE__ */ jsx("div", { className: clsx(elementIcon, "likec4-element-icon"), children: icon }) : null;
1938
1862
  };
1939
- function useXYFlowEvents() {
1940
- const diagramApi = useDiagramStoreApi(), lastClickTimestamp = useRef(), dblclickTimeout = useRef(), hoveredNodeFromOnEdgeEnterRef = useRef("");
1941
- return useMemo(() => {
1942
- const dbclickLock = () => dblclickTimeout.current !== void 0 ? !0 : (dblclickTimeout.current = window.setTimeout(() => {
1943
- dblclickTimeout.current = void 0;
1944
- }, 250), !1), lastClickWasRecent = (ms = 1500) => lastClickTimestamp.current && Date.now() - lastClickTimestamp.current < ms;
1863
+ class Rect {
1864
+ constructor() {
1865
+ __publicField(this, "id");
1866
+ __publicField(this, "minX", new Variable());
1867
+ __publicField(this, "minY", new Variable());
1868
+ __publicField(this, "maxX", new Variable());
1869
+ __publicField(this, "maxY", new Variable());
1870
+ }
1871
+ get x() {
1872
+ return this.minX;
1873
+ }
1874
+ get y() {
1875
+ return this.minY;
1876
+ }
1877
+ get positionAbsolute() {
1945
1878
  return {
1946
- onDoubleClick: (event) => {
1947
- const {
1948
- fitViewEnabled,
1949
- onCanvasDblClick,
1950
- resetLastClicked,
1951
- fitDiagram
1952
- } = diagramApi.getState();
1953
- fitViewEnabled && (fitDiagram(), onCanvasDblClick || event.stopPropagation()), resetLastClicked(), onCanvasDblClick == null || onCanvasDblClick(event);
1954
- },
1955
- onPaneClick: (event) => {
1956
- if (dbclickLock())
1957
- return;
1958
- const {
1959
- focusedNodeId,
1879
+ x: this.x.value(),
1880
+ y: this.y.value()
1881
+ };
1882
+ }
1883
+ get dimensions() {
1884
+ return {
1885
+ width: this.maxX.value() - this.minX.value(),
1886
+ height: this.maxY.value() - this.minY.value()
1887
+ };
1888
+ }
1889
+ // Position relative to parent
1890
+ get position() {
1891
+ const positionAbsolute = this.positionAbsolute;
1892
+ if (!this.parent)
1893
+ return positionAbsolute;
1894
+ const parentPosition = this.parent.positionAbsolute;
1895
+ return {
1896
+ x: positionAbsolute.x - parentPosition.x,
1897
+ y: positionAbsolute.y - parentPosition.y
1898
+ };
1899
+ }
1900
+ }
1901
+ class Compound extends Rect {
1902
+ constructor(solver, xynode, parent = null) {
1903
+ super();
1904
+ __publicField(this, "children", []);
1905
+ this.solver = solver, this.parent = parent, this.id = xynode.id, solver.createConstraint(this.maxX, Operator.Ge, this.minX), solver.createConstraint(this.maxY, Operator.Ge, this.minY), parent && parent.addChild(this);
1906
+ }
1907
+ addChild(rect) {
1908
+ this.children.push(rect);
1909
+ const leftPadding = new Expression(
1910
+ rect.minX,
1911
+ [-1, this.minX]
1912
+ ), { weak } = Strength;
1913
+ this.solver.createConstraint(leftPadding, Operator.Ge, 40), this.solver.createConstraint(leftPadding, Operator.Eq, 41, weak);
1914
+ const topPadding = new Expression(
1915
+ rect.minY,
1916
+ [-1, this.minY]
1917
+ );
1918
+ this.solver.createConstraint(topPadding, Operator.Ge, 54), this.solver.createConstraint(topPadding, Operator.Eq, 55, weak);
1919
+ const rightPadding = new Expression(
1920
+ this.maxX,
1921
+ [-1, rect.maxX]
1922
+ );
1923
+ this.solver.createConstraint(rightPadding, Operator.Ge, 40), this.solver.createConstraint(rightPadding, Operator.Eq, 41, weak);
1924
+ const bottomPadding = new Expression(
1925
+ this.maxY,
1926
+ [-1, rect.maxY]
1927
+ );
1928
+ this.solver.createConstraint(bottomPadding, Operator.Ge, 40), this.solver.createConstraint(bottomPadding, Operator.Eq, 41, weak);
1929
+ }
1930
+ }
1931
+ class Leaf extends Rect {
1932
+ constructor(solver, xynode, parent = null, isEditing = !1) {
1933
+ super(), this.parent = parent, this.isEditing = isEditing, this.id = xynode.id;
1934
+ const x = Math.ceil(xynode.internals.positionAbsolute.x), y = Math.ceil(xynode.internals.positionAbsolute.y), { width, height } = getNodeDimensions(xynode);
1935
+ if (this.maxX = new Expression(this.minX, Math.ceil(width)), this.maxY = new Expression(this.minY, Math.ceil(height)), isEditing) {
1936
+ const superStrong = Strength.create(100, 0, 0);
1937
+ solver.addEditVariable(this.minX, superStrong), solver.addEditVariable(this.minY, superStrong), solver.suggestValue(this.minX, x), solver.suggestValue(this.minY, y);
1938
+ } else {
1939
+ const halfMedium = Strength.create(0, 1, 0, 0.5);
1940
+ solver.createConstraint(this.minX, Operator.Eq, x, halfMedium), solver.createConstraint(this.minY, Operator.Eq, y, halfMedium);
1941
+ }
1942
+ parent && parent.addChild(this);
1943
+ }
1944
+ }
1945
+ function createLayoutConstraints(xyflowApi, draggingNodeId) {
1946
+ var _a;
1947
+ const { parentLookup, nodeLookup } = xyflowApi.getState(), solver = new Solver(), rects = /* @__PURE__ */ new Map(), traverse = new Array();
1948
+ for (const [, xynode] of nodeLookup)
1949
+ isNullish(xynode.parentId) && traverse.push({
1950
+ xynode,
1951
+ parent: null
1952
+ });
1953
+ for (; traverse.length > 0; ) {
1954
+ const { xynode, parent } = traverse.shift(), isDragging = xynode.dragging === !0 || xynode.id === draggingNodeId, shouldTraverse = !isDragging && xynode.type === "compound" && isAncestor(xynode.id, draggingNodeId), rect = shouldTraverse ? new Compound(solver, xynode, parent) : new Leaf(solver, xynode, parent, isDragging);
1955
+ rects.set(xynode.id, rect), shouldTraverse && ((_a = parentLookup.get(xynode.id)) == null || _a.forEach((child) => {
1956
+ traverse.push({
1957
+ xynode: child,
1958
+ parent: rect
1959
+ });
1960
+ }));
1961
+ }
1962
+ solver.updateVariables(), solver.maxIterations = 1e3;
1963
+ const rectsToUpdate = [...rects.values()].filter((r) => r.id !== draggingNodeId);
1964
+ function updateXYFlowNodes() {
1965
+ solver.updateVariables(), xyflowApi.getState().triggerNodeChanges(
1966
+ rectsToUpdate.reduce((acc, r) => (acc.push({
1967
+ id: r.id,
1968
+ type: "position",
1969
+ dragging: !1,
1970
+ position: r.position,
1971
+ positionAbsolute: r.positionAbsolute
1972
+ }), r instanceof Compound && acc.push({
1973
+ id: r.id,
1974
+ type: "dimensions",
1975
+ setAttributes: !0,
1976
+ dimensions: r.dimensions
1977
+ }), acc), [])
1978
+ );
1979
+ }
1980
+ let animationFrameId = null;
1981
+ function onNodeDrag(xynode) {
1982
+ rectsToUpdate.length !== 0 && (animationFrameId ?? (animationFrameId = requestAnimationFrame(() => {
1983
+ animationFrameId = null;
1984
+ const pos = nodeLookup.get(xynode.id).internals.positionAbsolute, rect = nonNullable(rects.get(xynode.id));
1985
+ solver.suggestValue(rect.minX, Math.ceil(pos.x)), solver.suggestValue(rect.minY, Math.ceil(pos.y)), updateXYFlowNodes();
1986
+ })));
1987
+ }
1988
+ return {
1989
+ onNodeDrag,
1990
+ updateXYFlowNodes
1991
+ };
1992
+ }
1993
+ function useLayoutConstraints() {
1994
+ const diagramApi = useDiagramStoreApi(), solverRef = useRef();
1995
+ return useMemo(() => ({
1996
+ onNodeDragStart: (_event, xynode) => {
1997
+ const { cancelSaveManualLayout, xystore } = diagramApi.getState();
1998
+ cancelSaveManualLayout(), solverRef.current = createLayoutConstraints(xystore, xynode.id);
1999
+ },
2000
+ onNodeDrag: (_event, xynode) => {
2001
+ var _a;
2002
+ invariant(solverRef.current, "solverRef.current should be defined"), (_a = solverRef.current) == null || _a.onNodeDrag(xynode);
2003
+ },
2004
+ onNodeDragStop: (_event, _xynode) => {
2005
+ diagramApi.getState().scheduleSaveManualLayout(), solverRef.current = void 0;
2006
+ }
2007
+ }), [diagramApi]);
2008
+ }
2009
+ function useXYFlowEvents() {
2010
+ const diagramApi = useDiagramStoreApi(), lastClickTimestamp = useRef(), dblclickTimeout = useRef(), hoveredNodeFromOnEdgeEnterRef = useRef("");
2011
+ return useMemo(() => {
2012
+ const dbclickLock = () => dblclickTimeout.current !== void 0 ? !0 : (dblclickTimeout.current = window.setTimeout(() => {
2013
+ dblclickTimeout.current = void 0;
2014
+ }, 250), !1), lastClickWasRecent = (ms = 1500) => lastClickTimestamp.current && Date.now() - lastClickTimestamp.current < ms;
2015
+ return {
2016
+ onDoubleClick: (event) => {
2017
+ const {
2018
+ onCanvasDblClick,
2019
+ xystore,
2020
+ resetLastClicked
2021
+ } = diagramApi.getState();
2022
+ resetLastClicked(), xystore.getState().resetSelectedElements(), onCanvasDblClick || event.stopPropagation(), onCanvasDblClick == null || onCanvasDblClick(event);
2023
+ },
2024
+ onPaneClick: (event) => {
2025
+ if (dbclickLock())
2026
+ return;
2027
+ const {
2028
+ focusedNodeId,
1960
2029
  activeDynamicViewStep,
1961
2030
  fitDiagram,
1962
2031
  onCanvasClick,
2032
+ xystore,
1963
2033
  resetLastClicked
1964
2034
  } = diagramApi.getState();
1965
- (focusedNodeId ?? activeDynamicViewStep) !== null && (fitDiagram(), onCanvasClick || event.stopPropagation()), resetLastClicked(), onCanvasClick == null || onCanvasClick(event);
2035
+ (focusedNodeId ?? activeDynamicViewStep) !== null && (fitDiagram(), onCanvasClick || event.stopPropagation()), resetLastClicked(), xystore.getState().resetSelectedElements(), onCanvasClick == null || onCanvasClick(event);
1966
2036
  },
1967
2037
  onNodeContextMenu: (event, xynode) => {
1968
2038
  var _a, _b;
@@ -1990,22 +2060,33 @@ function useXYFlowEvents() {
1990
2060
  fitDiagram,
1991
2061
  focusOnNode,
1992
2062
  onNodeClick,
2063
+ xystore,
1993
2064
  enableFocusMode,
1994
2065
  lastClickedNodeId,
1995
- setLastClickedNode
2066
+ nodesSelectable,
2067
+ setLastClickedNode,
2068
+ onOpenSourceElement
1996
2069
  } = diagramApi.getState();
1997
2070
  setLastClickedNode(xynode.id);
1998
2071
  const shallChangeFocus = !!focusedNodeId && focusedNodeId !== xynode.id, clickedRecently = lastClickedNodeId === xynode.id && lastClickWasRecent();
1999
- if (enableFocusMode) {
2000
- let stopPropagation = !1;
2072
+ if (clickedRecently && onOpenSourceElement && onOpenSourceElement(xynode.data.element.id), enableFocusMode || nodesSelectable) {
2073
+ let stopPropagation = !0;
2001
2074
  switch (!0) {
2002
- case (!focusedNodeId && clickedRecently):
2003
- case shallChangeFocus:
2004
- stopPropagation = !0, focusOnNode(xynode.id);
2075
+ case (enableFocusMode && !focusedNodeId && clickedRecently):
2076
+ case (enableFocusMode && shallChangeFocus): {
2077
+ focusOnNode(xynode.id);
2005
2078
  break;
2006
- case (focusedNodeId === xynode.id && clickedRecently):
2007
- stopPropagation = !0, fitDiagram();
2079
+ }
2080
+ case (enableFocusMode && focusedNodeId === xynode.id && clickedRecently): {
2081
+ fitDiagram();
2082
+ break;
2083
+ }
2084
+ case nodesSelectable: {
2085
+ xystore.getState().addSelectedNodes([xynode.id]);
2008
2086
  break;
2087
+ }
2088
+ default:
2089
+ stopPropagation = !1;
2009
2090
  }
2010
2091
  !onNodeClick && stopPropagation && event.stopPropagation();
2011
2092
  }
@@ -2033,6 +2114,8 @@ function useXYFlowEvents() {
2033
2114
  activateDynamicStep,
2034
2115
  activeDynamicViewStep,
2035
2116
  focusedNodeId,
2117
+ xystore,
2118
+ nodesSelectable,
2036
2119
  focusOnNode,
2037
2120
  onEdgeClick,
2038
2121
  setLastClickedEdge
@@ -2046,7 +2129,7 @@ function useXYFlowEvents() {
2046
2129
  return;
2047
2130
  }
2048
2131
  }
2049
- isEdgeOfFocusedNode && (focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source), onEdgeClick || event.stopPropagation()), onEdgeClick == null || onEdgeClick({
2132
+ isEdgeOfFocusedNode ? (focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source), onEdgeClick || event.stopPropagation()) : nodesSelectable && (xystore.getState().addSelectedEdges([xyedge.id]), onEdgeClick || event.stopPropagation()), onEdgeClick == null || onEdgeClick({
2050
2133
  edge: xyedge.data.edge,
2051
2134
  xyedge,
2052
2135
  event
@@ -2206,7 +2289,7 @@ function XYFlow({
2206
2289
  onNodeDragStop: layoutConstraints.onNodeDragStop
2207
2290
  },
2208
2291
  nodeDragThreshold: 3,
2209
- zoomOnDoubleClick: !1,
2292
+ zoomOnDoubleClick: zoomable,
2210
2293
  elevateNodesOnSelect: !1,
2211
2294
  selectNodesOnDrag: !1,
2212
2295
  onNodesChange,
@@ -2236,82 +2319,6 @@ function XYFlow({
2236
2319
  }
2237
2320
  );
2238
2321
  }
2239
- var panel$1 = "_19mimxc0";
2240
- const historySelector = (s) => ({
2241
- hasStepBack: s.navigationHistoryIndex > 0,
2242
- hasStepForward: s.navigationHistoryIndex < s.navigationHistory.length - 1,
2243
- goBack: s.goBack,
2244
- goForward: s.goForward
2245
- });
2246
- function BackwardForwardPanel() {
2247
- const {
2248
- hasStepBack,
2249
- hasStepForward,
2250
- goBack,
2251
- goForward
2252
- } = useDiagramState(historySelector);
2253
- return /* @__PURE__ */ jsx(
2254
- Group,
2255
- {
2256
- className: clsx(
2257
- "react-flow__panel",
2258
- panel$1,
2259
- "likec4-navigation-panel"
2260
- ),
2261
- gap: "xs",
2262
- children: /* @__PURE__ */ jsxs(AnimatePresence, { children: [
2263
- hasStepBack && /* @__PURE__ */ jsx(
2264
- m.div,
2265
- {
2266
- layout: !0,
2267
- initial: { opacity: 0.05, transform: "translateX(-10px)" },
2268
- animate: { opacity: 1, transform: "translateX(0)" },
2269
- exit: {
2270
- opacity: 0.05,
2271
- transform: "translateX(-10px)"
2272
- },
2273
- children: /* @__PURE__ */ jsx(
2274
- ActionIcon,
2275
- {
2276
- variant: "light",
2277
- color: "gray",
2278
- onClick: (e) => {
2279
- e.stopPropagation(), goBack();
2280
- },
2281
- children: /* @__PURE__ */ jsx(IconChevronLeft, {})
2282
- }
2283
- )
2284
- },
2285
- "back"
2286
- ),
2287
- hasStepForward && /* @__PURE__ */ jsx(
2288
- m.div,
2289
- {
2290
- layout: !0,
2291
- initial: { opacity: 0.05, transform: "translateX(10px)" },
2292
- animate: { opacity: 1, transform: "translateX(0)" },
2293
- exit: {
2294
- opacity: 0,
2295
- transform: "translateX(10px)"
2296
- },
2297
- children: /* @__PURE__ */ jsx(
2298
- ActionIcon,
2299
- {
2300
- variant: "light",
2301
- color: "gray",
2302
- onClick: (e) => {
2303
- e.stopPropagation(), goForward();
2304
- },
2305
- children: /* @__PURE__ */ jsx(IconChevronRight, {})
2306
- }
2307
- )
2308
- },
2309
- "forward"
2310
- )
2311
- ] })
2312
- }
2313
- );
2314
- }
2315
2322
  var container$1 = "hbkbtj0", card = "hbkbtj1", title = "hbkbtj2", description = "hbkbtj3";
2316
2323
  const selector = (s) => ({
2317
2324
  id: s.view.id,
@@ -2573,7 +2580,7 @@ const {
2573
2580
  return null;
2574
2581
  if (nodes.length !== selectedNodeIds.length)
2575
2582
  throw new Error("NodeOptions: nodes and props.nodes should have the same length");
2576
- const showShapeOption = nodes.some((node) => node.type === "element"), [firstNode, ...rest] = nodes, showOpacityOption = firstNode.type === "compound" && (rest.length === 0 || rest.every((node) => node.type === "compound")), triggerChange = (style) => {
2583
+ const showShapeOption = nodes.some((node) => node.type === "element"), [firstNode, ...rest] = nodes, showGoToSource = rest.length === 0 && diagramApi.getState().onOpenSourceElement, showOpacityOption = firstNode.type === "compound" && (rest.length === 0 || rest.every((node) => node.type === "compound")), triggerChange = (style) => {
2577
2584
  const targets = nodes.map((node) => node.data.element.id);
2578
2585
  invariant(hasAtLeast$1(targets, 1), "At least one target is required"), diagramApi.getState().triggerChangeElementStyle({
2579
2586
  op: "change-element-style",
@@ -2582,12 +2589,36 @@ const {
2582
2589
  });
2583
2590
  };
2584
2591
  return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
2585
- /* @__PURE__ */ jsxs("div", { children: [
2586
- /* @__PURE__ */ jsxs(Text$1, { fz: rem(9), fw: "500", c: "dimmed", children: [
2587
- "ELEMENT",
2588
- rest.length > 0 ? "S" : ""
2592
+ /* @__PURE__ */ jsxs(Group, { wrap: "nowrap", align: "flex-start", children: [
2593
+ /* @__PURE__ */ jsxs(Box, { flex: 1, children: [
2594
+ /* @__PURE__ */ jsxs(Text$1, { fz: rem(9), fw: "500", c: "dimmed", children: [
2595
+ "ELEMENT",
2596
+ rest.length > 0 ? "S" : ""
2597
+ ] }),
2598
+ /* @__PURE__ */ jsx(Text$1, { size: "xs", c: rest.length > 0 ? "dimmed" : "", truncate: !0, children: rest.length === 0 ? firstNode.data.element.title : "[ multiple ]" })
2589
2599
  ] }),
2590
- /* @__PURE__ */ jsx(Text$1, { size: "xs", c: rest.length > 0 ? "dimmed" : "", truncate: !0, children: rest.length === 0 ? firstNode.data.element.title : "[ multiple ]" })
2600
+ showGoToSource && /* @__PURE__ */ jsx(Box, { flex: 0, children: /* @__PURE__ */ jsx(
2601
+ ActionIcon$2,
2602
+ {
2603
+ size: "sm",
2604
+ variant: "light",
2605
+ color: "gray",
2606
+ onClick: (e) => {
2607
+ var _a, _b;
2608
+ e.stopPropagation(), (_b = (_a = diagramApi.getState()).onOpenSourceElement) == null || _b.call(_a, firstNode.data.element.id);
2609
+ },
2610
+ children: /* @__PURE__ */ jsx(
2611
+ IconFileSymlink,
2612
+ {
2613
+ stroke: 1.2,
2614
+ style: {
2615
+ width: "70%",
2616
+ height: "70%"
2617
+ }
2618
+ }
2619
+ )
2620
+ }
2621
+ ) })
2591
2622
  ] }),
2592
2623
  showShapeOption && /* @__PURE__ */ jsx(
2593
2624
  ShapeOption,
@@ -2645,7 +2676,7 @@ function Colors({
2645
2676
  return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(TooltipGroup, { openDelay: 400, closeDelay: 300, children: [
2646
2677
  /* @__PURE__ */ jsx(Divider, { label: "color", labelPosition: "left" }),
2647
2678
  /* @__PURE__ */ jsx(Flex, { mt: "xs", maw: 150, gap: "xs", justify: "flex-start", align: "flex-start", direction: "row", wrap: "wrap", children: themedColors.map(({ key, value }) => /* @__PURE__ */ jsx(
2648
- Tooltip,
2679
+ Tooltip$2,
2649
2680
  {
2650
2681
  label: key,
2651
2682
  fz: "xs",
@@ -2667,7 +2698,7 @@ function Colors({
2667
2698
  key
2668
2699
  )) }),
2669
2700
  /* @__PURE__ */ jsx(Flex, { mt: "sm", maw: 150, gap: "xs", justify: "flex-start", align: "flex-start", direction: "row", wrap: "wrap", children: colors.map(({ key, value }) => /* @__PURE__ */ jsx(
2670
- Tooltip,
2701
+ Tooltip$2,
2671
2702
  {
2672
2703
  label: key,
2673
2704
  fz: "xs",
@@ -2784,7 +2815,7 @@ function BorderStyleOption({
2784
2815
  }
2785
2816
  ) });
2786
2817
  }
2787
- var panel = "_1lmzt1h0";
2818
+ var panel$1 = "_1lmzt1h0";
2788
2819
  function OptionsPanel() {
2789
2820
  const { isFocused, viewId } = useDiagramState((s) => ({
2790
2821
  viewId: s.view.id,
@@ -2813,7 +2844,7 @@ function OptionsPanel() {
2813
2844
  scale: 0.85
2814
2845
  },
2815
2846
  transition: { duration: 0.14 },
2816
- className: clsx("react-flow__panel", panel),
2847
+ className: clsx("react-flow__panel", panel$1),
2817
2848
  style: {
2818
2849
  transformOrigin: "center right",
2819
2850
  maxWidth: "320px"
@@ -2823,6 +2854,323 @@ function OptionsPanel() {
2823
2854
  viewId
2824
2855
  ) });
2825
2856
  }
2857
+ const Tooltip$1 = Tooltip$2.withProps({
2858
+ color: "gray",
2859
+ fz: "xs",
2860
+ position: "right",
2861
+ openDelay: 400,
2862
+ closeDelay: 100,
2863
+ label: "",
2864
+ children: null,
2865
+ offset: 8,
2866
+ transitionProps: { transition: "fade-right", duration: 200 }
2867
+ }), ActionIcon$1 = ActionIcon$2.withProps({
2868
+ className: "action-icon",
2869
+ variant: "light",
2870
+ color: "gray"
2871
+ });
2872
+ var panel = "_1snasyb0", autolayoutIcon = "_1snasyb1", autolayoutIndicator = "_1snasyb2";
2873
+ const ActionIcon = ActionIcon$2.withProps({
2874
+ size: "md",
2875
+ variant: "subtle",
2876
+ color: "gray"
2877
+ }), Tooltip = Tooltip$2.withProps({
2878
+ color: "gray",
2879
+ fz: "xs",
2880
+ position: "right",
2881
+ openDelay: 300,
2882
+ label: "",
2883
+ children: null,
2884
+ offset: 5,
2885
+ transitionProps: { transition: "fade", duration: 200 }
2886
+ }), ChangeAutoLayoutButton = (props) => {
2887
+ const store = useDiagramStoreApi(), [rootRef, setRootRef] = useState(null), [controlsRefs, setControlsRefs] = useState({}), autoLayout = useDiagramState((s) => s.view.autoLayout), setControlRef = (name) => (node) => {
2888
+ controlsRefs[name] = node, setControlsRefs(controlsRefs);
2889
+ }, setAutoLayout = (layout) => (event) => {
2890
+ var _a, _b;
2891
+ event.stopPropagation(), (_b = (_a = store.getState()).onChange) == null || _b.call(_a, {
2892
+ change: {
2893
+ op: "change-autolayout",
2894
+ layout
2895
+ }
2896
+ });
2897
+ };
2898
+ return /* @__PURE__ */ jsxs(
2899
+ Popover,
2900
+ {
2901
+ position: "right-start",
2902
+ clickOutsideEvents: [
2903
+ "pointerdown"
2904
+ ],
2905
+ radius: "xs",
2906
+ shadow: "lg",
2907
+ offset: {
2908
+ mainAxis: 10
2909
+ },
2910
+ ...props,
2911
+ children: [
2912
+ /* @__PURE__ */ jsx(PopoverTarget, { children: /* @__PURE__ */ jsx(
2913
+ ActionIcon$2,
2914
+ {
2915
+ className: "action-icon",
2916
+ variant: "light",
2917
+ color: "gray",
2918
+ children: /* @__PURE__ */ jsx(IconLayoutDashboard, { stroke: 1.3 })
2919
+ }
2920
+ ) }),
2921
+ /* @__PURE__ */ jsx(PopoverDropdown, { className: "likec4-top-left-panel", p: 8, pt: 4, children: /* @__PURE__ */ jsxs(Box, { pos: "relative", ref: setRootRef, children: [
2922
+ /* @__PURE__ */ jsx(
2923
+ FloatingIndicator,
2924
+ {
2925
+ target: controlsRefs[autoLayout],
2926
+ parent: rootRef,
2927
+ className: autolayoutIndicator
2928
+ }
2929
+ ),
2930
+ /* @__PURE__ */ jsx(Box, { mb: 10, children: /* @__PURE__ */ jsx(Text$1, { inline: !0, fz: "xs", c: "dimmed", fw: 500, children: "Auto layout:" }) }),
2931
+ /* @__PURE__ */ jsx(TooltipGroup, { openDelay: 100, children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: 1, children: [
2932
+ /* @__PURE__ */ jsx(Tooltip, { label: "Top to Bottom", children: /* @__PURE__ */ jsx(
2933
+ ActionIcon,
2934
+ {
2935
+ className: autolayoutIcon,
2936
+ ref: setControlRef("TB"),
2937
+ onClick: setAutoLayout("TB"),
2938
+ children: /* @__PURE__ */ jsx(IconArrowBigDownLines, { stroke: 1.3 })
2939
+ }
2940
+ ) }),
2941
+ /* @__PURE__ */ jsxs(Group, { gap: 30, children: [
2942
+ /* @__PURE__ */ jsx(Tooltip, { label: "Left to Right", children: /* @__PURE__ */ jsx(
2943
+ ActionIcon,
2944
+ {
2945
+ className: autolayoutIcon,
2946
+ ref: setControlRef("LR"),
2947
+ onClick: setAutoLayout("LR"),
2948
+ children: /* @__PURE__ */ jsx(IconArrowBigDownLines, { stroke: 1.3, style: { rotate: "270deg" } })
2949
+ }
2950
+ ) }),
2951
+ /* @__PURE__ */ jsx(Tooltip, { label: "Right to Left", children: /* @__PURE__ */ jsx(
2952
+ ActionIcon,
2953
+ {
2954
+ className: autolayoutIcon,
2955
+ ref: setControlRef("RL"),
2956
+ onClick: setAutoLayout("RL"),
2957
+ children: /* @__PURE__ */ jsx(IconArrowBigDownLines, { stroke: 1.3, style: { rotate: "90deg" } })
2958
+ }
2959
+ ) })
2960
+ ] }),
2961
+ /* @__PURE__ */ jsx(Tooltip, { label: "Bottom to Top", children: /* @__PURE__ */ jsx(
2962
+ ActionIcon,
2963
+ {
2964
+ className: autolayoutIcon,
2965
+ ref: setControlRef("BT"),
2966
+ onClick: setAutoLayout("BT"),
2967
+ children: /* @__PURE__ */ jsx(IconArrowBigDownLines, { stroke: 1.3, style: { rotate: "180deg" } })
2968
+ }
2969
+ ) })
2970
+ ] }) })
2971
+ ] }) })
2972
+ ]
2973
+ }
2974
+ );
2975
+ }, historySelector = (s) => ({
2976
+ showBurgerMenu: !!s.onBurgerMenuClick,
2977
+ hasStepBack: s.navigationHistoryIndex > 0,
2978
+ hasStepForward: s.navigationHistoryIndex < s.navigationHistory.length - 1
2979
+ }), BackwardForwardButtons = () => {
2980
+ const store = useDiagramStoreApi(), {
2981
+ showBurgerMenu,
2982
+ hasStepBack,
2983
+ hasStepForward
2984
+ } = useDiagramState(historySelector);
2985
+ return /* @__PURE__ */ jsxs(
2986
+ Group,
2987
+ {
2988
+ className: "likec4-navigation-panel",
2989
+ gap: "xs",
2990
+ children: [
2991
+ showBurgerMenu && /* @__PURE__ */ jsx(
2992
+ ActionIcon$1,
2993
+ {
2994
+ onClick: (e) => {
2995
+ var _a, _b;
2996
+ e.stopPropagation(), (_b = (_a = store.getState()).onBurgerMenuClick) == null || _b.call(_a);
2997
+ },
2998
+ children: /* @__PURE__ */ jsx(IconMenu2, {})
2999
+ }
3000
+ ),
3001
+ /* @__PURE__ */ jsxs(AnimatePresence, { children: [
3002
+ hasStepBack && /* @__PURE__ */ jsx(
3003
+ m.div,
3004
+ {
3005
+ layout: !0,
3006
+ initial: { opacity: 0.05, transform: "translateX(-10px)" },
3007
+ animate: { opacity: 1, transform: "translateX(0)" },
3008
+ exit: {
3009
+ opacity: 0.05,
3010
+ transform: "translateX(-10px)"
3011
+ },
3012
+ children: /* @__PURE__ */ jsx(
3013
+ ActionIcon$1,
3014
+ {
3015
+ onClick: (e) => {
3016
+ e.stopPropagation(), store.getState().goBack();
3017
+ },
3018
+ children: /* @__PURE__ */ jsx(IconChevronLeft, {})
3019
+ }
3020
+ )
3021
+ },
3022
+ "back"
3023
+ ),
3024
+ hasStepForward && /* @__PURE__ */ jsx(
3025
+ m.div,
3026
+ {
3027
+ layout: !0,
3028
+ initial: { opacity: 0.05, transform: "translateX(10px)" },
3029
+ animate: { opacity: 1, transform: "translateX(0)" },
3030
+ exit: {
3031
+ opacity: 0,
3032
+ transform: "translateX(10px)"
3033
+ },
3034
+ children: /* @__PURE__ */ jsx(
3035
+ ActionIcon$1,
3036
+ {
3037
+ onClick: (e) => {
3038
+ e.stopPropagation(), store.getState().goForward();
3039
+ },
3040
+ children: /* @__PURE__ */ jsx(IconChevronRight, {})
3041
+ }
3042
+ )
3043
+ },
3044
+ "forward"
3045
+ )
3046
+ ] })
3047
+ ]
3048
+ }
3049
+ );
3050
+ }, LayoutDriftNotification = (props) => /* @__PURE__ */ jsxs(
3051
+ HoverCard,
3052
+ {
3053
+ position: "right-start",
3054
+ openDelay: 200,
3055
+ closeDelay: 100,
3056
+ ...props,
3057
+ children: [
3058
+ /* @__PURE__ */ jsx(HoverCardTarget, { children: /* @__PURE__ */ jsx(
3059
+ ActionIcon$1,
3060
+ {
3061
+ className: "action-icon",
3062
+ variant: "light",
3063
+ color: "orange",
3064
+ children: /* @__PURE__ */ jsx(IconAlertTriangle, {})
3065
+ }
3066
+ ) }),
3067
+ /* @__PURE__ */ jsx(HoverCardDropdown, { p: "0", children: /* @__PURE__ */ jsxs(
3068
+ Notification,
3069
+ {
3070
+ color: "orange",
3071
+ withBorder: !1,
3072
+ withCloseButton: !1,
3073
+ title: "Manual layout issues",
3074
+ children: [
3075
+ /* @__PURE__ */ jsxs(Text$1, { mt: 2, size: "sm", lh: "xs", children: [
3076
+ "View contains new elements or their sizes have changed,",
3077
+ /* @__PURE__ */ jsx("br", {}),
3078
+ "last manual layout can not be applied."
3079
+ ] }),
3080
+ /* @__PURE__ */ jsxs(Text$1, { mt: "xs", size: "sm", lh: "xs", children: [
3081
+ "Update view predicates or remove ",
3082
+ /* @__PURE__ */ jsx(Code, { children: "@likec4-generated" })
3083
+ ] })
3084
+ ]
3085
+ }
3086
+ ) })
3087
+ ]
3088
+ }
3089
+ ), TopLeftPanel = () => {
3090
+ const store = useDiagramStoreApi(), {
3091
+ showNavigationButtons,
3092
+ showFitDiagram,
3093
+ showLayoutDriftWarning,
3094
+ showChangeAutoLayout,
3095
+ portalProps: _portalProps,
3096
+ showGoToSource
3097
+ } = useDiagramState((s) => {
3098
+ const isNotActive = s.activeDynamicViewStep === null && s.focusedNodeId === null, target = s.getContainer();
3099
+ return {
3100
+ showNavigationButtons: !!s.onBurgerMenuClick || s.showNavigationButtons && !!s.onNavigateTo,
3101
+ showFitDiagram: s.fitViewEnabled && s.zoomable && s.viewportChanged,
3102
+ showLayoutDriftWarning: s.readonly !== !0 && s.view.hasLayoutDrift === !0 && isNotActive,
3103
+ showChangeAutoLayout: s.readonly !== !0 && !!s.onChange && isNotActive,
3104
+ showGoToSource: !!s.onOpenSourceView,
3105
+ portalProps: target ? { target } : void 0
3106
+ };
3107
+ }), portalProps = _portalProps ? { portalProps: _portalProps } : void 0;
3108
+ return /* @__PURE__ */ jsxs(
3109
+ Stack,
3110
+ {
3111
+ className: clsx(
3112
+ "react-flow__panel",
3113
+ panel,
3114
+ "likec4-top-left-panel"
3115
+ ),
3116
+ onClick: (e) => e.stopPropagation(),
3117
+ gap: "xs",
3118
+ children: [
3119
+ showNavigationButtons && /* @__PURE__ */ jsx(BackwardForwardButtons, {}),
3120
+ showGoToSource && /* @__PURE__ */ jsx(Tooltip$1, { label: "Go to source", ...portalProps, children: /* @__PURE__ */ jsx(
3121
+ ActionIcon$1,
3122
+ {
3123
+ className: "action-icon",
3124
+ onClick: (e) => {
3125
+ var _a, _b;
3126
+ e.stopPropagation(), (_b = (_a = store.getState()).onOpenSourceView) == null || _b.call(_a);
3127
+ },
3128
+ children: /* @__PURE__ */ jsx(IconFileSymlink, { stroke: 1.5 })
3129
+ }
3130
+ ) }),
3131
+ showChangeAutoLayout && /* @__PURE__ */ jsx(ChangeAutoLayoutButton, { ...portalProps }),
3132
+ /* @__PURE__ */ jsxs(AnimatePresence, { children: [
3133
+ showLayoutDriftWarning && /* @__PURE__ */ jsx(
3134
+ m.div,
3135
+ {
3136
+ initial: { opacity: 0.05, transform: "translateX(-40%)" },
3137
+ animate: { opacity: 1, transform: "translateX(0)" },
3138
+ exit: {
3139
+ opacity: 0,
3140
+ transform: "translateX(-40%)"
3141
+ },
3142
+ children: /* @__PURE__ */ jsx(LayoutDriftNotification, { ...portalProps })
3143
+ },
3144
+ "layout"
3145
+ ),
3146
+ showFitDiagram && /* @__PURE__ */ jsx(
3147
+ m.div,
3148
+ {
3149
+ initial: { opacity: 0.05, transform: "translateX(-20%)" },
3150
+ animate: { opacity: 1, transform: "translateX(0)" },
3151
+ exit: {
3152
+ opacity: 0,
3153
+ transform: "translateX(-30%)"
3154
+ },
3155
+ children: /* @__PURE__ */ jsx(Tooltip$1, { label: "Center diagram", ...portalProps, children: /* @__PURE__ */ jsx(
3156
+ ActionIcon$1,
3157
+ {
3158
+ className: "action-icon",
3159
+ onClick: (e) => {
3160
+ e.stopPropagation(), store.getState().fitDiagram();
3161
+ },
3162
+ children: /* @__PURE__ */ jsx(IconFocusCentered, {})
3163
+ }
3164
+ ) })
3165
+ },
3166
+ "fit-view"
3167
+ )
3168
+ ] })
3169
+ ]
3170
+ }
3171
+ );
3172
+ };
3173
+ TopLeftPanel.displayName = "TopLeftPanel";
2826
3174
  function literalToEnum(value) {
2827
3175
  switch (value) {
2828
3176
  case "dots":
@@ -2845,8 +3193,7 @@ const XYFlowInner = memo(function({
2845
3193
  children,
2846
3194
  background,
2847
3195
  controls,
2848
- showDiagramTitle,
2849
- showNavigationButtons
3196
+ showDiagramTitle
2850
3197
  }) {
2851
3198
  const {
2852
3199
  isDynamicView,
@@ -2862,10 +3209,10 @@ const XYFlowInner = memo(function({
2862
3209
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2863
3210
  background !== "transparent" && background !== "solid" && /* @__PURE__ */ jsx(XYFlowBackground, { background }),
2864
3211
  controls && /* @__PURE__ */ jsx(Controls, { position: "bottom-right" }),
2865
- readonly === !1 && /* @__PURE__ */ jsx(OptionsPanel, {}),
3212
+ /* @__PURE__ */ jsx(TopLeftPanel, {}),
3213
+ readonly !== !0 && /* @__PURE__ */ jsx(OptionsPanel, {}),
2866
3214
  showDiagramTitle === !0 && !isDynamicViewActive && /* @__PURE__ */ jsx(DiagramTitlePanel, {}),
2867
3215
  isDynamicView && enableDynamicViewWalkthrough && /* @__PURE__ */ jsx(DynamicViewWalkthrough, {}),
2868
- showNavigationButtons && /* @__PURE__ */ jsx(BackwardForwardPanel, {}),
2869
3216
  children
2870
3217
  ] });
2871
3218
  }, shallowEqual);
@@ -2884,9 +3231,8 @@ function LikeC4Diagram({
2884
3231
  controls = !1,
2885
3232
  showElementLinks = !0,
2886
3233
  showDiagramTitle = !0,
2887
- showNavigationButtons = !1,
2888
3234
  enableDynamicViewWalkthrough = !1,
2889
- enableFocusMode = readonly,
3235
+ enableFocusMode = !1,
2890
3236
  initialWidth,
2891
3237
  initialHeight,
2892
3238
  keepAspectRatio = !1,
@@ -2900,8 +3246,13 @@ function LikeC4Diagram({
2900
3246
  onNavigateTo,
2901
3247
  onNodeClick,
2902
3248
  onNodeContextMenu,
2903
- onEditorCommand,
2904
- renderIcon
3249
+ onOpenSourceElement,
3250
+ onOpenSourceView,
3251
+ onOpenSourceRelation,
3252
+ onBurgerMenuClick,
3253
+ renderIcon,
3254
+ where,
3255
+ showNavigationButtons = !!onNavigateTo
2905
3256
  }) {
2906
3257
  const initialRef = useRef({
2907
3258
  defaultNodes: [],
@@ -2909,7 +3260,7 @@ function LikeC4Diagram({
2909
3260
  initialWidth: initialWidth ?? view.bounds.width,
2910
3261
  initialHeight: initialHeight ?? view.bounds.height
2911
3262
  });
2912
- return /* @__PURE__ */ jsx(EnsureMantine, { children: /* @__PURE__ */ jsx(LazyMotion, { features: domAnimation, strict: !0, children: /* @__PURE__ */ jsx(
3263
+ return readonly !== !0 && where && console.warn("where filter is only supported in readonly mode"), /* @__PURE__ */ jsx(EnsureMantine, { children: /* @__PURE__ */ jsx(LazyMotion, { features: domAnimation, strict: !0, children: /* @__PURE__ */ jsx(
2913
3264
  ReactFlowProvider,
2914
3265
  {
2915
3266
  fitView,
@@ -2926,11 +3277,13 @@ function LikeC4Diagram({
2926
3277
  fitViewEnabled: fitView,
2927
3278
  fitViewPadding,
2928
3279
  showElementLinks,
3280
+ showNavigationButtons,
2929
3281
  nodesDraggable,
2930
3282
  nodesSelectable,
2931
3283
  experimentalEdgeEditing,
2932
3284
  enableDynamicViewWalkthrough,
2933
3285
  enableFocusMode,
3286
+ whereFilter: where ?? null,
2934
3287
  renderIcon: renderIcon ?? null,
2935
3288
  onCanvasClick: onCanvasClick ?? null,
2936
3289
  onCanvasContextMenu: onCanvasContextMenu ?? null,
@@ -2941,14 +3294,16 @@ function LikeC4Diagram({
2941
3294
  onChange: onChange ?? null,
2942
3295
  onNavigateTo: onNavigateTo ?? null,
2943
3296
  onCanvasDblClick: onCanvasDblClick ?? null,
2944
- onEditorCommand: onEditorCommand ?? null,
3297
+ onOpenSourceView: onOpenSourceView ?? null,
3298
+ onOpenSourceElement: onOpenSourceElement ?? null,
3299
+ onOpenSourceRelation: onOpenSourceRelation ?? null,
3300
+ onBurgerMenuClick: onBurgerMenuClick ?? null,
2945
3301
  children: /* @__PURE__ */ jsx(
2946
3302
  LikeC4DiagramInnerMemo,
2947
3303
  {
2948
3304
  controls,
2949
3305
  background,
2950
- showDiagramTitle,
2951
- showNavigationButtons
3306
+ showDiagramTitle
2952
3307
  }
2953
3308
  )
2954
3309
  }
@@ -2959,8 +3314,7 @@ function LikeC4Diagram({
2959
3314
  const LikeC4DiagramInnerMemo = memo(function({
2960
3315
  background,
2961
3316
  controls,
2962
- showDiagramTitle,
2963
- showNavigationButtons
3317
+ showDiagramTitle
2964
3318
  }) {
2965
3319
  const {
2966
3320
  isInitialized,
@@ -2988,7 +3342,6 @@ const LikeC4DiagramInnerMemo = memo(function({
2988
3342
  children: /* @__PURE__ */ jsx(
2989
3343
  XYFlowInner,
2990
3344
  {
2991
- showNavigationButtons,
2992
3345
  showDiagramTitle,
2993
3346
  background,
2994
3347
  controls
@@ -3023,6 +3376,7 @@ function StaticLikeC4Diagram({
3023
3376
  showElementLinks: !0,
3024
3377
  showDiagramTitle: !1,
3025
3378
  enableDynamicViewWalkthrough: !1,
3379
+ enableFocusMode: !1,
3026
3380
  nodesSelectable: !1,
3027
3381
  nodesDraggable: !1,
3028
3382
  ...rest