@likec4/diagram 1.5.0 → 1.6.1

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 (36) hide show
  1. package/LICENSE +1 -1
  2. package/dist/LikeC4Diagram.d.ts +1 -1
  3. package/dist/LikeC4Diagram.props.d.ts +18 -3
  4. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.mjs +490 -8409
  7. package/dist/state/DiagramContext.d.ts +18 -9
  8. package/dist/state/diagramStore.d.ts +12 -2
  9. package/dist/style.css +149 -85
  10. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +5 -0
  11. package/dist/xyflow/nodes/element/element.css.d.ts +4 -2
  12. package/package.json +17 -17
  13. package/src/LikeC4Diagram.props.ts +21 -3
  14. package/src/LikeC4Diagram.tsx +9 -11
  15. package/src/StaticLikeC4Diagram.tsx +1 -0
  16. package/src/index.ts +2 -0
  17. package/src/state/DiagramContext.tsx +5 -5
  18. package/src/state/diagramStore.ts +8 -1
  19. package/src/ui/BackwardForwardPanel.tsx +1 -1
  20. package/src/xyflow/FitviewOnDiagramChange.tsx +1 -1
  21. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +1 -1
  22. package/src/xyflow/SyncWithDiagram.tsx +1 -1
  23. package/src/xyflow/XYFlow.tsx +2 -4
  24. package/src/xyflow/XYFlowEvents.tsx +1 -1
  25. package/src/xyflow/XYFlowInner.tsx +2 -1
  26. package/src/xyflow/edges/RelationshipEdge.tsx +3 -3
  27. package/src/xyflow/edges/edges.css.ts +6 -6
  28. package/src/xyflow/hooks/useLayoutConstraints.ts +1 -1
  29. package/src/xyflow/nodes/compound/CompoundNode.tsx +1 -1
  30. package/src/xyflow/nodes/element/ElementLink.tsx +2 -1
  31. package/src/xyflow/nodes/element/ElementNode.tsx +73 -24
  32. package/src/xyflow/nodes/element/ElementShapeSvg.tsx +2 -2
  33. package/src/xyflow/nodes/element/element.css.ts +126 -54
  34. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  35. package/dist/xyflow/nodes/element/ElementIcon.d.ts +0 -7
  36. package/src/xyflow/nodes/element/ElementIcon.tsx +0 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -48,31 +48,36 @@
48
48
  "clean": "run -T rimraf -v -g 'dist/*' .tsbuildinfo"
49
49
  },
50
50
  "dependencies": {
51
- "@likec4/core": "1.5.0",
51
+ "@lume/kiwi": "^0.4.3",
52
52
  "@react-hookz/web": "^24.0.4",
53
- "@tabler/icons-react": "^3.9.0",
53
+ "@tabler/icons-react": "^3.11.0",
54
54
  "@vanilla-extract/css": "^1.15.3",
55
55
  "@vanilla-extract/css-utils": "^0.1.4",
56
56
  "@vanilla-extract/dynamic": "^2.1.1",
57
+ "@xyflow/react": "12.0.3",
58
+ "@xyflow/system": "0.0.37",
59
+ "bezier-js": "^6.1.4",
60
+ "clsx": "^2.1.1",
57
61
  "d3-shape": "^3.2.0",
58
62
  "esm-env": "^1.0.0",
59
63
  "fast-equals": "^5.0.1",
60
- "framer-motion": "^11.2.13",
64
+ "framer-motion": "^11.3.8",
61
65
  "remeda": "^2.3.0",
62
66
  "zustand": "^4.5.4"
63
67
  },
64
68
  "peerDependencies": {
65
- "@mantine/core": "^7.11.1",
66
- "@mantine/hooks": "^7.11.1",
69
+ "@likec4/core": "1.6.1",
70
+ "@mantine/core": "^7.11.2",
71
+ "@mantine/hooks": "^7.11.2",
67
72
  "react": "^18.3.1",
68
73
  "react-dom": "^18.3.1"
69
74
  },
70
75
  "devDependencies": {
71
- "@likec4/tsconfig": "1.5.0",
72
- "@lume/kiwi": "^0.4.3",
73
- "@mantine/core": "7.11.1",
74
- "@mantine/hooks": "7.11.1",
75
- "@mantine/vanilla-extract": "7.11.1",
76
+ "@likec4/core": "1.6.1",
77
+ "@likec4/tsconfig": "1.6.1",
78
+ "@mantine/core": "7.11.2",
79
+ "@mantine/hooks": "7.11.2",
80
+ "@mantine/vanilla-extract": "7.11.2",
76
81
  "@total-typescript/ts-reset": "^0.5.1",
77
82
  "@types/bezier-js": "^4.1.3",
78
83
  "@types/d3-shape": "^3.1.6",
@@ -80,18 +85,13 @@
80
85
  "@types/react-dom": "^18.3.0",
81
86
  "@vanilla-extract/esbuild-plugin": "^2.3.8",
82
87
  "@vitejs/plugin-react": "^4.3.1",
83
- "@xyflow/react": "12.0.1",
84
- "@xyflow/system": "0.0.35",
85
- "bezier-js": "^6.1.4",
86
- "clsx": "^2.1.1",
87
88
  "execa": "^9.3.0",
88
89
  "khroma": "^2.1.0",
89
90
  "react": "^18.3.1",
90
91
  "react-dom": "^18.3.1",
91
- "tilg": "^0.1.1",
92
92
  "type-fest": "^4.21.0",
93
93
  "typescript": "^5.5.3",
94
- "vite": "5.3.3",
94
+ "vite": "5.3.4",
95
95
  "vite-plugin-dts": "^3.9.1"
96
96
  }
97
97
  }
@@ -6,17 +6,29 @@ import type {
6
6
  EdgeId,
7
7
  ElementShape,
8
8
  Fqn,
9
+ IconUrl,
9
10
  NonEmptyArray,
10
11
  ThemeColor,
11
12
  ViewID
12
13
  } from '@likec4/core'
13
14
  import type { XYPosition } from '@xyflow/system'
14
- import type { MouseEvent as ReactMouseEvent } from 'react'
15
- import type { SetRequired, Simplify } from 'type-fest'
15
+ import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'
16
16
  import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
17
17
  import type { XYBackground } from './xyflow/XYFlowBackground'
18
18
 
19
- export type DiagramNodeWithNavigate = Simplify<SetRequired<DiagramNode, 'navigateTo'>>
19
+ export type DiagramNodeWithNavigate = Omit<DiagramNode, 'navigateTo'> & {
20
+ navigateTo: ViewID
21
+ }
22
+
23
+ type ElementIconNodeProps = {
24
+ id: string
25
+ title: string
26
+ icon?: string | undefined
27
+ }
28
+
29
+ export type ElementIconRenderer = (props: {
30
+ node: ElementIconNodeProps
31
+ }) => ReactNode
20
32
 
21
33
  export type OnNavigateTo = {
22
34
  (
@@ -177,6 +189,12 @@ export interface LikeC4DiagramProperties {
177
189
  * @default false
178
190
  */
179
191
  experimentalEdgeEditing?: boolean | undefined
192
+
193
+ /**
194
+ * Render custom icon for a node
195
+ * By default, if icon is http:// or https://, it will be rendered as an image
196
+ */
197
+ renderIcon?: ElementIconRenderer | undefined
180
198
  }
181
199
 
182
200
  export interface LikeC4DiagramEventHandlers {
@@ -1,13 +1,10 @@
1
1
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
2
- import '@xyflow/react/dist/style.css'
3
2
  import clsx from 'clsx'
4
- import { DEV } from 'esm-env'
5
3
  import { shallowEqual } from 'fast-equals'
6
4
  import { domAnimation, LazyMotion } from 'framer-motion'
7
5
  import { memo, useEffect, useRef, useState } from 'react'
8
- import useTilg from 'tilg'
9
6
  import { rootClassName } from './globals.css'
10
- import { cssDisablePan, cssNoControls, cssReactFlow, cssTransparentBg } from './LikeC4Diagram.css'
7
+ import * as css from './LikeC4Diagram.css'
11
8
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
12
9
  import { EnsureMantine } from './mantine/EnsureMantine'
13
10
  import { DiagramContextProvider } from './state/DiagramContext'
@@ -49,9 +46,9 @@ export function LikeC4Diagram({
49
46
  onEdgeContextMenu,
50
47
  onNavigateTo,
51
48
  onNodeClick,
52
- onNodeContextMenu
49
+ onNodeContextMenu,
50
+ renderIcon
53
51
  }: LikeC4DiagramProps) {
54
- DEV && useTilg()
55
52
  const initialRef = useRef<{
56
53
  defaultNodes: XYFlowData['nodes']
57
54
  defaultEdges: XYFlowData['edges']
@@ -70,6 +67,7 @@ export function LikeC4Diagram({
70
67
  initialHeight: initialHeight ?? view.height
71
68
  }
72
69
  }
70
+
73
71
  return (
74
72
  <EnsureMantine>
75
73
  <XYFlowProvider
@@ -89,6 +87,7 @@ export function LikeC4Diagram({
89
87
  nodesDraggable={nodesDraggable}
90
88
  nodesSelectable={nodesSelectable}
91
89
  experimentalEdgeEditing={experimentalEdgeEditing}
90
+ renderIcon={renderIcon ?? null}
92
91
  onCanvasClick={onCanvasClick}
93
92
  onCanvasContextMenu={onCanvasContextMenu}
94
93
  onEdgeClick={onEdgeClick}
@@ -143,7 +142,6 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
143
142
  showNavigationButtons,
144
143
  enableDynamicViewWalkthrough
145
144
  }) {
146
- DEV && useTilg()
147
145
  const diagramApi = useDiagramStoreApi()
148
146
  const [isInitialized, setIsInitialized] = useState(diagramApi.getState().initialized)
149
147
 
@@ -167,10 +165,10 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
167
165
  defaultEdges={defaultEdges}
168
166
  className={clsx(
169
167
  'likec4-diagram',
170
- cssReactFlow,
171
- controls === false && cssNoControls,
172
- pannable !== true && cssDisablePan,
173
- background === 'transparent' && cssTransparentBg
168
+ css.cssReactFlow,
169
+ controls === false && css.cssNoControls,
170
+ pannable !== true && css.cssDisablePan,
171
+ background === 'transparent' && css.cssTransparentBg
174
172
  )}
175
173
  >
176
174
  <XYFlowInner
@@ -11,6 +11,7 @@ export type StaticLikeC4DiagramProps = Pick<
11
11
  | 'background'
12
12
  | 'initialWidth'
13
13
  | 'initialHeight'
14
+ | 'renderIcon'
14
15
  >
15
16
 
16
17
  export function StaticLikeC4Diagram({
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import '@xyflow/react/dist/style.css'
2
+
1
3
  export { LikeC4Diagram } from './LikeC4Diagram'
2
4
  export { StaticLikeC4Diagram } from './StaticLikeC4Diagram'
3
5
 
@@ -2,7 +2,7 @@ import { shallowEqual } from 'fast-equals'
2
2
  import { createContext, type PropsWithChildren, useEffect, useRef } from 'react'
3
3
  import { hasSubObject, isNonNullish, pickBy } from 'remeda'
4
4
  import { useUpdateEffect } from '../hooks'
5
- import { useXYFlow, useXYStoreApi } from '../xyflow/hooks'
5
+ import { useXYFlow, useXYStoreApi } from '../xyflow/hooks/useXYFlow'
6
6
  import { createDiagramStore, type DiagramInitialState } from './diagramStore'
7
7
 
8
8
  export type DiagramZustandStore = ReturnType<typeof createDiagramStore>
@@ -52,16 +52,16 @@ export function DiagramContextProvider({
52
52
  () => {
53
53
  if (!store.current) return
54
54
  const state = store.current.getState()
55
- if (state.view !== view) {
56
- state.updateView(view)
57
- }
58
55
  const newProps = pickBy(props, isNonNullish)
59
56
  if (!hasSubObject(state, newProps)) {
60
57
  store.current.setState(newProps, false, 'update incoming props')
61
58
  }
59
+ if (state.view !== view) {
60
+ state.updateView(view)
61
+ }
62
62
  },
63
63
  [view, props],
64
- shallowEqual
64
+ (prev, next) => shallowEqual(prev[0], next[0]) && shallowEqual(prev[1], next[1])
65
65
  )
66
66
  return (
67
67
  <div
@@ -10,7 +10,12 @@ import type { Exact, Except, RequiredKeysOf, Simplify } from 'type-fest'
10
10
  import { devtools, subscribeWithSelector } from 'zustand/middleware'
11
11
  import { shallow } from 'zustand/shallow'
12
12
  import { createWithEqualityFn } from 'zustand/traditional'
13
- import type { Changes, DiagramNodeWithNavigate, LikeC4DiagramEventHandlers } from '../LikeC4Diagram.props'
13
+ import type {
14
+ Changes,
15
+ DiagramNodeWithNavigate,
16
+ ElementIconRenderer,
17
+ LikeC4DiagramEventHandlers
18
+ } from '../LikeC4Diagram.props'
14
19
  import { MinZoom } from '../xyflow/const'
15
20
  import type { XYStoreApi } from '../xyflow/hooks'
16
21
  import type { XYFlowEdge, XYFlowInstance, XYFlowNode } from '../xyflow/types'
@@ -29,6 +34,7 @@ export type DiagramStore = {
29
34
  nodesDraggable: boolean
30
35
  nodesSelectable: boolean
31
36
  experimentalEdgeEditing: boolean
37
+ renderIcon: ElementIconRenderer | null
32
38
  // Diagram Container, see DiagramContainer.tsx
33
39
  getContainer: () => HTMLDivElement | null
34
40
 
@@ -76,6 +82,7 @@ export type DiagramInitialState = // Required properties
76
82
  & Pick<
77
83
  DiagramStore,
78
84
  | 'view'
85
+ | 'renderIcon'
79
86
  | 'readonly'
80
87
  | 'showElementLinks'
81
88
  | 'fitViewEnabled'
@@ -2,7 +2,7 @@ import { ActionIcon, Group } from '@mantine/core'
2
2
  import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'
3
3
  import clsx from 'clsx'
4
4
  import { AnimatePresence, m } from 'framer-motion'
5
- import { useNavigationHistory } from '../state/hooks'
5
+ import { useNavigationHistory } from '../state/useNavigationHistory'
6
6
  import * as css from './BackwardForwardPanel.css'
7
7
 
8
8
  export function BackwardForwardPanel() {
@@ -3,7 +3,7 @@ import { useDebouncedEffect } from '@react-hookz/web'
3
3
  import { shallowEqual } from 'fast-equals'
4
4
  import { useEffect, useRef } from 'react'
5
5
  import { type DiagramState, type DiagramStoreApi, useDiagramState, useDiagramStoreApi } from '../state/hooks'
6
- import { useXYStore, useXYStoreApi, type XYStoreApi } from './hooks'
6
+ import { useXYStore, useXYStoreApi, type XYStoreApi } from './hooks/useXYFlow'
7
7
  import type { XYFlowState } from './types'
8
8
  import { toDomPrecision } from './utils'
9
9
 
@@ -2,7 +2,7 @@ import { nonNullable } from '@likec4/core'
2
2
  import type { EdgeChange, NodeChange } from '@xyflow/react'
3
3
  import { getBoundsOfRects, getViewportForBounds, nodeToRect } from '@xyflow/system'
4
4
  import { useEffect } from 'react'
5
- import { useDiagramStoreApi } from '../state/hooks'
5
+ import { useDiagramStoreApi } from '../state/useDiagramStore'
6
6
  import { MinZoom } from './const'
7
7
  import type { XYFlowEdge, XYFlowNode } from './types'
8
8
 
@@ -2,7 +2,7 @@ import { deepEqual as eq } from 'fast-equals'
2
2
 
3
3
  import { useEffect } from 'react'
4
4
  import { omit } from 'remeda'
5
- import { useDiagramState, useDiagramStoreApi } from '../state/hooks'
5
+ import { useDiagramState, useDiagramStoreApi } from '../state/useDiagramStore'
6
6
  import { diagramViewToXYFlowData } from './diagram-to-xyflow'
7
7
 
8
8
  /**
@@ -1,9 +1,8 @@
1
1
  import { ReactFlow, useOnViewportChange } from '@xyflow/react'
2
- import { DEV } from 'esm-env'
3
2
  import { deepEqual as eq } from 'fast-equals'
4
3
  import { type CSSProperties, type PropsWithChildren, useCallback } from 'react'
5
- import useTilg from 'tilg'
6
- import { type DiagramState, useDiagramState, useDiagramStoreApi } from '../state/hooks'
4
+ import type { DiagramState } from '../state/diagramStore'
5
+ import { useDiagramState, useDiagramStoreApi } from '../state/useDiagramStore'
7
6
  import { MinZoom } from './const'
8
7
  import { RelationshipEdge } from './edges/RelationshipEdge'
9
8
  import { useLayoutConstraints } from './hooks/useLayoutConstraints'
@@ -67,7 +66,6 @@ export function XYFlow({
67
66
  defaultEdges,
68
67
  style
69
68
  }: XYFlowWrapperProps) {
70
- DEV && useTilg()
71
69
  const xyflowApi = useXYStoreApi()
72
70
  const diagramApi = useDiagramStoreApi()
73
71
  const {
@@ -1,7 +1,7 @@
1
1
  import type { ReactFlowProps } from '@xyflow/react'
2
2
  import { useMemo, useRef } from 'react'
3
3
  import type { Simplify } from 'type-fest'
4
- import { useDiagramStoreApi } from '../state/hooks'
4
+ import { useDiagramStoreApi } from '../state/useDiagramStore'
5
5
  import type { XYFlowEdge, XYFlowNode } from './types'
6
6
 
7
7
  export type XYFlowEventHandlers = Simplify<
@@ -3,7 +3,7 @@ import { shallowEqual } from 'fast-equals'
3
3
  import { memo, type PropsWithChildren } from 'react'
4
4
  import { isNonNull } from 'remeda'
5
5
  import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
6
- import { useDiagramState } from '../state/hooks'
6
+ import { useDiagramState } from '../state/useDiagramStore'
7
7
  import { BackwardForwardPanel } from '../ui/BackwardForwardPanel'
8
8
  import DiagramTitlePanel from '../ui/DiagramTitlePanel'
9
9
  import { DynamicViewWalkthrough } from '../ui/DynamicViewWalkthrough'
@@ -52,3 +52,4 @@ export const XYFlowInner = memo(function XYFlowInnerR({
52
52
  </>
53
53
  )
54
54
  }, shallowEqual)
55
+ XYFlowInner.displayName = 'XYFlowInner'
@@ -16,10 +16,10 @@ import { curveCatmullRomOpen, line as d3line } from 'd3-shape'
16
16
  import { deepEqual, deepEqual as eq } from 'fast-equals'
17
17
  import { memo, type PointerEvent as ReactPointerEvent, useRef, useState } from 'react'
18
18
  import { first, hasAtLeast, isArray, isTruthy, last } from 'remeda'
19
- import { useDiagramState, useDiagramStoreApi } from '../../state/hooks'
19
+ import { useDiagramState, useDiagramStoreApi } from '../../state/useDiagramStore'
20
20
  import { ZIndexes } from '../const'
21
21
  import { EdgeMarkers } from '../EdgeMarkers'
22
- import { useXYStoreApi } from '../hooks'
22
+ import { useXYStoreApi } from '../hooks/useXYFlow'
23
23
  import { type RelationshipData, type XYFlowEdge } from '../types'
24
24
  import { bezierControlPoints } from '../utils'
25
25
  import * as edgesCss from './edges.css'
@@ -375,7 +375,7 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
375
375
  selected: selected ?? false,
376
376
  stepNum: data.stepNum,
377
377
  label: data.label,
378
- zIndex: edgeLookup.get(id)!.zIndex ?? ZIndexes.Edge,
378
+ zIndex: (edgeLookup.get(id)!.zIndex ?? ZIndexes.Edge) + 1,
379
379
  isHovered,
380
380
  isActive,
381
381
  isStepEdge
@@ -53,7 +53,7 @@ globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
53
53
  // })
54
54
 
55
55
  globalStyle(`.react-flow__edges > svg`, {
56
- mixBlendMode: 'screen'
56
+ mixBlendMode: 'plus-lighter'
57
57
  })
58
58
 
59
59
  // globalStyle(`:where([data-mantine-color-scheme="light"]) .react-flow__edges > svg`, {
@@ -63,7 +63,7 @@ globalStyle(`.react-flow__edges > svg`, {
63
63
  export const dimmed = style({})
64
64
 
65
65
  globalStyle(`.react-flow__edges > svg:has(${dimmed})`, {
66
- opacity: 0.8,
66
+ opacity: 0.6,
67
67
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
68
68
  transitionDelay: '200ms',
69
69
  filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
@@ -117,11 +117,11 @@ export const controlPoint = style({
117
117
  })
118
118
 
119
119
  export const controlDragging = style({
120
- cursor: 'grabbing'
120
+ cursor: 'grab'
121
121
  })
122
122
 
123
123
  globalStyle(`${controlDragging} *`, {
124
- cursor: 'grabbing'
124
+ cursor: 'grab'
125
125
  })
126
126
 
127
127
  const strokeKeyframes = keyframes({
@@ -174,7 +174,7 @@ export const edgeLabel = style({
174
174
  cursor: 'pointer',
175
175
  width: 'fit-content',
176
176
  transformOrigin: '50% 50%',
177
- mixBlendMode: 'screen',
177
+ mixBlendMode: 'plus-lighter',
178
178
  color: xyvars.edge.labelColor,
179
179
  backgroundColor: xyvars.edge.labelBgColor,
180
180
  borderRadius: 3,
@@ -188,7 +188,7 @@ export const edgeLabel = style({
188
188
  transform: `${varTranslate} scale(1.12)`
189
189
  },
190
190
  [`&:is(${dimmed})`]: {
191
- opacity: 0.6,
191
+ opacity: 0.3,
192
192
  transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
193
193
  transitionDelay: '200ms',
194
194
  filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
@@ -4,7 +4,7 @@ import type { InternalNode, ReactFlowProps, XYPosition } from '@xyflow/react'
4
4
  import { getNodeDimensions } from '@xyflow/system'
5
5
  import { useMemo, useRef } from 'react'
6
6
  import { isNullish } from 'remeda'
7
- import { useDiagramStoreApi } from '../../state/hooks'
7
+ import { useDiagramStoreApi } from '../../state/useDiagramStore'
8
8
  import type { XYFlowInstance, XYFlowNode } from '../types'
9
9
  import { isSamePoint } from '../utils'
10
10
  import { useXYStoreApi, type XYStoreApi } from './useXYFlow'
@@ -5,7 +5,7 @@ import clsx from 'clsx'
5
5
  import { deepEqual as eq } from 'fast-equals'
6
6
  import { memo } from 'react'
7
7
  import { clamp } from 'remeda'
8
- import { useDiagramState } from '../../../state/hooks'
8
+ import { useDiagramState } from '../../../state/useDiagramStore'
9
9
  import type { CompoundXYFlowNode } from '../../types'
10
10
  import { NavigateToBtn } from '../shared/NavigateToBtn'
11
11
  import * as css from './CompoundNode.css'
@@ -16,7 +16,8 @@ import { IconLink } from '@tabler/icons-react'
16
16
  import clsx from 'clsx'
17
17
  import { useId } from 'react'
18
18
  import { clamp } from 'remeda'
19
- import { type DiagramState, useDiagramState } from '../../../state/hooks'
19
+ import { type DiagramState } from '../../../state/diagramStore'
20
+ import { useDiagramState } from '../../../state/useDiagramStore'
20
21
  import type { XYFlowNode } from '../../types'
21
22
  import { elementLink, trigger } from './ElementLink.css'
22
23
 
@@ -1,19 +1,23 @@
1
- import { Text } from '@mantine/core'
1
+ import type { DiagramNode } from '@likec4/core/types'
2
+ import { Text as MantineText } from '@mantine/core'
2
3
  import { Handle, type NodeProps, Position } from '@xyflow/react'
3
4
  import clsx from 'clsx'
4
5
  import { deepEqual as eq } from 'fast-equals'
5
6
  import { m, type Variants } from 'framer-motion'
6
7
  import { memo } from 'react'
7
- import { isNumber } from 'remeda'
8
- import { useDiagramState } from '../../../state/hooks'
8
+ import { isNumber, isTruthy } from 'remeda'
9
+ import { type DiagramStoreApi, useDiagramState, useDiagramStoreApi } from '../../../state/useDiagramStore'
9
10
  import type { ElementXYFlowNode } from '../../types'
10
11
  import { toDomPrecision } from '../../utils'
11
12
  import { NavigateToBtn } from '../shared/NavigateToBtn'
12
13
  import * as css from './element.css'
13
- import { ElementIcon } from './ElementIcon'
14
14
  import { ElementLink } from './ElementLink'
15
15
  import { ElementShapeSvg, SelectedIndicator } from './ElementShapeSvg'
16
16
 
17
+ const Text = MantineText.withProps({
18
+ component: 'div'
19
+ })
20
+
17
21
  type ElementNodeProps = NodeProps<ElementXYFlowNode>
18
22
 
19
23
  const selectedScale = 1.015
@@ -67,7 +71,7 @@ const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
67
71
  prev.id === next.id
68
72
  && eq(prev.selected ?? false, next.selected ?? false)
69
73
  && eq(prev.dragging ?? false, next.dragging ?? false)
70
- && eq(prev.draggable ?? false, next.draggable ?? false)
74
+ // && eq(prev.draggable ?? false, next.draggable ?? false)
71
75
  && eq(prev.width ?? 0, next.width ?? 0)
72
76
  && eq(prev.height ?? 0, next.height ?? 0)
73
77
  && eq(prev.data.element, next.data.element)
@@ -84,6 +88,7 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
84
88
  width,
85
89
  height
86
90
  }) {
91
+ const store = useDiagramStoreApi()
87
92
  const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
88
93
  isHovered: s.hoveredNodeId === id,
89
94
  isDimmed: s.dimmed.has(id),
@@ -116,7 +121,7 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
116
121
  break
117
122
  }
118
123
 
119
- // useTilg(animate, isHovered, isInteractive)
124
+ const elementIcon = ElementIcon({ node: element, store })
120
125
 
121
126
  return (
122
127
  <>
@@ -143,6 +148,22 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
143
148
  whileTap: dragging ? animate : 'tap'
144
149
  })}
145
150
  >
151
+ {
152
+ /* <svg
153
+ className={clsx(
154
+ css.cssShapeSvgMultiple
155
+ )}
156
+ viewBox={`0 0 ${w} ${h}`}
157
+ width={w}
158
+ height={h}
159
+ >
160
+ <ElementShapeSvg
161
+ shape={element.shape}
162
+ w={w}
163
+ h={h}
164
+ />
165
+ </svg> */
166
+ }
146
167
  <svg
147
168
  className={clsx(
148
169
  css.cssShapeSvg
@@ -164,27 +185,32 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
164
185
  h={h}
165
186
  />
166
187
  </svg>
167
- <div className={clsx(css.element, 'likec4-element')}>
168
- <ElementIcon node={element} />
169
- <Text
170
- component="div"
171
- className={clsx(css.title, 'likec4-element-title')}>
172
- {element.title}
173
- </Text>
174
- {element.technology && (
175
- <Text
176
- component="div"
177
- className={clsx(css.technology, 'likec4-element-technology')}>
178
- {element.technology}
179
- </Text>
188
+ <div
189
+ className={clsx(
190
+ css.elementDataContainer,
191
+ isTruthy(elementIcon) && css.hasIcon,
192
+ 'likec4-element'
180
193
  )}
181
- {element.description && (
194
+ >
195
+ {elementIcon}
196
+ <div className={clsx(css.elementTextData, 'likec4-element-main-props')}>
182
197
  <Text
183
- component="div"
184
- className={clsx(css.description, 'likec4-element-description')}>
185
- {element.description}
198
+ className={clsx(css.title, 'likec4-element-title')}>
199
+ {element.title}
186
200
  </Text>
187
- )}
201
+ {element.technology && (
202
+ <Text
203
+ className={clsx(css.technology, 'likec4-element-technology')}>
204
+ {element.technology}
205
+ </Text>
206
+ )}
207
+ {element.description && (
208
+ <Text
209
+ className={clsx(css.description, 'likec4-element-description')}>
210
+ {element.description}
211
+ </Text>
212
+ )}
213
+ </div>
188
214
  </div>
189
215
  {isHovercards && element.links && <ElementLink element={element} />}
190
216
  {isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
@@ -197,3 +223,26 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
197
223
  </>
198
224
  )
199
225
  }, isEqualProps)
226
+
227
+ const ElementIcon = ({ node, store }: { node: DiagramNode; store: DiagramStoreApi }) => {
228
+ if (!node.icon) {
229
+ return null
230
+ }
231
+ if (node.icon.startsWith('http://') || node.icon.startsWith('https://')) {
232
+ return (
233
+ <div className={clsx(css.elementIcon, 'likec4-element-icon')}>
234
+ <img src={node.icon} alt={node.title} />
235
+ </div>
236
+ )
237
+ }
238
+ const RenderIcon = store.getState().renderIcon
239
+ const icon = RenderIcon ? <RenderIcon node={node} /> : null
240
+ if (!icon) {
241
+ return null
242
+ }
243
+ return (
244
+ <div className={clsx(css.elementIcon, 'likec4-element-icon')}>
245
+ {icon}
246
+ </div>
247
+ )
248
+ }
@@ -2,7 +2,7 @@ import { type ElementShape, nonexhaustive } from '@likec4/core'
2
2
  import { toDomPrecision } from '../../utils'
3
3
  import { fillElementFill, fillMixStroke } from './element.css'
4
4
 
5
- function cylinderSVGPath(diameter: number, height: number, tilt = 0.0725) {
5
+ export function cylinderSVGPath(diameter: number, height: number, tilt = 0.065) {
6
6
  const radius = Math.round(diameter / 2)
7
7
  // const tiltAdjustedHeight = height * Math.cos((tilt * Math.PI) / 2)
8
8
  const rx = radius
@@ -25,7 +25,7 @@ function cylinderSVGPath(diameter: number, height: number, tilt = 0.0725) {
25
25
  }
26
26
  }
27
27
 
28
- function queueSVGPath(width: number, height: number, tilt = 0.2) {
28
+ function queueSVGPath(width: number, height: number, tilt = 0.185) {
29
29
  const diameter = height
30
30
  const radius = Math.round(diameter / 2)
31
31
  const ry = radius