@likec4/diagram 1.2.2 → 1.4.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/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +27 -4
- package/dist/index.mjs +1543 -1346
- package/dist/state/DiagramContext.d.ts +2353 -264
- package/dist/state/diagramStore.d.ts +2360 -268
- package/dist/state/index.d.ts +0 -1
- package/dist/state/useDiagramStore.d.ts +3 -2534
- package/dist/state/useNavigationHistory.d.ts +5 -0
- package/dist/style.css +194 -131
- package/dist/theme.css.d.ts +5 -1
- package/dist/ui/BackwardForwardPanel.css.d.ts +1 -0
- package/dist/ui/BackwardForwardPanel.d.ts +1 -0
- package/dist/ui/DiagramTitlePanel.css.d.ts +1 -1
- package/dist/xyflow/XYFlow.d.ts +2 -1
- package/dist/xyflow/XYFlowEvents.d.ts +4 -4
- package/dist/xyflow/XYFlowInner.d.ts +2 -1
- package/dist/xyflow/const.d.ts +1 -1
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +4 -5
- package/dist/xyflow/edges/utils.d.ts +4 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +9 -9
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +3 -1
- package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
- package/dist/xyflow/nodes/element/element.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/index.d.ts +1 -1
- package/dist/xyflow/types.d.ts +3 -3
- package/dist/xyflow/utils.d.ts +3 -0
- package/package.json +18 -17
- package/src/LikeC4Diagram.css.ts +15 -5
- package/src/LikeC4Diagram.props.ts +33 -5
- package/src/LikeC4Diagram.tsx +103 -32
- package/src/globals.css.ts +1 -2
- package/src/mantine/theme.ts +1 -0
- package/src/state/DiagramContext.tsx +3 -4
- package/src/state/diagramStore.ts +177 -103
- package/src/state/index.ts +0 -1
- package/src/state/useDiagramStore.ts +6 -13
- package/src/state/useNavigationHistory.ts +39 -0
- package/src/theme.css.ts +5 -1
- package/src/ui/BackwardForwardPanel.css.ts +22 -0
- package/src/ui/BackwardForwardPanel.tsx +59 -0
- package/src/ui/DiagramTitlePanel.css.ts +11 -5
- package/src/ui/DiagramTitlePanel.tsx +108 -96
- package/src/ui/DynamicViewWalkthrough.css.ts +3 -2
- package/src/ui/DynamicViewWalkthrough.tsx +36 -17
- package/src/ui/OptionsPanel.tsx +28 -19
- package/src/ui/options/NodeOptions.tsx +16 -19
- package/src/xyflow/FitviewOnDiagramChange.tsx +15 -23
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +3 -6
- package/src/xyflow/SyncWithDiagram.tsx +21 -2
- package/src/xyflow/XYFlow.tsx +20 -23
- package/src/xyflow/XYFlowEvents.tsx +26 -8
- package/src/xyflow/XYFlowInner.tsx +10 -6
- package/src/xyflow/const.ts +1 -1
- package/src/xyflow/diagram-to-xyflow.ts +4 -41
- package/src/xyflow/edges/RelationshipEdge.tsx +160 -111
- package/src/xyflow/edges/edges.css.ts +41 -26
- package/src/xyflow/edges/utils.ts +5 -2
- package/src/xyflow/hooks/useLayoutConstraints.ts +19 -4
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +45 -37
- package/src/xyflow/nodes/compound/CompoundNode.tsx +36 -26
- package/src/xyflow/nodes/element/ElementNode.tsx +27 -14
- package/src/xyflow/nodes/element/element.css.ts +31 -18
- package/src/xyflow/nodes/element/index.ts +1 -1
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +7 -3
- package/src/xyflow/types.ts +5 -2
- package/src/xyflow/utils.ts +48 -0
- package/dist/ui/index.d.ts +0 -2
- package/src/ui/index.ts +0 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rem } from '@mantine/core'
|
|
2
|
-
import { createVar, fallbackVar,
|
|
2
|
+
import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
|
|
3
|
+
import { calc } from '@vanilla-extract/css-utils'
|
|
3
4
|
import { mantine } from '../../../mantine.css'
|
|
4
5
|
import { vars } from '../../../theme.css'
|
|
5
6
|
|
|
@@ -11,12 +12,18 @@ export const container = style({
|
|
|
11
12
|
margin: 0
|
|
12
13
|
})
|
|
13
14
|
|
|
14
|
-
export const dimmed = style({
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
export const dimmed = style({})
|
|
16
|
+
|
|
17
|
+
globalStyle(`.react-flow__node-compound:has(${dimmed})`, {
|
|
18
|
+
opacity: 0.25,
|
|
19
|
+
transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
|
|
20
|
+
transitionDelay: '200ms',
|
|
21
|
+
filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
|
|
22
|
+
willChange: 'opacity, filter'
|
|
17
23
|
})
|
|
18
24
|
|
|
19
|
-
export const
|
|
25
|
+
export const varOpacity = createVar('opacity')
|
|
26
|
+
export const varBorderTransparency = createVar('border-transparency')
|
|
20
27
|
|
|
21
28
|
const outlineColor = fallbackVar(
|
|
22
29
|
mantine.colors.primaryColors.outline,
|
|
@@ -31,7 +38,6 @@ export const compoundBody = style({
|
|
|
31
38
|
borderRadius: 6,
|
|
32
39
|
boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
|
|
33
40
|
padding: 8,
|
|
34
|
-
background: vars.element.fill,
|
|
35
41
|
transition: 'all 200ms ease-out',
|
|
36
42
|
selectors: {
|
|
37
43
|
[`:where(.react-flow__node.selected) &`]: {
|
|
@@ -42,39 +48,47 @@ export const compoundBody = style({
|
|
|
42
48
|
outline: `3px solid ${outlineColor}`,
|
|
43
49
|
outlineOffset: rem(1.5)
|
|
44
50
|
}
|
|
51
|
+
},
|
|
52
|
+
':before': {
|
|
53
|
+
content: '" "',
|
|
54
|
+
position: 'absolute',
|
|
55
|
+
top: 0,
|
|
56
|
+
left: 0,
|
|
57
|
+
width: '100%',
|
|
58
|
+
height: '100%',
|
|
59
|
+
borderRadius: 6,
|
|
60
|
+
pointerEvents: 'none',
|
|
61
|
+
transition: 'background 175ms ease-out, opacity 175ms ease-out',
|
|
62
|
+
background: vars.element.fill,
|
|
63
|
+
backgroundClip: 'padding-box'
|
|
45
64
|
}
|
|
46
65
|
})
|
|
47
66
|
|
|
48
|
-
const
|
|
49
|
-
const bgTransparencyDelta = createVar('bgTransparencyDelta')
|
|
67
|
+
const opacityDeltaOnHover = createVar('opacityDeltaOnHover')
|
|
50
68
|
export const transparent = style({
|
|
51
69
|
borderStyle: 'dashed',
|
|
52
70
|
borderWidth: 3,
|
|
53
71
|
padding: 6,
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
boxShadow: 'none',
|
|
73
|
+
borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent ${fallbackVar(varBorderTransparency, '10%')})`,
|
|
56
74
|
backgroundClip: 'padding-box',
|
|
57
|
-
// transitionTimingFunction: 'ease-in',
|
|
58
|
-
// transition: 'all 150ms ease-in-out',
|
|
59
75
|
vars: {
|
|
60
|
-
[
|
|
61
|
-
[bgTransparencyDelta]: '0%',
|
|
76
|
+
[opacityDeltaOnHover]: '0',
|
|
62
77
|
'--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
|
|
63
78
|
},
|
|
79
|
+
':before': {
|
|
80
|
+
transitionDelay: '200ms',
|
|
81
|
+
opacity: calc.add(fallbackVar(varOpacity, '1'), opacityDeltaOnHover)
|
|
82
|
+
},
|
|
64
83
|
selectors: {
|
|
65
|
-
[`:where(.react-flow__node.selected) &`]: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
[bgTransparencyDelta]: '5%'
|
|
71
|
-
}
|
|
72
|
-
},
|
|
84
|
+
// [`:where(.react-flow__node.selected) &`]: {
|
|
85
|
+
// vars: {
|
|
86
|
+
// [opacityDeltaOnHover]: '.05'
|
|
87
|
+
// }
|
|
88
|
+
// },
|
|
73
89
|
[`:where([data-hovered]) &`]: {
|
|
74
|
-
transitionDelay: '175ms',
|
|
75
|
-
// transitionTimingFunction: 'ease-out',
|
|
76
90
|
vars: {
|
|
77
|
-
[
|
|
91
|
+
[opacityDeltaOnHover]: '.07'
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
}
|
|
@@ -93,15 +107,10 @@ export const title = style({
|
|
|
93
107
|
lineHeight: 1,
|
|
94
108
|
opacity: 0.75,
|
|
95
109
|
color: `var(--_compound-title-color,${vars.compound.titleColor})`,
|
|
96
|
-
paddingLeft: 12
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
// [`:where([data-mantine-color-scheme='light']) &`]: {
|
|
102
|
-
// opacity: 1
|
|
103
|
-
// }
|
|
104
|
-
}
|
|
110
|
+
paddingLeft: 12
|
|
111
|
+
})
|
|
112
|
+
export const titleWithNavigation = style({
|
|
113
|
+
paddingLeft: 26
|
|
105
114
|
})
|
|
106
115
|
|
|
107
116
|
globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
|
|
@@ -114,8 +123,7 @@ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transpa
|
|
|
114
123
|
opacity: 1
|
|
115
124
|
})
|
|
116
125
|
|
|
117
|
-
const indicatorKeyframes =
|
|
118
|
-
globalKeyframes(indicatorKeyframes, {
|
|
126
|
+
const indicatorKeyframes = keyframes({
|
|
119
127
|
'from': {
|
|
120
128
|
opacity: 0.6
|
|
121
129
|
},
|
|
@@ -204,7 +212,7 @@ export const navigateBtn = style({
|
|
|
204
212
|
},
|
|
205
213
|
':hover': {
|
|
206
214
|
boxShadow: mantine.shadows.lg,
|
|
207
|
-
transform: 'scale(1.
|
|
215
|
+
transform: 'scale(1.42)',
|
|
208
216
|
opacity: 1,
|
|
209
217
|
transitionDelay: '0'
|
|
210
218
|
},
|
|
@@ -2,13 +2,14 @@ import { Box, Text } from '@mantine/core'
|
|
|
2
2
|
import { assignInlineVars } from '@vanilla-extract/dynamic'
|
|
3
3
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
|
+
import { DEV } from 'esm-env'
|
|
5
6
|
import { deepEqual as eq } from 'fast-equals'
|
|
6
7
|
import { memo } from 'react'
|
|
7
8
|
import { clamp } from 'remeda'
|
|
9
|
+
import useTilg from 'tilg'
|
|
8
10
|
import { useDiagramState } from '../../../state'
|
|
9
11
|
import type { CompoundXYFlowNode } from '../../types'
|
|
10
12
|
import { NavigateToBtn } from '../shared/NavigateToBtn'
|
|
11
|
-
import { compoundBody, container, indicator, navigateBtn, title } from './CompoundNode.css'
|
|
12
13
|
import * as css from './CompoundNode.css'
|
|
13
14
|
|
|
14
15
|
type CompoundNodeProps = Pick<
|
|
@@ -16,13 +17,12 @@ type CompoundNodeProps = Pick<
|
|
|
16
17
|
'id' | 'data' | 'width' | 'height' | 'selected'
|
|
17
18
|
>
|
|
18
19
|
|
|
19
|
-
// @ts-ignore
|
|
20
20
|
const isEqualProps = (prev: CompoundNodeProps, next: CompoundNodeProps) => (
|
|
21
21
|
prev.id === next.id
|
|
22
|
-
&& prev.selected === next.selected
|
|
23
|
-
&& prev.width
|
|
24
|
-
&& prev.height
|
|
25
|
-
&& eq(prev.data, next.data)
|
|
22
|
+
// && prev.selected === next.selected
|
|
23
|
+
// && eq(prev.width, next.width)
|
|
24
|
+
// && eq(prev.height, next.height)
|
|
25
|
+
&& eq(prev.data.element, next.data.element)
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function CompoundNode({
|
|
@@ -31,11 +31,18 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
31
31
|
element
|
|
32
32
|
}
|
|
33
33
|
}) {
|
|
34
|
-
|
|
34
|
+
DEV && useTilg()
|
|
35
35
|
const { color, style, depth = 1, ...compound } = element
|
|
36
36
|
// const w = toDomPrecision(width ?? compound.width)
|
|
37
37
|
// const h = toDomPrecision(height ?? compound.height)
|
|
38
|
-
const opacity = style.opacity ?? 100
|
|
38
|
+
const opacity = clamp((style.opacity ?? 100) / 100, {
|
|
39
|
+
min: 0,
|
|
40
|
+
max: 1
|
|
41
|
+
})
|
|
42
|
+
const borderTransparency = clamp(50 - opacity * 50, {
|
|
43
|
+
min: 0,
|
|
44
|
+
max: 50
|
|
45
|
+
})
|
|
39
46
|
|
|
40
47
|
const { isHovered, isDimmed, hasOnNavigateTo } = useDiagramState(s => ({
|
|
41
48
|
isHovered: s.hoveredNodeId === id,
|
|
@@ -44,30 +51,19 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
44
51
|
}))
|
|
45
52
|
const isnavigable = !!compound.navigateTo && hasOnNavigateTo
|
|
46
53
|
|
|
47
|
-
// invert opacity to transparency
|
|
48
|
-
const transparency = clamp(100 - opacity, {
|
|
49
|
-
min: 0,
|
|
50
|
-
max: 99
|
|
51
|
-
})
|
|
52
|
-
|
|
53
54
|
return (
|
|
54
55
|
<Box
|
|
55
56
|
className={clsx(
|
|
56
57
|
css.container,
|
|
57
58
|
'likec4-compound-node',
|
|
58
|
-
opacity <
|
|
59
|
+
opacity < 1 && 'likec4-compound-transparent',
|
|
59
60
|
isDimmed && css.dimmed
|
|
60
61
|
)}
|
|
61
62
|
mod={{
|
|
62
63
|
'compound-depth': depth,
|
|
63
64
|
'likec4-color': color,
|
|
64
|
-
'likec4-shape': compound.shape,
|
|
65
|
-
'likec4-navigable': isnavigable,
|
|
66
65
|
hovered: isHovered
|
|
67
66
|
}}
|
|
68
|
-
style={assignInlineVars({
|
|
69
|
-
[css.varTransparency]: transparency + '%'
|
|
70
|
-
})}
|
|
71
67
|
>
|
|
72
68
|
<Handle
|
|
73
69
|
type="target"
|
|
@@ -84,14 +80,28 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
84
80
|
/>
|
|
85
81
|
</svg>
|
|
86
82
|
<div
|
|
87
|
-
className={clsx(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
className={clsx(
|
|
84
|
+
css.compoundBody,
|
|
85
|
+
opacity < 1 && css.transparent,
|
|
86
|
+
'likec4-compound'
|
|
87
|
+
)}
|
|
88
|
+
style={opacity < 1
|
|
89
|
+
? {
|
|
90
|
+
...assignInlineVars({
|
|
91
|
+
[css.varBorderTransparency]: `${borderTransparency}%`,
|
|
92
|
+
[css.varOpacity]: opacity.toFixed(2)
|
|
93
|
+
}),
|
|
94
|
+
borderStyle: style.border ?? 'dashed'
|
|
95
|
+
}
|
|
96
|
+
: undefined}
|
|
91
97
|
>
|
|
92
98
|
<Text
|
|
93
99
|
component="div"
|
|
94
|
-
className={clsx(
|
|
100
|
+
className={clsx(
|
|
101
|
+
css.title,
|
|
102
|
+
isnavigable && css.titleWithNavigation,
|
|
103
|
+
'likec4-compound-title'
|
|
104
|
+
)}>
|
|
95
105
|
{compound.title}
|
|
96
106
|
</Text>
|
|
97
107
|
</div>
|
|
@@ -103,4 +113,4 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
103
113
|
/>
|
|
104
114
|
</Box>
|
|
105
115
|
)
|
|
106
|
-
})
|
|
116
|
+
}, isEqualProps)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Text } from '@mantine/core'
|
|
2
2
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
|
-
import {
|
|
4
|
+
import { DEV } from 'esm-env'
|
|
5
|
+
import { deepEqual as eq } from 'fast-equals'
|
|
6
|
+
import { m, type Variants } from 'framer-motion'
|
|
7
|
+
import { memo } from 'react'
|
|
5
8
|
import { isNumber } from 'remeda'
|
|
9
|
+
import useTilg from 'tilg'
|
|
6
10
|
import { useDiagramState } from '../../../state'
|
|
7
11
|
import type { ElementXYFlowNode } from '../../types'
|
|
8
12
|
import { toDomPrecision } from '../../utils'
|
|
@@ -20,13 +24,13 @@ const variants = {
|
|
|
20
24
|
idle: (_, { scale }) => ({
|
|
21
25
|
scale: 1,
|
|
22
26
|
transition: {
|
|
23
|
-
delay: isNumber(scale) && scale > selectedScale ? 0.
|
|
27
|
+
delay: isNumber(scale) && scale > selectedScale ? 0.09 : 0
|
|
24
28
|
}
|
|
25
29
|
}),
|
|
26
30
|
selected: (_, { scale }) => ({
|
|
27
31
|
scale: selectedScale,
|
|
28
32
|
transition: {
|
|
29
|
-
delay: isNumber(scale) && scale > selectedScale ? 0.
|
|
33
|
+
delay: isNumber(scale) && scale > selectedScale ? 0.09 : 0
|
|
30
34
|
}
|
|
31
35
|
}),
|
|
32
36
|
// dimmed: {
|
|
@@ -50,12 +54,9 @@ const variants = {
|
|
|
50
54
|
// delay: 1
|
|
51
55
|
// }
|
|
52
56
|
// }),
|
|
53
|
-
hovered:
|
|
54
|
-
scale: 1.06
|
|
55
|
-
|
|
56
|
-
delay: isNumber(scale) && scale !== 1 && scale !== selectedScale ? 0 : 0.15
|
|
57
|
-
}
|
|
58
|
-
}),
|
|
57
|
+
hovered: {
|
|
58
|
+
scale: 1.06
|
|
59
|
+
},
|
|
59
60
|
tap: {
|
|
60
61
|
scale: 0.975
|
|
61
62
|
}
|
|
@@ -64,7 +65,16 @@ const variants = {
|
|
|
64
65
|
// }
|
|
65
66
|
} satisfies Variants
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
|
|
69
|
+
prev.id === next.id
|
|
70
|
+
&& eq(prev.selected ?? false, next.selected ?? false)
|
|
71
|
+
&& eq(prev.dragging ?? false, next.dragging ?? false)
|
|
72
|
+
&& eq(prev.width ?? 0, next.width ?? 0)
|
|
73
|
+
&& eq(prev.height ?? 0, next.height ?? 0)
|
|
74
|
+
&& eq(prev.data.element, next.data.element)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
68
78
|
id,
|
|
69
79
|
data: {
|
|
70
80
|
element
|
|
@@ -73,7 +83,8 @@ export function ElementNode({
|
|
|
73
83
|
selected = false,
|
|
74
84
|
width,
|
|
75
85
|
height
|
|
76
|
-
}
|
|
86
|
+
}) {
|
|
87
|
+
DEV && useTilg()
|
|
77
88
|
const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
|
|
78
89
|
isHovered: s.hoveredNodeId === id,
|
|
79
90
|
isDimmed: s.dimmed.has(id),
|
|
@@ -109,13 +120,15 @@ export function ElementNode({
|
|
|
109
120
|
// useTilg(animate, isHovered, isInteractive)
|
|
110
121
|
|
|
111
122
|
return (
|
|
112
|
-
<
|
|
123
|
+
<m.div
|
|
113
124
|
id={id}
|
|
114
125
|
className={clsx([
|
|
115
126
|
css.container,
|
|
116
127
|
isDimmed && css.dimmed,
|
|
128
|
+
animate !== 'idle' && css.containerAnimated,
|
|
117
129
|
'likec4-element-node'
|
|
118
130
|
])}
|
|
131
|
+
data-hovered={!dragging && isHovered}
|
|
119
132
|
data-likec4-color={element.color}
|
|
120
133
|
data-likec4-shape={element.shape}
|
|
121
134
|
variants={variants}
|
|
@@ -225,6 +238,6 @@ export function ElementNode({
|
|
|
225
238
|
}
|
|
226
239
|
{isHovercards && element.links && <ElementLink element={element} />}
|
|
227
240
|
{isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
|
|
228
|
-
</
|
|
241
|
+
</m.div>
|
|
229
242
|
)
|
|
230
|
-
}
|
|
243
|
+
}, isEqualProps)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { rem } from '@mantine/core'
|
|
2
|
-
import { createVar, fallbackVar,
|
|
2
|
+
import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
|
|
3
3
|
import { mantine } from '../../../mantine.css'
|
|
4
4
|
import { vars } from '../../../theme.css'
|
|
5
5
|
|
|
@@ -8,26 +8,40 @@ const elPadding = createVar('el-padding')
|
|
|
8
8
|
// assignVars
|
|
9
9
|
|
|
10
10
|
export const container = style({
|
|
11
|
-
position: 'relative',
|
|
12
11
|
width: '100%',
|
|
13
12
|
height: '100%',
|
|
14
13
|
padding: 0,
|
|
15
14
|
margin: 0,
|
|
16
|
-
|
|
15
|
+
display: 'flex',
|
|
16
|
+
alignItems: 'stretch',
|
|
17
17
|
vars: {
|
|
18
18
|
[stokeFillMix]: `color-mix(in srgb, ${vars.element.stroke} 90%, ${vars.element.fill})`
|
|
19
|
+
},
|
|
20
|
+
selectors: {
|
|
21
|
+
':where(.react-flow__node.selected) &': {
|
|
22
|
+
willChange: 'transform'
|
|
23
|
+
},
|
|
24
|
+
'&[data-hovered="true"]': {
|
|
25
|
+
willChange: 'transform'
|
|
26
|
+
}
|
|
19
27
|
}
|
|
20
28
|
})
|
|
21
29
|
|
|
30
|
+
export const containerAnimated = style({
|
|
31
|
+
willChange: 'transform'
|
|
32
|
+
})
|
|
33
|
+
|
|
22
34
|
export const dimmed = style({})
|
|
23
35
|
|
|
24
|
-
globalStyle(`.react-flow__node:has(${dimmed})`, {
|
|
25
|
-
|
|
26
|
-
transition: 'filter
|
|
36
|
+
globalStyle(`.react-flow__node-element:has(${dimmed})`, {
|
|
37
|
+
opacity: 0.25,
|
|
38
|
+
transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
|
|
39
|
+
transitionDelay: '200ms',
|
|
40
|
+
filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(2px)')}`,
|
|
41
|
+
willChange: 'opacity, filter'
|
|
27
42
|
})
|
|
28
43
|
|
|
29
|
-
const indicatorKeyframes =
|
|
30
|
-
globalKeyframes(indicatorKeyframes, {
|
|
44
|
+
const indicatorKeyframes = keyframes({
|
|
31
45
|
'from': {
|
|
32
46
|
opacity: 0.6
|
|
33
47
|
},
|
|
@@ -47,22 +61,26 @@ export const indicator = style({
|
|
|
47
61
|
transformOrigin: 'center center',
|
|
48
62
|
strokeWidth: 6,
|
|
49
63
|
animationDuration: '800ms',
|
|
50
|
-
animationName: indicatorKeyframes,
|
|
51
64
|
animationIterationCount: 'infinite',
|
|
52
65
|
animationDirection: 'alternate',
|
|
66
|
+
opacity: 0.6,
|
|
53
67
|
visibility: 'hidden',
|
|
54
68
|
selectors: {
|
|
55
69
|
':where(.react-flow__node.selected) &': {
|
|
56
|
-
visibility: 'visible'
|
|
70
|
+
visibility: 'visible',
|
|
71
|
+
animationName: fallbackVar(vars.safariAnimationHook, indicatorKeyframes)
|
|
57
72
|
},
|
|
58
73
|
[`:where(.react-flow__node:focus-visible, ${container}:focus-visible) &`]: {
|
|
59
|
-
animationName: 'none',
|
|
60
74
|
strokeWidth: 10,
|
|
61
75
|
stroke: outlineColor,
|
|
62
76
|
visibility: 'visible'
|
|
63
77
|
},
|
|
64
78
|
':where([data-likec4-shape="queue"], [data-likec4-shape="cylinder"], [data-likec4-shape="storage"]) &': {
|
|
65
79
|
strokeWidth: 10
|
|
80
|
+
},
|
|
81
|
+
[`${dimmed} &`]: {
|
|
82
|
+
visibility: 'hidden',
|
|
83
|
+
display: 'none'
|
|
66
84
|
}
|
|
67
85
|
}
|
|
68
86
|
})
|
|
@@ -128,9 +146,7 @@ export const technology = style({
|
|
|
128
146
|
})
|
|
129
147
|
|
|
130
148
|
export const element = style({
|
|
131
|
-
|
|
132
|
-
width: '100%',
|
|
133
|
-
height: '100%',
|
|
149
|
+
flex: '1',
|
|
134
150
|
padding: elPadding,
|
|
135
151
|
display: 'flex',
|
|
136
152
|
flexDirection: 'column',
|
|
@@ -165,10 +181,6 @@ export const elementIcon = style({
|
|
|
165
181
|
flex: `0 1 ${iconMaxH}`,
|
|
166
182
|
maxHeight: iconMaxH,
|
|
167
183
|
marginTop: -8,
|
|
168
|
-
// display: 'flex',
|
|
169
|
-
// justifyContent: 'flex-end',
|
|
170
|
-
// flexDirection: 'column',
|
|
171
|
-
// alignItems: 'center',
|
|
172
184
|
position: 'relative',
|
|
173
185
|
userSelect: 'none',
|
|
174
186
|
pointerEvents: 'none',
|
|
@@ -203,6 +215,7 @@ export const cssShapeSvg = style({
|
|
|
203
215
|
filter: fallbackVar(filterShadowHovered, filterShadow),
|
|
204
216
|
transition: 'filter 300ms ease-out',
|
|
205
217
|
transitionDelay: '0ms',
|
|
218
|
+
zIndex: -1,
|
|
206
219
|
vars: {
|
|
207
220
|
[filterShadow]: `
|
|
208
221
|
drop-shadow(0 2px 1px rgba(0, 0, 0, 0.21))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ElementNode } from './ElementNode'
|
|
1
|
+
export { ElementNodeMemo as ElementNode } from './ElementNode'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ActionIcon } from '@mantine/core'
|
|
2
|
+
import { IconZoomScan } from '@tabler/icons-react'
|
|
2
3
|
import clsx from 'clsx'
|
|
3
|
-
import { ZoomIn } from '../../../icons'
|
|
4
4
|
import { useDiagramStoreApi } from '../../../state'
|
|
5
5
|
|
|
6
6
|
export type NavigateToBtnProps = {
|
|
@@ -13,7 +13,7 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
|
|
|
13
13
|
return (
|
|
14
14
|
<ActionIcon
|
|
15
15
|
className={clsx('nodrag nopan', className)}
|
|
16
|
-
radius="
|
|
16
|
+
radius="md"
|
|
17
17
|
onPointerDownCapture={e => e.stopPropagation()}
|
|
18
18
|
onClick={event => {
|
|
19
19
|
event.stopPropagation()
|
|
@@ -21,7 +21,11 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
|
|
|
21
21
|
}}
|
|
22
22
|
onDoubleClick={event => event.stopPropagation()}
|
|
23
23
|
>
|
|
24
|
-
<
|
|
24
|
+
<IconZoomScan
|
|
25
|
+
style={{
|
|
26
|
+
width: '75%',
|
|
27
|
+
height: '75%'
|
|
28
|
+
}} />
|
|
25
29
|
</ActionIcon>
|
|
26
30
|
)
|
|
27
31
|
}
|
package/src/xyflow/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BBox, DiagramEdge, DiagramNode, Fqn, Point } from '@likec4/core'
|
|
1
|
+
import type { BBox, DiagramEdge, DiagramNode, Fqn, Point, ViewID, XYPosition } from '@likec4/core'
|
|
2
2
|
import type {
|
|
3
3
|
Edge,
|
|
4
4
|
EdgeMouseHandler,
|
|
@@ -57,7 +57,10 @@ export namespace XYFlowNode {
|
|
|
57
57
|
|
|
58
58
|
export type RelationshipData = {
|
|
59
59
|
edge: DiagramEdge
|
|
60
|
-
|
|
60
|
+
// to replace edges on view change
|
|
61
|
+
// viewId: ViewID
|
|
62
|
+
// if set - edge was changed by user
|
|
63
|
+
controlPoints: XYPosition[] | null
|
|
61
64
|
headPoint: Point | null
|
|
62
65
|
tailPoint: Point | null
|
|
63
66
|
type: 'bezier' | 'poly'
|
package/src/xyflow/utils.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { hasAtLeast, invariant } from '@likec4/core'
|
|
2
|
+
import type { DiagramEdge, NonEmptyArray, Point } from '@likec4/core/types'
|
|
1
3
|
import type { XYPosition } from '@xyflow/react'
|
|
4
|
+
import { Bezier } from 'bezier-js'
|
|
5
|
+
import { isArray } from 'remeda'
|
|
2
6
|
|
|
3
7
|
export function toDomPrecision(v: number | null) {
|
|
4
8
|
if (v === null) {
|
|
@@ -10,3 +14,47 @@ export function toDomPrecision(v: number | null) {
|
|
|
10
14
|
export function distance(a: XYPosition, b: XYPosition) {
|
|
11
15
|
return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2))
|
|
12
16
|
}
|
|
17
|
+
|
|
18
|
+
export function bezierControlPoints(diagramEdge: DiagramEdge) {
|
|
19
|
+
let [start, ...bezierPoints] = diagramEdge.points
|
|
20
|
+
invariant(start, 'start should be defined')
|
|
21
|
+
const handles = [
|
|
22
|
+
// start
|
|
23
|
+
] as XYPosition[]
|
|
24
|
+
|
|
25
|
+
while (hasAtLeast(bezierPoints, 3)) {
|
|
26
|
+
const [cp1, cp2, end, ...rest] = bezierPoints
|
|
27
|
+
const bezier = new Bezier(start[0], start[1], cp1[0], cp1[1], cp2[0], cp2[1], end[0], end[1])
|
|
28
|
+
// TODO: temporary, we need correcly derive catmull-rom from bezier. Actually, from poly-bezier
|
|
29
|
+
const inflections = bezier.inflections()
|
|
30
|
+
if (inflections.length === 0) {
|
|
31
|
+
inflections.push(0.5)
|
|
32
|
+
}
|
|
33
|
+
inflections.forEach(t => {
|
|
34
|
+
const { x, y } = bezier.get(t)
|
|
35
|
+
handles.push({
|
|
36
|
+
x: toDomPrecision(x),
|
|
37
|
+
y: toDomPrecision(y)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
bezierPoints = rest
|
|
41
|
+
start = end
|
|
42
|
+
}
|
|
43
|
+
invariant(bezierPoints.length === 0, 'all points should be consumed')
|
|
44
|
+
if (diagramEdge.dir === 'back' && handles.length > 0) {
|
|
45
|
+
handles.reverse()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return handles
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If points are within 3px, consider them the same
|
|
52
|
+
const isClose = (a: number, b: number) => {
|
|
53
|
+
return Math.abs(a - b) < 3.1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isSamePoint(a: XYPosition | Point, b: XYPosition | Point) {
|
|
57
|
+
const [ax, ay] = isArray(a) ? a : [a.x, a.y]
|
|
58
|
+
const [bx, by] = isArray(b) ? b : [b.x, b.y]
|
|
59
|
+
return isClose(ax, bx) && isClose(ay, by)
|
|
60
|
+
}
|
package/dist/ui/index.d.ts
DELETED
package/src/ui/index.ts
DELETED