@likec4/diagram 1.3.0 → 1.5.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 (95) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +0 -1
  2. package/dist/LikeC4Diagram.d.ts +1 -1
  3. package/dist/LikeC4Diagram.props.d.ts +7 -2
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.mjs +1580 -1194
  6. package/dist/state/DiagramContext.d.ts +97 -4169
  7. package/dist/state/diagramStore.d.ts +98 -4172
  8. package/dist/state/hooks.d.ts +4 -0
  9. package/dist/state/index.d.ts +1 -3
  10. package/dist/state/useDiagramStore.d.ts +3 -4622
  11. package/dist/state/useNavigationHistory.d.ts +5 -0
  12. package/dist/style.css +217 -159
  13. package/dist/theme.css.d.ts +5 -0
  14. package/dist/ui/BackwardForwardPanel.css.d.ts +1 -0
  15. package/dist/ui/BackwardForwardPanel.d.ts +1 -0
  16. package/dist/ui/DiagramTitlePanel.css.d.ts +1 -1
  17. package/dist/ui/OptionsPanel.d.ts +1 -3
  18. package/dist/ui/options/EdgeOptions.d.ts +19 -0
  19. package/dist/ui/options/NodeOptions.d.ts +2 -2
  20. package/dist/xyflow/EdgeMarkers.d.ts +11 -0
  21. package/dist/xyflow/XYFlowEvents.d.ts +12 -9
  22. package/dist/xyflow/XYFlowInner.d.ts +1 -0
  23. package/dist/xyflow/const.d.ts +3 -3
  24. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  25. package/dist/xyflow/edges/RelationshipEdge.d.ts +4 -3
  26. package/dist/xyflow/edges/edges.css.d.ts +1 -0
  27. package/dist/xyflow/edges/utils.d.ts +1 -0
  28. package/dist/xyflow/hooks/index.d.ts +3 -2
  29. package/dist/xyflow/hooks/useXYFlow.d.ts +22 -21
  30. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
  31. package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -4
  32. package/dist/xyflow/nodes/element/element.css.d.ts +3 -1
  33. package/dist/xyflow/types.d.ts +2 -32
  34. package/package.json +21 -21
  35. package/src/LikeC4Diagram.css.ts +10 -8
  36. package/src/LikeC4Diagram.props.ts +12 -5
  37. package/src/LikeC4Diagram.tsx +43 -26
  38. package/src/globals.css.ts +11 -4
  39. package/src/index.ts +1 -1
  40. package/src/mantine.css.ts +2 -2
  41. package/src/state/DiagramContext.tsx +47 -16
  42. package/src/state/diagramStore.ts +89 -80
  43. package/src/state/hooks.ts +5 -0
  44. package/src/state/index.ts +1 -3
  45. package/src/state/useDiagramStore.ts +6 -13
  46. package/src/state/useNavigationHistory.ts +39 -0
  47. package/src/theme.css.ts +5 -0
  48. package/src/ui/BackwardForwardPanel.css.ts +22 -0
  49. package/src/ui/BackwardForwardPanel.tsx +59 -0
  50. package/src/ui/DiagramTitlePanel.css.ts +17 -6
  51. package/src/ui/DiagramTitlePanel.tsx +108 -94
  52. package/src/ui/DynamicViewWalkthrough.tsx +6 -4
  53. package/src/ui/OptionsPanel.tsx +37 -18
  54. package/src/ui/options/EdgeOptions.tsx +157 -0
  55. package/src/ui/options/NodeOptions.tsx +37 -29
  56. package/src/xyflow/EdgeMarkers.tsx +148 -0
  57. package/src/xyflow/FitviewOnDiagramChange.tsx +8 -4
  58. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
  59. package/src/xyflow/SyncWithDiagram.tsx +13 -20
  60. package/src/xyflow/XYFlow.tsx +2 -4
  61. package/src/xyflow/XYFlowEvents.tsx +48 -33
  62. package/src/xyflow/XYFlowInner.tsx +6 -3
  63. package/src/xyflow/const.ts +3 -3
  64. package/src/xyflow/diagram-to-xyflow.ts +18 -53
  65. package/src/xyflow/edges/RelationshipEdge.tsx +218 -126
  66. package/src/xyflow/edges/edges.css.ts +44 -26
  67. package/src/xyflow/edges/utils.ts +10 -0
  68. package/src/xyflow/hooks/index.ts +4 -2
  69. package/src/xyflow/hooks/useLayoutConstraints.ts +5 -5
  70. package/src/xyflow/hooks/useXYFlow.ts +28 -1
  71. package/src/xyflow/nodes/compound/CompoundNode.css.ts +41 -31
  72. package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
  73. package/src/xyflow/nodes/element/ElementIcon.tsx +11 -22
  74. package/src/xyflow/nodes/element/ElementLink.tsx +11 -16
  75. package/src/xyflow/nodes/element/ElementNode.tsx +69 -114
  76. package/src/xyflow/nodes/element/element.css.ts +48 -15
  77. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  78. package/src/xyflow/types.ts +2 -4
  79. package/src/xyflow/utils.ts +3 -6
  80. package/dist/KeepAspectRatioContainer.d.ts +0 -9
  81. package/dist/icons/Clipboard.d.ts +0 -3
  82. package/dist/icons/Link.d.ts +0 -3
  83. package/dist/icons/ZoomIn.d.ts +0 -3
  84. package/dist/icons/index.d.ts +0 -3
  85. package/dist/mantine/theme.d.ts +0 -3
  86. package/dist/state/WhenInitialized.d.ts +0 -3
  87. package/dist/ui/Camera.d.ts +0 -3
  88. package/src/KeepAspectRatioContainer.tsx +0 -36
  89. package/src/icons/Clipboard.tsx +0 -24
  90. package/src/icons/Link.tsx +0 -23
  91. package/src/icons/ZoomIn.tsx +0 -25
  92. package/src/icons/index.ts +0 -3
  93. package/src/mantine/theme.ts +0 -20
  94. package/src/state/WhenInitialized.tsx +0 -24
  95. package/src/ui/Camera.tsx +0 -174
@@ -1,5 +1,5 @@
1
1
  import { rem } from '@mantine/core'
2
- import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
2
+ import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
3
  import { mantine } from '../../mantine.css'
4
4
  import { vars, xyvars } from '../../theme.css'
5
5
 
@@ -11,8 +11,8 @@ export const container = style({
11
11
  [xyvars.edge.stroke]: vars.relation.lineColor,
12
12
  [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
13
13
  [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 20%)`,
14
- [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 35%)`,
15
- [xyvars.edge.strokeWidth]: '2.2px'
14
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 40%)`,
15
+ [xyvars.edge.strokeWidth]: '3'
16
16
  }
17
17
  })
18
18
 
@@ -20,7 +20,7 @@ globalStyle(`:where([data-mantine-color-scheme="dark"]) ${container}`, {
20
20
  vars: {
21
21
  [mixColor]: `white`,
22
22
  [xyvars.edge.labelColor]: vars.relation.labelColor,
23
- [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 55%)`
23
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
24
24
  }
25
25
  })
26
26
 
@@ -29,7 +29,7 @@ const isSelected = '.react-flow__edge.selected'
29
29
  globalStyle(`:where(${isSelected}) ${container}`, {
30
30
  vars: {
31
31
  [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
32
- [xyvars.edge.strokeWidth]: '3px'
32
+ [xyvars.edge.strokeWidth]: '3'
33
33
  }
34
34
  })
35
35
 
@@ -37,13 +37,13 @@ globalStyle(`${container}:is([data-edge-hovered='true'],[data-edge-active='true'
37
37
  vars: {
38
38
  // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
39
39
  [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
40
- [xyvars.edge.strokeWidth]: '3px'
40
+ [xyvars.edge.strokeWidth]: '3'
41
41
  }
42
42
  })
43
43
 
44
44
  globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
45
45
  vars: {
46
- [xyvars.edge.strokeWidth]: '3.6px'
46
+ [xyvars.edge.strokeWidth]: '4'
47
47
  }
48
48
  })
49
49
  // globalStyle(`${container}[data-edge-hovered='true']`, {
@@ -56,18 +56,22 @@ globalStyle(`.react-flow__edges > svg`, {
56
56
  mixBlendMode: 'screen'
57
57
  })
58
58
 
59
+ // globalStyle(`:where([data-mantine-color-scheme="light"]) .react-flow__edges > svg`, {
60
+ // mixBlendMode: 'screen'
61
+ // })
62
+
59
63
  export const dimmed = style({})
60
64
 
61
65
  globalStyle(`.react-flow__edges > svg:has(${dimmed})`, {
62
66
  opacity: 0.8,
63
67
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
64
68
  transitionDelay: '200ms',
65
- filter: 'grayscale(0.85) blur(1px)',
69
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
66
70
  willChange: 'opacity, filter'
67
71
  })
68
72
 
69
73
  export const edgePathBg = style({
70
- strokeWidth: xyvars.edge.strokeWidth,
74
+ // strokeWidth: xyvars.edge.strokeWidth,
71
75
  strokeOpacity: 0.08,
72
76
  // transition: 'stroke-width 175ms ease-in-out',
73
77
  // transition: 'stroke-width 175ms ease-in-out, stroke-opacity 150ms ease-out',
@@ -76,7 +80,7 @@ export const edgePathBg = style({
76
80
  transitionTimingFunction: 'ease-out',
77
81
  selectors: {
78
82
  [`:where(${isSelected}, [data-edge-active='true'], [data-edge-hovered='true']) &`]: {
79
- strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8px)`,
83
+ strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8)`,
80
84
  strokeOpacity: 0.15
81
85
  }
82
86
  }
@@ -93,7 +97,7 @@ export const controlPoint = style({
93
97
  stroke: xyvars.edge.stroke,
94
98
  fillOpacity: 0.75,
95
99
  strokeWidth: 1,
96
- cursor: 'grab',
100
+ cursor: 'default',
97
101
  pointerEvents: 'auto',
98
102
  visibility: 'hidden',
99
103
  ':hover': {
@@ -112,13 +116,20 @@ export const controlPoint = style({
112
116
  }
113
117
  })
114
118
 
115
- const strokeKeyframes = generateIdentifier('strokeKeyframes')
116
- globalKeyframes(strokeKeyframes, {
119
+ export const controlDragging = style({
120
+ cursor: 'grabbing'
121
+ })
122
+
123
+ globalStyle(`${controlDragging} *`, {
124
+ cursor: 'grabbing'
125
+ })
126
+
127
+ const strokeKeyframes = keyframes({
117
128
  'from': {
118
- strokeDashoffset: 18 * 2
129
+ strokeDashoffset: 18 * 2 + 10
119
130
  },
120
131
  'to': {
121
- strokeDashoffset: 1
132
+ strokeDashoffset: 10
122
133
  }
123
134
  })
124
135
 
@@ -126,19 +137,24 @@ export const cssEdgePath = style({
126
137
  animationDuration: '800ms',
127
138
  animationIterationCount: 'infinite',
128
139
  animationTimingFunction: 'linear',
140
+ animationFillMode: 'both',
141
+ strokeDashoffset: 10,
129
142
  selectors: {
130
143
  [`:where([data-edge-hovered='true']) &`]: {
131
144
  animationName: strokeKeyframes,
132
- animationDelay: '350ms',
133
- transition: 'stroke,stroke-width 130ms ease-out'
145
+ animationDelay: '450ms',
146
+ transition: 'stroke 130ms ease-out,stroke-width 130ms ease-out'
134
147
  },
135
148
  [`:where(${isSelected}, [data-edge-active='true']) &`]: {
136
149
  animationName: strokeKeyframes,
137
150
  animationDelay: '0ms',
138
- transition: 'stroke,stroke-width 130ms ease-out'
151
+ transition: 'stroke 130ms ease-out,stroke-width 130ms ease-out'
139
152
  },
140
153
  [`:where([data-edge-dir='back']) &`]: {
141
154
  animationDirection: 'reverse'
155
+ },
156
+ [`${dimmed} &`]: {
157
+ animationPlayState: 'paused'
142
158
  }
143
159
  }
144
160
  })
@@ -151,29 +167,31 @@ const varTranslate = createVar('translate')
151
167
  export const edgeLabel = style({
152
168
  top: 0,
153
169
  left: 0,
170
+ padding: '2px 4px 4px 4px',
154
171
  fontFamily: vars.likec4.font,
155
172
  position: 'absolute',
156
173
  pointerEvents: 'all',
157
174
  cursor: 'pointer',
175
+ width: 'fit-content',
158
176
  transformOrigin: '50% 50%',
159
177
  mixBlendMode: 'screen',
160
178
  color: xyvars.edge.labelColor,
161
179
  backgroundColor: xyvars.edge.labelBgColor,
162
- borderRadius: '3px',
180
+ borderRadius: 3,
163
181
  transform: varTranslate,
164
182
  vars: {
165
183
  [varTranslate]: `translate(${fallbackVar(varLabelX, '-50%')}, ${fallbackVar(varLabelY, '-50%')})`
166
184
  },
167
185
  selectors: {
168
186
  '&[data-edge-hovered="true"]': {
169
- transition: 'all 140ms ease-out',
170
- transform: `${varTranslate} scale(1.1)`
187
+ transition: 'all 140ms ease-in-out',
188
+ transform: `${varTranslate} scale(1.12)`
171
189
  },
172
190
  [`&:is(${dimmed})`]: {
173
- opacity: 0.8,
191
+ opacity: 0.6,
174
192
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
175
193
  transitionDelay: '200ms',
176
- filter: 'grayscale(0.85) blur(2px)',
194
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
177
195
  willChange: 'opacity, filter'
178
196
  }
179
197
  }
@@ -203,8 +221,8 @@ export const stepEdgeNumber = style({
203
221
  })
204
222
 
205
223
  export const edgeLabelText = style({
206
- padding: '2px 6px 4px 6px',
207
224
  textAlign: 'left',
208
- fontSize: rem(12.5),
209
- lineHeight: 1.2
225
+ whiteSpaceCollapse: 'preserve-breaks',
226
+ fontSize: rem(14),
227
+ lineHeight: 1.185
210
228
  })
@@ -3,6 +3,16 @@ import { Position, type XYPosition } from '@xyflow/react'
3
3
  import { getNodeDimensions } from '@xyflow/system'
4
4
  import type { InternalXYFlowNode } from '../types'
5
5
 
6
+ export function getNodeCenter(node: InternalXYFlowNode): XYPosition {
7
+ const { width, height } = getNodeDimensions(node)
8
+ const { x, y } = node.internals.positionAbsolute
9
+
10
+ return {
11
+ x: x + width / 2,
12
+ y: y + height / 2
13
+ }
14
+ }
15
+
6
16
  // this helper function returns the intersection point
7
17
  // of the line between the center of the intersectionNode and the target node
8
18
  export function getNodeIntersectionFromCenterToPoint(
@@ -1,2 +1,4 @@
1
- export * from './useLayoutConstraints'
2
- export * from './useXYFlow'
1
+ export { useLayoutConstraints } from './useLayoutConstraints'
2
+ export { useXYFlow, useXYNodesData, useXYStore, useXYStoreApi } from './useXYFlow'
3
+
4
+ export type * from './useXYFlow'
@@ -1,12 +1,11 @@
1
1
  import { invariant, isAncestor, nonNullable } from '@likec4/core'
2
2
  import { Expression, Expression as Expr, Operator, Solver, Strength, Variable } from '@lume/kiwi'
3
- import { useDebouncedCallback } from '@react-hookz/web'
4
3
  import type { InternalNode, ReactFlowProps, XYPosition } from '@xyflow/react'
5
4
  import { getNodeDimensions } from '@xyflow/system'
6
5
  import { useMemo, useRef } from 'react'
7
6
  import { isNullish } from 'remeda'
8
- import { useDiagramStoreApi } from '../../state'
9
- import { type XYFlowInstance, XYFlowNode } from '../types'
7
+ import { useDiagramStoreApi } from '../../state/hooks'
8
+ import type { XYFlowInstance, XYFlowNode } from '../types'
10
9
  import { isSamePoint } from '../utils'
11
10
  import { useXYStoreApi, type XYStoreApi } from './useXYFlow'
12
11
 
@@ -286,7 +285,8 @@ export function useLayoutConstraints(): LayoutConstraints {
286
285
  x: event.clientX,
287
286
  y: event.clientY
288
287
  }
289
- const { xyflow } = diagramApi.getState()
288
+ const { xyflow, cancelSaveManualLayout } = diagramApi.getState()
289
+ cancelSaveManualLayout()
290
290
  solverRef.current = createLayoutConstraints(xyflow, xyflowApi, xynode.id)
291
291
  },
292
292
  onNodeDrag: (_event, xynode) => {
@@ -295,7 +295,7 @@ export function useLayoutConstraints(): LayoutConstraints {
295
295
  },
296
296
  onNodeDragStop: (event, _xynode) => {
297
297
  if (!isSamePoint(dragStartedAt.current, { x: event.clientX, y: event.clientY })) {
298
- diagramApi.getState().triggerSaveManualLayout(xyflowApi)
298
+ diagramApi.getState().triggerSaveManualLayout()
299
299
  }
300
300
  solverRef.current = undefined
301
301
  }
@@ -1,5 +1,6 @@
1
1
  import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
2
- import { shallowEqual } from 'fast-equals'
2
+ import { deepEqual, shallowEqual } from 'fast-equals'
3
+ import { useCallback } from 'react'
3
4
  import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../types'
4
5
 
5
6
  export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
@@ -18,3 +19,29 @@ export function useXYStore<StateSlice = unknown>(
18
19
  }
19
20
  export const useXYStoreApi = useStoreApi<XYFlowNode, XYFlowEdge>
20
21
  export type XYStoreApi = ReturnType<typeof useXYStoreApi>
22
+
23
+ export function useXYEdgesData(edgeIds: string[]): Pick<XYFlowEdge, 'id' | 'data'>[] {
24
+ const ids = edgeIds.join(',')
25
+ const edgesData = useXYStore(
26
+ useCallback(
27
+ (s) => {
28
+ const data = [] as Pick<XYFlowEdge, 'id' | 'data'>[]
29
+ for (const id of edgeIds) {
30
+ const edge = s.edgeLookup.get(id)
31
+ if (edge) {
32
+ data.push({
33
+ id: edge.id,
34
+ data: edge.data
35
+ })
36
+ }
37
+ }
38
+
39
+ return data
40
+ },
41
+ [ids]
42
+ ),
43
+ deepEqual
44
+ )
45
+
46
+ return edgesData
47
+ }
@@ -12,13 +12,19 @@ export const container = style({
12
12
  margin: 0
13
13
  })
14
14
 
15
+ export const nodeHandlerInCenter = style({
16
+ top: '50%',
17
+ left: '50%',
18
+ visibility: 'hidden'
19
+ })
20
+
15
21
  export const dimmed = style({})
16
22
 
17
23
  globalStyle(`.react-flow__node-compound:has(${dimmed})`, {
18
24
  opacity: 0.25,
19
25
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
20
26
  transitionDelay: '200ms',
21
- filter: 'grayscale(0.85) blur(1px)',
27
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
22
28
  willChange: 'opacity, filter'
23
29
  })
24
30
 
@@ -37,8 +43,12 @@ export const compoundBody = style({
37
43
  position: 'relative',
38
44
  borderRadius: 6,
39
45
  boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
40
- padding: 8,
46
+ padding: 0,
47
+ margin: 0,
41
48
  transition: 'all 200ms ease-out',
49
+ backgroundClip: 'padding-box',
50
+ overflow: 'hidden',
51
+ cursor: 'default',
42
52
  selectors: {
43
53
  [`:where(.react-flow__node.selected) &`]: {
44
54
  boxShadow: 'none'
@@ -56,11 +66,9 @@ export const compoundBody = style({
56
66
  left: 0,
57
67
  width: '100%',
58
68
  height: '100%',
59
- borderRadius: 6,
60
69
  pointerEvents: 'none',
61
70
  transition: 'background 175ms ease-out, opacity 175ms ease-out',
62
- background: vars.element.fill,
63
- backgroundClip: 'padding-box'
71
+ background: vars.element.fill
64
72
  }
65
73
  })
66
74
 
@@ -68,27 +76,26 @@ const opacityDeltaOnHover = createVar('opacityDeltaOnHover')
68
76
  export const transparent = style({
69
77
  borderStyle: 'dashed',
70
78
  borderWidth: 3,
71
- padding: 6,
72
79
  boxShadow: 'none',
73
80
  borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent ${fallbackVar(varBorderTransparency, '10%')})`,
74
- backgroundClip: 'padding-box',
75
81
  vars: {
76
82
  [opacityDeltaOnHover]: '0',
77
83
  '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
78
84
  },
79
85
  ':before': {
80
- transitionDelay: '200ms',
86
+ borderRadius: 'unset',
87
+ transitionDelay: '100ms',
81
88
  opacity: calc.add(fallbackVar(varOpacity, '1'), opacityDeltaOnHover)
82
89
  },
83
90
  selectors: {
84
- [`:where(.react-flow__node.selected) &`]: {
85
- vars: {
86
- [opacityDeltaOnHover]: '.05'
87
- }
88
- },
91
+ // [`:where(.react-flow__node.selected) &`]: {
92
+ // vars: {
93
+ // [opacityDeltaOnHover]: '.05'
94
+ // }
95
+ // },
89
96
  [`:where([data-hovered]) &`]: {
90
97
  vars: {
91
- [opacityDeltaOnHover]: '.1'
98
+ [opacityDeltaOnHover]: '.08'
92
99
  }
93
100
  }
94
101
  }
@@ -96,21 +103,28 @@ export const transparent = style({
96
103
 
97
104
  export const title = style({
98
105
  fontFamily: vars.compound.font,
99
- fontOpticalSizing: 'auto',
100
- fontStyle: 'normal',
101
106
  textAlign: 'left',
102
- display: 'inline-block',
103
107
  fontWeight: 600,
104
- fontSize: '14px',
108
+ fontSize: rem(15),
105
109
  textTransform: 'uppercase',
106
110
  letterSpacing: '0.2px',
107
111
  lineHeight: 1,
108
- opacity: 0.75,
109
112
  color: `var(--_compound-title-color,${vars.compound.titleColor})`,
110
- paddingLeft: 12
113
+ paddingLeft: 14,
114
+ paddingTop: 13,
115
+ paddingBottom: 6,
116
+ mixBlendMode: 'screen',
117
+ selectors: {
118
+ [`:where(.react-flow__node.draggable) &`]: {
119
+ cursor: 'grab'
120
+ },
121
+ [`:where(.likec4-compound-transparent) &`]: {
122
+ paddingTop: 10
123
+ }
124
+ }
111
125
  })
112
126
  export const titleWithNavigation = style({
113
- paddingLeft: 26
127
+ paddingLeft: 32
114
128
  })
115
129
 
116
130
  globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
@@ -119,10 +133,6 @@ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transpa
119
133
  }
120
134
  })
121
135
 
122
- globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) ${title}`, {
123
- opacity: 1
124
- })
125
-
126
136
  const indicatorKeyframes = keyframes({
127
137
  'from': {
128
138
  opacity: 0.6
@@ -197,10 +207,10 @@ export const navigateBtn = style({
197
207
  position: 'absolute',
198
208
  pointerEvents: 'all',
199
209
  left: 3,
200
- top: 7,
210
+ top: 6,
201
211
  cursor: 'pointer',
202
212
  color: `var(--_compound-title-color,${navigateBtnColor})`,
203
- transformOrigin: '90% 70%',
213
+ transformOrigin: '90% 50%',
204
214
  opacity: 0.75,
205
215
  transition: 'all 150ms ease-out',
206
216
  transitionDelay: '0ms',
@@ -212,7 +222,7 @@ export const navigateBtn = style({
212
222
  },
213
223
  ':hover': {
214
224
  boxShadow: mantine.shadows.lg,
215
- transform: 'scale(1.466)',
225
+ transform: 'scale(1.42)',
216
226
  opacity: 1,
217
227
  transitionDelay: '0'
218
228
  },
@@ -225,7 +235,7 @@ export const navigateBtn = style({
225
235
  [`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) &`]: {
226
236
  opacity: 0.85,
227
237
  vars: {
228
- '--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 50%)`,
238
+ '--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 60%)`,
229
239
  '--ai-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 20%)`
230
240
  }
231
241
  },
@@ -241,8 +251,8 @@ export const navigateBtn = style({
241
251
  }
242
252
  })
243
253
  globalStyle(`${navigateBtn} svg.icon`, {
244
- width: '70%',
245
- height: '70%',
254
+ width: '75%',
255
+ height: '75%',
246
256
  strokeWidth: 1.5
247
257
  })
248
258
 
@@ -2,12 +2,10 @@ import { Box, Text } from '@mantine/core'
2
2
  import { assignInlineVars } from '@vanilla-extract/dynamic'
3
3
  import { Handle, type NodeProps, Position } from '@xyflow/react'
4
4
  import clsx from 'clsx'
5
- import { DEV } from 'esm-env'
6
5
  import { deepEqual as eq } from 'fast-equals'
7
6
  import { memo } from 'react'
8
7
  import { clamp } from 'remeda'
9
- import useTilg from 'tilg'
10
- import { useDiagramState } from '../../../state'
8
+ import { useDiagramState } from '../../../state/hooks'
11
9
  import type { CompoundXYFlowNode } from '../../types'
12
10
  import { NavigateToBtn } from '../shared/NavigateToBtn'
13
11
  import * as css from './CompoundNode.css'
@@ -31,7 +29,6 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
31
29
  element
32
30
  }
33
31
  }) {
34
- DEV && useTilg()
35
32
  const { color, style, depth = 1, ...compound } = element
36
33
  // const w = toDomPrecision(width ?? compound.width)
37
34
  // const h = toDomPrecision(height ?? compound.height)
@@ -52,65 +49,67 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
52
49
  const isnavigable = !!compound.navigateTo && hasOnNavigateTo
53
50
 
54
51
  return (
55
- <Box
56
- className={clsx(
57
- css.container,
58
- 'likec4-compound-node',
59
- opacity < 1 && 'likec4-compound-transparent',
60
- isDimmed && css.dimmed
61
- )}
62
- mod={{
63
- 'compound-depth': depth,
64
- 'likec4-color': color,
65
- hovered: isHovered
66
- }}
67
- >
52
+ <>
68
53
  <Handle
69
54
  type="target"
70
55
  position={Position.Top}
71
- style={{ visibility: 'hidden' }}
56
+ className={css.nodeHandlerInCenter}
72
57
  />
73
- <svg className={css.indicator}>
74
- <rect
75
- x={0}
76
- y={0}
77
- width={'100%'}
78
- height={'100%'}
79
- rx={6}
80
- />
81
- </svg>
82
- <div
58
+ <Box
83
59
  className={clsx(
84
- css.compoundBody,
85
- opacity < 1 && css.transparent,
86
- 'likec4-compound'
60
+ css.container,
61
+ 'likec4-compound-node',
62
+ opacity < 1 && 'likec4-compound-transparent',
63
+ isDimmed && css.dimmed
87
64
  )}
88
- style={opacity < 1
89
- ? {
90
- ...assignInlineVars({
91
- [css.varBorderTransparency]: `${borderTransparency}%`,
92
- [css.varOpacity]: opacity.toFixed(2)
93
- }),
94
- borderStyle: style.border ?? 'dashed'
95
- }
96
- : undefined}
65
+ mod={{
66
+ 'compound-depth': depth,
67
+ 'likec4-color': color,
68
+ hovered: isHovered
69
+ }}
97
70
  >
98
- <Text
99
- component="div"
71
+ <svg className={css.indicator}>
72
+ <rect
73
+ x={0}
74
+ y={0}
75
+ width={'100%'}
76
+ height={'100%'}
77
+ rx={6}
78
+ />
79
+ </svg>
80
+ <div
100
81
  className={clsx(
101
- css.title,
102
- isnavigable && css.titleWithNavigation,
103
- 'likec4-compound-title'
104
- )}>
105
- {compound.title}
106
- </Text>
107
- </div>
108
- {isnavigable && <NavigateToBtn xynodeId={id} className={css.navigateBtn} />}
82
+ css.compoundBody,
83
+ opacity < 1 && css.transparent,
84
+ 'likec4-compound'
85
+ )}
86
+ style={opacity < 1
87
+ ? {
88
+ ...assignInlineVars({
89
+ [css.varBorderTransparency]: `${borderTransparency}%`,
90
+ [css.varOpacity]: opacity.toFixed(2)
91
+ }),
92
+ borderStyle: style.border ?? 'dashed'
93
+ }
94
+ : undefined}
95
+ >
96
+ <Text
97
+ component="div"
98
+ className={clsx(
99
+ css.title,
100
+ isnavigable && css.titleWithNavigation,
101
+ 'likec4-compound-title'
102
+ )}>
103
+ {compound.title}
104
+ </Text>
105
+ </div>
106
+ {isnavigable && <NavigateToBtn xynodeId={id} className={css.navigateBtn} />}
107
+ </Box>
109
108
  <Handle
110
109
  type="source"
111
- position={Position.Bottom}
112
- style={{ visibility: 'hidden' }}
110
+ position={Position.Top}
111
+ className={css.nodeHandlerInCenter}
113
112
  />
114
- </Box>
113
+ </>
115
114
  )
116
115
  }, isEqualProps)
@@ -1,43 +1,32 @@
1
1
  import { assignInlineVars } from '@vanilla-extract/dynamic'
2
2
  import clsx from 'clsx'
3
3
  import type { XYFlowNode } from '../../types'
4
- import { elementIcon, iconMaxH } from './element.css'
4
+ import { elementIcon, iconOffsetY } from './element.css'
5
5
 
6
6
  type ElementIconProps = {
7
7
  node: XYFlowNode['data']['element']
8
- maxWidth?: number | undefined
9
- paddingX?: number | undefined
10
- paddingY?: number | undefined
11
8
  }
12
9
 
13
10
  export function ElementIcon({
14
- node,
15
- paddingY = 32
16
- }: ElementIconProps) {
17
- if (!node.icon) {
18
- return null
11
+ node: {
12
+ icon,
13
+ title,
14
+ labelBBox
19
15
  }
20
- const firstLabel = node.labels[0]
21
- if (!firstLabel) {
16
+ }: ElementIconProps) {
17
+ if (!icon) {
22
18
  return null
23
19
  }
24
20
 
25
- const firstLabelY = Math.floor(firstLabel.pt[1] - firstLabel.fontSize * 1.25)
26
- const maxIconHeight = Math.floor(firstLabelY - paddingY - 16)
27
- // const maxIconWidth = maxWidth ?? node.width - paddingX * 2
28
-
29
- // const centerY = paddingY + Math.floor(maxIconHeight / 2)
30
- // const centerX = Math.floor(node.width / 2)
31
-
32
21
  return (
33
22
  <div
34
23
  className={clsx(elementIcon, 'likec4-element-icon')}
35
- style={assignInlineVars({ iconMaxH }, {
36
- iconMaxH: maxIconHeight + 'px'
24
+ style={assignInlineVars({ iconOffsetY }, {
25
+ iconOffsetY: labelBBox.y + 'px'
37
26
  })}>
38
27
  <img
39
- src={node.icon!}
40
- alt={node.title}
28
+ src={icon}
29
+ alt={title}
41
30
  />
42
31
  </div>
43
32
  )