@likec4/diagram 1.0.1 → 1.1.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/KeepAspectRatioContainer.d.ts +9 -0
- package/dist/LikeC4Diagram.css.d.ts +1 -0
- package/dist/index.mjs +243 -678
- package/dist/state/DiagramContext.d.ts +36 -9
- package/dist/state/diagramStore.d.ts +42 -12
- package/dist/state/useDiagramStore.d.ts +36 -9
- package/dist/style.css +600 -53
- package/dist/ui/index.d.ts +0 -1
- package/dist/xyflow/{XYFlowWrapper.d.ts → XYFlow.d.ts} +2 -2
- package/dist/xyflow/XYFlowEvents.d.ts +127 -13
- package/dist/xyflow/edges/RelationshipEdge.d.ts +2 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +20 -20
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +7 -5
- package/dist/xyflow/types.d.ts +2 -2
- package/package.json +9 -9
- package/src/{ui/KeepAspectRatio.tsx → KeepAspectRatioContainer.tsx} +7 -6
- package/src/LikeC4Diagram.css.ts +6 -0
- package/src/LikeC4Diagram.tsx +6 -6
- package/src/globals.css.ts +42 -37
- package/src/state/diagramStore.ts +26 -7
- package/src/ui/index.ts +0 -1
- package/src/xyflow/FitviewOnDiagramChange.tsx +24 -18
- package/src/xyflow/SyncWithDiagram.tsx +16 -10
- package/src/xyflow/{XYFlowWrapper.tsx → XYFlow.tsx} +17 -24
- package/src/xyflow/XYFlowEvents.tsx +96 -43
- package/src/xyflow/diagram-to-xyflow.ts +4 -4
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +85 -39
- package/src/xyflow/nodes/compound/CompoundNode.tsx +29 -31
- package/src/xyflow/nodes/element/ElementNode.tsx +2 -2
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +2 -1
- package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
- package/dist/ui/KeepAspectRatio.d.ts +0 -9
- package/src/ui/KeepAspectRatio.css.ts +0 -7
|
@@ -3,7 +3,7 @@ import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyl
|
|
|
3
3
|
import { mantine } from '../../../mantine.css'
|
|
4
4
|
import { vars } from '../../../theme.css'
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const container = style({
|
|
7
7
|
width: '100%',
|
|
8
8
|
height: '100%',
|
|
9
9
|
position: 'relative',
|
|
@@ -11,7 +11,7 @@ export const cssContainer = style({
|
|
|
11
11
|
margin: 0
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
export const varTransparency = createVar('transparency')
|
|
15
15
|
|
|
16
16
|
const outlineColor = fallbackVar(
|
|
17
17
|
mantine.colors.primaryColors.outline,
|
|
@@ -19,15 +19,15 @@ const outlineColor = fallbackVar(
|
|
|
19
19
|
vars.element.stroke
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
export const
|
|
22
|
+
export const compoundBody = style({
|
|
23
23
|
width: '100%',
|
|
24
24
|
height: '100%',
|
|
25
25
|
position: 'relative',
|
|
26
26
|
borderRadius: rem(6),
|
|
27
27
|
boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
|
|
28
|
-
padding:
|
|
28
|
+
padding: 8,
|
|
29
29
|
background: vars.element.fill,
|
|
30
|
-
transition: 'all
|
|
30
|
+
transition: 'all 200ms ease-out',
|
|
31
31
|
selectors: {
|
|
32
32
|
[`:where(.react-flow__node.selected) &`]: {
|
|
33
33
|
boxShadow: 'none'
|
|
@@ -40,62 +40,72 @@ export const cssCompound = style({
|
|
|
40
40
|
}
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
position: 'relative',
|
|
48
|
-
borderRadius: rem(8),
|
|
43
|
+
const bgTransparency = createVar('bgTransparency')
|
|
44
|
+
const bgTransparencyDelta = createVar('bgTransparencyDelta')
|
|
45
|
+
export const transparent = style({
|
|
46
|
+
borderRadius: 8,
|
|
49
47
|
borderStyle: 'dashed',
|
|
50
|
-
borderWidth:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
padding: rem(8),
|
|
48
|
+
borderWidth: 3,
|
|
49
|
+
padding: 6,
|
|
50
|
+
borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent min(${bgTransparency}, 30%))`,
|
|
54
51
|
background: `color-mix(in srgb , ${vars.element.fill}, transparent ${bgTransparency})`,
|
|
55
|
-
|
|
52
|
+
backgroundClip: 'padding-box',
|
|
53
|
+
// transitionTimingFunction: 'ease-in',
|
|
54
|
+
// transition: 'all 150ms ease-in-out',
|
|
56
55
|
vars: {
|
|
57
|
-
[bgTransparency]: '
|
|
56
|
+
[bgTransparency]: `calc(${fallbackVar(varTransparency, '80%')} - ${bgTransparencyDelta})`,
|
|
57
|
+
[bgTransparencyDelta]: '0%',
|
|
58
|
+
'--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
|
|
58
59
|
},
|
|
59
60
|
selectors: {
|
|
60
|
-
[`:where(${
|
|
61
|
-
transitionDelay: '
|
|
61
|
+
[`:where(${container}[data-hovered]) &`]: {
|
|
62
|
+
transitionDelay: '175ms',
|
|
63
|
+
// transitionTimingFunction: 'ease-out',
|
|
62
64
|
vars: {
|
|
63
|
-
[
|
|
65
|
+
[bgTransparencyDelta]: '15%'
|
|
64
66
|
}
|
|
65
67
|
},
|
|
66
68
|
[`:where(.react-flow__node.selected) &`]: {
|
|
67
69
|
transitionDelay: '50ms',
|
|
68
|
-
boxShadow: 'none',
|
|
69
70
|
borderColor: 'transparent',
|
|
71
|
+
// transitionTimingFunction: 'ease-out',
|
|
70
72
|
vars: {
|
|
71
|
-
[
|
|
73
|
+
[bgTransparencyDelta]: '25%'
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
})
|
|
76
78
|
|
|
77
|
-
export const
|
|
79
|
+
export const title = style({
|
|
78
80
|
fontFamily: vars.compound.font,
|
|
79
81
|
fontOpticalSizing: 'auto',
|
|
80
82
|
fontStyle: 'normal',
|
|
81
83
|
textAlign: 'left',
|
|
82
84
|
display: 'inline-block',
|
|
83
85
|
fontWeight: 600,
|
|
84
|
-
fontSize: '
|
|
86
|
+
fontSize: '14px',
|
|
85
87
|
textTransform: 'uppercase',
|
|
86
88
|
letterSpacing: '0.2px',
|
|
87
89
|
lineHeight: 1,
|
|
88
90
|
opacity: 0.75,
|
|
89
|
-
color: vars.compound.titleColor
|
|
90
|
-
paddingLeft:
|
|
91
|
-
mixBlendMode: 'lighten',
|
|
91
|
+
color: `var(--_compound-title-color,${vars.compound.titleColor})`,
|
|
92
|
+
paddingLeft: 12,
|
|
92
93
|
selectors: {
|
|
93
94
|
[`:where([data-likec4-navigable='true']) &`]: {
|
|
94
|
-
paddingLeft:
|
|
95
|
+
paddingLeft: 26
|
|
96
|
+
},
|
|
97
|
+
[`:where([data-mantine-color-scheme='light']) &`]: {
|
|
98
|
+
opacity: 1
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
})
|
|
98
102
|
|
|
103
|
+
globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
|
|
104
|
+
vars: {
|
|
105
|
+
['--_compound-title-color']: vars.element.stroke
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
99
109
|
const indicatorKeyframes = generateIdentifier('indicator')
|
|
100
110
|
globalKeyframes(indicatorKeyframes, {
|
|
101
111
|
'from': {
|
|
@@ -106,7 +116,9 @@ globalKeyframes(indicatorKeyframes, {
|
|
|
106
116
|
}
|
|
107
117
|
})
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
const indicatorStroke = createVar('indicator-stroke')
|
|
120
|
+
const indicatorStrokeWidth = createVar('indicator-stroke-width')
|
|
121
|
+
export const indicator = style({
|
|
110
122
|
position: 'absolute',
|
|
111
123
|
top: '0',
|
|
112
124
|
left: '0',
|
|
@@ -115,17 +127,32 @@ export const cssIndicator = style({
|
|
|
115
127
|
pointerEvents: 'none',
|
|
116
128
|
overflow: 'visible',
|
|
117
129
|
visibility: 'hidden',
|
|
130
|
+
vars: {
|
|
131
|
+
[indicatorStroke]: vars.element.loContrast,
|
|
132
|
+
[indicatorStrokeWidth]: '6'
|
|
133
|
+
},
|
|
118
134
|
selectors: {
|
|
119
135
|
':where(.react-flow__node.selected:not(:focus-visible)) &': {
|
|
120
136
|
visibility: 'visible'
|
|
137
|
+
},
|
|
138
|
+
':where(.react-flow__node:focus-within:not(.selected)) &': {
|
|
139
|
+
vars: {
|
|
140
|
+
[indicatorStroke]: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
|
|
141
|
+
[indicatorStrokeWidth]: '8'
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[`:where([data-mantine-color-scheme='light']) &`]: {
|
|
145
|
+
vars: {
|
|
146
|
+
[indicatorStroke]: `color-mix(in srgb, ${vars.element.stroke} 80%, #000)`
|
|
147
|
+
}
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
})
|
|
124
151
|
|
|
125
|
-
globalStyle(`${
|
|
126
|
-
stroke:
|
|
152
|
+
globalStyle(`${indicator} rect`, {
|
|
153
|
+
stroke: indicatorStroke,
|
|
127
154
|
transformOrigin: 'center center',
|
|
128
|
-
strokeWidth:
|
|
155
|
+
strokeWidth: indicatorStrokeWidth,
|
|
129
156
|
animationDuration: '800ms',
|
|
130
157
|
animationName: indicatorKeyframes,
|
|
131
158
|
animationIterationCount: 'infinite',
|
|
@@ -133,18 +160,30 @@ globalStyle(`${cssIndicator} rect`, {
|
|
|
133
160
|
fill: 'none'
|
|
134
161
|
})
|
|
135
162
|
|
|
136
|
-
globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${
|
|
137
|
-
|
|
138
|
-
|
|
163
|
+
// globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${indicator} rect`, {
|
|
164
|
+
// stroke: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
|
|
165
|
+
// strokeWidth: 8
|
|
166
|
+
// })
|
|
167
|
+
const navigateBtnColor = createVar('navigateBtnColor')
|
|
168
|
+
|
|
169
|
+
globalStyle(`${container}`, {
|
|
170
|
+
vars: {
|
|
171
|
+
[navigateBtnColor]: vars.element.loContrast
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
|
|
175
|
+
vars: {
|
|
176
|
+
[navigateBtnColor]: vars.element.stroke
|
|
177
|
+
}
|
|
139
178
|
})
|
|
140
179
|
|
|
141
|
-
export const
|
|
180
|
+
export const navigateBtn = style({
|
|
142
181
|
position: 'absolute',
|
|
143
182
|
pointerEvents: 'all',
|
|
144
183
|
left: 3,
|
|
145
184
|
top: 7,
|
|
146
185
|
cursor: 'pointer',
|
|
147
|
-
color:
|
|
186
|
+
color: `var(--_compound-title-color,${navigateBtnColor})`,
|
|
148
187
|
transformOrigin: '90% 70%',
|
|
149
188
|
opacity: 0.75,
|
|
150
189
|
transition: 'all 150ms ease-out',
|
|
@@ -167,7 +206,14 @@ export const cssNavigateBtn = style({
|
|
|
167
206
|
transitionDelay: '0'
|
|
168
207
|
},
|
|
169
208
|
selectors: {
|
|
170
|
-
[`:where(.
|
|
209
|
+
[`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) &`]: {
|
|
210
|
+
opacity: 0.85,
|
|
211
|
+
vars: {
|
|
212
|
+
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 50%)`,
|
|
213
|
+
'--ai-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 20%)`
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
[`:where(.react-flow__node:not(.dragging) ${container}:hover) &:not(:hover)`]: {
|
|
171
217
|
boxShadow: mantine.shadows.lg,
|
|
172
218
|
transform: 'scale(1.223)',
|
|
173
219
|
opacity: 1,
|
|
@@ -178,7 +224,7 @@ export const cssNavigateBtn = style({
|
|
|
178
224
|
}
|
|
179
225
|
}
|
|
180
226
|
})
|
|
181
|
-
globalStyle(`${
|
|
227
|
+
globalStyle(`${navigateBtn} svg.icon`, {
|
|
182
228
|
width: '70%',
|
|
183
229
|
height: '70%',
|
|
184
230
|
strokeWidth: 1.5
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { defaultTheme } from '@likec4/core'
|
|
2
1
|
import { Box, Text } from '@mantine/core'
|
|
3
2
|
import { assignInlineVars } from '@vanilla-extract/dynamic'
|
|
4
3
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
5
4
|
import clsx from 'clsx'
|
|
6
5
|
import { deepEqual as eq } from 'fast-equals'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { memo } from 'react'
|
|
7
|
+
import { clamp } from 'remeda'
|
|
9
8
|
import { useDiagramState } from '../../../state'
|
|
10
|
-
import { vars } from '../../../theme.css'
|
|
11
9
|
import type { CompoundXYFlowNode } from '../../types'
|
|
12
10
|
import { NavigateToBtn } from '../shared/NavigateToBtn'
|
|
13
|
-
import {
|
|
11
|
+
import { compoundBody, container, indicator, navigateBtn, title } from './CompoundNode.css'
|
|
12
|
+
import * as css from './CompoundNode.css'
|
|
14
13
|
|
|
15
14
|
type CompoundNodeProps = Pick<
|
|
16
15
|
NodeProps<CompoundXYFlowNode>,
|
|
@@ -26,14 +25,6 @@ const isEqualProps = (prev: CompoundNodeProps, next: CompoundNodeProps) => (
|
|
|
26
25
|
&& eq(prev.data, next.data)
|
|
27
26
|
)
|
|
28
27
|
|
|
29
|
-
const compoundColor = (color: string, depth: number) =>
|
|
30
|
-
toHex(
|
|
31
|
-
scale(color, {
|
|
32
|
-
l: -35 - 5 * depth,
|
|
33
|
-
s: -15 - 5 * depth
|
|
34
|
-
})
|
|
35
|
-
)
|
|
36
|
-
|
|
37
28
|
export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function CompoundNode({
|
|
38
29
|
id,
|
|
39
30
|
data: {
|
|
@@ -41,17 +32,10 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
41
32
|
}
|
|
42
33
|
}) {
|
|
43
34
|
// useTilg()
|
|
44
|
-
const { color, depth =
|
|
45
|
-
const colors = useMemo(() => {
|
|
46
|
-
const colors = defaultTheme.elements[color]
|
|
47
|
-
return {
|
|
48
|
-
fill: compoundColor(colors.fill, depth),
|
|
49
|
-
stroke: compoundColor(colors.stroke, depth)
|
|
50
|
-
}
|
|
51
|
-
}, [color, depth])
|
|
52
|
-
|
|
35
|
+
const { color, style, depth = 1, ...compound } = element
|
|
53
36
|
// const w = toDomPrecision(width ?? compound.width)
|
|
54
37
|
// const h = toDomPrecision(height ?? compound.height)
|
|
38
|
+
const opacity = style.opacity ?? 100
|
|
55
39
|
|
|
56
40
|
const { isHovered, hasOnNavigateTo } = useDiagramState(s => ({
|
|
57
41
|
isHovered: s.hoveredNodeId === id,
|
|
@@ -59,26 +43,35 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
59
43
|
}))
|
|
60
44
|
const isnavigable = !!compound.navigateTo && hasOnNavigateTo
|
|
61
45
|
|
|
46
|
+
const transparency = clamp(100 - opacity, {
|
|
47
|
+
min: 0,
|
|
48
|
+
max: 98
|
|
49
|
+
})
|
|
50
|
+
|
|
62
51
|
return (
|
|
63
52
|
<Box
|
|
64
|
-
className={clsx(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
className={clsx(
|
|
54
|
+
css.container,
|
|
55
|
+
'likec4-compound-node',
|
|
56
|
+
opacity < 100 && 'likec4-compound-transparent'
|
|
57
|
+
)}
|
|
69
58
|
mod={{
|
|
59
|
+
'compound-depth': depth,
|
|
70
60
|
'likec4-color': color,
|
|
71
61
|
'likec4-shape': compound.shape,
|
|
72
62
|
'likec4-navigable': isnavigable,
|
|
73
63
|
hovered: isHovered
|
|
74
64
|
}}
|
|
65
|
+
style={assignInlineVars({
|
|
66
|
+
[css.varTransparency]: transparency + '%'
|
|
67
|
+
})}
|
|
75
68
|
>
|
|
76
69
|
<Handle
|
|
77
70
|
type="target"
|
|
78
71
|
position={Position.Top}
|
|
79
72
|
style={{ visibility: 'hidden' }}
|
|
80
73
|
/>
|
|
81
|
-
<svg className={
|
|
74
|
+
<svg className={css.indicator}>
|
|
82
75
|
<rect
|
|
83
76
|
x={0}
|
|
84
77
|
y={0}
|
|
@@ -87,14 +80,19 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
|
|
|
87
80
|
rx={6}
|
|
88
81
|
/>
|
|
89
82
|
</svg>
|
|
90
|
-
<div
|
|
83
|
+
<div
|
|
84
|
+
className={clsx(css.compoundBody, opacity < 100 && css.transparent, 'likec4-compound')}
|
|
85
|
+
style={{
|
|
86
|
+
borderStyle: opacity < 100 ? style.border : undefined
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
91
89
|
<Text
|
|
92
90
|
component="div"
|
|
93
|
-
className={clsx(
|
|
91
|
+
className={clsx(css.title, 'likec4-compound-title')}>
|
|
94
92
|
{compound.title}
|
|
95
93
|
</Text>
|
|
96
94
|
</div>
|
|
97
|
-
{isnavigable && <NavigateToBtn xynodeId={id} className={
|
|
95
|
+
{isnavigable && <NavigateToBtn xynodeId={id} className={css.navigateBtn} />}
|
|
98
96
|
<Handle
|
|
99
97
|
type="source"
|
|
100
98
|
position={Position.Bottom}
|
|
@@ -78,7 +78,7 @@ export function ElementNode({
|
|
|
78
78
|
}: ElementNodeProps) {
|
|
79
79
|
const { isHovered, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
|
|
80
80
|
isHovered: s.hoveredNodeId === id,
|
|
81
|
-
isInteractive: s.nodesDraggable || s.nodesSelectable,
|
|
81
|
+
isInteractive: s.nodesDraggable || s.nodesSelectable || !!s.onNavigateTo,
|
|
82
82
|
isHovercards: s.showElementLinks,
|
|
83
83
|
hasOnNavigateTo: !!s.onNavigateTo
|
|
84
84
|
}))
|
|
@@ -99,7 +99,7 @@ export function ElementNode({
|
|
|
99
99
|
case dragging:
|
|
100
100
|
animate = 'idle'
|
|
101
101
|
break
|
|
102
|
-
case
|
|
102
|
+
case isInteractive && isHovered:
|
|
103
103
|
animate = 'hovered'
|
|
104
104
|
break
|
|
105
105
|
case selected:
|
|
@@ -15,9 +15,10 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
|
|
|
15
15
|
className={clsx('nodrag nopan', className)}
|
|
16
16
|
radius="xl"
|
|
17
17
|
onClick={event => {
|
|
18
|
-
diagramApi.getState().triggerOnNavigateTo(xynodeId, event)
|
|
19
18
|
event.stopPropagation()
|
|
19
|
+
diagramApi.getState().triggerOnNavigateTo(xynodeId, event)
|
|
20
20
|
}}
|
|
21
|
+
onDoubleClick={event => event.stopPropagation()}
|
|
21
22
|
>
|
|
22
23
|
<ZoomIn />
|
|
23
24
|
</ActionIcon>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const wrapper: string;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
2
|
-
|
|
3
|
-
type KeepAspectRatioProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
-
enabled: boolean;
|
|
5
|
-
width: number;
|
|
6
|
-
height: number;
|
|
7
|
-
};
|
|
8
|
-
export declare function KeepAspectRatio({ children, width, height, enabled, className, style, ...rest }: KeepAspectRatioProps): JSX.Element;
|
|
9
|
-
export {};
|