@likec4/diagram 1.44.0 → 1.45.0

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 (134) hide show
  1. package/bundle/index.js +18160 -17671
  2. package/dist/LikeC4Diagram.js +7 -5
  3. package/dist/LikeC4Diagram.props.d.ts +9 -8
  4. package/dist/LikeC4Styles.js +0 -3
  5. package/dist/LikeC4View.d.ts +3 -3
  6. package/dist/LikeC4View.js +7 -3
  7. package/dist/ReactLikeC4.d.ts +2 -2
  8. package/dist/base/Base.d.ts +1 -1
  9. package/dist/base/updateEdges.js +25 -15
  10. package/dist/base/updateNodes.d.ts +14 -0
  11. package/dist/base/updateNodes.js +51 -18
  12. package/dist/base-primitives/Markdown.d.ts +30 -4
  13. package/dist/base-primitives/Markdown.js +3 -2
  14. package/dist/base-primitives/compound/CompoundActionButton.d.ts +3 -2
  15. package/dist/base-primitives/compound/CompoundDetailsButton.d.ts +3 -2
  16. package/dist/base-primitives/edge/EdgeActionButton.d.ts +3 -2
  17. package/dist/base-primitives/edge/EdgeContainer.d.ts +1 -1
  18. package/dist/base-primitives/edge/EdgeContainer.js +4 -1
  19. package/dist/base-primitives/edge/EdgeLabel.d.ts +2 -2
  20. package/dist/base-primitives/edge/EdgePath.js +24 -4
  21. package/dist/base-primitives/element/ElementActionButtons.d.ts +3 -2
  22. package/dist/base-primitives/element/ElementActionButtons.js +2 -2
  23. package/dist/base-primitives/element/ElementData.d.ts +3 -3
  24. package/dist/base-primitives/element/ElementData.js +13 -7
  25. package/dist/base-primitives/element/ElementDetailsButton.d.ts +3 -2
  26. package/dist/base-primitives/element/ElementDetailsButton.js +3 -0
  27. package/dist/base-primitives/element/ElementShape.js +5 -5
  28. package/dist/base-primitives/element/ElementTags.d.ts +6 -4
  29. package/dist/context/DiagramFeatures.d.ts +1 -0
  30. package/dist/context/TagStylesContext.js +1 -1
  31. package/dist/custom/hooks.d.ts +2 -2
  32. package/dist/custom/index.js +1 -1
  33. package/dist/hooks/index.d.ts +2 -1
  34. package/dist/hooks/useDiagramCompareLayout.js +7 -2
  35. package/dist/hooks/useLikeC4Project.d.ts +6 -1
  36. package/dist/hooks/useLikeC4Project.js +14 -2
  37. package/dist/hooks/useMediaPrint.d.ts +6 -0
  38. package/dist/likec4diagram/DiagramXYFlow.js +11 -6
  39. package/dist/likec4diagram/custom/edges/EdgeDrifts.d.ts +6 -0
  40. package/dist/likec4diagram/custom/edges/EdgeDrifts.js +38 -0
  41. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +24 -14
  42. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +3 -1
  43. package/dist/likec4diagram/custom/edges/useControlPoints.js +3 -3
  44. package/dist/likec4diagram/custom/edges/useRelationshipEdgePath.js +5 -5
  45. package/dist/likec4diagram/custom/nodes/NodeDrifts.d.ts +6 -0
  46. package/dist/likec4diagram/custom/nodes/{ElementNodeDrifts.js → NodeDrifts.js} +8 -4
  47. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +4 -2
  48. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  49. package/dist/likec4diagram/custom/nodes/nodes.js +64 -23
  50. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +3 -3
  51. package/dist/likec4diagram/relationship-popover/actor.d.ts +1 -1
  52. package/dist/likec4diagram/state/DiagramActorProvider.d.ts +2 -2
  53. package/dist/likec4diagram/state/DiagramActorProvider.js +0 -1
  54. package/dist/likec4diagram/state/assign.d.ts +10 -6
  55. package/dist/likec4diagram/state/assign.js +17 -192
  56. package/dist/likec4diagram/state/createViewChange.js +15 -26
  57. package/dist/likec4diagram/state/diagram-api.d.ts +2 -6
  58. package/dist/likec4diagram/state/diagram-api.js +2 -10
  59. package/dist/likec4diagram/state/hotkeyActor.d.ts +0 -7
  60. package/dist/likec4diagram/state/hotkeyActor.js +1 -17
  61. package/dist/likec4diagram/state/machine.actions.d.ts +430 -746
  62. package/dist/likec4diagram/state/machine.actions.js +193 -284
  63. package/dist/likec4diagram/state/machine.actions.layout.d.ts +579 -0
  64. package/dist/likec4diagram/state/machine.actions.layout.js +129 -0
  65. package/dist/likec4diagram/state/machine.d.ts +17 -3712
  66. package/dist/likec4diagram/state/machine.js +59 -73
  67. package/dist/likec4diagram/state/machine.setup.d.ts +61 -18
  68. package/dist/likec4diagram/state/machine.setup.js +27 -5
  69. package/dist/likec4diagram/state/machine.state.initializing.d.ts +25 -0
  70. package/dist/likec4diagram/state/machine.state.initializing.js +5 -5
  71. package/dist/likec4diagram/state/machine.state.navigating.d.ts +5 -108
  72. package/dist/likec4diagram/state/machine.state.navigating.js +167 -34
  73. package/dist/likec4diagram/state/machine.state.ready.d.ts +2316 -2851
  74. package/dist/likec4diagram/state/machine.state.ready.focused.d.ts +386 -0
  75. package/dist/likec4diagram/state/machine.state.ready.focused.js +85 -0
  76. package/dist/likec4diagram/state/machine.state.ready.idle.d.ts +474 -0
  77. package/dist/likec4diagram/state/machine.state.ready.idle.js +96 -0
  78. package/dist/likec4diagram/state/machine.state.ready.js +144 -319
  79. package/dist/likec4diagram/state/machine.state.ready.printing.d.ts +128 -0
  80. package/dist/likec4diagram/state/machine.state.ready.printing.js +40 -0
  81. package/dist/likec4diagram/state/machine.state.ready.walkthrough.d.ts +511 -0
  82. package/dist/likec4diagram/state/machine.state.ready.walkthrough.js +177 -0
  83. package/dist/likec4diagram/state/mediaPrintActor.d.ts +12 -0
  84. package/dist/likec4diagram/state/mediaPrintActor.js +18 -0
  85. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +6 -240
  86. package/dist/likec4diagram/state/syncManualLayoutActor.js +150 -89
  87. package/dist/likec4diagram/state/types.d.ts +5 -7
  88. package/dist/likec4diagram/state/utils.d.ts +15 -2
  89. package/dist/likec4diagram/state/utils.js +43 -3
  90. package/dist/likec4diagram/types.d.ts +8 -13
  91. package/dist/likec4diagram/ui/notation/NotationPanel.css.js +5 -2
  92. package/dist/likec4diagram/useLayoutConstraints.js +107 -102
  93. package/dist/likec4diagram/xyflow-diagram/diagram-view.js +12 -15
  94. package/dist/likec4diagram/xyflow-sequence/sequence-layout.js +11 -8
  95. package/dist/navigationpanel/NavigationPanel.js +5 -1
  96. package/dist/navigationpanel/actor.d.ts +4 -204
  97. package/dist/navigationpanel/actor.js +1 -1
  98. package/dist/navigationpanel/controls/ToggleReadonly.js +8 -3
  99. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +66 -66
  100. package/dist/navigationpanel/walkthrough/WalkthroughPanel.js +4 -3
  101. package/dist/overlays/element-details/ElementDetailsCard.css.js +3 -3
  102. package/dist/overlays/element-details/MetadataValue.d.ts +2 -1
  103. package/dist/overlays/element-details/actor.d.ts +8 -83
  104. package/dist/overlays/element-details/actor.js +0 -8
  105. package/dist/overlays/overlay/Overlay.js +10 -10
  106. package/dist/overlays/overlaysActor.d.ts +7 -392
  107. package/dist/overlays/overlaysActor.js +67 -46
  108. package/dist/overlays/relationship-details/_types.d.ts +3 -4
  109. package/dist/overlays/relationship-details/actor.d.ts +6 -562
  110. package/dist/overlays/relationship-details/actor.js +1 -0
  111. package/dist/overlays/relationship-details/layout-to-xyflow.js +3 -5
  112. package/dist/overlays/relationship-details/layout.d.ts +3 -3
  113. package/dist/overlays/relationship-details/layout.js +4 -4
  114. package/dist/overlays/relationships-browser/_types.d.ts +2 -3
  115. package/dist/overlays/relationships-browser/actor.d.ts +7 -739
  116. package/dist/overlays/relationships-browser/layout.d.ts +2 -2
  117. package/dist/overlays/relationships-browser/layout.js +3 -3
  118. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +0 -3
  119. package/dist/overlays/types.d.ts +12 -13
  120. package/dist/search/components/_shared.css.d.ts +1 -1
  121. package/dist/search/searchActor.d.ts +3 -66
  122. package/dist/shadowroot/ShadowRoot.d.ts +5 -7
  123. package/dist/shadowroot/ShadowRoot.js +7 -5
  124. package/dist/styles.css2.js +1 -1
  125. package/dist/utils/vector.d.ts +5 -0
  126. package/dist/utils/vector.js +7 -0
  127. package/dist/utils/view-bounds.js +7 -6
  128. package/dist/utils/xyflow.d.ts +4 -2
  129. package/dist/utils/xyflow.js +18 -50
  130. package/package.json +19 -18
  131. package/panda.buildinfo.json +17 -5
  132. package/styles-min.css +1 -1
  133. package/styles.css +1 -1
  134. package/dist/likec4diagram/custom/nodes/ElementNodeDrifts.d.ts +0 -4
@@ -151,11 +151,11 @@ function ShapeSvgOutline({ shape, w, h }) {
151
151
  svg = /* @__PURE__ */ jsx(
152
152
  "rect",
153
153
  {
154
- x: -1,
155
- y: -1,
156
- width: w + 2,
157
- height: h + 2,
158
- rx: 6
154
+ x: -3,
155
+ y: -3,
156
+ width: w + 6,
157
+ height: h + 6,
158
+ rx: 8
159
159
  }
160
160
  );
161
161
  break;
@@ -1,11 +1,13 @@
1
- import { BoxProps } from '@likec4/styles/jsx';
1
+ import { ComponentPropsWithoutRef, ForwardRefExoticComponent, DetailedHTMLProps, HTMLAttributes, RefAttributes, MemoExoticComponent } from 'react';
2
2
  import { BaseNodePropsWithData } from '../../base/types';
3
- import { ForwardRefExoticComponent, RefAttributes, MemoExoticComponent } from 'react';
4
3
  export type ElementTagProps = {
5
4
  tag: string;
6
5
  cursor?: 'pointer' | 'default';
7
- } & Omit<BoxProps, 'children'>;
8
- export declare const ElementTag: ForwardRefExoticComponent<Omit<ElementTagProps, "ref"> & RefAttributes<HTMLDivElement>>;
6
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'color'>;
7
+ export declare const ElementTag: ForwardRefExoticComponent<{
8
+ tag: string;
9
+ cursor?: "pointer" | "default";
10
+ } & Omit<Omit< DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "children" | "color"> & RefAttributes<HTMLDivElement>>;
9
11
  type Data = {
10
12
  tags: readonly string[] | null | undefined;
11
13
  width: number;
@@ -2,6 +2,7 @@ import { ExclusiveUnion } from '@likec4/core/types';
2
2
  import { PropsWithChildren } from 'react';
3
3
  declare const FeatureNames: readonly ["Controls", "ReadOnly", "FocusMode", "NavigateTo", "ElementDetails", "RelationshipDetails", "RelationshipBrowser", "Search", "NavigationButtons", "Notations", "DynamicViewWalkthrough", "FitView", "CompareWithLatest", "Vscode", "ElementTags"];
4
4
  export type FeatureName = typeof FeatureNames[number];
5
+ export type TogglableFeature = ('ReadOnly' | 'CompareWithLatest') & FeatureName;
5
6
  export type EnabledFeatures = {
6
7
  [P in `enable${FeatureName}`]: boolean;
7
8
  };
@@ -13,7 +13,7 @@ const generateColorVars = (spec) => {
13
13
  if (isTagColorSpecified(spec)) {
14
14
  return `
15
15
  --colors-likec4-tag-bg: ${color};
16
- --colors-likec4-tag-bg-hover: color-mix(in srgb, ${color}, var(--colors-likec4-mix-color) 20%);
16
+ --colors-likec4-tag-bg-hover: color-mix(in oklab, ${color}, var(--colors-likec4-mix-color) 20%);
17
17
  `;
18
18
  }
19
19
  if (!radixColors.includes(color)) {
@@ -1,11 +1,11 @@
1
- export type { DiagramActorEvent, DiagramActorRef, DiagramContext, } from '../likec4diagram/state/types';
1
+ export type { DiagramActorRef, DiagramActorSnapshot, DiagramContext, DiagramEmittedEvents, DiagramEvents, } from '../likec4diagram/state/types';
2
2
  export { useDiagramEventHandlers, } from '../context/DiagramEventHandlers';
3
3
  export { type EnabledFeatures, type FeatureName, useEnabledFeatures, } from '../context/DiagramFeatures';
4
4
  export { useCurrentView, useCurrentViewId, } from '../hooks/useCurrentView';
5
5
  export { type DiagramApi, useDiagram, useDiagramActorRef, useDiagramContext, useOnDiagramEvent, } from '../hooks/useDiagram';
6
6
  export { useCurrentViewModel, } from '../hooks/useCurrentViewModel';
7
7
  export { useLikeC4Model, useLikeC4Specification, useLikeC4ViewModel, } from '../hooks/useLikeC4Model';
8
- export { useHasProjects, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext, } from '../hooks/useLikeC4Project';
8
+ export { useHasProjects, useLikeC4Project, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext, } from '../hooks/useLikeC4Project';
9
9
  export { useLikeC4Styles, } from '../hooks/useLikeC4Styles';
10
10
  export { useSetState } from '../hooks/useSetState';
11
11
  export { useUpdateEffect } from '../hooks/useUpdateEffect';
@@ -18,7 +18,7 @@ export { useDiagram, useDiagramActorRef } from '../hooks/safeContext.js';
18
18
  export { useDiagramContext, useOnDiagramEvent } from '../hooks/useDiagram.js';
19
19
  export { useCurrentViewModel } from '../hooks/useCurrentViewModel.js';
20
20
  export { useLikeC4Model, useLikeC4Specification, useLikeC4ViewModel } from '../hooks/useLikeC4Model.js';
21
- export { useHasProjects, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext } from '../hooks/useLikeC4Project.js';
21
+ export { useHasProjects, useLikeC4Project, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext } from '../hooks/useLikeC4Project.js';
22
22
  export { useLikeC4Styles } from '../hooks/useLikeC4Styles.js';
23
23
  export { useSetState } from '../hooks/useSetState.js';
24
24
  export { useUpdateEffect } from '../hooks/useUpdateEffect.js';
@@ -1,6 +1,7 @@
1
1
  export { useCallbackRef } from './useCallbackRef';
2
2
  export { useId } from './useId';
3
3
  export { useMantinePortalProps } from './useMantinePortalProps';
4
+ export { useMediaPrint } from './useMediaPrint';
4
5
  export { useSetState } from './useSetState';
5
6
  export { useUpdateEffect } from './useUpdateEffect';
6
7
  export { useCurrentZoom, useIsZoomTooSmall, useXYFlow, useXYInternalNode, useXYStore, useXYStoreApi } from './useXYFlow';
@@ -8,6 +9,6 @@ export type { XYFlowInstance, XYStoreApi, XYStoreState } from './useXYFlow';
8
9
  export { useCurrentView, useCurrentViewId } from './useCurrentView';
9
10
  export { useCurrentViewModel } from './useCurrentViewModel';
10
11
  export { useLikeC4Model, useLikeC4Specification, useLikeC4ViewModel } from './useLikeC4Model';
11
- export { useHasProjects, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext, } from './useLikeC4Project';
12
+ export { useHasProjects, useLikeC4Project, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext, } from './useLikeC4Project';
12
13
  export { useLikeC4Styles } from './useLikeC4Styles';
13
14
  export * from './useDiagram';
@@ -1,5 +1,6 @@
1
1
  import { useSelector } from '@xstate/react';
2
2
  import { shallowEqual } from 'fast-equals';
3
+ import { deriveToggledFeatures } from '../likec4diagram/state/machine.setup.js';
3
4
  import { useDiagramActorRef } from './safeContext.js';
4
5
  import { useCallbackRef } from './useCallbackRef.js';
5
6
 
@@ -14,10 +15,14 @@ const selectCompareLayoutState = ({ context }) => {
14
15
  layout: context.view._layout ?? "auto"
15
16
  };
16
17
  }
18
+ const {
19
+ enableCompareWithLatest,
20
+ enableReadOnly
21
+ } = deriveToggledFeatures(context);
17
22
  return {
18
23
  isEnabled: true,
19
- isEditable: !context.features.enableReadOnly,
20
- isActive: context.toggledFeatures.enableCompareWithLatest === true,
24
+ isEditable: !enableReadOnly,
25
+ isActive: enableCompareWithLatest === true,
21
26
  drifts,
22
27
  layout: context.view._layout ?? "auto"
23
28
  };
@@ -17,6 +17,11 @@ export declare function useLikeC4ProjectsContext(): LikeC4ProjectsContext;
17
17
  */
18
18
  export declare function useHasProjects(): boolean;
19
19
  /**
20
- * @returns The current project id.
20
+ * @returns Current project id, as provided by LikeC4Model
21
21
  */
22
22
  export declare function useLikeC4ProjectId(): ProjectId;
23
+ /**
24
+ * Returns current LikeC4 project.
25
+ * Requires both LikeC4ModelProvider and LikeC4ProjectsProvider in the tree.*
26
+ */
27
+ export declare function useLikeC4Project(): LikeC4Project;
@@ -29,9 +29,21 @@ function useHasProjects() {
29
29
  function useLikeC4ProjectId() {
30
30
  const ctx = useContext(LikeC4ModelContext);
31
31
  if (!ctx) {
32
- throw new Error("No LikeC4ModelContext found");
32
+ throw new Error("No LikeC4ModelProvider found");
33
33
  }
34
34
  return ctx.projectId;
35
35
  }
36
+ function useLikeC4Project() {
37
+ const projectId = useLikeC4ProjectId();
38
+ const projectsCtx = useContext(LikeC4ProjectsContext);
39
+ if (!projectsCtx) {
40
+ throw new Error("No LikeC4ProjectsProvider found");
41
+ }
42
+ const project = projectsCtx.projects.find((p) => p.id === projectId);
43
+ if (!project) {
44
+ throw new Error(`Project with id '${projectId}' not found in LikeC4ProjectsProvider`);
45
+ }
46
+ return project;
47
+ }
36
48
 
37
- export { useHasProjects, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext };
49
+ export { useHasProjects, useLikeC4Project, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext };
@@ -0,0 +1,6 @@
1
+ import { ReadableAtom } from 'nanostores';
2
+ export declare const $isPrint: ReadableAtom<boolean>;
3
+ /**
4
+ * Hook to determine if the current media is print.
5
+ */
6
+ export declare function useMediaPrint(): boolean;
@@ -11,6 +11,7 @@ import { useIsReducedGraphics, usePanningAtom } from '../context/RootContainerCo
11
11
  import { useDiagramActorSnapshot } from '../hooks/useDiagram.js';
12
12
  import { depsShallowEqual, useUpdateEffect } from '../hooks/useUpdateEffect.js';
13
13
  import { BuiltinEdges, BuiltinNodes } from './custom/index.js';
14
+ import { deriveToggledFeatures } from './state/machine.setup.js';
14
15
  import { useLayoutConstraints } from './useLayoutConstraints.js';
15
16
  import { useDiagram } from '../hooks/safeContext.js';
16
17
  import { useDiagramEventHandlers } from '../context/DiagramEventHandlers.js';
@@ -44,8 +45,8 @@ function prepareNodeTypes(nodeTypes) {
44
45
  };
45
46
  }
46
47
  const selectXYProps = ({ context: ctx, children }) => {
47
- const toggledFeatures = ctx.toggledFeatures;
48
- const enableReadOnly = ctx.features.enableReadOnly || toggledFeatures.enableReadOnly === true;
48
+ ctx.toggledFeatures;
49
+ const { enableReadOnly } = deriveToggledFeatures(ctx);
49
50
  const isNotEditingEdge = enableReadOnly || children.syncLayout?.getSnapshot().context.editing !== "edge";
50
51
  return {
51
52
  enableReadOnly,
@@ -57,11 +58,11 @@ const selectXYProps = ({ context: ctx, children }) => {
57
58
  nodesDraggable: !enableReadOnly && ctx.nodesDraggable,
58
59
  nodesSelectable: ctx.nodesSelectable && isNotEditingEdge,
59
60
  fitViewPadding: ctx.fitViewPadding,
60
- enableFitView: ctx.toggledFeatures.enableFitView ?? ctx.features.enableFitView,
61
+ enableFitView: ctx.features.enableFitView,
61
62
  ...!ctx.features.enableFitView && {
62
63
  viewport: {
63
- x: -Math.min(ctx.view.bounds.x, 0),
64
- y: -Math.min(ctx.view.bounds.y, 0),
64
+ x: -ctx.view.bounds.x,
65
+ y: -ctx.view.bounds.y,
65
66
  zoom: 1
66
67
  }
67
68
  }
@@ -109,7 +110,11 @@ function LikeC4DiagramXYFlow({
109
110
  notPanning();
110
111
  }), onMoveEnd = useCallbackRef((event, viewport) => {
111
112
  isPanning.clear();
112
- diagram.send({ type: "xyflow.viewportMoved", viewport, manually: !!event });
113
+ diagram.send({
114
+ type: "xyflow.viewportMoved",
115
+ viewport,
116
+ manually: !!event
117
+ });
113
118
  }), onViewportResize = useCallbackRef(() => {
114
119
  diagram.send({ type: "xyflow.resized" });
115
120
  }), nodeTypes = useCustomCompareMemo(
@@ -0,0 +1,6 @@
1
+ import { Types } from '../../types';
2
+ export type EdgeDriftsProps = {
3
+ edgeProps: Types.EdgeProps;
4
+ svgPath: string;
5
+ };
6
+ export declare function EdgeDrifts({ edgeProps: { data }, svgPath, }: EdgeDriftsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,38 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { cx, css } from '@likec4/styles/css';
3
+
4
+ function EdgeDrifts({
5
+ edgeProps: { data },
6
+ svgPath
7
+ }) {
8
+ const drifts = data.drifts;
9
+ if (!drifts || drifts.length === 0) {
10
+ return null;
11
+ }
12
+ return /* @__PURE__ */ jsx(
13
+ "path",
14
+ {
15
+ className: cx(
16
+ "react-flow__edge-path",
17
+ css({
18
+ pointerEvents: "none",
19
+ stroke: "likec4.compare.manual.outline",
20
+ fill: "none",
21
+ mixBlendMode: "plus-lighter",
22
+ strokeWidth: {
23
+ base: "8px",
24
+ _whenHovered: "12px"
25
+ },
26
+ strokeOpacity: {
27
+ base: 0.3,
28
+ _whenHovered: 0.5
29
+ }
30
+ })
31
+ ),
32
+ d: svgPath,
33
+ strokeLinecap: "round"
34
+ }
35
+ );
36
+ }
37
+
38
+ export { EdgeDrifts };
@@ -1,8 +1,7 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { cx } from '@likec4/styles/css';
3
- import { useRafEffect } from '@react-hookz/web';
4
3
  import { EdgeLabelRenderer } from '@xyflow/react';
5
- import { useState, useRef } from 'react';
4
+ import { useState, useRef, useEffect } from 'react';
6
5
  import { ZIndexes } from '../../../base/const.js';
7
6
  import { useEnabledFeatures } from '../../../context/DiagramFeatures.js';
8
7
  import { useCallbackRef } from '../../../hooks/useCallbackRef.js';
@@ -12,6 +11,7 @@ import { useDiagram } from '../../../hooks/safeContext.js';
12
11
  import { useUpdateEffect } from '../../../hooks/useUpdateEffect.js';
13
12
  import { useXYFlow, useXYStoreApi } from '../../../hooks/useXYFlow.js';
14
13
  import { isSamePoint } from '../../../utils/xyflow.js';
14
+ import { EdgeDrifts } from './EdgeDrifts.js';
15
15
  import { controlDragging, controlPointsContainer, controlPoint } from './edges.css.js';
16
16
  import { useControlPoints } from './useControlPoints.js';
17
17
  import { useRelationshipEdgePath } from './useRelationshipEdgePath.js';
@@ -31,11 +31,14 @@ const getEdgeCenter = (path) => {
31
31
  };
32
32
  const RelationshipEdge = memoEdge((props) => {
33
33
  const [isControlPointDragging, setIsControlPointDragging] = useState(false);
34
+ const isControlPointDraggingRef = useRef(isControlPointDragging);
35
+ isControlPointDraggingRef.current = isControlPointDragging;
34
36
  const xyflow = useXYFlow();
35
37
  const diagram = useDiagram();
36
38
  const {
37
39
  enableNavigateTo,
38
- enableReadOnly
40
+ enableReadOnly,
41
+ enableCompareWithLatest
39
42
  } = useEnabledFeatures();
40
43
  const enabledEditing = !enableReadOnly;
41
44
  const {
@@ -66,21 +69,22 @@ const RelationshipEdge = memoEdge((props) => {
66
69
  y: labelXY?.y ?? labelY
67
70
  });
68
71
  useUpdateEffect(() => {
69
- setLabelPos({
72
+ if (isControlPointDraggingRef.current) {
73
+ return;
74
+ }
75
+ const next = {
70
76
  x: labelX,
71
77
  y: labelY
72
- });
78
+ };
79
+ setLabelPos((current) => isSamePoint(current, next) ? current : next);
73
80
  }, [labelX, labelY]);
74
81
  const svgPathRef = useRef(null);
75
- useRafEffect(() => {
82
+ useEffect(() => {
76
83
  const path = svgPathRef.current;
77
84
  if (!path || !isControlPointDragging) return;
78
- setLabelPos(getEdgeCenter(path));
85
+ const next = getEdgeCenter(path);
86
+ setLabelPos((current) => isSamePoint(current, next) ? current : next);
79
87
  }, [edgePath, isControlPointDragging]);
80
- if (isControlPointDragging) {
81
- labelX = labelPos.x;
82
- labelY = labelPos.y;
83
- }
84
88
  const updateEdgeData = useCallbackRef((controlPoints2) => {
85
89
  const point = svgPathRef.current ? getEdgeCenter(svgPathRef.current) : null;
86
90
  if (labelBBox && point) {
@@ -89,8 +93,7 @@ const RelationshipEdge = memoEdge((props) => {
89
93
  labelBBox: {
90
94
  ...labelBBox,
91
95
  ...point
92
- },
93
- labelXY: point
96
+ }
94
97
  });
95
98
  } else {
96
99
  diagram.updateEdgeData(id, { controlPoints: controlPoints2 });
@@ -165,11 +168,18 @@ const RelationshipEdge = memoEdge((props) => {
165
168
  }
166
169
  }
167
170
  ),
171
+ enableCompareWithLatest && /* @__PURE__ */ jsx(
172
+ EdgeDrifts,
173
+ {
174
+ edgeProps: props,
175
+ svgPath: edgePath
176
+ }
177
+ ),
168
178
  labelBBox && /* @__PURE__ */ jsx(
169
179
  EdgeLabelContainer,
170
180
  {
171
181
  edgeProps: props,
172
- labelPosition: { x: labelX, y: labelY },
182
+ labelPosition: isControlPointDragging ? labelPos : { x: labelX, y: labelY },
173
183
  children: /* @__PURE__ */ jsx(
174
184
  EdgeLabel,
175
185
  {
@@ -5,6 +5,7 @@ import '@xstate/react';
5
5
  import 'fast-equals';
6
6
  import 'react';
7
7
  import { useDiagram } from '../../../hooks/safeContext.js';
8
+ import { EdgeDrifts } from './EdgeDrifts.js';
8
9
  import { EdgeContainer } from '../../../base-primitives/edge/EdgeContainer.js';
9
10
  import { EdgePath } from '../../../base-primitives/edge/EdgePath.js';
10
11
  import { EdgeLabelContainer } from '../../../base-primitives/edge/EdgeLabelContainer.js';
@@ -13,7 +14,7 @@ import { EdgeActionButton } from '../../../base-primitives/edge/EdgeActionButton
13
14
 
14
15
  const LABEL_OFFSET = 16;
15
16
  function SequenceStepEdge(props) {
16
- const { enableNavigateTo } = useEnabledFeatures();
17
+ const { enableNavigateTo, enableCompareWithLatest } = useEnabledFeatures();
17
18
  const diagram = useDiagram();
18
19
  const { navigateTo } = props.data;
19
20
  const isSelfLoop = props.source === props.target;
@@ -50,6 +51,7 @@ function SequenceStepEdge(props) {
50
51
  svgPath: path
51
52
  }
52
53
  ),
54
+ enableCompareWithLatest && /* @__PURE__ */ jsx(EdgeDrifts, { edgeProps: props, svgPath: path }),
53
55
  /* @__PURE__ */ jsx(
54
56
  EdgeLabelContainer,
55
57
  {
@@ -19,8 +19,8 @@ function useControlPoints({
19
19
  const next = data.controlPoints ?? bezierControlPoints(data.points);
20
20
  setControlPoints((prev) => deepEqual(prev, next) ? prev : next);
21
21
  }, [
22
- data.controlPoints?.map((p) => `${p.x},${p.y}`).join("|") ?? "",
23
- data.points.map((p) => `${p[0]},${p[1]}`).join("|")
22
+ data.controlPoints?.map((p) => `${Math.round(p.x)},${Math.round(p.y)}`).join("|") ?? "",
23
+ data.points.map((p) => `${Math.round(p[0])},${Math.round(p[1])}`).join("|")
24
24
  ]);
25
25
  const insertControlPoint = useCallbackRef(({ x, y }) => {
26
26
  const sourceV = vector(sourceX, sourceY);
@@ -30,7 +30,7 @@ function useControlPoints({
30
30
  ...controlPoints.map(vector) || [],
31
31
  data.dir === "back" ? sourceV : targetV
32
32
  ];
33
- const newPointV = vector(Math.round(x), Math.round(y));
33
+ const newPointV = vector(x, y).round();
34
34
  let insertionIndex = 0;
35
35
  let minDistance = Infinity;
36
36
  for (let i = 0; i < points.length - 1; i++) {
@@ -3,10 +3,10 @@ import { line, curveCatmullRomOpen } from 'd3-shape';
3
3
  import { useCallback } from 'react';
4
4
  import { isTruthy, first, last } from 'remeda';
5
5
  import { useXYStore } from '../../../hooks/useXYFlow.js';
6
- import { extractMinimalInternalNode, getNodeIntersectionFromCenterToPoint, bezierPath, isEqualMinimalInternalNodes } from '../../../utils/xyflow.js';
6
+ import { nodeToRect, getNodeIntersectionFromCenterToPoint, bezierPath, isEqualRects } from '../../../utils/xyflow.js';
7
7
 
8
8
  const curve = line().curve(curveCatmullRomOpen.alpha(0.7)).x((d) => Math.round(d.x)).y((d) => Math.round(d.y));
9
- const isSameNodes = (a, b) => isEqualMinimalInternalNodes(a.sourceNode, b.sourceNode) && isEqualMinimalInternalNodes(a.targetNode, b.targetNode);
9
+ const isSameRects = (a, b) => isEqualRects(a.sourceNode, b.sourceNode) && isEqualRects(a.targetNode, b.targetNode);
10
10
  function useRelationshipEdgePath({
11
11
  props: {
12
12
  sourceX,
@@ -28,11 +28,11 @@ function useRelationshipEdgePath({
28
28
  const sourceNode2 = nonNullable(nodeLookup.get(source), `source node ${source} not found`);
29
29
  const targetNode2 = nonNullable(nodeLookup.get(target), `target node ${target} not found`);
30
30
  return {
31
- sourceNode: extractMinimalInternalNode(sourceNode2),
32
- targetNode: extractMinimalInternalNode(targetNode2)
31
+ sourceNode: nodeToRect(sourceNode2),
32
+ targetNode: nodeToRect(targetNode2)
33
33
  };
34
34
  }, [source, target]),
35
- isSameNodes
35
+ isSameRects
36
36
  );
37
37
  const isModified = isTruthy(data.controlPoints) || isControlPointDragging;
38
38
  if (isModified) {
@@ -0,0 +1,6 @@
1
+ import { Types } from '../../types';
2
+ export type NodeDriftsProps = {
3
+ nodeProps: Types.NodeProps;
4
+ position?: 'top' | 'bottom';
5
+ };
6
+ export declare function NodeDrifts({ nodeProps: { data }, position, }: NodeDriftsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,13 +1,17 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { Box } from '@likec4/styles/jsx';
3
3
  import { Notification, Text } from '@mantine/core';
4
- import { NodeToolbar, Position } from '@xyflow/react';
4
+ import { Position, NodeToolbar } from '@xyflow/react';
5
5
 
6
- function ElementNodeDrifts({ data }) {
6
+ function NodeDrifts({
7
+ nodeProps: { data },
8
+ position = "bottom"
9
+ }) {
7
10
  const drifts = data.drifts;
8
11
  if (!drifts || drifts.length === 0) {
9
12
  return null;
10
13
  }
14
+ const toolbarPosition = position === "top" ? Position.Top : Position.Bottom;
11
15
  return /* @__PURE__ */ jsx(
12
16
  Box,
13
17
  {
@@ -26,7 +30,7 @@ function ElementNodeDrifts({ data }) {
26
30
  outlineOffset: "1"
27
31
  }
28
32
  },
29
- children: /* @__PURE__ */ jsx(NodeToolbar, { isVisible: data.hovered === true, align: "start", position: Position.Bottom, children: /* @__PURE__ */ jsx(
33
+ children: /* @__PURE__ */ jsx(NodeToolbar, { isVisible: data.hovered === true, align: "start", position: toolbarPosition, children: /* @__PURE__ */ jsx(
30
34
  Notification,
31
35
  {
32
36
  color: "orange",
@@ -43,4 +47,4 @@ function ElementNodeDrifts({ data }) {
43
47
  );
44
48
  }
45
49
 
46
- export { ElementNodeDrifts };
50
+ export { NodeDrifts };
@@ -5,6 +5,7 @@ import { Handle, Position } from '@xyflow/react';
5
5
  import { isTruthy } from 'remeda';
6
6
  import { useEnabledFeatures } from '../../../context/DiagramFeatures.js';
7
7
  import { ElementActions } from './ElementActions.js';
8
+ import { NodeDrifts } from './NodeDrifts.js';
8
9
  import { ElementDetailsButtonWithHandler } from './nodes.js';
9
10
  import { ElementNodeContainer } from '../../../base-primitives/element/ElementNodeContainer.js';
10
11
  import { ElementShape } from '../../../base-primitives/element/ElementShape.js';
@@ -68,7 +69,7 @@ const ActorStepPort = ({
68
69
  };
69
70
  const hasModelFqn = (data) => isTruthy(data.modelFqn);
70
71
  function SequenceActorNode(props) {
71
- const { enableElementDetails } = useEnabledFeatures();
72
+ const { enableElementDetails, enableCompareWithLatest } = useEnabledFeatures();
72
73
  const data = props.data;
73
74
  const {
74
75
  id,
@@ -105,6 +106,7 @@ function SequenceActorNode(props) {
105
106
  }
106
107
  ),
107
108
  /* @__PURE__ */ jsxs(ElementNodeContainer, { nodeProps: props, children: [
109
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
108
110
  /* @__PURE__ */ jsx(ElementShape, { ...props }),
109
111
  /* @__PURE__ */ jsx(ElementData, { ...props }),
110
112
  hasModelFqn(data) && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -128,7 +130,7 @@ function SequenceParallelArea(props) {
128
130
  borderWidth: 1,
129
131
  "--_color": {
130
132
  base: "var(--likec4-palette-stroke)",
131
- _dark: "[color-mix(in srgb, var(--likec4-palette-hiContrast) 40%, var(--likec4-palette-fill))]"
133
+ _dark: "[color-mix(in oklab, var(--likec4-palette-hiContrast) 40%, var(--likec4-palette-fill))]"
132
134
  },
133
135
  borderColor: "[var(--_color)/30]",
134
136
  backgroundColor: "var(--likec4-palette-fill)/15",
@@ -2,6 +2,7 @@ export { CompoundActions } from './CompoundActions';
2
2
  export { DeploymentElementActions, ElementActions } from './ElementActions';
3
3
  export { CompoundDeploymentNode, CompoundDetailsButtonWithHandler, CompoundElementNode, DeploymentNode, ElementDetailsButtonWithHandler, ElementNode, ViewGroupNode, } from './nodes';
4
4
  export { SequenceActorNode, SequenceParallelArea } from './SequenceActorNode';
5
+ export { NodeDrifts, type NodeDriftsProps } from './NodeDrifts';
5
6
  export * from './CompoundActions';
6
7
  export * from './ElementActions';
7
8
  export * from './toolbar/CompoundToolbar';
@@ -1,4 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { css } from '@likec4/styles/css';
2
3
  import { useEnabledFeatures } from '../../../context/DiagramFeatures.js';
3
4
  import '@xstate/react';
4
5
  import 'fast-equals';
@@ -6,7 +7,7 @@ import 'react';
6
7
  import { useDiagram } from '../../../hooks/safeContext.js';
7
8
  import { CompoundActions } from './CompoundActions.js';
8
9
  import { DeploymentElementActions, ElementActions } from './ElementActions.js';
9
- import { ElementNodeDrifts } from './ElementNodeDrifts.js';
10
+ import { NodeDrifts } from './NodeDrifts.js';
10
11
  import { CompoundDeploymentToolbar, CompoundElementToolbar } from './toolbar/CompoundToolbar.js';
11
12
  import { DeploymentElementToolbar, ElementToolbar } from './toolbar/ElementToolbar.js';
12
13
  import { CompoundNodeContainer } from '../../../base-primitives/compound/CompoundNodeContainer.js';
@@ -52,7 +53,7 @@ function CompoundDetailsButtonWithHandler(props) {
52
53
  function ElementNode(props) {
53
54
  const { enableElementTags, enableElementDetails, enableReadOnly, enableCompareWithLatest } = useEnabledFeatures();
54
55
  return /* @__PURE__ */ jsxs(ElementNodeContainer, { nodeProps: props, children: [
55
- enableCompareWithLatest && /* @__PURE__ */ jsx(ElementNodeDrifts, { ...props }),
56
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
56
57
  /* @__PURE__ */ jsx(ElementShape, { ...props }),
57
58
  /* @__PURE__ */ jsx(ElementData, { ...props }),
58
59
  enableElementTags && /* @__PURE__ */ jsx(ElementTags, { ...props }),
@@ -65,7 +66,7 @@ function ElementNode(props) {
65
66
  function DeploymentNode(props) {
66
67
  const { enableElementTags, enableElementDetails, enableReadOnly, enableCompareWithLatest } = useEnabledFeatures();
67
68
  return /* @__PURE__ */ jsxs(ElementNodeContainer, { nodeProps: props, children: [
68
- enableCompareWithLatest && /* @__PURE__ */ jsx(ElementNodeDrifts, { ...props }),
69
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
69
70
  /* @__PURE__ */ jsx(ElementShape, { ...props }),
70
71
  /* @__PURE__ */ jsx(ElementData, { ...props }),
71
72
  enableElementTags && /* @__PURE__ */ jsx(ElementTags, { ...props }),
@@ -75,31 +76,71 @@ function DeploymentNode(props) {
75
76
  /* @__PURE__ */ jsx(DefaultHandles, {})
76
77
  ] });
77
78
  }
79
+ const compoundHasDrifts = css({
80
+ outlineColor: "likec4.compare.manual.outline",
81
+ outlineWidth: {
82
+ base: "2px",
83
+ _light: "4px"
84
+ },
85
+ outlineStyle: "solid",
86
+ outlineOffset: "1"
87
+ });
88
+ const hasDrifts = (props) => {
89
+ return props.data.drifts && props.data.drifts.length > 0;
90
+ };
78
91
  function CompoundElementNode(props) {
79
- const { enableElementDetails, enableReadOnly } = useEnabledFeatures();
80
- return /* @__PURE__ */ jsxs(CompoundNodeContainer, { nodeProps: props, children: [
81
- /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
82
- /* @__PURE__ */ jsx(CompoundActions, { ...props }),
83
- enableElementDetails && /* @__PURE__ */ jsx(CompoundDetailsButtonWithHandler, { ...props }),
84
- !enableReadOnly && /* @__PURE__ */ jsx(CompoundElementToolbar, { ...props }),
85
- /* @__PURE__ */ jsx(DefaultHandles, {})
86
- ] });
92
+ const { enableElementDetails, enableReadOnly, enableCompareWithLatest } = useEnabledFeatures();
93
+ const showDrifts = enableCompareWithLatest && hasDrifts(props);
94
+ return /* @__PURE__ */ jsxs(
95
+ CompoundNodeContainer,
96
+ {
97
+ className: showDrifts ? compoundHasDrifts : void 0,
98
+ nodeProps: props,
99
+ children: [
100
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
101
+ /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
102
+ /* @__PURE__ */ jsx(CompoundActions, { ...props }),
103
+ enableElementDetails && /* @__PURE__ */ jsx(CompoundDetailsButtonWithHandler, { ...props }),
104
+ !enableReadOnly && /* @__PURE__ */ jsx(CompoundElementToolbar, { ...props }),
105
+ /* @__PURE__ */ jsx(DefaultHandles, {})
106
+ ]
107
+ }
108
+ );
87
109
  }
88
110
  function CompoundDeploymentNode(props) {
89
- const { enableElementDetails, enableReadOnly } = useEnabledFeatures();
90
- return /* @__PURE__ */ jsxs(CompoundNodeContainer, { nodeProps: props, children: [
91
- /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
92
- /* @__PURE__ */ jsx(CompoundActions, { ...props }),
93
- enableElementDetails && /* @__PURE__ */ jsx(CompoundDetailsButtonWithHandler, { ...props }),
94
- !enableReadOnly && /* @__PURE__ */ jsx(CompoundDeploymentToolbar, { ...props }),
95
- /* @__PURE__ */ jsx(DefaultHandles, {})
96
- ] });
111
+ const { enableElementDetails, enableReadOnly, enableCompareWithLatest } = useEnabledFeatures();
112
+ const showDrifts = enableCompareWithLatest && hasDrifts(props);
113
+ return /* @__PURE__ */ jsxs(
114
+ CompoundNodeContainer,
115
+ {
116
+ className: showDrifts ? compoundHasDrifts : void 0,
117
+ nodeProps: props,
118
+ children: [
119
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
120
+ /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
121
+ /* @__PURE__ */ jsx(CompoundActions, { ...props }),
122
+ enableElementDetails && /* @__PURE__ */ jsx(CompoundDetailsButtonWithHandler, { ...props }),
123
+ !enableReadOnly && /* @__PURE__ */ jsx(CompoundDeploymentToolbar, { ...props }),
124
+ /* @__PURE__ */ jsx(DefaultHandles, {})
125
+ ]
126
+ }
127
+ );
97
128
  }
98
129
  function ViewGroupNode(props) {
99
- return /* @__PURE__ */ jsxs(CompoundNodeContainer, { nodeProps: props, children: [
100
- /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
101
- /* @__PURE__ */ jsx(DefaultHandles, {})
102
- ] });
130
+ const { enableCompareWithLatest } = useEnabledFeatures();
131
+ const showDrifts = enableCompareWithLatest && hasDrifts(props);
132
+ return /* @__PURE__ */ jsxs(
133
+ CompoundNodeContainer,
134
+ {
135
+ className: showDrifts ? compoundHasDrifts : void 0,
136
+ nodeProps: props,
137
+ children: [
138
+ enableCompareWithLatest && /* @__PURE__ */ jsx(NodeDrifts, { nodeProps: props }),
139
+ /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
140
+ /* @__PURE__ */ jsx(DefaultHandles, {})
141
+ ]
142
+ }
143
+ );
103
144
  }
104
145
 
105
146
  export { CompoundDeploymentNode, CompoundDetailsButtonWithHandler, CompoundElementNode, DeploymentNode, ElementDetailsButtonWithHandler, ElementNode, ViewGroupNode };