@likec4/diagram 1.9.0 → 1.12.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.
- package/package.json +20 -43
- package/src/LikeC4CustomColors.tsx +5 -2
- package/src/LikeC4Diagram.css.ts +1 -1
- package/src/LikeC4Diagram.props.ts +40 -94
- package/src/LikeC4Diagram.tsx +14 -4
- package/src/LikeC4ModelProvider.tsx +21 -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 +9 -0
- package/src/likec4model/LikeC4ModelContext.ts +4 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4Model.ts +58 -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 +25 -3
- 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 +5 -2
- package/src/ui/notation/NotationPanel.css.ts +2 -3
- package/src/ui/notation/NotationPanel.tsx +10 -11
- package/src/ui/options/NodeOptions.tsx +4 -1
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
- 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 +48 -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 +13 -17
- package/src/xyflow/XYFlowEvents.tsx +58 -58
- package/src/xyflow/XYFlowInner.tsx +4 -8
- 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 +25 -8
- package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
- package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
- package/src/xyflow/nodes/element/element.css.ts +1 -2
- package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
- 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,47 @@
|
|
|
1
|
+
import { rem } from '@mantine/core'
|
|
1
2
|
import { fallbackVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
-
import {
|
|
3
|
-
import { vars } from '../../theme
|
|
3
|
+
import { calc } from '@vanilla-extract/css-utils'
|
|
4
|
+
import { mantine, vars } from '../../theme-vars'
|
|
5
|
+
|
|
6
|
+
export const backwardForwardButtons = style({
|
|
7
|
+
gap: calc(mantine.spacing.xs).divide(1.5).toString()
|
|
8
|
+
})
|
|
4
9
|
|
|
5
10
|
export const panel = style({
|
|
6
11
|
top: fallbackVar(vars.navigationPanel.top, '1rem'),
|
|
7
12
|
left: fallbackVar(vars.navigationPanel.left, '1rem'),
|
|
13
|
+
gap: calc(mantine.spacing.xs).divide(1.5).toString(),
|
|
8
14
|
margin: 0
|
|
9
15
|
})
|
|
10
16
|
|
|
11
17
|
globalStyle(`.likec4-top-left-panel .action-icon`, {
|
|
12
18
|
vars: {
|
|
13
|
-
['--ai-size']: '
|
|
19
|
+
['--ai-size']: '2rem'
|
|
14
20
|
}
|
|
15
21
|
})
|
|
16
22
|
globalStyle(`.likec4-top-left-panel .action-icon .tabler-icon`, {
|
|
17
|
-
width: '
|
|
18
|
-
height: '
|
|
23
|
+
width: '65%',
|
|
24
|
+
height: '65%'
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const actionIconGroup = style({
|
|
28
|
+
boxShadow: mantine.shadows.md
|
|
19
29
|
})
|
|
20
30
|
|
|
21
31
|
export const autolayoutIcon = style({})
|
|
22
32
|
|
|
23
33
|
globalStyle(`${autolayoutIcon} .tabler-icon`, {
|
|
24
|
-
width: '
|
|
25
|
-
height: '
|
|
34
|
+
width: '65%',
|
|
35
|
+
height: '65%'
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export const autolayoutButton = style({
|
|
39
|
+
flex: '1 1 40%',
|
|
40
|
+
textAlign: 'center',
|
|
41
|
+
fontWeight: 500,
|
|
42
|
+
padding: '4px 6px',
|
|
43
|
+
fontSize: rem(11),
|
|
44
|
+
zIndex: 1
|
|
26
45
|
})
|
|
27
46
|
|
|
28
47
|
export const autolayoutIndicator = style({
|
|
@@ -32,7 +51,28 @@ export const autolayoutIndicator = style({
|
|
|
32
51
|
selectors: {
|
|
33
52
|
[`:where([data-mantine-color-scheme="dark"]) &`]: {
|
|
34
53
|
background: mantine.colors.dark[5],
|
|
35
|
-
|
|
54
|
+
borderColor: mantine.colors.dark[4]
|
|
36
55
|
}
|
|
37
56
|
}
|
|
38
57
|
})
|
|
58
|
+
|
|
59
|
+
export const spacingSliderBody = style({
|
|
60
|
+
position: 'relative',
|
|
61
|
+
borderRadius: mantine.radius.sm,
|
|
62
|
+
background: mantine.colors.gray[3],
|
|
63
|
+
boxShadow: 'inset 1px 1px 3px 0px #00000024',
|
|
64
|
+
selectors: {
|
|
65
|
+
[`:where([data-mantine-color-scheme="dark"]) &`]: {
|
|
66
|
+
background: mantine.colors.dark[7]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export const spacingSliderThumb = style({
|
|
72
|
+
position: 'absolute',
|
|
73
|
+
width: 8,
|
|
74
|
+
height: 8,
|
|
75
|
+
border: `2px solid ${mantine.colors.gray[5]}`,
|
|
76
|
+
borderRadius: 3,
|
|
77
|
+
transform: 'translate(-50%, -50%)'
|
|
78
|
+
})
|
|
@@ -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
|
|
3
|
+
import { type CSSProperties, type PropsWithChildren, useMemo } from 'react'
|
|
4
4
|
import { useDiagramState } from '../hooks/useDiagramState'
|
|
5
5
|
import { useXYStoreApi } from '../hooks/useXYFlow'
|
|
6
6
|
import type { DiagramState } from '../state/diagramStore'
|
|
@@ -46,8 +46,8 @@ const selector = (s: DiagramState) => ({
|
|
|
46
46
|
nodesDraggable: s.nodesDraggable,
|
|
47
47
|
fitView: s.fitViewEnabled,
|
|
48
48
|
fitViewPadding: s.fitViewPadding,
|
|
49
|
-
hasOnNavigateTo: !!s.onNavigateTo,
|
|
50
|
-
hasOnNodeClick: !!s.onNodeClick,
|
|
49
|
+
// hasOnNavigateTo: !!s.onNavigateTo,
|
|
50
|
+
// hasOnNodeClick: !!s.onNodeClick,
|
|
51
51
|
hasOnNodeContextMenu: !!s.onNodeContextMenu,
|
|
52
52
|
hasOnCanvasContextMenu: !!s.onCanvasContextMenu,
|
|
53
53
|
hasOnEdgeContextMenu: !!s.onEdgeContextMenu,
|
|
@@ -79,23 +79,14 @@ export function XYFlow({
|
|
|
79
79
|
fitViewPadding,
|
|
80
80
|
pannable,
|
|
81
81
|
zoomable,
|
|
82
|
-
hasOnNodeClick,
|
|
83
|
-
hasOnNavigateTo,
|
|
84
82
|
hasOnNodeContextMenu,
|
|
85
83
|
hasOnCanvasContextMenu,
|
|
86
84
|
hasOnEdgeContextMenu,
|
|
87
85
|
translateX,
|
|
88
86
|
translateY
|
|
89
87
|
} = useDiagramState(selector, shallowEqual)
|
|
90
|
-
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(zoomable)
|
|
91
|
-
|
|
92
|
-
// useLogger('XYFlow',[
|
|
93
|
-
// nodes,
|
|
94
|
-
// edges,
|
|
95
|
-
// ])
|
|
96
|
-
|
|
88
|
+
// const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(zoomable)
|
|
97
89
|
const layoutConstraints = useLayoutConstraints()
|
|
98
|
-
|
|
99
90
|
const handlers = useXYFlowEvents()
|
|
100
91
|
|
|
101
92
|
/**
|
|
@@ -111,10 +102,15 @@ export function XYFlow({
|
|
|
111
102
|
if (x !== roundedX || y !== roundedY) {
|
|
112
103
|
xyflowApi.setState({ transform: [roundedX, roundedY, zoom] })
|
|
113
104
|
}
|
|
114
|
-
setZoomOnDoubleClick(zoomable && zoom <
|
|
105
|
+
// setZoomOnDoubleClick(zoomable && zoom < 0.75)
|
|
115
106
|
}
|
|
116
107
|
})
|
|
117
108
|
|
|
109
|
+
// useEffect(() => {
|
|
110
|
+
// const zoom = xyflowApi.getState().transform[2]
|
|
111
|
+
// setZoomOnDoubleClick(zoomable && zoom < 0.75)
|
|
112
|
+
// }, [])
|
|
113
|
+
|
|
118
114
|
return (
|
|
119
115
|
<ReactFlow<XYFlowNode, XYFlowEdge>
|
|
120
116
|
className={className}
|
|
@@ -129,6 +125,7 @@ export function XYFlow({
|
|
|
129
125
|
{...(!zoomable && {
|
|
130
126
|
zoomActivationKeyCode: null
|
|
131
127
|
})}
|
|
128
|
+
zoomOnDoubleClick={false}
|
|
132
129
|
maxZoom={zoomable ? MaxZoom : 1}
|
|
133
130
|
minZoom={zoomable ? MinZoom : 1}
|
|
134
131
|
fitView={fitView}
|
|
@@ -155,7 +152,7 @@ export function XYFlow({
|
|
|
155
152
|
selectionKeyCode: null
|
|
156
153
|
})}
|
|
157
154
|
elementsSelectable={nodesSelectable}
|
|
158
|
-
nodesFocusable={nodesDraggable || nodesSelectable
|
|
155
|
+
nodesFocusable={nodesDraggable || nodesSelectable}
|
|
159
156
|
edgesFocusable={false}
|
|
160
157
|
nodesDraggable={nodesDraggable}
|
|
161
158
|
{...nodesDraggable && {
|
|
@@ -163,8 +160,7 @@ export function XYFlow({
|
|
|
163
160
|
onNodeDrag: layoutConstraints.onNodeDrag,
|
|
164
161
|
onNodeDragStop: layoutConstraints.onNodeDragStop
|
|
165
162
|
}}
|
|
166
|
-
nodeDragThreshold={
|
|
167
|
-
zoomOnDoubleClick={zoomOnDoubleClick}
|
|
163
|
+
nodeDragThreshold={4}
|
|
168
164
|
elevateNodesOnSelect={false} // or edges are not visible after select\
|
|
169
165
|
selectNodesOnDrag={false}
|
|
170
166
|
onNodesChange={onNodesChange}
|