@likec4/diagram 1.4.0 → 1.6.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 (87) hide show
  1. package/LICENSE +1 -1
  2. package/dist/LikeC4Diagram.css.d.ts +0 -1
  3. package/dist/LikeC4Diagram.d.ts +1 -1
  4. package/dist/LikeC4Diagram.props.d.ts +19 -4
  5. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.mjs +3937 -3646
  8. package/dist/state/DiagramContext.d.ts +95 -4159
  9. package/dist/state/diagramStore.d.ts +100 -4164
  10. package/dist/state/hooks.d.ts +4 -0
  11. package/dist/state/index.d.ts +1 -2
  12. package/dist/style.css +281 -191
  13. package/dist/ui/options/EdgeOptions.d.ts +19 -0
  14. package/dist/ui/options/NodeOptions.d.ts +2 -2
  15. package/dist/xyflow/EdgeMarkers.d.ts +11 -0
  16. package/dist/xyflow/XYFlowEvents.d.ts +8 -5
  17. package/dist/xyflow/const.d.ts +3 -3
  18. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  19. package/dist/xyflow/edges/RelationshipEdge.d.ts +3 -2
  20. package/dist/xyflow/edges/edges.css.d.ts +1 -0
  21. package/dist/xyflow/edges/utils.d.ts +1 -0
  22. package/dist/xyflow/hooks/index.d.ts +3 -2
  23. package/dist/xyflow/hooks/useXYFlow.d.ts +13 -12
  24. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
  25. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +5 -0
  26. package/dist/xyflow/nodes/element/element.css.d.ts +5 -2
  27. package/dist/xyflow/types.d.ts +2 -32
  28. package/package.json +19 -19
  29. package/src/LikeC4Diagram.css.ts +0 -8
  30. package/src/LikeC4Diagram.props.ts +23 -5
  31. package/src/LikeC4Diagram.tsx +18 -21
  32. package/src/StaticLikeC4Diagram.tsx +1 -0
  33. package/src/globals.css.ts +10 -3
  34. package/src/index.ts +1 -1
  35. package/src/mantine.css.ts +2 -2
  36. package/src/state/DiagramContext.tsx +44 -13
  37. package/src/state/diagramStore.ts +72 -53
  38. package/src/state/hooks.ts +5 -0
  39. package/src/state/index.ts +1 -2
  40. package/src/ui/BackwardForwardPanel.css.ts +9 -9
  41. package/src/ui/DiagramTitlePanel.css.ts +7 -2
  42. package/src/ui/DiagramTitlePanel.tsx +5 -3
  43. package/src/ui/DynamicViewWalkthrough.tsx +6 -4
  44. package/src/ui/OptionsPanel.tsx +37 -27
  45. package/src/ui/options/EdgeOptions.tsx +157 -0
  46. package/src/ui/options/NodeOptions.tsx +37 -29
  47. package/src/xyflow/EdgeMarkers.tsx +148 -0
  48. package/src/xyflow/FitviewOnDiagramChange.tsx +9 -5
  49. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
  50. package/src/xyflow/SyncWithDiagram.tsx +13 -20
  51. package/src/xyflow/XYFlow.tsx +3 -4
  52. package/src/xyflow/XYFlowEvents.tsx +48 -33
  53. package/src/xyflow/const.ts +3 -3
  54. package/src/xyflow/diagram-to-xyflow.ts +17 -41
  55. package/src/xyflow/edges/RelationshipEdge.tsx +208 -119
  56. package/src/xyflow/edges/edges.css.ts +40 -26
  57. package/src/xyflow/edges/utils.ts +10 -0
  58. package/src/xyflow/hooks/index.ts +4 -2
  59. package/src/xyflow/hooks/useLayoutConstraints.ts +3 -4
  60. package/src/xyflow/hooks/useXYFlow.ts +28 -1
  61. package/src/xyflow/nodes/compound/CompoundNode.css.ts +34 -24
  62. package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
  63. package/src/xyflow/nodes/element/ElementLink.tsx +12 -16
  64. package/src/xyflow/nodes/element/ElementNode.tsx +118 -114
  65. package/src/xyflow/nodes/element/ElementShapeSvg.tsx +2 -2
  66. package/src/xyflow/nodes/element/element.css.ts +147 -55
  67. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
  68. package/src/xyflow/types.ts +2 -4
  69. package/src/xyflow/utils.ts +3 -6
  70. package/dist/KeepAspectRatioContainer.d.ts +0 -9
  71. package/dist/icons/Clipboard.d.ts +0 -3
  72. package/dist/icons/Link.d.ts +0 -3
  73. package/dist/icons/ZoomIn.d.ts +0 -3
  74. package/dist/icons/index.d.ts +0 -3
  75. package/dist/mantine/theme.d.ts +0 -3
  76. package/dist/state/WhenInitialized.d.ts +0 -3
  77. package/dist/ui/Camera.d.ts +0 -3
  78. package/dist/xyflow/nodes/element/ElementIcon.d.ts +0 -10
  79. package/src/KeepAspectRatioContainer.tsx +0 -36
  80. package/src/icons/Clipboard.tsx +0 -24
  81. package/src/icons/Link.tsx +0 -23
  82. package/src/icons/ZoomIn.tsx +0 -25
  83. package/src/icons/index.ts +0 -3
  84. package/src/mantine/theme.ts +0 -20
  85. package/src/state/WhenInitialized.tsx +0 -24
  86. package/src/ui/Camera.tsx +0 -174
  87. package/src/xyflow/nodes/element/ElementIcon.tsx +0 -44
package/src/ui/Camera.tsx DELETED
@@ -1,174 +0,0 @@
1
- import { shallowEqual } from '@mantine/hooks'
2
- import { useDebouncedEffect, useIsMounted } from '@react-hookz/web'
3
- import {
4
- type ReactFlowState,
5
- useOnSelectionChange,
6
- useOnViewportChange,
7
- useStore,
8
- useUpdateNodeInternals,
9
- type Viewport
10
- } from '@xyflow/react'
11
- import { memo, useEffect, useRef, useState } from 'react'
12
- import { uniq } from 'remeda'
13
- import { useXYFlow } from '../xyflow/hooks'
14
- import type { XYFlowNode } from '../xyflow/types'
15
- import { distance } from '../xyflow/utils'
16
-
17
- const selector = (state: ReactFlowState) => {
18
- return {
19
- isUserSelectionActive: state.userSelectionActive,
20
- isDragging: state.paneDragging || state.nodes.some(n => n.dragging ?? false)
21
- }
22
- }
23
-
24
- const CameraMemo = /* @__PURE__ */ memo(function Camera() {
25
- const isMounted = useIsMounted()
26
- // const isUserSelectionActive = useStore(selectUserSelectionActive)
27
- const {
28
- isUserSelectionActive,
29
- isDragging
30
- } = useStore(selector, shallowEqual)
31
- const reactflow = useXYFlow()
32
- const updateNd = useUpdateNodeInternals()
33
-
34
- const padding = 0 // editor.fitViewPadding
35
- const viewId = '' // editor.viewId
36
- const fitOnSelect: boolean = false // editor.fitOnSelect
37
-
38
- const previousViewport = useRef<Viewport | null>(null)
39
- const isZoomPendingRef = useRef(false)
40
-
41
- const viewportChangeStart = useRef<Viewport | null>(null)
42
- const prevViewId = useRef(viewId)
43
- const isReady = reactflow.viewportInitialized
44
- const selectedNodesRef = useRef([] as string[])
45
-
46
- const [selectedNodes, setSelectedNodes] = useState([] as XYFlowNode[])
47
-
48
- // WORKAROUND
49
-
50
- useOnViewportChange({
51
- onStart: (viewport) => {
52
- viewportChangeStart.current = { ...viewport }
53
- },
54
- onEnd: (end) => {
55
- if (!previousViewport.current) {
56
- viewportChangeStart.current = null
57
- }
58
- if (!viewportChangeStart.current) {
59
- return
60
- }
61
- const start = {
62
- ...viewportChangeStart.current
63
- }
64
- const d = distance(start, end)
65
- if (d > 15) {
66
- previousViewport.current = null
67
- }
68
- viewportChangeStart.current = null
69
- }
70
- })
71
-
72
- const fixUseOnViewportChange = () => {
73
- // WORKAROUND:
74
- // react-flow triggers useOnViewportChange
75
- // We reset the viewportChangeStart, before useOnViewportChange.onEnd called
76
- setTimeout(() => {
77
- viewportChangeStart.current = null
78
- }, 50)
79
- }
80
-
81
- useOnSelectionChange({
82
- onChange: ({ nodes, edges }) => {
83
- if (!isMounted() || fitOnSelect === false) {
84
- return
85
- }
86
- if (nodes.length === 0 && edges.length === 0) {
87
- setSelectedNodes([])
88
- return
89
- }
90
- const selected = new Set([
91
- ...nodes.map(({ id }) => reactflow.getNode(id)),
92
- ...edges.flatMap((edge) => [
93
- reactflow.getNode(edge.source),
94
- reactflow.getNode(edge.target)
95
- ])
96
- ].filter(Boolean))
97
- setSelectedNodes([...selected])
98
- }
99
- })
100
-
101
- const selectedNodesHash = selectedNodes.map((node) => node.id).sort().join('\n')
102
- useEffect(() => {
103
- isZoomPendingRef.current = selectedNodesHash !== ''
104
- }, [selectedNodesHash])
105
-
106
- useDebouncedEffect(
107
- () => {
108
- if (isUserSelectionActive || isDragging || !isReady) {
109
- return
110
- }
111
- if (selectedNodes.length === 0) {
112
- if (previousViewport.current) {
113
- console.log(`Camera: revert viewport`)
114
- reactflow.setViewport(previousViewport.current, {
115
- duration: 300
116
- })
117
- previousViewport.current = null
118
- }
119
- return
120
- }
121
- if (!isZoomPendingRef.current) {
122
- return
123
- }
124
- isZoomPendingRef.current = false
125
- previousViewport.current ??= { ...reactflow.getViewport() }
126
- const zoom = previousViewport.current.zoom
127
- reactflow.fitView({
128
- duration: 350,
129
- maxZoom: Math.max(1, zoom),
130
- padding,
131
- nodes: selectedNodes
132
- })
133
- fixUseOnViewportChange()
134
- },
135
- [selectedNodesHash, isReady, isDragging, isUserSelectionActive],
136
- previousViewport.current ? 100 : 1500
137
- // previousViewport.current ? 200 : 2500
138
- )
139
-
140
- useEffect(() => {
141
- const ids = selectedNodes.map((node) => node.id)
142
- for (const id of uniq([...selectedNodesRef.current, ...ids])) {
143
- try {
144
- updateNd(id)
145
- } catch (e) {
146
- console.warn(`Camera: failed useUpdateNodeInternals`, e)
147
- }
148
- }
149
- selectedNodesRef.current = ids
150
- }, [selectedNodesHash, updateNd])
151
-
152
- useDebouncedEffect(
153
- () => {
154
- if (!isReady || prevViewId.current === viewId) {
155
- return
156
- }
157
- const zoom = previousViewport.current?.zoom ?? reactflow.getZoom()
158
- reactflow.fitView({
159
- duration: 400,
160
- maxZoom: Math.max(1, zoom),
161
- padding
162
- })
163
- previousViewport.current = null
164
- prevViewId.current = viewId
165
- },
166
- [isReady, viewId],
167
- 50,
168
- 500
169
- )
170
-
171
- return null
172
- })
173
-
174
- export default CameraMemo
@@ -1,44 +0,0 @@
1
- import { assignInlineVars } from '@vanilla-extract/dynamic'
2
- import clsx from 'clsx'
3
- import type { XYFlowNode } from '../../types'
4
- import { elementIcon, iconMaxH } from './element.css'
5
-
6
- type ElementIconProps = {
7
- node: XYFlowNode['data']['element']
8
- maxWidth?: number | undefined
9
- paddingX?: number | undefined
10
- paddingY?: number | undefined
11
- }
12
-
13
- export function ElementIcon({
14
- node,
15
- paddingY = 32
16
- }: ElementIconProps) {
17
- if (!node.icon) {
18
- return null
19
- }
20
- const firstLabel = node.labels[0]
21
- if (!firstLabel) {
22
- return null
23
- }
24
-
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
- return (
33
- <div
34
- className={clsx(elementIcon, 'likec4-element-icon')}
35
- style={assignInlineVars({ iconMaxH }, {
36
- iconMaxH: maxIconHeight + 'px'
37
- })}>
38
- <img
39
- src={node.icon!}
40
- alt={node.title}
41
- />
42
- </div>
43
- )
44
- }