@likec4/diagram 1.9.0 → 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.
- package/package.json +18 -41
- package/src/LikeC4CustomColors.tsx +1 -1
- package/src/LikeC4Diagram.css.ts +1 -1
- package/src/LikeC4Diagram.props.ts +40 -94
- package/src/LikeC4Diagram.tsx +12 -2
- package/src/LikeC4ModelProvider.tsx +49 -0
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/globals.css.ts +2 -3
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useMantinePortalProps.ts +8 -0
- package/src/hooks/useXYFlow.ts +1 -2
- package/src/index.ts +3 -0
- package/src/likec4model/LikeC4ModelContext.ts +4 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4Model.ts +55 -0
- package/src/state/diagram-to-xyflow.ts +1 -2
- package/src/state/diagramStore.ts +185 -66
- package/src/{theme.css.ts → theme-vars.ts} +11 -4
- package/src/ui/DiagramTitlePanel.css.ts +2 -3
- package/src/ui/DiagramTitlePanel.tsx +17 -1
- package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
- package/src/ui/DynamicViewWalkthrough.tsx +97 -52
- package/src/ui/OptionsPanel.css.ts +1 -2
- package/src/ui/OptionsPanel.tsx +1 -1
- package/src/ui/notation/NotationPanel.css.ts +2 -3
- package/src/ui/notation/NotationPanel.tsx +9 -10
- package/src/ui/options/NodeOptions.tsx +1 -1
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +20 -32
- package/src/ui/top-left/TopLeftPanel.tsx +128 -117
- package/src/ui/top-left/_shared.ts +7 -6
- package/src/ui/top-left/styles.css.ts +17 -8
- package/src/utils/vector.ts +57 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
- package/src/xyflow/XYFlow.tsx +9 -11
- package/src/xyflow/XYFlowEvents.tsx +59 -60
- package/src/xyflow/XYFlowInner.tsx +5 -5
- package/src/xyflow/edges/EdgeLabel.tsx +185 -0
- package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
- package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
- package/src/xyflow/edges/edges.css.ts +75 -7
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
- package/src/xyflow/nodes/compound/CompoundNode.tsx +4 -4
- package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
- package/src/xyflow/nodes/element/ElementLink.tsx +3 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +44 -0
- package/src/xyflow/nodes/element/element.css.ts +1 -2
- package/src/xyflow/types.ts +1 -24
- package/src/xyflow/useLayoutConstraints.ts +10 -6
- package/dist/LikeC4CustomColors.d.ts +0 -3
- package/dist/LikeC4CustomColors.props.d.ts +0 -5
- package/dist/LikeC4Diagram.css.d.ts +0 -4
- package/dist/LikeC4Diagram.d.ts +0 -7
- package/dist/LikeC4Diagram.props.d.ts +0 -154
- package/dist/StaticLikeC4Diagram.d.ts +0 -4
- package/dist/globals.css.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/useDiagramState.d.ts +0 -6
- package/dist/hooks/useSetState.d.ts +0 -3
- package/dist/hooks/useUpdateEffect.d.ts +0 -5
- package/dist/hooks/useXYFlow.d.ts +0 -267
- package/dist/index.d.ts +0 -9
- package/dist/index.mjs +0 -3684
- package/dist/mantine/EnsureMantine.d.ts +0 -8
- package/dist/mantine.css.d.ts +0 -3
- package/dist/state/DiagramContext.d.ts +0 -1192
- package/dist/state/diagram-to-xyflow.d.ts +0 -12
- package/dist/state/diagramStore.d.ts +0 -1268
- package/dist/style.css +0 -2139
- package/dist/theme.css.d.ts +0 -63
- package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
- package/dist/ui/DiagramTitlePanel.d.ts +0 -1
- package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
- package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
- package/dist/ui/OptionsPanel.css.d.ts +0 -1
- package/dist/ui/OptionsPanel.d.ts +0 -1
- package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
- package/dist/ui/notation/NotationPanel.d.ts +0 -1
- package/dist/ui/notation/index.d.ts +0 -1
- package/dist/ui/options/EdgeOptions.d.ts +0 -19
- package/dist/ui/options/NodeOptions.d.ts +0 -19
- package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
- package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
- package/dist/ui/top-left/_shared.d.ts +0 -5
- package/dist/ui/top-left/styles.css.d.ts +0 -3
- package/dist/xyflow/EdgeMarkers.d.ts +0 -12
- package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
- package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
- package/dist/xyflow/XYFlow.d.ts +0 -12
- package/dist/xyflow/XYFlowBackground.d.ts +0 -11
- package/dist/xyflow/XYFlowEvents.d.ts +0 -161
- package/dist/xyflow/XYFlowInner.d.ts +0 -10
- package/dist/xyflow/const.d.ts +0 -7
- package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
- package/dist/xyflow/edges/edges.css.d.ts +0 -12
- package/dist/xyflow/edges/utils.d.ts +0 -17
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
- package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
- package/dist/xyflow/nodes/compound/index.d.ts +0 -1
- package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
- package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
- package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
- package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
- package/dist/xyflow/nodes/element/index.d.ts +0 -1
- package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
- package/dist/xyflow/types.d.ts +0 -50
- package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
- package/dist/xyflow/utils.d.ts +0 -18
- package/src/mantine.css.ts +0 -7
- package/src/reset.d.ts +0 -2
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ActionIconGroup,
|
|
2
3
|
Code,
|
|
3
4
|
Group,
|
|
4
5
|
HoverCard,
|
|
5
6
|
HoverCardDropdown,
|
|
6
7
|
HoverCardTarget,
|
|
7
8
|
Notification,
|
|
9
|
+
Overlay,
|
|
8
10
|
type PopoverProps,
|
|
9
11
|
Stack,
|
|
10
|
-
Text
|
|
12
|
+
Text,
|
|
13
|
+
TooltipGroup,
|
|
14
|
+
type TooltipProps
|
|
11
15
|
} from '@mantine/core'
|
|
12
16
|
import {
|
|
13
17
|
IconAlertTriangle,
|
|
@@ -15,11 +19,15 @@ import {
|
|
|
15
19
|
IconChevronRight,
|
|
16
20
|
IconFileSymlink,
|
|
17
21
|
IconFocusCentered,
|
|
18
|
-
IconMenu2
|
|
22
|
+
IconMenu2,
|
|
23
|
+
IconRouteOff
|
|
19
24
|
} from '@tabler/icons-react'
|
|
20
25
|
import clsx from 'clsx'
|
|
21
|
-
import { AnimatePresence, m } from 'framer-motion'
|
|
26
|
+
import { AnimatePresence, LayoutGroup, m } from 'framer-motion'
|
|
27
|
+
import { isNullish } from 'remeda'
|
|
22
28
|
import { type DiagramState, useDiagramState, useDiagramStoreApi } from '../../hooks/useDiagramState'
|
|
29
|
+
import { useMantinePortalProps } from '../../hooks/useMantinePortalProps'
|
|
30
|
+
import { mantine } from '../../theme-vars'
|
|
23
31
|
import { ActionIcon, Tooltip } from './_shared'
|
|
24
32
|
import { ChangeAutoLayoutButton } from './ChangeAutoLayoutButton'
|
|
25
33
|
import * as css from './styles.css'
|
|
@@ -38,59 +46,62 @@ const BackwardForwardButtons = () => {
|
|
|
38
46
|
hasStepForward
|
|
39
47
|
} = useDiagramState(historySelector)
|
|
40
48
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{hasStepBack && (
|
|
55
|
-
<m.div
|
|
56
|
-
layout
|
|
57
|
-
initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
|
|
58
|
-
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
59
|
-
exit={{
|
|
60
|
-
opacity: 0.05,
|
|
61
|
-
transform: 'translateX(-10px)'
|
|
62
|
-
}}
|
|
63
|
-
key={'back'}>
|
|
64
|
-
<ActionIcon
|
|
65
|
-
onClick={e => {
|
|
66
|
-
e.stopPropagation()
|
|
67
|
-
store.getState().goBack()
|
|
68
|
-
}}>
|
|
69
|
-
<IconChevronLeft />
|
|
70
|
-
</ActionIcon>
|
|
71
|
-
</m.div>
|
|
72
|
-
)}
|
|
73
|
-
{hasStepForward && (
|
|
74
|
-
<m.div
|
|
75
|
-
layout
|
|
76
|
-
initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
|
|
77
|
-
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
78
|
-
exit={{
|
|
79
|
-
opacity: 0,
|
|
80
|
-
transform: 'translateX(10px)'
|
|
81
|
-
}}
|
|
82
|
-
key={'forward'}>
|
|
83
|
-
<ActionIcon
|
|
84
|
-
onClick={e => {
|
|
85
|
-
e.stopPropagation()
|
|
86
|
-
store.getState().goForward()
|
|
87
|
-
}}>
|
|
88
|
-
<IconChevronRight />
|
|
89
|
-
</ActionIcon>
|
|
90
|
-
</m.div>
|
|
49
|
+
<LayoutGroup>
|
|
50
|
+
<Group
|
|
51
|
+
className={clsx(css.backwardForwardButtons, 'likec4-navigation-webview')}
|
|
52
|
+
align="flex-start"
|
|
53
|
+
gap={'xs'}>
|
|
54
|
+
{showBurgerMenu && (
|
|
55
|
+
<ActionIcon
|
|
56
|
+
onClick={e => {
|
|
57
|
+
e.stopPropagation()
|
|
58
|
+
store.getState().onBurgerMenuClick?.()
|
|
59
|
+
}}>
|
|
60
|
+
<IconMenu2 />
|
|
61
|
+
</ActionIcon>
|
|
91
62
|
)}
|
|
92
|
-
|
|
93
|
-
|
|
63
|
+
<AnimatePresence>
|
|
64
|
+
{hasStepBack && (
|
|
65
|
+
<m.div
|
|
66
|
+
layout
|
|
67
|
+
initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
|
|
68
|
+
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
69
|
+
exit={{
|
|
70
|
+
opacity: 0.05,
|
|
71
|
+
transform: 'translateX(-10px)'
|
|
72
|
+
}}
|
|
73
|
+
key={'back'}>
|
|
74
|
+
<ActionIcon
|
|
75
|
+
onClick={e => {
|
|
76
|
+
e.stopPropagation()
|
|
77
|
+
store.getState().goBack()
|
|
78
|
+
}}>
|
|
79
|
+
<IconChevronLeft />
|
|
80
|
+
</ActionIcon>
|
|
81
|
+
</m.div>
|
|
82
|
+
)}
|
|
83
|
+
{hasStepForward && (
|
|
84
|
+
<m.div
|
|
85
|
+
layout
|
|
86
|
+
initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
|
|
87
|
+
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
88
|
+
exit={{
|
|
89
|
+
opacity: 0,
|
|
90
|
+
transform: 'translateX(10px)'
|
|
91
|
+
}}
|
|
92
|
+
key={'forward'}>
|
|
93
|
+
<ActionIcon
|
|
94
|
+
onClick={e => {
|
|
95
|
+
e.stopPropagation()
|
|
96
|
+
store.getState().goForward()
|
|
97
|
+
}}>
|
|
98
|
+
<IconChevronRight />
|
|
99
|
+
</ActionIcon>
|
|
100
|
+
</m.div>
|
|
101
|
+
)}
|
|
102
|
+
</AnimatePresence>
|
|
103
|
+
</Group>
|
|
104
|
+
</LayoutGroup>
|
|
94
105
|
)
|
|
95
106
|
}
|
|
96
107
|
|
|
@@ -101,10 +112,8 @@ const LayoutDriftNotification = (props: PopoverProps) => (
|
|
|
101
112
|
closeDelay={100}
|
|
102
113
|
{...props}>
|
|
103
114
|
<HoverCardTarget>
|
|
104
|
-
<ActionIcon
|
|
105
|
-
|
|
106
|
-
variant="light"
|
|
107
|
-
color="orange">
|
|
115
|
+
<ActionIcon c="orange">
|
|
116
|
+
<Overlay color={mantine.colors.orange.lightHover} backgroundOpacity={1} />
|
|
108
117
|
<IconAlertTriangle />
|
|
109
118
|
</ActionIcon>
|
|
110
119
|
</HoverCardTarget>
|
|
@@ -126,6 +135,22 @@ const LayoutDriftNotification = (props: PopoverProps) => (
|
|
|
126
135
|
</HoverCard>
|
|
127
136
|
)
|
|
128
137
|
|
|
138
|
+
const ResetControlPointsButton = (props: Omit<TooltipProps, 'label' | 'children'>) => {
|
|
139
|
+
const store = useDiagramStoreApi()
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Tooltip label="Reset all control points" {...props}>
|
|
143
|
+
<ActionIcon
|
|
144
|
+
onClick={e => {
|
|
145
|
+
e.stopPropagation()
|
|
146
|
+
store.getState().resetEdgeControlPoints()
|
|
147
|
+
}}>
|
|
148
|
+
<IconRouteOff />
|
|
149
|
+
</ActionIcon>
|
|
150
|
+
</Tooltip>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
129
154
|
export const TopLeftPanel = () => {
|
|
130
155
|
const store = useDiagramStoreApi()
|
|
131
156
|
const {
|
|
@@ -133,69 +158,55 @@ export const TopLeftPanel = () => {
|
|
|
133
158
|
showFitDiagram,
|
|
134
159
|
showLayoutDriftWarning,
|
|
135
160
|
showChangeAutoLayout,
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
showGoToSource,
|
|
162
|
+
viewportChanged,
|
|
163
|
+
showResetControlPoints
|
|
138
164
|
} = useDiagramState(s => {
|
|
139
|
-
const
|
|
165
|
+
const isNotWalkthrough = isNullish(s.activeWalkthrough)
|
|
166
|
+
const isNotFocused = isNullish(s.focusedNodeId)
|
|
167
|
+
const isNotActive = isNotWalkthrough && isNotFocused
|
|
140
168
|
return ({
|
|
141
169
|
showNavigationButtons: !!s.onBurgerMenuClick || s.showNavigationButtons && !!s.onNavigateTo,
|
|
142
|
-
showFitDiagram: s.fitViewEnabled && s.zoomable &&
|
|
143
|
-
showLayoutDriftWarning: s.readonly
|
|
144
|
-
showChangeAutoLayout: s.
|
|
145
|
-
showGoToSource: !!s.onOpenSourceView,
|
|
146
|
-
|
|
170
|
+
showFitDiagram: s.fitViewEnabled && s.zoomable && isNotWalkthrough,
|
|
171
|
+
showLayoutDriftWarning: !s.readonly && s.view.hasLayoutDrift === true && isNotActive,
|
|
172
|
+
showChangeAutoLayout: s.isEditable() && isNotActive,
|
|
173
|
+
showGoToSource: !!s.onOpenSourceView && isNotWalkthrough,
|
|
174
|
+
viewportChanged: s.viewportChanged,
|
|
175
|
+
showResetControlPoints: s.readonly !== true && s.experimentalEdgeEditing === true
|
|
147
176
|
})
|
|
148
177
|
})
|
|
149
|
-
const portalProps =
|
|
178
|
+
const portalProps = useMantinePortalProps()
|
|
150
179
|
|
|
151
180
|
return (
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
onClick={e => e.stopPropagation()}
|
|
159
|
-
gap={'xs'}>
|
|
160
|
-
{showNavigationButtons && <BackwardForwardButtons />}
|
|
161
|
-
{showGoToSource && (
|
|
162
|
-
<Tooltip label="Go to source" {...portalProps}>
|
|
163
|
-
<ActionIcon
|
|
164
|
-
className="action-icon"
|
|
165
|
-
onClick={e => {
|
|
166
|
-
e.stopPropagation()
|
|
167
|
-
store.getState().onOpenSourceView?.()
|
|
168
|
-
}}>
|
|
169
|
-
<IconFileSymlink stroke={1.5} />
|
|
170
|
-
</ActionIcon>
|
|
171
|
-
</Tooltip>
|
|
172
|
-
)}
|
|
173
|
-
{showChangeAutoLayout && <ChangeAutoLayoutButton {...portalProps} />}
|
|
174
|
-
<AnimatePresence>
|
|
175
|
-
{showLayoutDriftWarning && (
|
|
176
|
-
<m.div
|
|
177
|
-
initial={{ opacity: 0.05, transform: 'translateX(-40%)' }}
|
|
178
|
-
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
179
|
-
exit={{
|
|
180
|
-
opacity: 0,
|
|
181
|
-
transform: 'translateX(-40%)'
|
|
182
|
-
}}
|
|
183
|
-
key={'layout'}>
|
|
184
|
-
<LayoutDriftNotification {...portalProps} />
|
|
185
|
-
</m.div>
|
|
181
|
+
<TooltipGroup openDelay={500} closeDelay={150}>
|
|
182
|
+
<Stack
|
|
183
|
+
className={clsx(
|
|
184
|
+
'react-flow__panel',
|
|
185
|
+
css.panel,
|
|
186
|
+
'likec4-top-left-panel'
|
|
186
187
|
)}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
188
|
+
align="flex-start"
|
|
189
|
+
onClick={e => e.stopPropagation()}
|
|
190
|
+
gap={'xs'}>
|
|
191
|
+
{showNavigationButtons && <BackwardForwardButtons />}
|
|
192
|
+
<ActionIconGroup className={css.actionIconGroup} orientation="vertical">
|
|
193
|
+
{showGoToSource && (
|
|
194
|
+
<Tooltip label="Open source" {...portalProps}>
|
|
195
|
+
<ActionIcon
|
|
196
|
+
onClick={e => {
|
|
197
|
+
e.stopPropagation()
|
|
198
|
+
store.getState().onOpenSourceView?.()
|
|
199
|
+
}}>
|
|
200
|
+
<IconFileSymlink stroke={1.5} />
|
|
201
|
+
</ActionIcon>
|
|
202
|
+
</Tooltip>
|
|
203
|
+
)}
|
|
204
|
+
{showChangeAutoLayout && <ChangeAutoLayoutButton {...portalProps} />}
|
|
205
|
+
{showLayoutDriftWarning && <LayoutDriftNotification {...portalProps} />}
|
|
206
|
+
{showResetControlPoints && <ResetControlPointsButton {...portalProps} />}
|
|
207
|
+
{showFitDiagram && (
|
|
208
|
+
<Tooltip label={viewportChanged ? 'Center camera' : 'Camera is centered'} {...portalProps}>
|
|
197
209
|
<ActionIcon
|
|
198
|
-
className="action-icon"
|
|
199
210
|
onClick={e => {
|
|
200
211
|
e.stopPropagation()
|
|
201
212
|
store.getState().fitDiagram()
|
|
@@ -203,10 +214,10 @@ export const TopLeftPanel = () => {
|
|
|
203
214
|
<IconFocusCentered />
|
|
204
215
|
</ActionIcon>
|
|
205
216
|
</Tooltip>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
</
|
|
209
|
-
</
|
|
217
|
+
)}
|
|
218
|
+
</ActionIconGroup>
|
|
219
|
+
</Stack>
|
|
220
|
+
</TooltipGroup>
|
|
210
221
|
)
|
|
211
222
|
}
|
|
212
223
|
TopLeftPanel.displayName = 'TopLeftPanel'
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { ActionIcon as MantineActionIcon, Tooltip as MantineTooltip } from '@mantine/core'
|
|
2
2
|
|
|
3
3
|
export const Tooltip = MantineTooltip.withProps({
|
|
4
|
-
color: '
|
|
4
|
+
color: 'dark',
|
|
5
5
|
fz: 'xs',
|
|
6
6
|
position: 'right',
|
|
7
7
|
openDelay: 400,
|
|
8
|
-
closeDelay:
|
|
8
|
+
closeDelay: 150,
|
|
9
9
|
label: '',
|
|
10
10
|
children: null,
|
|
11
|
-
offset: 8
|
|
12
|
-
transitionProps: { transition: 'fade-right', duration: 200 }
|
|
11
|
+
offset: 8
|
|
13
12
|
})
|
|
14
13
|
|
|
15
14
|
export const ActionIcon = MantineActionIcon.withProps({
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
classNames: {
|
|
16
|
+
root: 'action-icon'
|
|
17
|
+
},
|
|
18
|
+
variant: 'default',
|
|
18
19
|
color: 'gray'
|
|
19
20
|
})
|
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
import { fallbackVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
-
import {
|
|
3
|
-
import { vars } from '../../theme
|
|
2
|
+
import { calc } from '@vanilla-extract/css-utils'
|
|
3
|
+
import { mantine, vars } from '../../theme-vars'
|
|
4
|
+
|
|
5
|
+
export const backwardForwardButtons = style({
|
|
6
|
+
gap: calc(mantine.spacing.xs).divide(1.5).toString()
|
|
7
|
+
})
|
|
4
8
|
|
|
5
9
|
export const panel = style({
|
|
6
10
|
top: fallbackVar(vars.navigationPanel.top, '1rem'),
|
|
7
11
|
left: fallbackVar(vars.navigationPanel.left, '1rem'),
|
|
12
|
+
gap: calc(mantine.spacing.xs).divide(1.5).toString(),
|
|
8
13
|
margin: 0
|
|
9
14
|
})
|
|
10
15
|
|
|
11
16
|
globalStyle(`.likec4-top-left-panel .action-icon`, {
|
|
12
17
|
vars: {
|
|
13
|
-
['--ai-size']: '
|
|
18
|
+
['--ai-size']: '2rem'
|
|
14
19
|
}
|
|
15
20
|
})
|
|
16
21
|
globalStyle(`.likec4-top-left-panel .action-icon .tabler-icon`, {
|
|
17
|
-
width: '
|
|
18
|
-
height: '
|
|
22
|
+
width: '65%',
|
|
23
|
+
height: '65%'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const actionIconGroup = style({
|
|
27
|
+
boxShadow: mantine.shadows.md
|
|
19
28
|
})
|
|
20
29
|
|
|
21
30
|
export const autolayoutIcon = style({})
|
|
22
31
|
|
|
23
32
|
globalStyle(`${autolayoutIcon} .tabler-icon`, {
|
|
24
|
-
width: '
|
|
25
|
-
height: '
|
|
33
|
+
width: '65%',
|
|
34
|
+
height: '65%'
|
|
26
35
|
})
|
|
27
36
|
|
|
28
37
|
export const autolayoutIndicator = style({
|
|
@@ -32,7 +41,7 @@ export const autolayoutIndicator = style({
|
|
|
32
41
|
selectors: {
|
|
33
42
|
[`:where([data-mantine-color-scheme="dark"]) &`]: {
|
|
34
43
|
background: mantine.colors.dark[5],
|
|
35
|
-
|
|
44
|
+
borderColor: mantine.colors.dark[4]
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
47
|
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface Vector {
|
|
2
|
+
x: number
|
|
3
|
+
y: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class VectorImpl implements Vector {
|
|
7
|
+
constructor(public x: number, public y: number) {}
|
|
8
|
+
|
|
9
|
+
static create(position: Vector): VectorImpl {
|
|
10
|
+
return new VectorImpl(position.x, position.y)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static add(a: Vector, b: Vector): Vector {
|
|
14
|
+
return { x: a.x + b.x, y: a.y + b.y }
|
|
15
|
+
}
|
|
16
|
+
static sub(a: Vector, b: Vector): Vector {
|
|
17
|
+
return { x: a.x - b.x, y: a.y - b.y }
|
|
18
|
+
}
|
|
19
|
+
static mul(a: Vector, b: number): Vector {
|
|
20
|
+
return { x: a.x * b, y: a.y * b }
|
|
21
|
+
}
|
|
22
|
+
static dot(a: Vector, b: Vector): number {
|
|
23
|
+
return a.x * b.x + a.y * b.y
|
|
24
|
+
}
|
|
25
|
+
static cross(a: Vector, b: Vector): VectorImpl {
|
|
26
|
+
return new VectorImpl(a.y * b.x - a.x * b.y, a.x * b.y - a.y * b.x)
|
|
27
|
+
}
|
|
28
|
+
static setLength(a: Vector, length: number): Vector {
|
|
29
|
+
return vector(a).setLength(length)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
add(b: Vector): VectorImpl {
|
|
33
|
+
return new VectorImpl(this.x + b.x, this.y + b.y)
|
|
34
|
+
}
|
|
35
|
+
sub(b: Vector): VectorImpl {
|
|
36
|
+
return new VectorImpl(this.x - b.x, this.y - b.y)
|
|
37
|
+
}
|
|
38
|
+
mul(b: number): VectorImpl {
|
|
39
|
+
return new VectorImpl(this.x * b, this.y * b)
|
|
40
|
+
}
|
|
41
|
+
dot(b: Vector): number {
|
|
42
|
+
return this.x * b.x + this.y * b.y
|
|
43
|
+
}
|
|
44
|
+
cross(b: Vector): VectorImpl {
|
|
45
|
+
return new VectorImpl(this.y * b.x - this.x * b.y, this.x * b.y - this.y * b.x)
|
|
46
|
+
}
|
|
47
|
+
abs(): number {
|
|
48
|
+
return Math.sqrt(this.x * this.x + this.y * this.y)
|
|
49
|
+
}
|
|
50
|
+
setLength(length: number): VectorImpl {
|
|
51
|
+
return this.mul(length / this.abs())
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function vector(source: Vector): VectorImpl {
|
|
56
|
+
return VectorImpl.create(source)
|
|
57
|
+
}
|
|
@@ -36,16 +36,16 @@ function FitViewOnViewportResize({ diagramApi }: {
|
|
|
36
36
|
fitDiagram(250)
|
|
37
37
|
},
|
|
38
38
|
[dimensions, diagramApi],
|
|
39
|
-
250
|
|
40
|
-
1000
|
|
39
|
+
250
|
|
41
40
|
)
|
|
42
41
|
|
|
43
42
|
return null
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function selector({ view, viewportChanged, fitViewPadding }: DiagramState) {
|
|
45
|
+
function selector({ view, activeWalkthrough, viewportChanged, fitViewPadding }: DiagramState) {
|
|
47
46
|
return {
|
|
48
47
|
layoutId: view.id + '_' + view.autoLayout + '_' + fitViewPadding,
|
|
48
|
+
isActiveWalkthrough: !!activeWalkthrough,
|
|
49
49
|
viewportNotMoved: !viewportChanged
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -56,6 +56,7 @@ function selector({ view, viewportChanged, fitViewPadding }: DiagramState) {
|
|
|
56
56
|
export function FitViewOnDiagramChange() {
|
|
57
57
|
const {
|
|
58
58
|
layoutId,
|
|
59
|
+
isActiveWalkthrough,
|
|
59
60
|
viewportNotMoved
|
|
60
61
|
} = useDiagramState(selector, shallowEqual)
|
|
61
62
|
const diagramApi = useDiagramStoreApi()
|
|
@@ -75,7 +76,7 @@ export function FitViewOnDiagramChange() {
|
|
|
75
76
|
50
|
|
76
77
|
)
|
|
77
78
|
|
|
78
|
-
if (viewportNotMoved && !requiresFit) {
|
|
79
|
+
if (viewportNotMoved && !isActiveWalkthrough && !requiresFit) {
|
|
79
80
|
return <FitViewOnViewportResize diagramApi={diagramApi} />
|
|
80
81
|
}
|
|
81
82
|
return null
|
|
@@ -4,6 +4,7 @@ import type { EdgeChange, NodeChange } from '@xyflow/react'
|
|
|
4
4
|
import { getBoundsOfRects, getViewportForBounds } from '@xyflow/system'
|
|
5
5
|
import { useUpdateEffect } from '../hooks'
|
|
6
6
|
import { useDiagramState, useDiagramStoreApi } from '../hooks/useDiagramState'
|
|
7
|
+
import { MinZoom } from './const'
|
|
7
8
|
import type { XYFlowEdge, XYFlowNode } from './types'
|
|
8
9
|
import { nodeToRect } from './utils'
|
|
9
10
|
|
|
@@ -20,7 +21,6 @@ export function SelectEdgesOnNodeFocus() {
|
|
|
20
21
|
if (!container) {
|
|
21
22
|
return
|
|
22
23
|
}
|
|
23
|
-
const { width, height } = container.getBoundingClientRect()
|
|
24
24
|
|
|
25
25
|
const edgeChanges = [] as EdgeChange<XYFlowEdge>[]
|
|
26
26
|
const nodeChanges = [] as NodeChange<XYFlowNode>[]
|
|
@@ -68,6 +68,8 @@ export function SelectEdgesOnNodeFocus() {
|
|
|
68
68
|
}
|
|
69
69
|
triggerNodeChanges(nodeChanges)
|
|
70
70
|
|
|
71
|
+
const { width, height } = container.getBoundingClientRect()
|
|
72
|
+
|
|
71
73
|
const maxZoom = Math.max(1, transform[2])
|
|
72
74
|
const viewport = getViewportForBounds(
|
|
73
75
|
{
|
|
@@ -78,7 +80,7 @@ export function SelectEdgesOnNodeFocus() {
|
|
|
78
80
|
},
|
|
79
81
|
width,
|
|
80
82
|
height,
|
|
81
|
-
|
|
83
|
+
MinZoom,
|
|
82
84
|
maxZoom,
|
|
83
85
|
0
|
|
84
86
|
)
|
|
@@ -91,10 +93,10 @@ export function SelectEdgesOnNodeFocus() {
|
|
|
91
93
|
focusedNodeId !== null
|
|
92
94
|
? [
|
|
93
95
|
['Escape', (e) => {
|
|
94
|
-
e.
|
|
95
|
-
const {
|
|
96
|
+
e.stopPropagation()
|
|
97
|
+
const { resetFocusAndLastClicked, fitDiagram } = diagramStore.getState()
|
|
98
|
+
resetFocusAndLastClicked()
|
|
96
99
|
fitDiagram()
|
|
97
|
-
xystore.getState().resetSelectedElements()
|
|
98
100
|
}, { preventDefault: true }]
|
|
99
101
|
]
|
|
100
102
|
: []
|
package/src/xyflow/XYFlow.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactFlow, useOnViewportChange } from '@xyflow/react'
|
|
2
2
|
import { shallowEqual } from 'fast-equals'
|
|
3
|
-
import { type CSSProperties, type PropsWithChildren, useMemo, useState } from 'react'
|
|
3
|
+
import { type CSSProperties, type PropsWithChildren, useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { useDiagramState } from '../hooks/useDiagramState'
|
|
5
5
|
import { useXYStoreApi } from '../hooks/useXYFlow'
|
|
6
6
|
import type { DiagramState } from '../state/diagramStore'
|
|
@@ -88,14 +88,7 @@ export function XYFlow({
|
|
|
88
88
|
translateY
|
|
89
89
|
} = useDiagramState(selector, shallowEqual)
|
|
90
90
|
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(zoomable)
|
|
91
|
-
|
|
92
|
-
// useLogger('XYFlow',[
|
|
93
|
-
// nodes,
|
|
94
|
-
// edges,
|
|
95
|
-
// ])
|
|
96
|
-
|
|
97
91
|
const layoutConstraints = useLayoutConstraints()
|
|
98
|
-
|
|
99
92
|
const handlers = useXYFlowEvents()
|
|
100
93
|
|
|
101
94
|
/**
|
|
@@ -111,10 +104,15 @@ export function XYFlow({
|
|
|
111
104
|
if (x !== roundedX || y !== roundedY) {
|
|
112
105
|
xyflowApi.setState({ transform: [roundedX, roundedY, zoom] })
|
|
113
106
|
}
|
|
114
|
-
setZoomOnDoubleClick(zoomable && zoom <
|
|
107
|
+
setZoomOnDoubleClick(zoomable && zoom < 0.75)
|
|
115
108
|
}
|
|
116
109
|
})
|
|
117
110
|
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const zoom = xyflowApi.getState().transform[2]
|
|
113
|
+
setZoomOnDoubleClick(zoomable && zoom < 0.75)
|
|
114
|
+
}, [])
|
|
115
|
+
|
|
118
116
|
return (
|
|
119
117
|
<ReactFlow<XYFlowNode, XYFlowEdge>
|
|
120
118
|
className={className}
|
|
@@ -129,6 +127,7 @@ export function XYFlow({
|
|
|
129
127
|
{...(!zoomable && {
|
|
130
128
|
zoomActivationKeyCode: null
|
|
131
129
|
})}
|
|
130
|
+
zoomOnDoubleClick={zoomOnDoubleClick}
|
|
132
131
|
maxZoom={zoomable ? MaxZoom : 1}
|
|
133
132
|
minZoom={zoomable ? MinZoom : 1}
|
|
134
133
|
fitView={fitView}
|
|
@@ -163,8 +162,7 @@ export function XYFlow({
|
|
|
163
162
|
onNodeDrag: layoutConstraints.onNodeDrag,
|
|
164
163
|
onNodeDragStop: layoutConstraints.onNodeDragStop
|
|
165
164
|
}}
|
|
166
|
-
nodeDragThreshold={
|
|
167
|
-
zoomOnDoubleClick={zoomOnDoubleClick}
|
|
165
|
+
nodeDragThreshold={4}
|
|
168
166
|
elevateNodesOnSelect={false} // or edges are not visible after select\
|
|
169
167
|
selectNodesOnDrag={false}
|
|
170
168
|
onNodesChange={onNodesChange}
|