@likec4/diagram 1.2.2 → 1.4.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 (69) hide show
  1. package/dist/LikeC4Diagram.d.ts +1 -1
  2. package/dist/LikeC4Diagram.props.d.ts +27 -4
  3. package/dist/index.mjs +1543 -1346
  4. package/dist/state/DiagramContext.d.ts +2353 -264
  5. package/dist/state/diagramStore.d.ts +2360 -268
  6. package/dist/state/index.d.ts +0 -1
  7. package/dist/state/useDiagramStore.d.ts +3 -2534
  8. package/dist/state/useNavigationHistory.d.ts +5 -0
  9. package/dist/style.css +194 -131
  10. package/dist/theme.css.d.ts +5 -1
  11. package/dist/ui/BackwardForwardPanel.css.d.ts +1 -0
  12. package/dist/ui/BackwardForwardPanel.d.ts +1 -0
  13. package/dist/ui/DiagramTitlePanel.css.d.ts +1 -1
  14. package/dist/xyflow/XYFlow.d.ts +2 -1
  15. package/dist/xyflow/XYFlowEvents.d.ts +4 -4
  16. package/dist/xyflow/XYFlowInner.d.ts +2 -1
  17. package/dist/xyflow/const.d.ts +1 -1
  18. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  19. package/dist/xyflow/edges/RelationshipEdge.d.ts +4 -5
  20. package/dist/xyflow/edges/utils.d.ts +4 -2
  21. package/dist/xyflow/hooks/useXYFlow.d.ts +9 -9
  22. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +3 -1
  23. package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
  24. package/dist/xyflow/nodes/element/element.css.d.ts +1 -0
  25. package/dist/xyflow/nodes/element/index.d.ts +1 -1
  26. package/dist/xyflow/types.d.ts +3 -3
  27. package/dist/xyflow/utils.d.ts +3 -0
  28. package/package.json +18 -17
  29. package/src/LikeC4Diagram.css.ts +15 -5
  30. package/src/LikeC4Diagram.props.ts +33 -5
  31. package/src/LikeC4Diagram.tsx +103 -32
  32. package/src/globals.css.ts +1 -2
  33. package/src/mantine/theme.ts +1 -0
  34. package/src/state/DiagramContext.tsx +3 -4
  35. package/src/state/diagramStore.ts +177 -103
  36. package/src/state/index.ts +0 -1
  37. package/src/state/useDiagramStore.ts +6 -13
  38. package/src/state/useNavigationHistory.ts +39 -0
  39. package/src/theme.css.ts +5 -1
  40. package/src/ui/BackwardForwardPanel.css.ts +22 -0
  41. package/src/ui/BackwardForwardPanel.tsx +59 -0
  42. package/src/ui/DiagramTitlePanel.css.ts +11 -5
  43. package/src/ui/DiagramTitlePanel.tsx +108 -96
  44. package/src/ui/DynamicViewWalkthrough.css.ts +3 -2
  45. package/src/ui/DynamicViewWalkthrough.tsx +36 -17
  46. package/src/ui/OptionsPanel.tsx +28 -19
  47. package/src/ui/options/NodeOptions.tsx +16 -19
  48. package/src/xyflow/FitviewOnDiagramChange.tsx +15 -23
  49. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +3 -6
  50. package/src/xyflow/SyncWithDiagram.tsx +21 -2
  51. package/src/xyflow/XYFlow.tsx +20 -23
  52. package/src/xyflow/XYFlowEvents.tsx +26 -8
  53. package/src/xyflow/XYFlowInner.tsx +10 -6
  54. package/src/xyflow/const.ts +1 -1
  55. package/src/xyflow/diagram-to-xyflow.ts +4 -41
  56. package/src/xyflow/edges/RelationshipEdge.tsx +160 -111
  57. package/src/xyflow/edges/edges.css.ts +41 -26
  58. package/src/xyflow/edges/utils.ts +5 -2
  59. package/src/xyflow/hooks/useLayoutConstraints.ts +19 -4
  60. package/src/xyflow/nodes/compound/CompoundNode.css.ts +45 -37
  61. package/src/xyflow/nodes/compound/CompoundNode.tsx +36 -26
  62. package/src/xyflow/nodes/element/ElementNode.tsx +27 -14
  63. package/src/xyflow/nodes/element/element.css.ts +31 -18
  64. package/src/xyflow/nodes/element/index.ts +1 -1
  65. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +7 -3
  66. package/src/xyflow/types.ts +5 -2
  67. package/src/xyflow/utils.ts +48 -0
  68. package/dist/ui/index.d.ts +0 -2
  69. package/src/ui/index.ts +0 -3
@@ -2,10 +2,11 @@ import { CSSProperties, PropsWithChildren } from 'react';
2
2
  import { XYFlowEdge, XYFlowNode } from './types';
3
3
 
4
4
  type XYFlowWrapperProps = PropsWithChildren<{
5
+ colorMode?: 'system' | 'light' | 'dark';
5
6
  className?: string | undefined;
6
7
  defaultNodes: XYFlowNode[];
7
8
  defaultEdges: XYFlowEdge[];
8
9
  style?: CSSProperties | undefined;
9
10
  }>;
10
- export declare function XYFlow({ className, children, defaultNodes, defaultEdges, style }: XYFlowWrapperProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function XYFlow({ colorMode, className, children, defaultNodes, defaultEdges, style }: XYFlowWrapperProps): import("react/jsx-runtime").JSX.Element;
11
12
  export {};
@@ -28,7 +28,7 @@ export declare function useXYFlowEvents(): {
28
28
  labelBgBorderRadius?: number;
29
29
  style?: import('react').CSSProperties;
30
30
  className?: string;
31
- updatable?: import('@xyflow/react').EdgeUpdatable;
31
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
32
32
  focusable?: boolean;
33
33
  data: import('./types').RelationshipData;
34
34
  readonly id: string;
@@ -59,7 +59,7 @@ export declare function useXYFlowEvents(): {
59
59
  labelBgBorderRadius?: number;
60
60
  style?: import('react').CSSProperties;
61
61
  className?: string;
62
- updatable?: import('@xyflow/react').EdgeUpdatable;
62
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
63
63
  focusable?: boolean;
64
64
  data: import('./types').RelationshipData;
65
65
  readonly id: string;
@@ -91,7 +91,7 @@ export declare function useXYFlowEvents(): {
91
91
  labelBgBorderRadius?: number;
92
92
  style?: import('react').CSSProperties;
93
93
  className?: string;
94
- updatable?: import('@xyflow/react').EdgeUpdatable;
94
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
95
95
  focusable?: boolean;
96
96
  data: import('./types').RelationshipData;
97
97
  readonly id: string;
@@ -120,7 +120,7 @@ export declare function useXYFlowEvents(): {
120
120
  labelBgBorderRadius?: number;
121
121
  style?: import('react').CSSProperties;
122
122
  className?: string;
123
- updatable?: import('@xyflow/react').EdgeUpdatable;
123
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
124
124
  focusable?: boolean;
125
125
  data: import('./types').RelationshipData;
126
126
  readonly id: string;
@@ -5,7 +5,8 @@ type XYFlowInnerProps = PropsWithChildren<{
5
5
  background: NonNullable<LikeC4DiagramProperties['background']>;
6
6
  controls: boolean;
7
7
  showDiagramTitle: boolean;
8
+ showNavigationButtons: boolean;
8
9
  enableDynamicViewWalkthrough: boolean;
9
10
  }>;
10
- export declare function XYFlowInner({ children, background, controls, showDiagramTitle, enableDynamicViewWalkthrough }: XYFlowInnerProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare const XYFlowInner: import('react').NamedExoticComponent<XYFlowInnerProps>;
11
12
  export {};
@@ -3,4 +3,4 @@ export declare const ZIndexes: {
3
3
  readonly Edge: 2;
4
4
  readonly Element: 3;
5
5
  };
6
- export declare const MinZoom = 0.05;
6
+ export declare const MinZoom = 0.1;
@@ -1,7 +1,7 @@
1
1
  import { DiagramView } from '@likec4/core';
2
2
  import { XYFlowData } from '../xyflow/types';
3
3
 
4
- export declare function diagramViewToXYFlowData(view: Pick<DiagramView, 'nodes' | 'edges' | '__'>, opts: {
4
+ export declare function diagramViewToXYFlowData(view: Pick<DiagramView, 'id' | 'nodes' | 'edges' | '__' | 'manualLayout'>, opts: {
5
5
  draggable: boolean;
6
6
  selectable: boolean;
7
7
  }): XYFlowData;
@@ -1,5 +1,4 @@
1
1
  import { EdgeProps } from '@xyflow/react';
2
- import { CSSProperties } from 'react';
3
2
 
4
3
  export declare const RelationshipEdge: import('react').NamedExoticComponent<EdgeProps<{
5
4
  source: string;
@@ -17,14 +16,14 @@ export declare const RelationshipEdge: import('react').NamedExoticComponent<Edge
17
16
  ariaLabel?: string;
18
17
  interactionWidth?: number;
19
18
  label?: import('react').ReactNode;
20
- labelStyle?: CSSProperties;
19
+ labelStyle?: import('react').CSSProperties;
21
20
  labelShowBg?: boolean;
22
- labelBgStyle?: CSSProperties;
21
+ labelBgStyle?: import('react').CSSProperties;
23
22
  labelBgPadding?: [number, number];
24
23
  labelBgBorderRadius?: number;
25
- style?: CSSProperties;
24
+ style?: import('react').CSSProperties;
26
25
  className?: string;
27
- updatable?: import('@xyflow/react').EdgeUpdatable;
26
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
28
27
  focusable?: boolean;
29
28
  data: import('../types').RelationshipData;
30
29
  readonly id: string;
@@ -1,8 +1,10 @@
1
- import { Point } from '@likec4/core';
2
1
  import { Position, XYPosition } from '@xyflow/react';
3
2
  import { InternalXYFlowNode } from '../types';
4
3
 
5
- export declare function getNodeIntersectionFromCenterToPoint(intersectionNode: InternalXYFlowNode, [x1, y1]: Point): readonly [number, number];
4
+ export declare function getNodeIntersectionFromCenterToPoint(intersectionNode: InternalXYFlowNode, { x: x1, y: y1 }: XYPosition): {
5
+ x: number;
6
+ y: number;
7
+ };
6
8
  export declare function getPointPosition(node: InternalXYFlowNode, intersectionPoint: XYPosition): readonly [number, number, Position];
7
9
  export declare function getEdgeParams(source: InternalXYFlowNode, target: InternalXYFlowNode): {
8
10
  sx: number;
@@ -23,7 +23,7 @@ export declare const useXYFlow: () => import('@xyflow/react').ReactFlowInstance<
23
23
  labelBgBorderRadius?: number;
24
24
  style?: import('react').CSSProperties;
25
25
  className?: string;
26
- updatable?: import('@xyflow/react').EdgeUpdatable;
26
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
27
27
  focusable?: boolean;
28
28
  data: import('../types').RelationshipData;
29
29
  readonly id: string;
@@ -58,7 +58,7 @@ export declare const useXYStoreApi: () => {
58
58
  labelBgBorderRadius?: number;
59
59
  style?: import('react').CSSProperties;
60
60
  className?: string;
61
- updatable?: import('@xyflow/react').EdgeUpdatable;
61
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
62
62
  focusable?: boolean;
63
63
  data: import('../types').RelationshipData;
64
64
  readonly id: string;
@@ -87,7 +87,7 @@ export declare const useXYStoreApi: () => {
87
87
  labelBgBorderRadius?: number;
88
88
  style?: import('react').CSSProperties;
89
89
  className?: string;
90
- updatable?: import('@xyflow/react').EdgeUpdatable;
90
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
91
91
  focusable?: boolean;
92
92
  data: import('../types').RelationshipData;
93
93
  readonly id: string;
@@ -115,7 +115,7 @@ export declare const useXYStoreApi: () => {
115
115
  labelBgBorderRadius?: number;
116
116
  style?: import('react').CSSProperties;
117
117
  className?: string;
118
- updatable?: import('@xyflow/react').EdgeUpdatable;
118
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
119
119
  focusable?: boolean;
120
120
  data: import('../types').RelationshipData;
121
121
  readonly id: string;
@@ -143,7 +143,7 @@ export declare const useXYStoreApi: () => {
143
143
  labelBgBorderRadius?: number;
144
144
  style?: import('react').CSSProperties;
145
145
  className?: string;
146
- updatable?: import('@xyflow/react').EdgeUpdatable;
146
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
147
147
  focusable?: boolean;
148
148
  data: import('../types').RelationshipData;
149
149
  readonly id: string;
@@ -171,7 +171,7 @@ export declare const useXYStoreApi: () => {
171
171
  labelBgBorderRadius?: number;
172
172
  style?: import('react').CSSProperties;
173
173
  className?: string;
174
- updatable?: import('@xyflow/react').EdgeUpdatable;
174
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
175
175
  focusable?: boolean;
176
176
  data: import('../types').RelationshipData;
177
177
  readonly id: string;
@@ -199,7 +199,7 @@ export declare const useXYStoreApi: () => {
199
199
  labelBgBorderRadius?: number;
200
200
  style?: import('react').CSSProperties;
201
201
  className?: string;
202
- updatable?: import('@xyflow/react').EdgeUpdatable;
202
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
203
203
  focusable?: boolean;
204
204
  data: import('../types').RelationshipData;
205
205
  readonly id: string;
@@ -228,7 +228,7 @@ export declare const useXYStoreApi: () => {
228
228
  labelBgBorderRadius?: number;
229
229
  style?: import('react').CSSProperties;
230
230
  className?: string;
231
- updatable?: import('@xyflow/react').EdgeUpdatable;
231
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
232
232
  focusable?: boolean;
233
233
  data: import('../types').RelationshipData;
234
234
  readonly id: string;
@@ -256,7 +256,7 @@ export declare const useXYStoreApi: () => {
256
256
  labelBgBorderRadius?: number;
257
257
  style?: import('react').CSSProperties;
258
258
  className?: string;
259
- updatable?: import('@xyflow/react').EdgeUpdatable;
259
+ reconnectable?: boolean | import('@xyflow/react').HandleType;
260
260
  focusable?: boolean;
261
261
  data: import('../types').RelationshipData;
262
262
  readonly id: string;
@@ -1,8 +1,10 @@
1
1
  export declare const container: string;
2
2
  export declare const dimmed: string;
3
- export declare const varTransparency: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
3
+ export declare const varOpacity: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
4
+ export declare const varBorderTransparency: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
4
5
  export declare const compoundBody: string;
5
6
  export declare const transparent: string;
6
7
  export declare const title: string;
8
+ export declare const titleWithNavigation: string;
7
9
  export declare const indicator: string;
8
10
  export declare const navigateBtn: string;
@@ -2,5 +2,5 @@ import { NodeProps } from '@xyflow/react';
2
2
  import { ElementXYFlowNode } from '../../types';
3
3
 
4
4
  type ElementNodeProps = NodeProps<ElementXYFlowNode>;
5
- export declare function ElementNode({ id, data: { element }, dragging, selected, width, height }: ElementNodeProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare const ElementNodeMemo: import('react').NamedExoticComponent<ElementNodeProps>;
6
6
  export {};
@@ -1,5 +1,6 @@
1
1
  export declare const stokeFillMix: `var(--${string})` | `var(--${string}, ${string})` | `var(--${string}, ${number})`;
2
2
  export declare const container: string;
3
+ export declare const containerAnimated: string;
3
4
  export declare const dimmed: string;
4
5
  export declare const indicator: string;
5
6
  export declare const fillElementFill: string;
@@ -1 +1 @@
1
- export { ElementNode } from './ElementNode';
1
+ export { ElementNodeMemo as ElementNode } from './ElementNode';
@@ -1,4 +1,4 @@
1
- import { BBox, DiagramEdge, DiagramNode, Fqn, Point } from '@likec4/core';
1
+ import { BBox, DiagramEdge, DiagramNode, Fqn, Point, XYPosition } from '@likec4/core';
2
2
  import { Edge, EdgeMouseHandler, InternalNode, Node, NodeMouseHandler, OnMoveEnd, OnMoveStart, ReactFlowInstance, ReactFlowState } from '@xyflow/react';
3
3
  import { MouseEvent as ReactMouseEvent } from 'react';
4
4
  import { SetReadonly, SetRequired } from 'type-fest';
@@ -25,7 +25,7 @@ export declare namespace XYFlowNode {
25
25
  }
26
26
  export type RelationshipData = {
27
27
  edge: DiagramEdge;
28
- controlPoints: Point[];
28
+ controlPoints: XYPosition[] | null;
29
29
  headPoint: Point | null;
30
30
  tailPoint: Point | null;
31
31
  type: 'bezier' | 'poly';
@@ -62,7 +62,7 @@ export declare namespace XYFlowEdge {
62
62
  labelBgBorderRadius?: number;
63
63
  style?: import("react").CSSProperties;
64
64
  className?: string;
65
- updatable?: import("@xyflow/react").EdgeUpdatable;
65
+ reconnectable?: boolean | import("@xyflow/react").HandleType;
66
66
  focusable?: boolean;
67
67
  data: RelationshipData;
68
68
  readonly id: string;
@@ -1,4 +1,7 @@
1
+ import { DiagramEdge, Point } from '@likec4/core/types';
1
2
  import { XYPosition } from '@xyflow/react';
2
3
 
3
4
  export declare function toDomPrecision(v: number | null): number;
4
5
  export declare function distance(a: XYPosition, b: XYPosition): number;
6
+ export declare function bezierControlPoints(diagramEdge: DiagramEdge): XYPosition[];
7
+ export declare function isSamePoint(a: XYPosition | Point, b: XYPosition | Point): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -48,38 +48,40 @@
48
48
  "clean": "run -T rimraf -v -g 'dist/*' .tsbuildinfo"
49
49
  },
50
50
  "dependencies": {
51
- "@likec4/core": "1.2.2",
51
+ "@likec4/core": "1.4.0",
52
52
  "@react-hookz/web": "^24.0.4",
53
- "@tabler/icons-react": "^3.0.0",
53
+ "@tabler/icons-react": "^3.7.0",
54
+ "@vanilla-extract/css": "^1.15.3",
55
+ "@vanilla-extract/css-utils": "^0.1.4",
56
+ "@vanilla-extract/dynamic": "^2.1.1",
54
57
  "d3-shape": "^3.2.0",
55
58
  "esm-env": "^1.0.0",
56
59
  "fast-equals": "^5.0.1",
57
- "framer-motion": "^11.2.2",
60
+ "framer-motion": "^11.2.12",
58
61
  "remeda": "^1.61.0",
59
- "zustand": "^4.5.2"
62
+ "zustand": "^4.5.4"
60
63
  },
61
64
  "peerDependencies": {
62
- "@mantine/core": "^7.10.1",
63
- "@mantine/hooks": "^7.10.1",
65
+ "@mantine/core": "7.10.2",
66
+ "@mantine/hooks": "7.10.2",
64
67
  "react": "^18.3.1",
65
68
  "react-dom": "^18.3.1"
66
69
  },
67
70
  "devDependencies": {
68
- "@likec4/tsconfig": "1.2.2",
71
+ "@likec4/tsconfig": "1.4.0",
69
72
  "@lume/kiwi": "^0.4.3",
70
- "@mantine/core": "^7.10.1",
71
- "@mantine/hooks": "^7.10.1",
72
- "@mantine/vanilla-extract": "^7.10.1",
73
+ "@mantine/core": "7.10.2",
74
+ "@mantine/hooks": "7.10.2",
75
+ "@mantine/vanilla-extract": "7.10.2",
73
76
  "@total-typescript/ts-reset": "^0.5.1",
74
77
  "@types/bezier-js": "^4.1.3",
75
78
  "@types/d3-shape": "^3",
76
79
  "@types/react": "^18.3.3",
77
80
  "@types/react-dom": "^18.3.0",
78
- "@vanilla-extract/css": "^1.15.2",
79
- "@vanilla-extract/dynamic": "^2.1.1",
80
- "@vanilla-extract/esbuild-plugin": "^2.3.6",
81
+ "@vanilla-extract/esbuild-plugin": "^2.3.8",
81
82
  "@vitejs/plugin-react": "^4.3.1",
82
- "@xyflow/react": "^12.0.0-next",
83
+ "@xyflow/react": "12.0.0-next.24",
84
+ "@xyflow/system": "0.0.30",
83
85
  "bezier-js": "^6.1.4",
84
86
  "clsx": "^2.1.1",
85
87
  "execa": "^9.1.0",
@@ -89,8 +91,7 @@
89
91
  "tilg": "^0.1.1",
90
92
  "type-fest": "^4.18.2",
91
93
  "typescript": "^5.4.5",
92
- "unbuild": "^2.0.0",
93
- "vite": "5.3.0",
94
+ "vite": "5.3.2",
94
95
  "vite-plugin-dts": "^3.9.1"
95
96
  }
96
97
  }
@@ -9,7 +9,17 @@ export const keepAspectRatioContainer = style({
9
9
  padding: 0
10
10
  })
11
11
 
12
- export const cssReactFlow = style({})
12
+ export const cssReactFlow = style({
13
+ '@supports': {
14
+ // https://wojtek.im/journal/targeting-safari-with-css-media-query
15
+ '(hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none)': {
16
+ // TODO: this workaround disables animations in Safari (to improve performance)
17
+ vars: {
18
+ [vars.safariAnimationHook]: ''
19
+ }
20
+ }
21
+ }
22
+ })
13
23
 
14
24
  globalStyle(`.react-flow${cssReactFlow}`, {
15
25
  vars: {
@@ -17,10 +27,10 @@ globalStyle(`.react-flow${cssReactFlow}`, {
17
27
  [xyvars.background.pattern.color]: vars.likec4.background.pattern.color
18
28
  }
19
29
  })
20
- globalStyle(`.react-flow${cssReactFlow} .react-flow__viewport`, {
21
- // willChange: 'transform'
22
- backfaceVisibility: 'hidden'
23
- })
30
+
31
+ // globalStyle(`.react-flow${cssReactFlow} .react-flow__node`, {
32
+ // willChange: 'transform'
33
+ // })
24
34
 
25
35
  export const cssDisablePan = style({})
26
36
 
@@ -3,12 +3,14 @@ import type {
3
3
  DiagramEdge,
4
4
  DiagramNode,
5
5
  DiagramView,
6
+ EdgeId,
6
7
  ElementShape,
7
8
  Fqn,
8
9
  NonEmptyArray,
9
10
  ThemeColor,
10
11
  ViewID
11
12
  } from '@likec4/core'
13
+ import type { XYPosition } from '@xyflow/system'
12
14
  import type { MouseEvent as ReactMouseEvent } from 'react'
13
15
  import type { SetRequired, Simplify } from 'type-fest'
14
16
  import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
@@ -19,9 +21,10 @@ export type DiagramNodeWithNavigate = Simplify<SetRequired<DiagramNode, 'navigat
19
21
  export type OnNavigateTo = {
20
22
  (
21
23
  to: ViewID,
22
- event: ReactMouseEvent,
23
- element: DiagramNodeWithNavigate,
24
- xynode: XYFlowNode
24
+ // These fields present if navigateTo triggered by a node click
25
+ event?: ReactMouseEvent,
26
+ element?: DiagramNodeWithNavigate,
27
+ xynode?: XYFlowNode
25
28
  ): void
26
29
  }
27
30
  export type OnNodeClick = (
@@ -55,12 +58,25 @@ export namespace Changes {
55
58
  }
56
59
  targets: NonEmptyArray<Fqn>
57
60
  }
61
+
62
+ export interface SaveManualLayout {
63
+ op: 'save-manual-layout'
64
+ nodes: Record<Fqn, {
65
+ x: number
66
+ y: number
67
+ width: number
68
+ height: number
69
+ }>
70
+ edges: Record<EdgeId, {
71
+ controlPoints: XYPosition[]
72
+ }>
73
+ }
58
74
  }
59
75
 
60
- export type Change = Changes.ChangeElementStyle
76
+ export type Change = Changes.ChangeElementStyle | Changes.SaveManualLayout
61
77
 
62
78
  export type ChangeEvent = {
63
- changes: NonEmptyArray<Change>
79
+ change: Change
64
80
  }
65
81
  export type OnChange = {
66
82
  (event: ChangeEvent): void
@@ -144,11 +160,23 @@ export interface LikeC4DiagramProperties {
144
160
  */
145
161
  showDiagramTitle?: boolean | undefined
146
162
 
163
+ /**
164
+ * Show back/forward navigation buttons
165
+ * @default false
166
+ */
167
+ showNavigationButtons?: undefined | boolean
168
+
147
169
  /**
148
170
  * If Walkthrough for dynamic views should be enabled
149
171
  * @default false
150
172
  */
151
173
  enableDynamicViewWalkthrough?: boolean | undefined
174
+
175
+ /**
176
+ * Experimental feature to enable edge editing
177
+ * @default false
178
+ */
179
+ experimentalEdgeEditing?: boolean | undefined
152
180
  }
153
181
 
154
182
  export interface LikeC4DiagramEventHandlers {
@@ -1,19 +1,23 @@
1
- import '@xyflow/react/dist/style.css'
2
1
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
2
+ import '@xyflow/react/dist/style.css'
3
3
  import clsx from 'clsx'
4
- import { useRef } from 'react'
4
+ import { DEV } from 'esm-env'
5
+ import { shallowEqual } from 'fast-equals'
6
+ import { domAnimation, LazyMotion } from 'framer-motion'
7
+ import { memo, useEffect, useRef, useState } from 'react'
8
+ import useTilg from 'tilg'
5
9
  import { rootClassName } from './globals.css'
6
10
  import { KeepAspectRatioContainer } from './KeepAspectRatioContainer'
7
11
  import { cssDisablePan, cssNoControls, cssReactFlow, cssTransparentBg } from './LikeC4Diagram.css'
8
12
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
9
13
  import { EnsureMantine } from './mantine/EnsureMantine'
10
14
  import { DiagramContextProvider } from './state/DiagramContext'
11
- import { WhenInitialized } from './state/WhenInitialized'
15
+ import { useDiagramStoreApi } from './state/useDiagramStore'
12
16
  import { diagramViewToXYFlowData } from './xyflow/diagram-to-xyflow'
13
17
  import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
14
18
  import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
15
19
  import { SyncWithDiagram } from './xyflow/SyncWithDiagram'
16
- import type { XYFlowData } from './xyflow/types'
20
+ import type { XYFlowData, XYFlowEdge, XYFlowNode } from './xyflow/types'
17
21
  import { XYFlow } from './xyflow/XYFlow'
18
22
  import { XYFlowInner } from './xyflow/XYFlowInner'
19
23
 
@@ -29,13 +33,15 @@ export function LikeC4Diagram({
29
33
  nodesSelectable = !readonly,
30
34
  nodesDraggable = !readonly,
31
35
  background = 'dots',
32
- controls = !readonly,
36
+ controls = false,
33
37
  showElementLinks = true,
34
38
  showDiagramTitle = true,
39
+ showNavigationButtons = false,
35
40
  enableDynamicViewWalkthrough = true,
36
41
  initialWidth,
37
42
  initialHeight,
38
43
  keepAspectRatio = false,
44
+ experimentalEdgeEditing = false,
39
45
  onCanvasClick,
40
46
  onCanvasContextMenu,
41
47
  onCanvasDblClick,
@@ -46,7 +52,7 @@ export function LikeC4Diagram({
46
52
  onNodeClick,
47
53
  onNodeContextMenu
48
54
  }: LikeC4DiagramProps) {
49
- console.log('LikeC4Diagram')
55
+ DEV && useTilg()
50
56
  const initialRef = useRef<{
51
57
  defaultNodes: XYFlowData['nodes']
52
58
  defaultEdges: XYFlowData['edges']
@@ -81,6 +87,7 @@ export function LikeC4Diagram({
81
87
  showElementLinks={showElementLinks}
82
88
  nodesDraggable={nodesDraggable}
83
89
  nodesSelectable={nodesSelectable}
90
+ experimentalEdgeEditing={experimentalEdgeEditing}
84
91
  onCanvasClick={onCanvasClick}
85
92
  onCanvasContextMenu={onCanvasContextMenu}
86
93
  onEdgeClick={onEdgeClick}
@@ -91,38 +98,102 @@ export function LikeC4Diagram({
91
98
  onNavigateTo={onNavigateTo}
92
99
  onCanvasDblClick={onCanvasDblClick}
93
100
  >
94
- <KeepAspectRatioContainer
95
- className={clsx(rootClassName, className)}
96
- enabled={keepAspectRatio}
97
- width={view.width}
98
- height={view.height}
99
- >
100
- <XYFlow
101
- className={clsx(
102
- 'likec4-diagram',
103
- cssReactFlow,
104
- controls === false && cssNoControls,
105
- pannable !== true && cssDisablePan,
106
- background === 'transparent' && cssTransparentBg
107
- )}
108
- defaultNodes={initialRef.current.defaultNodes}
109
- defaultEdges={initialRef.current.defaultEdges}
101
+ <LazyMotion features={domAnimation} strict>
102
+ <KeepAspectRatioContainer
103
+ className={clsx(rootClassName, className)}
104
+ enabled={keepAspectRatio}
105
+ width={view.width}
106
+ height={view.height}
110
107
  >
111
- <XYFlowInner
112
- showDiagramTitle={showDiagramTitle}
113
- enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
108
+ <LikeC4DiagramInnerMemo
109
+ defaultNodes={initialRef.current.defaultNodes}
110
+ defaultEdges={initialRef.current.defaultEdges}
111
+ fitView={fitView}
112
+ zoomable={zoomable}
114
113
  background={background}
115
114
  controls={controls}
115
+ pannable={pannable}
116
+ showDiagramTitle={showDiagramTitle}
117
+ showNavigationButtons={showNavigationButtons}
118
+ enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
116
119
  />
117
- </XYFlow>
118
- <WhenInitialized>
119
- <SyncWithDiagram />
120
- {fitView && <FitViewOnDiagramChange />}
121
- {fitView && zoomable && <SelectEdgesOnNodeFocus />}
122
- </WhenInitialized>
123
- </KeepAspectRatioContainer>
120
+ </KeepAspectRatioContainer>
121
+ </LazyMotion>
124
122
  </DiagramContextProvider>
125
123
  </XYFlowProvider>
126
124
  </EnsureMantine>
127
125
  )
128
126
  }
127
+
128
+ type LikeC4DiagramInnerProps = {
129
+ background: NonNullable<LikeC4DiagramProperties['background']>
130
+ fitView: boolean
131
+ zoomable: boolean
132
+ pannable: boolean
133
+ controls: boolean
134
+ defaultNodes: XYFlowNode[]
135
+ defaultEdges: XYFlowEdge[]
136
+ showDiagramTitle: boolean
137
+ showNavigationButtons: boolean
138
+ enableDynamicViewWalkthrough: boolean
139
+ }
140
+ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
141
+ background,
142
+ fitView,
143
+ zoomable,
144
+ controls,
145
+ pannable,
146
+ defaultNodes,
147
+ defaultEdges,
148
+ showDiagramTitle,
149
+ showNavigationButtons,
150
+ enableDynamicViewWalkthrough
151
+ }) {
152
+ DEV && useTilg()
153
+ const diagramApi = useDiagramStoreApi()
154
+ const [isInitialized, setIsInitialized] = useState(diagramApi.getState().initialized)
155
+
156
+ useEffect(() => {
157
+ if (isInitialized) {
158
+ return
159
+ }
160
+ return diagramApi.subscribe(
161
+ s => s.initialized,
162
+ setIsInitialized,
163
+ {
164
+ fireImmediately: true
165
+ }
166
+ )
167
+ }, [isInitialized])
168
+
169
+ return (
170
+ <>
171
+ <XYFlow
172
+ defaultNodes={defaultNodes}
173
+ defaultEdges={defaultEdges}
174
+ className={clsx(
175
+ 'likec4-diagram',
176
+ cssReactFlow,
177
+ controls === false && cssNoControls,
178
+ pannable !== true && cssDisablePan,
179
+ background === 'transparent' && cssTransparentBg
180
+ )}
181
+ >
182
+ <XYFlowInner
183
+ showNavigationButtons={showNavigationButtons}
184
+ showDiagramTitle={showDiagramTitle}
185
+ enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
186
+ background={background}
187
+ controls={controls}
188
+ />
189
+ </XYFlow>
190
+ {isInitialized && (
191
+ <>
192
+ <SyncWithDiagram />
193
+ {fitView && <FitViewOnDiagramChange />}
194
+ {fitView && zoomable && <SelectEdgesOnNodeFocus />}
195
+ </>
196
+ )}
197
+ </>
198
+ )
199
+ }, shallowEqual)
@@ -13,7 +13,7 @@ globalStyle(`.${rootClassName}`, {
13
13
  })
14
14
 
15
15
  createGlobalTheme(`.${rootClassName}`, {
16
- ...omit(vars, ['optionsPanel', 'default'])
16
+ ...omit(vars, ['optionsPanel', 'navigationPanel', 'safariAnimationHook', 'default'])
17
17
  }, {
18
18
  likec4: {
19
19
  font:
@@ -25,7 +25,6 @@ createGlobalTheme(`.${rootClassName}`, {
25
25
  }
26
26
  }
27
27
  },
28
- filterDimmed: 'brightness(.6) grayscale(.8) opacity(.5) blur(1px)',
29
28
  compound: {
30
29
  font: vars.likec4.font,
31
30
  titleColor: vars.element.loContrast