@likec4/diagram 1.8.1 → 1.12.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 (114) hide show
  1. package/package.json +21 -45
  2. package/src/LikeC4CustomColors.props.ts +5 -0
  3. package/src/LikeC4CustomColors.tsx +37 -0
  4. package/src/LikeC4Diagram.css.ts +1 -1
  5. package/src/LikeC4Diagram.props.ts +43 -95
  6. package/src/LikeC4Diagram.tsx +21 -2
  7. package/src/LikeC4ModelProvider.tsx +49 -0
  8. package/src/StaticLikeC4Diagram.tsx +1 -0
  9. package/src/globals.css.ts +2 -3
  10. package/src/hooks/index.ts +1 -0
  11. package/src/hooks/useMantinePortalProps.ts +8 -0
  12. package/src/hooks/useXYFlow.ts +1 -2
  13. package/src/index.ts +3 -0
  14. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  15. package/src/likec4model/index.ts +1 -0
  16. package/src/likec4model/useLikeC4Model.ts +55 -0
  17. package/src/mantine/EnsureMantine.tsx +1 -0
  18. package/src/state/DiagramContext.tsx +1 -0
  19. package/src/state/diagram-to-xyflow.ts +2 -3
  20. package/src/state/diagramStore.ts +185 -66
  21. package/src/{theme.css.ts → theme-vars.ts} +11 -4
  22. package/src/ui/DiagramTitlePanel.css.ts +2 -3
  23. package/src/ui/DiagramTitlePanel.tsx +17 -1
  24. package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
  25. package/src/ui/DynamicViewWalkthrough.tsx +97 -52
  26. package/src/ui/OptionsPanel.css.ts +1 -2
  27. package/src/ui/OptionsPanel.tsx +1 -1
  28. package/src/ui/notation/NotationPanel.css.ts +2 -3
  29. package/src/ui/notation/NotationPanel.tsx +11 -12
  30. package/src/ui/options/NodeOptions.tsx +1 -1
  31. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +20 -32
  32. package/src/ui/top-left/TopLeftPanel.tsx +128 -117
  33. package/src/ui/top-left/_shared.ts +7 -6
  34. package/src/ui/top-left/styles.css.ts +17 -8
  35. package/src/utils/vector.ts +57 -0
  36. package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
  37. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
  38. package/src/xyflow/XYFlow.tsx +10 -11
  39. package/src/xyflow/XYFlowEvents.tsx +59 -60
  40. package/src/xyflow/XYFlowInner.tsx +5 -5
  41. package/src/xyflow/edges/EdgeLabel.tsx +185 -0
  42. package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
  43. package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
  44. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
  45. package/src/xyflow/edges/edges.css.ts +75 -7
  46. package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
  47. package/src/xyflow/nodes/compound/CompoundNode.tsx +4 -4
  48. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  49. package/src/xyflow/nodes/element/ElementLink.tsx +3 -8
  50. package/src/xyflow/nodes/element/ElementNode.tsx +47 -4
  51. package/src/xyflow/nodes/element/element.css.ts +1 -2
  52. package/src/xyflow/types.ts +1 -24
  53. package/src/xyflow/useLayoutConstraints.ts +10 -6
  54. package/src/xyflow/utils.ts +1 -1
  55. package/dist/LikeC4Diagram.css.d.ts +0 -4
  56. package/dist/LikeC4Diagram.d.ts +0 -4
  57. package/dist/LikeC4Diagram.props.d.ts +0 -185
  58. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  59. package/dist/globals.css.d.ts +0 -1
  60. package/dist/hooks/index.d.ts +0 -4
  61. package/dist/hooks/useDiagramState.d.ts +0 -6
  62. package/dist/hooks/useSetState.d.ts +0 -3
  63. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  64. package/dist/hooks/useXYFlow.d.ts +0 -267
  65. package/dist/index.d.ts +0 -9
  66. package/dist/index.mjs +0 -3651
  67. package/dist/mantine/EnsureMantine.d.ts +0 -5
  68. package/dist/mantine.css.d.ts +0 -3
  69. package/dist/state/DiagramContext.d.ts +0 -1189
  70. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  71. package/dist/state/diagramStore.d.ts +0 -1268
  72. package/dist/style.css +0 -2139
  73. package/dist/theme.css.d.ts +0 -63
  74. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  75. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  76. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  77. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  78. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  79. package/dist/ui/OptionsPanel.d.ts +0 -1
  80. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  81. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  82. package/dist/ui/notation/index.d.ts +0 -1
  83. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  84. package/dist/ui/options/NodeOptions.d.ts +0 -19
  85. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  86. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  87. package/dist/ui/top-left/_shared.d.ts +0 -5
  88. package/dist/ui/top-left/styles.css.d.ts +0 -3
  89. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  90. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  91. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  92. package/dist/xyflow/XYFlow.d.ts +0 -9
  93. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  94. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  95. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  96. package/dist/xyflow/const.d.ts +0 -7
  97. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  98. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  99. package/dist/xyflow/edges/utils.d.ts +0 -17
  100. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  101. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  102. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  103. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  104. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  105. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  106. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  107. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  108. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  109. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  110. package/dist/xyflow/types.d.ts +0 -50
  111. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  112. package/dist/xyflow/utils.d.ts +0 -18
  113. package/src/mantine.css.ts +0 -7
  114. package/src/reset.d.ts +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.8.1",
3
+ "version": "1.12.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -14,84 +14,60 @@
14
14
  "package.json",
15
15
  "LICENSE",
16
16
  "README.md",
17
- "dist",
18
17
  "src",
19
- "!dist/src",
20
18
  "!**/*.spec.*",
21
19
  "!**/*.d.ts.map"
22
20
  ],
23
21
  "sideEffects": false,
24
22
  "type": "module",
25
23
  "exports": {
26
- ".": {
27
- "types": "./dist/index.d.ts",
28
- "default": "./dist/index.mjs"
29
- },
30
- "./style.css": "./dist/style.css"
24
+ ".": "./src/index.ts"
31
25
  },
32
- "publishConfig": {
33
- "registry": "https://registry.npmjs.org",
34
- "access": "public",
35
- "exports": {
36
- ".": {
37
- "types": "./dist/index.d.ts",
38
- "default": "./dist/index.mjs"
39
- },
40
- "./style.css": "./dist/style.css"
41
- }
42
- },
43
- "packageManager": "yarn@4.3.1",
26
+ "packageManager": "yarn@4.5.0",
44
27
  "scripts": {
45
28
  "typecheck": "tsc --noEmit",
46
- "prepack": "vite build --mode production",
47
29
  "lint": "run -T eslint src/ --fix",
48
- "clean": "run -T rimraf -v -g 'dist/*' .tsbuildinfo"
30
+ "clean": "rm -r -f -g 'dist/*' .tsbuildinfo"
49
31
  },
50
32
  "dependencies": {
51
- "@lume/kiwi": "^0.4.3",
33
+ "@likec4/core": "1.12.0",
34
+ "@lume/kiwi": "^0.4.4",
35
+ "@mantine/vanilla-extract": "7.12.2",
52
36
  "@react-hookz/web": "^24.0.4",
53
- "@tabler/icons-react": "^3.14.0",
54
- "@vanilla-extract/css": "^1.15.5",
37
+ "@tabler/icons-react": "^3.19.0",
38
+ "@vanilla-extract/css": "^1.16.0",
55
39
  "@vanilla-extract/css-utils": "^0.1.4",
56
40
  "@vanilla-extract/dynamic": "^2.1.2",
57
- "@xyflow/react": "12.1.1",
58
- "@xyflow/system": "0.0.39",
41
+ "@xyflow/react": "12.3.1",
42
+ "@xyflow/system": "0.0.43",
59
43
  "bezier-js": "^6.1.4",
60
44
  "clsx": "^2.1.1",
61
45
  "d3-shape": "^3.2.0",
62
46
  "esm-env": "^1.0.0",
63
47
  "fast-equals": "^5.0.1",
64
- "framer-motion": "^11.3.30",
65
- "remeda": "^2.11.0",
48
+ "framer-motion": "^11.5.6",
49
+ "khroma": "^2.1.0",
50
+ "remeda": "^2.14.0",
51
+ "type-fest": "4.26.1",
66
52
  "zustand": "^4.5.5"
67
53
  },
68
54
  "peerDependencies": {
69
- "@likec4/core": "1.8.1",
70
- "@mantine/core": "^7.11.2",
71
- "@mantine/hooks": "^7.11.2",
55
+ "@mantine/core": "^7.12.2",
56
+ "@mantine/hooks": "^7.12.2",
72
57
  "react": "^18.3.1",
73
58
  "react-dom": "^18.3.1"
74
59
  },
75
60
  "devDependencies": {
76
- "@likec4/core": "1.8.1",
77
- "@likec4/tsconfig": "1.8.1",
78
- "@mantine/core": "7.11.2",
79
- "@mantine/hooks": "7.11.2",
80
- "@mantine/vanilla-extract": "7.11.2",
81
- "@total-typescript/ts-reset": "^0.5.1",
61
+ "@likec4/tsconfig": "1.12.0",
62
+ "@mantine/core": "7.12.2",
63
+ "@mantine/hooks": "7.12.2",
82
64
  "@types/bezier-js": "^4.1.3",
83
65
  "@types/d3-shape": "^3.1.6",
84
66
  "@types/react": "^18.3.4",
85
67
  "@types/react-dom": "^18.3.0",
86
- "@vanilla-extract/esbuild-plugin": "^2.3.10",
87
- "@vitejs/plugin-react": "^4.3.1",
88
68
  "execa": "^9.3.1",
89
- "khroma": "^2.1.0",
90
69
  "react": "^18.3.1",
91
70
  "react-dom": "^18.3.1",
92
- "type-fest": "^4.21.0",
93
- "typescript": "^5.5.4",
94
- "vite": "5.3.4",
95
- "vite-plugin-dts": "^3.9.1"
71
+ "typescript": "^5.6.2"
96
72
  }
97
73
  }
@@ -0,0 +1,5 @@
1
+ import { type CustomColorDefinitions } from '@likec4/core'
2
+
3
+ export interface LikeC4CustomColorsProperties {
4
+ customColors: CustomColorDefinitions
5
+ }
@@ -0,0 +1,37 @@
1
+ import { type ThemeColorValues } from '@likec4/core'
2
+ import { entries } from 'remeda'
3
+ import type { LikeC4CustomColorsProperties } from './LikeC4CustomColors.props'
4
+ import { vars } from './theme-vars'
5
+
6
+ type CSSVarFunction = `var(--${string})` | `var(--${string}, ${string | number} )`
7
+
8
+ export function LikeC4CustomColors({ customColors }: LikeC4CustomColorsProperties) {
9
+ function toStyle(name: String, colorValues: ThemeColorValues): String {
10
+ const rules = new Array<String>(
11
+ ...entries(colorValues.elements).map(([key, value]) => `${stripCssVarReference(vars.element[key])}: ${value};`),
12
+ ...entries(colorValues.relationships).map(([key, value]) =>
13
+ `${stripCssVarReference(vars.relation[key])}: ${value};`
14
+ )
15
+ )
16
+ .join('\n')
17
+
18
+ return `:where([data-likec4-color=${name}]) {
19
+ ${rules}
20
+ }`
21
+ }
22
+
23
+ function stripCssVarReference(ref: CSSVarFunction): String {
24
+ const end = ref.indexOf(',')
25
+ return ref.substring(4, end == -1 ? ref.length - 1 : end)
26
+ }
27
+
28
+ const styles = entries(customColors)
29
+ .map(([name, color]) => toStyle(name, color))
30
+ .join('\n')
31
+
32
+ return (
33
+ <>
34
+ <style type="text/css" dangerouslySetInnerHTML={{ __html: styles }} />
35
+ </>
36
+ )
37
+ }
@@ -1,5 +1,5 @@
1
1
  import { globalStyle, style } from '@vanilla-extract/css'
2
- import { vars, xyvars } from './theme.css'
2
+ import { vars, xyvars } from './theme-vars'
3
3
 
4
4
  export const cssReactFlow = style({
5
5
  '@supports': {
@@ -3,73 +3,18 @@ import type {
3
3
  DiagramNode,
4
4
  DiagramView,
5
5
  Fqn,
6
- NonEmptyArray,
7
6
  RelationID,
8
7
  ViewChange,
9
- ViewID
8
+ ViewID,
9
+ WhereOperator
10
10
  } from '@likec4/core'
11
11
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'
12
- import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
13
12
  import type { XYBackground } from './xyflow/XYFlowBackground'
14
13
 
15
- type EqualOperator<V> = {
16
- eq: V
17
- neq?: never
18
- } | {
19
- eq?: never
20
- neq: V
21
- }
22
-
23
- type AllNever = {
24
- not?: never
25
- and?: never
26
- or?: never
27
- tag?: never
28
- kind?: never
29
- }
30
-
31
- type TagKindOperator<Tag, Kind> =
32
- & Omit<AllNever, 'tag' | 'kind'>
33
- & ({
34
- tag: EqualOperator<Tag>
35
- kind?: never
36
- } | {
37
- tag?: never
38
- kind: EqualOperator<Kind>
39
- })
40
-
41
- type NotOperator<Tag, Kind> = Omit<AllNever, 'not'> & {
42
- not: TagKindOperator<Tag, Kind> | AndOperator<Tag, Kind> | OrOperator<Tag, Kind>
43
- }
14
+ export type { WhereOperator }
44
15
 
45
- type AndOperator<Tag, Kind> = Omit<AllNever, 'and'> & {
46
- and: NonEmptyArray<
47
- | TagKindOperator<Tag, Kind>
48
- | OrOperator<Tag, Kind>
49
- | NotOperator<Tag, Kind>
50
- >
51
- }
52
-
53
- type OrOperator<Tag, Kind> = Omit<AllNever, 'or'> & {
54
- or: NonEmptyArray<
55
- | TagKindOperator<Tag, Kind>
56
- | AndOperator<Tag, Kind>
57
- | NotOperator<Tag, Kind>
58
- >
59
- }
60
-
61
- // export const isOrOperator = (operator: WhereOperator): operator is OrOperator<WhereOperator> => {
62
- // return 'or' in operator
63
- // }
64
-
65
- export type WhereOperator<Tag extends string, Kind extends string> =
66
- | TagKindOperator<Tag, Kind>
67
- | NotOperator<Tag, Kind>
68
- | AndOperator<Tag, Kind>
69
- | OrOperator<Tag, Kind>
70
-
71
- export type DiagramNodeWithNavigate = Omit<DiagramNode, 'navigateTo'> & {
72
- navigateTo: ViewID
16
+ export type DiagramNodeWithNavigate<ID extends string = ViewID> = Omit<DiagramNode, 'navigateTo'> & {
17
+ navigateTo: ID
73
18
  }
74
19
 
75
20
  type ElementIconNodeProps = {
@@ -82,28 +27,21 @@ export type ElementIconRenderer = (props: {
82
27
  node: ElementIconNodeProps
83
28
  }) => ReactNode
84
29
 
85
- export type OnNavigateTo = {
86
- (
87
- to: ViewID,
88
- // These fields present if navigateTo triggered by a node click
89
- event?: ReactMouseEvent,
90
- element?: DiagramNodeWithNavigate,
91
- xynode?: XYFlowNode
92
- ): void
93
- }
30
+ export type OnNavigateTo<ID extends string = ViewID> = (
31
+ to: ID,
32
+ // These fields present if navigateTo triggered by a node click
33
+ event?: ReactMouseEvent,
34
+ element?: DiagramNodeWithNavigate<ID>
35
+ ) => void
36
+
94
37
  export type OnNodeClick = (
95
- event: {
96
- element: DiagramNode
97
- xynode: XYFlowNode
98
- event: ReactMouseEvent
99
- }
38
+ node: DiagramNode,
39
+ event: ReactMouseEvent
100
40
  ) => void
41
+
101
42
  export type OnEdgeClick = (
102
- event: {
103
- edge: DiagramEdge
104
- xyedge: XYFlowEdge
105
- event: ReactMouseEvent
106
- }
43
+ edge: DiagramEdge,
44
+ event: ReactMouseEvent
107
45
  ) => void
108
46
 
109
47
  /**
@@ -114,9 +52,8 @@ export type OnCanvasClick = (event: ReactMouseEvent) => void
114
52
  export type ChangeEvent = {
115
53
  change: ViewChange
116
54
  }
117
- export type OnChange = {
118
- (event: ChangeEvent): void
119
- }
55
+ export type OnChange = (event: ChangeEvent) => void
56
+
120
57
  export type LikeC4ColorScheme = 'light' | 'dark'
121
58
 
122
59
  export interface LikeC4DiagramProperties {
@@ -125,15 +62,7 @@ export interface LikeC4DiagramProperties {
125
62
  className?: string | undefined
126
63
 
127
64
  /**
128
- * Controls color scheme used for styling the flow
129
- * By default inherits from system or surrounding MantineProvider
130
- *
131
- * @example 'light' | 'dark'
132
- */
133
- // colorScheme?: LikeC4ColorScheme | undefined
134
-
135
- /**
136
- * Show/hide controls menu
65
+ * Show/hide ReactFlow controls menu
137
66
  * @default false
138
67
  */
139
68
  controls?: boolean | undefined
@@ -163,10 +92,13 @@ export interface LikeC4DiagramProperties {
163
92
  */
164
93
  fitViewPadding?: number | undefined
165
94
 
95
+ /**
96
+ * @default false if readonly
97
+ */
166
98
  nodesSelectable?: boolean | undefined
167
99
 
168
100
  /**
169
- * @default false
101
+ * @default false if readonly
170
102
  */
171
103
  nodesDraggable?: boolean | undefined
172
104
 
@@ -192,14 +124,14 @@ export interface LikeC4DiagramProperties {
192
124
  showElementLinks?: boolean | undefined
193
125
 
194
126
  /**
195
- * Display panel with diagram title / description
127
+ * Display webview with diagram title / description
196
128
  * @default true
197
129
  */
198
130
  showDiagramTitle?: boolean | undefined
199
131
 
200
132
  /**
201
133
  * Show back/forward navigation buttons
202
- * @default false
134
+ * @default true if `onNavigateTo` is set
203
135
  */
204
136
  showNavigationButtons?: undefined | boolean
205
137
 
@@ -209,6 +141,12 @@ export interface LikeC4DiagramProperties {
209
141
  */
210
142
  showNotations?: boolean | undefined
211
143
 
144
+ /**
145
+ * Display dropdown with details on relationship's label click
146
+ * @default true
147
+ */
148
+ showRelationshipDetails?: boolean | undefined
149
+
212
150
  /**
213
151
  * If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
214
152
  * @default true
@@ -228,11 +166,14 @@ export interface LikeC4DiagramProperties {
228
166
  experimentalEdgeEditing?: boolean | undefined
229
167
 
230
168
  /**
231
- * Render custom icon for a node
169
+ * Render icon for an element, bundled or remote
232
170
  * By default, if icon is http:// or https://, it will be rendered as an image
233
171
  */
234
172
  renderIcon?: ElementIconRenderer | undefined
235
173
 
174
+ /**
175
+ * Dynamic filter, applies both to nodes and edges
176
+ */
236
177
  where?: WhereOperator<string, string> | undefined
237
178
  }
238
179
 
@@ -247,8 +188,15 @@ export interface LikeC4DiagramEventHandlers {
247
188
  onCanvasClick?: OnCanvasClick | null | undefined
248
189
  onCanvasDblClick?: OnCanvasClick | null | undefined
249
190
 
191
+ // if set, will render a burger menu icon in the top left corner
250
192
  onBurgerMenuClick?: null | undefined | (() => void)
193
+
194
+ // if set, will render a source code icon in the top left corner
251
195
  onOpenSourceView?: null | undefined | (() => void)
196
+
197
+ // if set, will render an icon in properties webview for each element
252
198
  onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void)
199
+
200
+ // if set, will be called on edge click
253
201
  onOpenSourceRelation?: null | undefined | ((id: RelationID) => void)
254
202
  }
@@ -1,12 +1,15 @@
1
+ import type { DiagramView } from '@likec4/core'
1
2
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
2
3
  import clsx from 'clsx'
3
4
  import { shallowEqual } from 'fast-equals'
4
5
  import { domAnimation, LazyMotion } from 'framer-motion'
5
- import { memo, useRef } from 'react'
6
+ import { memo, useEffect, useRef } from 'react'
6
7
  import { rootClassName } from './globals.css'
7
8
  import { useDiagramState } from './hooks/useDiagramState'
9
+ import { LikeC4CustomColors } from './LikeC4CustomColors'
8
10
  import * as css from './LikeC4Diagram.css'
9
11
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
12
+ import { useLikeC4Model } from './likec4model'
10
13
  import { EnsureMantine } from './mantine/EnsureMantine'
11
14
  import { DiagramContextProvider } from './state/DiagramContext'
12
15
  import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
@@ -31,6 +34,7 @@ export function LikeC4Diagram({
31
34
  showElementLinks = true,
32
35
  showDiagramTitle = true,
33
36
  showNotations = true,
37
+ showRelationshipDetails = true,
34
38
  enableDynamicViewWalkthrough = false,
35
39
  enableFocusMode = false,
36
40
  initialWidth,
@@ -54,6 +58,7 @@ export function LikeC4Diagram({
54
58
  where,
55
59
  showNavigationButtons = !!onNavigateTo
56
60
  }: LikeC4DiagramProps) {
61
+ const hasLikec4model = !!useLikeC4Model()
57
62
  const initialRef = useRef({
58
63
  defaultNodes: [] as XYFlowNode[],
59
64
  defaultEdges: [] as XYFlowEdge[],
@@ -63,6 +68,12 @@ export function LikeC4Diagram({
63
68
  if (readonly !== true && !!where) {
64
69
  console.warn('where filter is only supported in readonly mode')
65
70
  }
71
+ useEffect(() => {
72
+ if (showRelationshipDetails && !hasLikec4model) {
73
+ console.warn('Relationship details require LikeC4ModelProvider')
74
+ }
75
+ }, [showRelationshipDetails, hasLikec4model])
76
+
66
77
  return (
67
78
  <EnsureMantine>
68
79
  <LazyMotion features={domAnimation} strict>
@@ -70,6 +81,7 @@ export function LikeC4Diagram({
70
81
  fitView={fitView}
71
82
  {...initialRef.current}
72
83
  >
84
+ {customColorsDefined(view) && <LikeC4CustomColors customColors={view.customColorDefinitions} />}
73
85
  <DiagramContextProvider
74
86
  view={view}
75
87
  keepAspectRatio={keepAspectRatio}
@@ -82,6 +94,7 @@ export function LikeC4Diagram({
82
94
  showElementLinks={showElementLinks}
83
95
  showNavigationButtons={showNavigationButtons}
84
96
  showNotations={showNotations}
97
+ showRelationshipDetails={showRelationshipDetails && hasLikec4model}
85
98
  nodesDraggable={nodesDraggable}
86
99
  nodesSelectable={nodesSelectable}
87
100
  experimentalEdgeEditing={experimentalEdgeEditing}
@@ -114,13 +127,14 @@ export function LikeC4Diagram({
114
127
  </EnsureMantine>
115
128
  )
116
129
  }
130
+ LikeC4Diagram.displayName = 'LikeC4Diagram'
117
131
 
118
132
  type LikeC4DiagramInnerProps = {
119
133
  background: NonNullable<LikeC4DiagramProperties['background']>
120
134
  controls: boolean
121
135
  showDiagramTitle: boolean
122
136
  }
123
- const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
137
+ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
124
138
  background,
125
139
  controls,
126
140
  showDiagramTitle
@@ -164,3 +178,8 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
164
178
  </>
165
179
  )
166
180
  }, shallowEqual)
181
+ LikeC4DiagramInnerMemo.displayName = 'LikeC4DiagramInnerMemo'
182
+
183
+ function customColorsDefined(view: DiagramView) {
184
+ return Object.keys(view.customColorDefinitions).length > 0
185
+ }
@@ -0,0 +1,49 @@
1
+ import { LikeC4Model } from '@likec4/core'
2
+ import { useCustomCompareMemo } from '@react-hookz/web'
3
+ import { type PropsWithChildren } from 'react'
4
+ import type { RequireExactlyOne } from 'type-fest'
5
+ import { depsShallowEqual } from './hooks/useUpdateEffect'
6
+ import { LikeC4ModelContext } from './likec4model/LikeC4ModelContext'
7
+ import { useLikeC4Model } from './likec4model/useLikeC4Model'
8
+
9
+ export type LikeC4ModelProviderProps = PropsWithChildren<
10
+ RequireExactlyOne<{
11
+ likec4model: LikeC4Model
12
+ layouted: LikeC4Model.Layouted.SourceModel
13
+ computed: LikeC4Model.Computed.SourceModel
14
+ }>
15
+ >
16
+
17
+ /**
18
+ * Ensures LikeC4Model context
19
+ */
20
+ export function LikeC4ModelProvider({
21
+ children,
22
+ ...props
23
+ }: LikeC4ModelProviderProps) {
24
+ const model = useLikeC4Model()
25
+
26
+ if (model) {
27
+ return <>{children}</>
28
+ }
29
+
30
+ const value = useCustomCompareMemo(
31
+ () => {
32
+ if (props.likec4model) {
33
+ return props.likec4model
34
+ }
35
+ if (props.layouted) {
36
+ return LikeC4Model.layouted(props.layouted)
37
+ }
38
+ return LikeC4Model.computed(props.computed)
39
+ },
40
+ [props.likec4model ?? props.layouted ?? props.computed],
41
+ depsShallowEqual
42
+ )
43
+
44
+ return (
45
+ <LikeC4ModelContext.Provider value={value}>
46
+ {children}
47
+ </LikeC4ModelContext.Provider>
48
+ )
49
+ }
@@ -35,6 +35,7 @@ export function StaticLikeC4Diagram({
35
35
  showElementLinks
36
36
  showDiagramTitle={false}
37
37
  showNotations={false}
38
+ showRelationshipDetails={false}
38
39
  enableDynamicViewWalkthrough={false}
39
40
  enableFocusMode={false}
40
41
  nodesSelectable={false}
@@ -2,8 +2,7 @@ import { defaultTheme } from '@likec4/core'
2
2
  import { createGlobalTheme, globalStyle } from '@vanilla-extract/css'
3
3
  import { scale, toHex } from 'khroma'
4
4
  import { keys, omit } from 'remeda'
5
- import { mantine, whereDark } from './mantine.css'
6
- import { vars } from './theme.css'
5
+ import { mantine, vars, whereDark } from './theme-vars'
7
6
 
8
7
  export const rootClassName = 'likec4-diagram-root'
9
8
 
@@ -18,7 +17,7 @@ globalStyle(`.${rootClassName}`, {
18
17
  WebkitFontSmoothing: mantine.webkitFontSmoothing,
19
18
  MozOsxFontSmoothing: mantine.mozFontSmoothing
20
19
  })
21
- globalStyle(`.${rootClassName} .mantine-ActionIcon-icon .tabler-icon`, {
20
+ globalStyle(`:where(.${rootClassName}) .mantine-ActionIcon-icon .tabler-icon`, {
22
21
  width: '75%',
23
22
  height: '75%'
24
23
  })
@@ -1,4 +1,5 @@
1
1
  export * from './useDiagramState'
2
+ export * from './useMantinePortalProps'
2
3
  export * from './useSetState'
3
4
  export * from './useUpdateEffect'
4
5
  export * from './useXYFlow'
@@ -0,0 +1,8 @@
1
+ import { useMemo } from 'react'
2
+
3
+ import { useDiagramState } from './useDiagramState'
4
+
5
+ export function useMantinePortalProps() {
6
+ const target = useDiagramState(s => s.getContainer())
7
+ return useMemo(() => target ? { portalProps: { target }, withinPortal: true } : { withinPortal: false }, [target])
8
+ }
@@ -12,8 +12,7 @@ export function useXYStore<StateSlice = unknown>(
12
12
  equalityFn?: (a: StateSlice, b: StateSlice) => boolean
13
13
  ): StateSlice {
14
14
  return useStore(
15
- // @ts-expect-error - types are not correct
16
- selector,
15
+ selector as any,
17
16
  equalityFn ?? shallowEqual
18
17
  )
19
18
  }
package/src/index.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import '@xyflow/react/dist/style.css'
2
2
 
3
3
  export { LikeC4Diagram } from './LikeC4Diagram'
4
+ export * from './likec4model/useLikeC4Model'
5
+ export { LikeC4ModelProvider } from './LikeC4ModelProvider'
4
6
  export { StaticLikeC4Diagram } from './StaticLikeC4Diagram'
5
7
 
6
8
  export { useSetState } from './hooks/useSetState'
7
9
  export { useUpdateEffect } from './hooks/useUpdateEffect'
8
10
  export type * from './LikeC4Diagram'
9
11
  export type * from './LikeC4Diagram.props'
12
+ export type * from './LikeC4ModelProvider'
10
13
  export type * from './StaticLikeC4Diagram'
11
14
  export type * from './xyflow/types'
@@ -0,0 +1,4 @@
1
+ import type { LikeC4Model } from '@likec4/core'
2
+ import { createContext } from 'react'
3
+
4
+ export const LikeC4ModelContext = createContext<LikeC4Model | null>(null)
@@ -0,0 +1 @@
1
+ export * from './useLikeC4Model'
@@ -0,0 +1,55 @@
1
+ import { type ComputedView, type DiagramView, type LikeC4Model, type ViewID } from '@likec4/core'
2
+ import { useContext } from 'react'
3
+ import { isDefined, isNonNullish, isString } from 'remeda'
4
+ import type { LiteralUnion } from 'type-fest'
5
+ import { LikeC4ModelContext } from './LikeC4ModelContext'
6
+
7
+ export function useLikeC4Model(): LikeC4Model | null
8
+ export function useLikeC4Model(strict: true): LikeC4Model
9
+ export function useLikeC4Model(strict: boolean): LikeC4Model | null
10
+ export function useLikeC4Model(strict: true, type: 'layouted'): LikeC4Model.Layouted
11
+ export function useLikeC4Model(strict: true, type: 'computed'): LikeC4Model.Computed
12
+ export function useLikeC4Model(strict: true, type: LikeC4Model['type'] | undefined): LikeC4Model
13
+ export function useLikeC4Model(strict: boolean, type: 'layouted'): LikeC4Model.Layouted | null
14
+ export function useLikeC4Model(strict: boolean, type: 'computed'): LikeC4Model.Computed | null
15
+ export function useLikeC4Model(strict: boolean, type: LikeC4Model['type'] | undefined): LikeC4Model | null
16
+ export function useLikeC4Model(strict?: boolean, type?: LikeC4Model['type']) {
17
+ const model = useContext(LikeC4ModelContext)
18
+
19
+ if (isString(type) && isNonNullish(model) && model.type !== type) {
20
+ throw new Error(`Invalid LikeC4ModelContext, expected "${type}" but got "${model.type}" in context`)
21
+ }
22
+
23
+ if (isDefined(strict) && strict === true && !model) {
24
+ throw new Error('No LikeC4Model found in context')
25
+ }
26
+ return model
27
+ }
28
+
29
+ export function useLikeC4Views(): LikeC4Model.SourceModel['views'] {
30
+ return useLikeC4Model(true).sourcemodel.views
31
+ }
32
+
33
+ export function useLikeC4ViewModel(viewId: LiteralUnion<ViewID, string>): LikeC4Model.ViewModel {
34
+ return useLikeC4Model(true).view(viewId)
35
+ }
36
+
37
+ export function useLikeC4View(viewId: LiteralUnion<ViewID, string>): ComputedView | DiagramView | null {
38
+ const model = useLikeC4Model(true)
39
+ try {
40
+ return model.view(viewId).view
41
+ } catch (error) {
42
+ console.error(error)
43
+ return null
44
+ }
45
+ }
46
+
47
+ export function useLikeC4DiagramView(viewId: LiteralUnion<ViewID, string>): DiagramView | null {
48
+ const model = useLikeC4Model(true, 'layouted')
49
+ try {
50
+ return model.view(viewId).view
51
+ } catch (error) {
52
+ console.error(error)
53
+ return null
54
+ }
55
+ }
@@ -12,3 +12,4 @@ export function EnsureMantine({ children }: EnsureMantineProps) {
12
12
 
13
13
  return <>{children}</>
14
14
  }
15
+ EnsureMantine.displayName = 'EnsureMantine'
@@ -79,3 +79,4 @@ export function DiagramContextProvider({
79
79
  </div>
80
80
  )
81
81
  }
82
+ DiagramContextProvider.displayName = 'DiagramContextProvider'