@likec4/diagram 1.12.2 → 1.13.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.
Files changed (49) hide show
  1. package/package.json +8 -8
  2. package/src/LikeC4CustomColors.tsx +2 -2
  3. package/src/LikeC4Diagram.props.ts +15 -7
  4. package/src/LikeC4Diagram.tsx +23 -14
  5. package/src/StaticLikeC4Diagram.tsx +2 -0
  6. package/src/components/Overlay.css.ts +78 -0
  7. package/src/components/Overlay.tsx +102 -0
  8. package/src/components/OverlayContext.tsx +11 -0
  9. package/src/components/index.ts +2 -0
  10. package/src/likec4model/index.ts +1 -0
  11. package/src/likec4model/useLikeC4ElementsTree.ts +44 -0
  12. package/src/overlays/edge-details/EdgeDetailsOverlay.css.ts +13 -0
  13. package/src/overlays/edge-details/EdgeDetailsOverlay.tsx +37 -0
  14. package/src/overlays/index.tsx +33 -0
  15. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +33 -0
  16. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +359 -0
  17. package/src/overlays/relationships-of/SelectElement.css.ts +18 -0
  18. package/src/overlays/relationships-of/SelectElement.tsx +133 -0
  19. package/src/overlays/relationships-of/_types.ts +56 -0
  20. package/src/overlays/relationships-of/styles.css.ts +34 -0
  21. package/src/overlays/relationships-of/use-layouted-relationships.ts +607 -0
  22. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +76 -0
  23. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +130 -0
  24. package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +22 -0
  25. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +59 -0
  26. package/src/overlays/relationships-of/xyflow/styles.css.ts +170 -0
  27. package/src/state/diagram-to-xyflow.ts +1 -1
  28. package/src/state/diagramStore.ts +119 -27
  29. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +1 -1
  30. package/src/ui/top-left/ManualLayoutToolsButton.tsx +110 -0
  31. package/src/ui/top-left/TopLeftPanel.tsx +15 -31
  32. package/src/ui/top-left/_shared.ts +2 -2
  33. package/src/xyflow/EdgeMarkers.tsx +19 -0
  34. package/src/xyflow/XYFlowEvents.tsx +21 -24
  35. package/src/xyflow/XYFlowInner.tsx +6 -10
  36. package/src/xyflow/edges/EdgeLabel.tsx +6 -5
  37. package/src/xyflow/edges/RelationshipEdge.tsx +4 -3
  38. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +16 -0
  39. package/src/xyflow/edges/edges.css.ts +5 -11
  40. package/src/xyflow/nodes/compound/CompoundNode.css.ts +17 -40
  41. package/src/xyflow/nodes/compound/CompoundNode.tsx +219 -28
  42. package/src/xyflow/nodes/element/ElementLink.tsx +11 -8
  43. package/src/xyflow/nodes/element/ElementNode.tsx +211 -140
  44. package/src/xyflow/nodes/element/element.css.ts +22 -33
  45. package/src/xyflow/nodes/shared/Toolbar.tsx +81 -27
  46. package/src/xyflow/nodes/use-animate-variants.tsx +64 -0
  47. package/src/xyflow/useLayoutConstraints.ts +95 -125
  48. package/src/xyflow/utils.ts +4 -0
  49. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +0 -32
@@ -0,0 +1,359 @@
1
+ import { isAncestor, type Relation } from '@likec4/core'
2
+ import { Box, Button, Group, SegmentedControl, Space, Text } from '@mantine/core'
3
+ import { useLocalStorage, useStateHistory } from '@mantine/hooks'
4
+ import { useDebouncedEffect } from '@react-hookz/web'
5
+ import { IconArrowLeft, IconArrowRight } from '@tabler/icons-react'
6
+ import { Panel, ReactFlow, type ReactFlowInstance, useReactFlow, useStoreApi } from '@xyflow/react'
7
+ import { memo, useEffect, useRef } from 'react'
8
+ import { map, only, prop, unique } from 'remeda'
9
+ import { useOverlayDialog } from '../../components'
10
+ import { useDiagramStoreApi } from '../../hooks/useDiagramState'
11
+ import type { XYFlowTypes } from './_types'
12
+ import { SelectElement } from './SelectElement'
13
+ import * as css from './styles.css'
14
+ import { useLayoutedRelationships, ZIndexes } from './use-layouted-relationships'
15
+ import { CompoundNode } from './xyflow/CompoundNode'
16
+ import { ElementNode } from './xyflow/ElementNode'
17
+ import { EmptyNode } from './xyflow/EmptyNode'
18
+ import { RelationshipEdge } from './xyflow/RelationshipEdge'
19
+
20
+ const nodeTypes = {
21
+ element: ElementNode,
22
+ compound: CompoundNode,
23
+ empty: EmptyNode
24
+ }
25
+ const edgeTypes = {
26
+ relation: RelationshipEdge
27
+ }
28
+
29
+ const resetDimmedAndHovered = (xyflow: ReactFlowInstance<XYFlowTypes.Node, XYFlowTypes.Edge>) => {
30
+ xyflow.setEdges(edges =>
31
+ edges.map(edge => ({
32
+ ...edge,
33
+ data: {
34
+ ...edge.data,
35
+ dimmed: false,
36
+ hovered: false
37
+ },
38
+ animated: false
39
+ }))
40
+ )
41
+ xyflow.setNodes(nodes =>
42
+ nodes.map(n =>
43
+ ({
44
+ ...n,
45
+ data: {
46
+ ...n.data,
47
+ dimmed: false,
48
+ hovered: false
49
+ }
50
+ }) as XYFlowTypes.Node
51
+ )
52
+ )
53
+ }
54
+
55
+ const animateEdge = (node: XYFlowTypes.Node, animated = true) => (edges: XYFlowTypes.Edge[]) => {
56
+ return edges.map(edge => {
57
+ const isConnected = edge.source === node.id || edge.target === node.id || isAncestor(node.id, edge.source)
58
+ || isAncestor(node.id, edge.target)
59
+ return {
60
+ ...edge,
61
+ animated: animated && isConnected
62
+ }
63
+ })
64
+ }
65
+
66
+ const onlyOneUnique = <T extends keyof Relation>(
67
+ data: XYFlowTypes.Edge['data'],
68
+ property: T
69
+ ): Relation[T] | undefined => {
70
+ return only(unique(map(data.relations, prop(property))))
71
+ }
72
+
73
+ export const RelationshipsXYFlow = memo(function RelationshipsXYFlow() {
74
+ const diagramStore = useDiagramStoreApi()
75
+ const lastClickedNodeRef = useRef<XYFlowTypes.NonEmptyNode | null>(null)
76
+ const [_scope, setScope] = useLocalStorage<'global' | 'view'>({
77
+ key: 'likec4:scope-relationships-of',
78
+ getInitialValueInEffect: false,
79
+ defaultValue: 'view'
80
+ })
81
+ const {
82
+ view,
83
+ viewIncludesSubject,
84
+ edges,
85
+ nodes,
86
+ subject
87
+ } = useLayoutedRelationships(_scope)
88
+ const scope = viewIncludesSubject ? _scope : 'global'
89
+ const showSubjectWarning = !viewIncludesSubject && _scope === 'view'
90
+
91
+ const [historySubjectId, historyOps, { history, current }] = useStateHistory(subject.id)
92
+
93
+ const xyflow = useReactFlow<XYFlowTypes.Node, XYFlowTypes.Edge>()
94
+ const xystore = useStoreApi()
95
+ const overlay = useOverlayDialog()
96
+
97
+ useEffect(() => {
98
+ if (historySubjectId !== subject.id) {
99
+ historyOps.set(subject.id)
100
+ }
101
+ }, [subject.id])
102
+
103
+ useEffect(() => {
104
+ if (historySubjectId !== subject.id) {
105
+ overlay.openOverlay({
106
+ relationshipsOf: historySubjectId
107
+ })
108
+ }
109
+ }, [historySubjectId])
110
+
111
+ useEffect(() => {
112
+ const nextSubjectNode = nodes.find((n): n is XYFlowTypes.NonEmptyNode =>
113
+ n.type !== 'empty' && n.data.column === 'subject'
114
+ )
115
+ if (!nextSubjectNode) {
116
+ console.error('Subject node not found')
117
+ } else if (nextSubjectNode.data.fqn !== subject.id) {
118
+ console.error(`Subject node mismatch, expected: ${subject.id} got: ${nextSubjectNode.data.fqn}`)
119
+ }
120
+ const existingNode = lastClickedNodeRef.current
121
+ ?? xyflow.getNodes().find(n => n.type !== 'empty' && n.data.column !== 'subject' && n.data.fqn === subject.id)
122
+ lastClickedNodeRef.current = null
123
+ // Correct position if we have subject in existing nodes
124
+ if (nextSubjectNode && existingNode && existingNode.data.column !== 'subject') {
125
+ const xynodeFrom = xyflow.getInternalNode(existingNode.id)!
126
+ const fromPos = xyflow.flowToScreenPosition({
127
+ x: xynodeFrom.internals.positionAbsolute.x,
128
+ y: xynodeFrom.internals.positionAbsolute.y
129
+ }),
130
+ toPos = xyflow.flowToScreenPosition({
131
+ x: nextSubjectNode.position.x,
132
+ y: nextSubjectNode.position.y
133
+ }),
134
+ diff = {
135
+ x: fromPos.x - toPos.x,
136
+ y: fromPos.y - toPos.y
137
+ }
138
+ xystore.getState().panBy(diff)
139
+ xystore.getState().setNodes(nodes)
140
+ xystore.getState().setEdges(edges)
141
+ return
142
+ }
143
+ xyflow.setNodes(nodes)
144
+ xyflow.setEdges(edges)
145
+ }, [nodes, edges])
146
+
147
+ const zoomable = true
148
+
149
+ useDebouncedEffect(
150
+ () => {
151
+ xyflow.fitView({
152
+ padding: 0.2,
153
+ includeHiddenNodes: true,
154
+ maxZoom: 1,
155
+ duration: 450
156
+ })
157
+ },
158
+ [subject.id, scope],
159
+ 200
160
+ )
161
+
162
+ return (
163
+ <ReactFlow
164
+ className={css.root}
165
+ defaultEdges={[] as XYFlowTypes.Edge[]}
166
+ defaultNodes={[] as XYFlowTypes.Node[]}
167
+ nodeTypes={nodeTypes}
168
+ edgeTypes={edgeTypes}
169
+ defaultMarkerColor="var(--xy-edge-stroke)"
170
+ zoomOnPinch={zoomable}
171
+ zoomOnScroll={false}
172
+ zoomOnDoubleClick={false}
173
+ maxZoom={1.5}
174
+ minZoom={0.1}
175
+ fitView
176
+ fitViewOptions={{
177
+ padding: 0.2,
178
+ maxZoom: 1,
179
+ minZoom: 0.1,
180
+ includeHiddenNodes: true
181
+ }}
182
+ preventScrolling={true}
183
+ noDragClassName="nodrag"
184
+ noPanClassName="nopan"
185
+ panOnScroll
186
+ panOnDrag
187
+ elementsSelectable
188
+ nodesFocusable={false}
189
+ edgesFocusable={false}
190
+ nodesDraggable={false}
191
+ onNodeMouseEnter={(_, node) => {
192
+ if (node.type === 'empty') return
193
+ xyflow.setEdges(animateEdge(node, true))
194
+ }}
195
+ onNodeMouseLeave={() => {
196
+ resetDimmedAndHovered(xyflow)
197
+ }}
198
+ onEdgeMouseEnter={(_, edge) => {
199
+ xyflow.setEdges(edges =>
200
+ edges.map(e => ({
201
+ ...e,
202
+ data: {
203
+ ...e.data,
204
+ dimmed: e.id !== edge.id,
205
+ hovered: e.id === edge.id
206
+ },
207
+ zIndex: e.id === edge.id ? ZIndexes.max : ZIndexes.edge,
208
+ animated: e.id === edge.id
209
+ }))
210
+ )
211
+ xyflow.setNodes(nodes =>
212
+ nodes.map(n => ({
213
+ ...n,
214
+ data: {
215
+ ...n.data,
216
+ dimmed: n.id !== edge.source && n.id !== edge.target
217
+ }
218
+ } as XYFlowTypes.Node))
219
+ )
220
+ }}
221
+ onEdgeMouseLeave={() => {
222
+ resetDimmedAndHovered(xyflow)
223
+ }}
224
+ onNodeClick={(e, node) => {
225
+ e.stopPropagation()
226
+ if (node.type === 'empty') return
227
+ if (subject.id !== node.data.fqn) {
228
+ lastClickedNodeRef.current = node
229
+ }
230
+ overlay.openOverlay({
231
+ relationshipsOf: node.data.fqn
232
+ })
233
+ }}
234
+ onDoubleClick={e => {
235
+ e.stopPropagation()
236
+ xyflow.fitView({
237
+ includeHiddenNodes: true,
238
+ maxZoom: 1,
239
+ duration: 450
240
+ })
241
+ }}
242
+ onEdgeClick={(e, edge) => {
243
+ e.stopPropagation()
244
+ if (edge.data.relations.length <= 1) {
245
+ const relationId = only(edge.data.relations)?.id
246
+ if (relationId) {
247
+ diagramStore.getState().onOpenSourceRelation?.(relationId)
248
+ }
249
+ return
250
+ }
251
+ const nodeId = onlyOneUnique(edge.data, 'source') ? edge.source : edge.target
252
+ const next = xyflow.getNode(nodeId)
253
+ if (next && next.type !== 'empty') {
254
+ lastClickedNodeRef.current = next
255
+ overlay.openOverlay({
256
+ relationshipsOf: next.data.fqn
257
+ })
258
+ }
259
+ }}
260
+ >
261
+ <Panel position="top-center">
262
+ <Group gap={'xs'} wrap={'nowrap'}>
263
+ <Button
264
+ leftSection={<IconArrowLeft stroke={3} size={14} />}
265
+ color="dimmed"
266
+ variant="subtle"
267
+ style={{
268
+ visibility: current > 0 ? 'visible' : 'hidden',
269
+ padding: '0.25rem 0.75rem'
270
+ }}
271
+ styles={{
272
+ label: {
273
+ fontWeight: 400
274
+ },
275
+ section: {
276
+ marginInlineEnd: 6
277
+ }
278
+ }}
279
+ size="sm"
280
+ onClick={(e) => {
281
+ e.stopPropagation()
282
+ historyOps.back()
283
+ }}>
284
+ Back
285
+ </Button>
286
+ <Space w={2} />
287
+ <Group gap={'xs'} pos={'relative'} wrap="nowrap" flex={'1 0 auto'}>
288
+ <Box fz={'sm'} fw={'400'} style={{ whiteSpace: 'nowrap' }}>Relationships of</Box>
289
+ <Box flex={'1 0 auto'}>
290
+ <SelectElement
291
+ scope={scope}
292
+ subject={subject.element}
293
+ onSelect={fqn =>
294
+ overlay.openOverlay({
295
+ relationshipsOf: fqn
296
+ })}
297
+ viewId={view.id} />
298
+ </Box>
299
+ <SegmentedControl
300
+ flex={'1 0 auto'}
301
+ size="xs"
302
+ withItemsBorders={false}
303
+ value={scope}
304
+ onChange={setScope as any}
305
+ data={[
306
+ { label: 'Global', value: 'global' },
307
+ { label: 'Current view', value: 'view', disabled: !viewIncludesSubject }
308
+ ]}
309
+ />
310
+ {showSubjectWarning && (
311
+ <Box
312
+ pos={'absolute'}
313
+ top={'calc(100% + .5rem)'}
314
+ left={'50%'}
315
+ w={'max-content'}
316
+ style={{
317
+ transform: 'translateX(-50%)',
318
+ textAlign: 'center',
319
+ cursor: 'pointer'
320
+ }}
321
+ onClick={(e) => {
322
+ e.stopPropagation()
323
+ setScope('global')
324
+ }}
325
+ >
326
+ <Text fw={500} size="xs" c="orange" component="div">
327
+ Current view doesn't include selected element, switched to Global
328
+ </Text>
329
+ </Box>
330
+ )}
331
+ </Group>
332
+ <Button
333
+ rightSection={<IconArrowRight stroke={3} size={14} />}
334
+ color="dimmed"
335
+ variant="subtle"
336
+ style={{
337
+ visibility: current + 1 < history.length ? 'visible' : 'hidden',
338
+ padding: '0.25rem 0.75rem'
339
+ }}
340
+ styles={{
341
+ label: {
342
+ fontWeight: 400
343
+ },
344
+ section: {
345
+ marginInlineStart: 4
346
+ }
347
+ }}
348
+ size="sm"
349
+ onClick={(e) => {
350
+ e.stopPropagation()
351
+ historyOps.forward()
352
+ }}>
353
+ Forward
354
+ </Button>
355
+ </Group>
356
+ </Panel>
357
+ </ReactFlow>
358
+ )
359
+ })
@@ -0,0 +1,18 @@
1
+ import { style } from '@vanilla-extract/css'
2
+ import { mantine, whereDark } from '../../theme-vars'
3
+
4
+ export const node = style({
5
+ margin: 0
6
+ })
7
+
8
+ export const label = style({
9
+ ':hover': {
10
+ backgroundColor: mantine.colors.gray[0]
11
+ },
12
+ selectors: {
13
+ [`${whereDark} &:hover`]: {
14
+ backgroundColor: mantine.colors.defaultHover,
15
+ color: mantine.colors.white
16
+ }
17
+ }
18
+ })
@@ -0,0 +1,133 @@
1
+ import { ancestorsFqn, type Element, type Fqn, type ViewID } from '@likec4/core'
2
+ import {
3
+ ActionIcon,
4
+ Box,
5
+ Button,
6
+ Group,
7
+ Popover,
8
+ PopoverDropdown,
9
+ PopoverTarget,
10
+ ScrollAreaAutosize,
11
+ Text,
12
+ Tree,
13
+ useComputedColorScheme,
14
+ useTree
15
+ } from '@mantine/core'
16
+ import { IconChevronRight, IconSelector } from '@tabler/icons-react'
17
+ import { useEffect, useRef } from 'react'
18
+ import { useLikeC4ElementsTree } from '../../likec4model'
19
+ import * as classes from './SelectElement.css'
20
+
21
+ export const SelectElement = ({
22
+ subject,
23
+ viewId,
24
+ scope,
25
+ onSelect
26
+ }: {
27
+ subject: Element
28
+ viewId: ViewID
29
+ scope: 'global' | 'view'
30
+ onSelect: (id: Fqn) => void
31
+ }) => {
32
+ const viewport = useRef<HTMLDivElement>(null)
33
+ const data = useLikeC4ElementsTree(scope === 'view' ? viewId : undefined)
34
+ const tree = useTree({
35
+ multiple: false
36
+ })
37
+
38
+ useEffect(() => {
39
+ ancestorsFqn(subject.id).reverse().forEach(id => {
40
+ tree.expand(id)
41
+ })
42
+ tree.select(subject.id)
43
+ }, [subject.id])
44
+
45
+ const theme = useComputedColorScheme()
46
+
47
+ return (
48
+ <Popover
49
+ position="bottom"
50
+ shadow="md"
51
+ keepMounted
52
+ withinPortal={false}
53
+ closeOnClickOutside
54
+ clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
55
+ onOpen={() => {
56
+ setTimeout(() => {
57
+ const item = viewport.current?.querySelector(`[data-value="${subject.id}"]`)
58
+ console.log('item', item)
59
+ item?.scrollIntoView({ behavior: 'instant', block: 'nearest' })
60
+ }, 100)
61
+ }}
62
+ >
63
+ <PopoverTarget>
64
+ <Button
65
+ size="xs"
66
+ variant="light"
67
+ color={theme === 'light' ? 'dark' : 'gray'}
68
+ fw={'500'}
69
+ maw={250}
70
+ style={{ padding: '0.25rem 0.75rem' }}
71
+ rightSection={<IconSelector size={16} />}
72
+ >
73
+ {subject.title}
74
+ </Button>
75
+ </PopoverTarget>
76
+ <PopoverDropdown p={0} miw={250} maw={400}>
77
+ <ScrollAreaAutosize mah={'70vh'} scrollbars="y" type="never" viewportRef={viewport}>
78
+ <Tree
79
+ allowRangeSelection={false}
80
+ selectOnClick={false}
81
+ tree={tree}
82
+ data={data}
83
+ classNames={classes}
84
+ levelOffset={8}
85
+ styles={{
86
+ label: {
87
+ paddingTop: 5,
88
+ paddingBottom: 6
89
+ }
90
+ }}
91
+ renderNode={({ node, selected, expanded, elementProps, hasChildren }) => (
92
+ <Group gap={2} wrap="nowrap" {...elementProps} py="3">
93
+ <ActionIcon
94
+ variant="subtle"
95
+ size={18}
96
+ // color={theme === 'light' ? 'gray' : 'gray'}
97
+ c={'dimmed'}
98
+ style={{
99
+ visibility: hasChildren ? 'visible' : 'hidden'
100
+ }}>
101
+ <IconChevronRight
102
+ stroke={3.5}
103
+ style={{
104
+ transition: 'transform 150ms ease',
105
+ transform: `rotate(${expanded ? '90deg' : '0'})`,
106
+ width: '80%'
107
+ }} />
108
+ </ActionIcon>
109
+ <Box
110
+ flex={'1 1 100%'}
111
+ w={'100%'}
112
+ // gap={3}
113
+ onClick={e => {
114
+ e.stopPropagation()
115
+ onSelect(node.value as Fqn)
116
+ tree.select(node.value)
117
+ tree.expand(node.value)
118
+ }}>
119
+ <Text
120
+ fz="sm"
121
+ fw={selected ? '600' : '400'}
122
+ truncate="end">
123
+ {node.label}
124
+ </Text>
125
+ </Box>
126
+ </Group>
127
+ )}
128
+ />
129
+ </ScrollAreaAutosize>
130
+ </PopoverDropdown>
131
+ </Popover>
132
+ )
133
+ }
@@ -0,0 +1,56 @@
1
+ import type { ComputedNode, Fqn, Relation, ViewID } from '@likec4/core'
2
+ import type { Edge as ReactFlowEdge, Node as ReactFlowNode, ReactFlowInstance } from '@xyflow/react'
3
+ import type { SetRequired } from 'type-fest'
4
+
5
+ export namespace XYFlowTypes {
6
+ /**
7
+ * Handle in ReactFlow terms
8
+ */
9
+ export type Port = {
10
+ id: string
11
+ type: 'in' | 'out'
12
+ }
13
+
14
+ type NodeProps = {
15
+ // level: number
16
+ column: 'incomers' | 'subject' | 'outgoers'
17
+ fqn: Fqn
18
+ existsInCurrentView: boolean
19
+ element: Pick<ComputedNode, 'color' | 'title' | 'description' | 'shape' | 'kind'>
20
+ ports: {
21
+ left: Port[]
22
+ right: Port[]
23
+ }
24
+ navigateTo: ViewID | null
25
+ hovered?: boolean
26
+ dimmed?: boolean
27
+ }
28
+
29
+ export type ElementNode = SetRequired<ReactFlowNode<NodeProps, 'element'>, 'type'>
30
+
31
+ export type CompoundNode = SetRequired<ReactFlowNode<NodeProps, 'compound'>, 'type'>
32
+
33
+ export type NonEmptyNode = ElementNode | CompoundNode
34
+
35
+ export type EmptyNode = SetRequired<
36
+ ReactFlowNode<{
37
+ column: 'incomers' | 'outgoers'
38
+ hovered?: boolean
39
+ dimmed?: boolean
40
+ }, 'empty'>,
41
+ 'type'
42
+ >
43
+
44
+ export type Node = NonEmptyNode | EmptyNode
45
+
46
+ export type Edge = Omit<ReactFlowEdge, 'data' | 'type'> & {
47
+ data: {
48
+ relations: [Relation, ...Relation[]]
49
+ hovered?: boolean
50
+ dimmed?: boolean
51
+ }
52
+ type: 'relation'
53
+ }
54
+
55
+ export type Instance = ReactFlowInstance<Node, Edge>
56
+ }
@@ -0,0 +1,34 @@
1
+ import { createVar, globalStyle, style } from '@vanilla-extract/css'
2
+ import { vars, xyvars } from '../../theme-vars'
3
+
4
+ const mixColor = createVar('mix-color')
5
+
6
+ export const root = 'likec4-relationships-xyflow'
7
+
8
+ export const overlay = style({
9
+ vars: {
10
+ [mixColor]: `black`,
11
+ [xyvars.edge.stroke]: vars.relation.lineColor,
12
+ [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
13
+ [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 50%)`,
14
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 30%)`
15
+ }
16
+ })
17
+
18
+ globalStyle(`:where([data-mantine-color-scheme="dark"]) ${overlay}`, {
19
+ vars: {
20
+ [mixColor]: `white`,
21
+ [xyvars.edge.labelColor]: vars.relation.labelColor,
22
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
23
+ }
24
+ })
25
+
26
+ globalStyle(`:where(${overlay}) .react-flow__attribution`, {
27
+ display: 'none'
28
+ })
29
+ globalStyle(`:where(${overlay}) .react-flow__edge-text`, {
30
+ fontSize: 14
31
+ })
32
+ globalStyle(`:where(${overlay}) .react-flow__node-compound`, {
33
+ mixBlendMode: 'hard-light'
34
+ })