@likec4/diagram 1.0.2 → 1.1.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 (50) hide show
  1. package/dist/KeepAspectRatioContainer.d.ts +9 -0
  2. package/dist/LikeC4Diagram.css.d.ts +1 -0
  3. package/dist/LikeC4Diagram.d.ts +1 -1
  4. package/dist/LikeC4Diagram.props.d.ts +16 -11
  5. package/dist/index.mjs +639 -762
  6. package/dist/state/DiagramContext.d.ts +54 -18
  7. package/dist/state/diagramStore.d.ts +61 -21
  8. package/dist/state/useDiagramStore.d.ts +54 -18
  9. package/dist/style.css +690 -79
  10. package/dist/ui/DiagramTitlePanel.css.d.ts +4 -0
  11. package/dist/ui/DiagramTitlePanel.d.ts +2 -0
  12. package/dist/ui/OptionsPanel.d.ts +1 -2
  13. package/dist/ui/index.d.ts +0 -1
  14. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +1 -0
  15. package/dist/xyflow/{XYFlowWrapper.d.ts → XYFlow.d.ts} +2 -2
  16. package/dist/xyflow/XYFlowEvents.d.ts +133 -13
  17. package/dist/xyflow/edges/RelationshipEdge.d.ts +2 -2
  18. package/dist/xyflow/edges/edges.css.d.ts +1 -0
  19. package/dist/xyflow/hooks/useXYFlow.d.ts +20 -20
  20. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +7 -5
  21. package/dist/xyflow/types.d.ts +2 -2
  22. package/package.json +5 -3
  23. package/src/{ui/KeepAspectRatio.tsx → KeepAspectRatioContainer.tsx} +7 -6
  24. package/src/LikeC4Diagram.css.ts +8 -1
  25. package/src/LikeC4Diagram.props.ts +17 -12
  26. package/src/LikeC4Diagram.tsx +9 -6
  27. package/src/StaticLikeC4Diagram.tsx +1 -0
  28. package/src/globals.css.ts +48 -38
  29. package/src/state/diagramStore.ts +125 -68
  30. package/src/ui/DiagramTitlePanel.css.ts +32 -0
  31. package/src/ui/DiagramTitlePanel.tsx +139 -0
  32. package/src/ui/OptionsPanel.tsx +4 -23
  33. package/src/ui/index.ts +0 -1
  34. package/src/ui/options/NodeOptions.tsx +107 -30
  35. package/src/xyflow/FitviewOnDiagramChange.tsx +26 -20
  36. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +57 -0
  37. package/src/xyflow/SyncWithDiagram.tsx +35 -26
  38. package/src/xyflow/{XYFlowWrapper.tsx → XYFlow.tsx} +24 -27
  39. package/src/xyflow/XYFlowEvents.tsx +175 -81
  40. package/src/xyflow/diagram-to-xyflow.ts +4 -4
  41. package/src/xyflow/edges/RelationshipEdge.tsx +43 -47
  42. package/src/xyflow/edges/edges.css.ts +77 -36
  43. package/src/xyflow/nodes/compound/CompoundNode.css.ts +94 -44
  44. package/src/xyflow/nodes/compound/CompoundNode.tsx +29 -31
  45. package/src/xyflow/nodes/element/ElementNode.tsx +2 -2
  46. package/src/xyflow/nodes/element/element.css.ts +1 -0
  47. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +2 -1
  48. package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
  49. package/dist/ui/KeepAspectRatio.d.ts +0 -9
  50. 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 cssContainer = style({
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 bgTransparency = createVar('bgTransparency')
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 cssCompound = style({
22
+ export const compoundBody = style({
23
23
  width: '100%',
24
24
  height: '100%',
25
25
  position: 'relative',
26
- borderRadius: rem(6),
26
+ borderRadius: 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: rem(8),
28
+ padding: 8,
29
29
  background: vars.element.fill,
30
- transition: 'all 300ms ease-out',
30
+ transition: 'all 200ms ease-out',
31
31
  selectors: {
32
32
  [`:where(.react-flow__node.selected) &`]: {
33
33
  boxShadow: 'none'
@@ -40,62 +40,76 @@ export const cssCompound = style({
40
40
  }
41
41
  })
42
42
 
43
- // @ts-ignore
44
- const cssTransparentCompound = style({
45
- width: '100%',
46
- height: '100%',
47
- position: 'relative',
48
- borderRadius: rem(8),
43
+ const bgTransparency = createVar('bgTransparency')
44
+ const bgTransparencyDelta = createVar('bgTransparencyDelta')
45
+ export const transparent = style({
49
46
  borderStyle: 'dashed',
50
- borderWidth: rem(3.5),
51
- borderColor: vars.element.fill,
52
- boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.05) , 0 2px 2px -1px rgba(0,0,0,0.04)',
53
- padding: rem(8),
47
+ borderWidth: 3,
48
+ padding: 6,
49
+ borderColor: `color-mix(in srgb , ${vars.element.stroke}, transparent min(${bgTransparency}, 30%))`,
54
50
  background: `color-mix(in srgb , ${vars.element.fill}, transparent ${bgTransparency})`,
55
- transition: 'all 200ms ease-out',
51
+ backgroundClip: 'padding-box',
52
+ // transitionTimingFunction: 'ease-in',
53
+ // transition: 'all 150ms ease-in-out',
56
54
  vars: {
57
- [bgTransparency]: '90%'
55
+ [bgTransparency]: `calc(${fallbackVar(varTransparency, '80%')} - ${bgTransparencyDelta})`,
56
+ [bgTransparencyDelta]: '0%',
57
+ '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
58
58
  },
59
59
  selectors: {
60
- [`:where(${cssContainer}[data-hovered]) &`]: {
61
- transitionDelay: '300ms',
60
+ [`:where(.react-flow__node.selected) &`]: {
61
+ transitionDelay: '50ms',
62
+ // borderColor: 'transparent',
63
+ // transitionTimingFunction: 'ease-out',
62
64
  vars: {
63
- [bgTransparency]: '65%'
65
+ [bgTransparencyDelta]: '5%'
64
66
  }
65
67
  },
66
- [`:where(.react-flow__node.selected) &`]: {
67
- transitionDelay: '50ms',
68
- boxShadow: 'none',
69
- borderColor: 'transparent',
68
+ [`:where([data-hovered]) &`]: {
69
+ transitionDelay: '175ms',
70
+ // transitionTimingFunction: 'ease-out',
70
71
  vars: {
71
- [bgTransparency]: '15%'
72
+ [bgTransparencyDelta]: '15%'
72
73
  }
73
- }
74
+ },
75
+
74
76
  }
75
77
  })
76
78
 
77
- export const cssTitle = style({
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: '13px',
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: rem(12),
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: rem(26)
95
+ paddingLeft: 26
95
96
  }
97
+ // [`:where([data-mantine-color-scheme='light']) &`]: {
98
+ // opacity: 1
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
+
109
+ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) ${title}`, {
110
+ opacity: 1
111
+ })
112
+
99
113
  const indicatorKeyframes = generateIdentifier('indicator')
100
114
  globalKeyframes(indicatorKeyframes, {
101
115
  'from': {
@@ -106,7 +120,9 @@ globalKeyframes(indicatorKeyframes, {
106
120
  }
107
121
  })
108
122
 
109
- export const cssIndicator = style({
123
+ const indicatorStroke = createVar('indicator-stroke')
124
+ const indicatorStrokeWidth = createVar('indicator-stroke-width')
125
+ export const indicator = style({
110
126
  position: 'absolute',
111
127
  top: '0',
112
128
  left: '0',
@@ -115,17 +131,32 @@ export const cssIndicator = style({
115
131
  pointerEvents: 'none',
116
132
  overflow: 'visible',
117
133
  visibility: 'hidden',
134
+ vars: {
135
+ [indicatorStroke]: vars.element.loContrast,
136
+ [indicatorStrokeWidth]: '6'
137
+ },
118
138
  selectors: {
119
139
  ':where(.react-flow__node.selected:not(:focus-visible)) &': {
120
140
  visibility: 'visible'
141
+ },
142
+ ':where(.react-flow__node:focus-within:not(.selected)) &': {
143
+ vars: {
144
+ [indicatorStroke]: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
145
+ [indicatorStrokeWidth]: '8'
146
+ }
147
+ },
148
+ [`:where([data-mantine-color-scheme='light']) &`]: {
149
+ vars: {
150
+ [indicatorStroke]: `color-mix(in srgb, ${vars.element.stroke} 80%, #000)`
151
+ }
121
152
  }
122
153
  }
123
154
  })
124
155
 
125
- globalStyle(`${cssIndicator} rect`, {
126
- stroke: vars.element.loContrast,
156
+ globalStyle(`${indicator} rect`, {
157
+ stroke: indicatorStroke,
127
158
  transformOrigin: 'center center',
128
- strokeWidth: 6,
159
+ strokeWidth: indicatorStrokeWidth,
129
160
  animationDuration: '800ms',
130
161
  animationName: indicatorKeyframes,
131
162
  animationIterationCount: 'infinite',
@@ -133,18 +164,30 @@ globalStyle(`${cssIndicator} rect`, {
133
164
  fill: 'none'
134
165
  })
135
166
 
136
- globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${cssIndicator} rect`, {
137
- stroke: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
138
- strokeWidth: 8
167
+ // globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${indicator} rect`, {
168
+ // stroke: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
169
+ // strokeWidth: 8
170
+ // })
171
+ const navigateBtnColor = createVar('navigateBtnColor')
172
+
173
+ globalStyle(`${container}`, {
174
+ vars: {
175
+ [navigateBtnColor]: vars.element.loContrast
176
+ }
177
+ })
178
+ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent)`, {
179
+ vars: {
180
+ [navigateBtnColor]: vars.element.stroke
181
+ }
139
182
  })
140
183
 
141
- export const cssNavigateBtn = style({
184
+ export const navigateBtn = style({
142
185
  position: 'absolute',
143
186
  pointerEvents: 'all',
144
187
  left: 3,
145
188
  top: 7,
146
189
  cursor: 'pointer',
147
- color: vars.element.loContrast,
190
+ color: `var(--_compound-title-color,${navigateBtnColor})`,
148
191
  transformOrigin: '90% 70%',
149
192
  opacity: 0.75,
150
193
  transition: 'all 150ms ease-out',
@@ -167,7 +210,14 @@ export const cssNavigateBtn = style({
167
210
  transitionDelay: '0'
168
211
  },
169
212
  selectors: {
170
- [`:where(.react-flow__node:not(.dragging) ${cssContainer}:hover) &:not(:hover)`]: {
213
+ [`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) &`]: {
214
+ opacity: 0.85,
215
+ vars: {
216
+ '--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 50%)`,
217
+ '--ai-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 20%)`
218
+ }
219
+ },
220
+ [`:where(.react-flow__node:not(.dragging) ${container}:hover) &:not(:hover)`]: {
171
221
  boxShadow: mantine.shadows.lg,
172
222
  transform: 'scale(1.223)',
173
223
  opacity: 1,
@@ -178,7 +228,7 @@ export const cssNavigateBtn = style({
178
228
  }
179
229
  }
180
230
  })
181
- globalStyle(`${cssNavigateBtn} svg.icon`, {
231
+ globalStyle(`${navigateBtn} svg.icon`, {
182
232
  width: '70%',
183
233
  height: '70%',
184
234
  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 { scale, toHex } from 'khroma'
8
- import { memo, useMemo } from 'react'
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 { cssCompound, cssContainer, cssIndicator, cssNavigateBtn, cssTitle } from './CompoundNode.css'
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 = 0, ...compound } = element
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(cssContainer, 'likec4-compound-node')}
65
- style={assignInlineVars({ fill: vars.element.fill, stroke: vars.element.stroke }, {
66
- fill: colors.fill,
67
- stroke: colors.stroke
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={cssIndicator}>
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 className={clsx(cssCompound, 'likec4-compound')}>
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(cssTitle, 'likec4-compound-title')}>
91
+ className={clsx(css.title, 'likec4-compound-title')}>
94
92
  {compound.title}
95
93
  </Text>
96
94
  </div>
97
- {isnavigable && <NavigateToBtn xynodeId={id} className={cssNavigateBtn} />}
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 (isInteractive || hasOnNavigateTo) && isHovered:
102
+ case isInteractive && isHovered:
103
103
  animate = 'hovered'
104
104
  break
105
105
  case selected:
@@ -13,6 +13,7 @@ export const container = style({
13
13
  height: '100%',
14
14
  padding: 0,
15
15
  margin: 0,
16
+ backfaceVisibility: 'hidden',
16
17
  vars: {
17
18
  [stokeFillMix]: `color-mix(in srgb, ${vars.element.stroke} 90%, ${vars.element.fill})`
18
19
  }
@@ -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 {};
@@ -1,7 +0,0 @@
1
- import { style } from '@vanilla-extract/css'
2
-
3
- export const wrapper = style({
4
- width: '100%',
5
- height: '100%',
6
- padding: 0
7
- })