@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.
Files changed (113) hide show
  1. package/package.json +20 -43
  2. package/src/LikeC4CustomColors.tsx +5 -2
  3. package/src/LikeC4Diagram.css.ts +1 -1
  4. package/src/LikeC4Diagram.props.ts +40 -94
  5. package/src/LikeC4Diagram.tsx +14 -4
  6. package/src/LikeC4ModelProvider.tsx +21 -0
  7. package/src/StaticLikeC4Diagram.tsx +1 -0
  8. package/src/globals.css.ts +2 -3
  9. package/src/hooks/index.ts +1 -0
  10. package/src/hooks/useMantinePortalProps.ts +8 -0
  11. package/src/hooks/useXYFlow.ts +1 -2
  12. package/src/index.ts +9 -0
  13. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  14. package/src/likec4model/index.ts +1 -0
  15. package/src/likec4model/useLikeC4Model.ts +58 -0
  16. package/src/state/diagram-to-xyflow.ts +1 -2
  17. package/src/state/diagramStore.ts +185 -66
  18. package/src/{theme.css.ts → theme-vars.ts} +11 -4
  19. package/src/ui/DiagramTitlePanel.css.ts +2 -3
  20. package/src/ui/DiagramTitlePanel.tsx +25 -3
  21. package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
  22. package/src/ui/DynamicViewWalkthrough.tsx +97 -52
  23. package/src/ui/OptionsPanel.css.ts +1 -2
  24. package/src/ui/OptionsPanel.tsx +5 -2
  25. package/src/ui/notation/NotationPanel.css.ts +2 -3
  26. package/src/ui/notation/NotationPanel.tsx +10 -11
  27. package/src/ui/options/NodeOptions.tsx +4 -1
  28. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
  29. package/src/ui/top-left/TopLeftPanel.tsx +128 -117
  30. package/src/ui/top-left/_shared.ts +7 -6
  31. package/src/ui/top-left/styles.css.ts +48 -8
  32. package/src/utils/vector.ts +57 -0
  33. package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
  34. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
  35. package/src/xyflow/XYFlow.tsx +13 -17
  36. package/src/xyflow/XYFlowEvents.tsx +58 -58
  37. package/src/xyflow/XYFlowInner.tsx +4 -8
  38. package/src/xyflow/edges/EdgeLabel.tsx +185 -0
  39. package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
  40. package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
  41. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
  42. package/src/xyflow/edges/edges.css.ts +75 -7
  43. package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
  44. package/src/xyflow/nodes/compound/CompoundNode.tsx +25 -8
  45. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  46. package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
  47. package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
  48. package/src/xyflow/nodes/element/element.css.ts +1 -2
  49. package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
  50. package/src/xyflow/types.ts +1 -24
  51. package/src/xyflow/useLayoutConstraints.ts +10 -6
  52. package/dist/LikeC4CustomColors.d.ts +0 -3
  53. package/dist/LikeC4CustomColors.props.d.ts +0 -5
  54. package/dist/LikeC4Diagram.css.d.ts +0 -4
  55. package/dist/LikeC4Diagram.d.ts +0 -7
  56. package/dist/LikeC4Diagram.props.d.ts +0 -154
  57. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  58. package/dist/globals.css.d.ts +0 -1
  59. package/dist/hooks/index.d.ts +0 -4
  60. package/dist/hooks/useDiagramState.d.ts +0 -6
  61. package/dist/hooks/useSetState.d.ts +0 -3
  62. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  63. package/dist/hooks/useXYFlow.d.ts +0 -267
  64. package/dist/index.d.ts +0 -9
  65. package/dist/index.mjs +0 -3684
  66. package/dist/mantine/EnsureMantine.d.ts +0 -8
  67. package/dist/mantine.css.d.ts +0 -3
  68. package/dist/state/DiagramContext.d.ts +0 -1192
  69. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  70. package/dist/state/diagramStore.d.ts +0 -1268
  71. package/dist/style.css +0 -2139
  72. package/dist/theme.css.d.ts +0 -63
  73. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  74. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  75. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  76. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  77. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  78. package/dist/ui/OptionsPanel.d.ts +0 -1
  79. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  80. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  81. package/dist/ui/notation/index.d.ts +0 -1
  82. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  83. package/dist/ui/options/NodeOptions.d.ts +0 -19
  84. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  85. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  86. package/dist/ui/top-left/_shared.d.ts +0 -5
  87. package/dist/ui/top-left/styles.css.d.ts +0 -3
  88. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  89. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  90. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  91. package/dist/xyflow/XYFlow.d.ts +0 -12
  92. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  93. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  94. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  95. package/dist/xyflow/const.d.ts +0 -7
  96. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  97. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  98. package/dist/xyflow/edges/utils.d.ts +0 -17
  99. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  100. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  101. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  102. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  103. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  104. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  105. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  106. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  107. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  108. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  109. package/dist/xyflow/types.d.ts +0 -50
  110. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  111. package/dist/xyflow/utils.d.ts +0 -18
  112. package/src/mantine.css.ts +0 -7
  113. 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
- <Group
42
- className={'likec4-navigation-panel'}
43
- gap={'xs'}>
44
- {showBurgerMenu && (
45
- <ActionIcon
46
- onClick={e => {
47
- e.stopPropagation()
48
- store.getState().onBurgerMenuClick?.()
49
- }}>
50
- <IconMenu2 />
51
- </ActionIcon>
52
- )}
53
- <AnimatePresence>
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
- </AnimatePresence>
93
- </Group>
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
- className="action-icon"
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
- target,
137
- showGoToSource
161
+ showGoToSource,
162
+ viewportChanged,
163
+ showResetControlPoints
138
164
  } = useDiagramState(s => {
139
- const isNotActive = s.activeDynamicViewStep === null && s.focusedNodeId === null
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 && s.viewportChanged,
143
- showLayoutDriftWarning: s.readonly !== true && s.view.hasLayoutDrift === true && isNotActive,
144
- showChangeAutoLayout: s.readonly !== true && !!s.onChange && isNotActive,
145
- showGoToSource: !!s.onOpenSourceView,
146
- target: s.getContainer()
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 = target ? { portalProps: { target } } : undefined
178
+ const portalProps = useMantinePortalProps()
150
179
 
151
180
  return (
152
- <Stack
153
- className={clsx(
154
- 'react-flow__panel',
155
- css.panel,
156
- 'likec4-top-left-panel'
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
- {showFitDiagram && (
188
- <m.div
189
- initial={{ opacity: 0.05, transform: 'translateX(-20%)' }}
190
- animate={{ opacity: 1, transform: 'translateX(0)' }}
191
- exit={{
192
- opacity: 0,
193
- transform: 'translateX(-30%)'
194
- }}
195
- key={'fit-view'}>
196
- <Tooltip label="Center diagram" {...portalProps}>
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
- </m.div>
207
- )}
208
- </AnimatePresence>
209
- </Stack>
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: 'gray',
4
+ color: 'dark',
5
5
  fz: 'xs',
6
6
  position: 'right',
7
7
  openDelay: 400,
8
- closeDelay: 100,
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
- className: 'action-icon',
17
- variant: 'light',
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 { mantine } from '../../mantine.css'
3
- import { vars } from '../../theme.css'
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']: 'var(--ai-size-lg)'
19
+ ['--ai-size']: '2rem'
14
20
  }
15
21
  })
16
22
  globalStyle(`.likec4-top-left-panel .action-icon .tabler-icon`, {
17
- width: '70%',
18
- height: '70%'
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: '75%',
25
- height: '75%'
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
- border: `1px solid ${mantine.colors.dark[4]}`
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
- 0.1,
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.stopImmediatePropagation()
95
- const { xystore, fitDiagram } = diagramStore.getState()
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
  : []
@@ -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, 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 < 1.1)
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 || hasOnNodeClick || hasOnNavigateTo}
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={3}
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}