@likec4/diagram 1.20.1 → 1.20.3

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 (134) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +1 -0
  2. package/dist/LikeC4Diagram.css.js +3 -1
  3. package/dist/base/BaseXYFlow.js +15 -14
  4. package/dist/base/Link.css.d.ts +3 -0
  5. package/dist/base/Link.css.js +9 -0
  6. package/dist/base/Link.d.ts +5 -0
  7. package/dist/base/Link.js +58 -0
  8. package/dist/base/const.d.ts +1 -0
  9. package/dist/base/const.js +2 -1
  10. package/dist/base/example/custom.d.ts +1 -1
  11. package/dist/base/index.d.ts +1 -0
  12. package/dist/base/primitives/compound/CompoundActionButton.js +2 -1
  13. package/dist/base/primitives/compound/CompoundDetailsButton.js +2 -1
  14. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
  15. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -6
  16. package/dist/base/primitives/compound/CompoundNodeContainer.js +2 -2
  17. package/dist/base/primitives/customEdge.d.ts +1 -1
  18. package/dist/base/primitives/customEdge.js +1 -1
  19. package/dist/base/primitives/customNode.js +1 -1
  20. package/dist/base/primitives/edge/EdgeLabel.js +3 -1
  21. package/dist/base/primitives/edge/EdgePath.d.ts +2 -1
  22. package/dist/base/primitives/edge/EdgePath.js +2 -0
  23. package/dist/base/primitives/element/ElementActionButtons.js +2 -1
  24. package/dist/base/primitives/element/ElementDetailsButton.js +2 -1
  25. package/dist/base/primitives/element/ElementNodeContainer.css.d.ts +0 -1
  26. package/dist/base/primitives/element/ElementNodeContainer.css.js +1 -3
  27. package/dist/base/primitives/element/ElementNodeContainer.js +2 -3
  28. package/dist/base/primitives/element/ElementShape.js +4 -2
  29. package/dist/base/primitives/element/ElementTitle.js +2 -1
  30. package/dist/base/updateEdges.js +3 -3
  31. package/dist/base/updateNodes.js +2 -2
  32. package/dist/context/FramerMotionConfig.js +1 -1
  33. package/dist/hooks/useDiagram.d.ts +313 -90
  34. package/dist/hooks/useDiagramActor.d.ts +2 -2
  35. package/dist/hooks/useDiagramContext.d.ts +1 -1
  36. package/dist/hooks/useXYFlow.d.ts +2 -1
  37. package/dist/hooks/useXYFlow.js +5 -0
  38. package/dist/likec4diagram/XYFlow.js +12 -10
  39. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +1 -1
  40. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -2
  41. package/dist/likec4diagram/custom/index.d.ts +1 -1
  42. package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
  43. package/dist/likec4diagram/state/actorContext.d.ts +1438 -364
  44. package/dist/likec4diagram/state/assign.js +2 -2
  45. package/dist/likec4diagram/state/machine.d.ts +321 -93
  46. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +1 -1
  47. package/dist/likec4diagram/types.d.ts +1 -0
  48. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +4 -3
  49. package/dist/likec4diagram/useLayoutConstraints.d.ts +2 -1
  50. package/dist/likec4diagram/useLayoutConstraints.js +14 -15
  51. package/dist/likec4diagram/useViewToNodesEdges.js +3 -1
  52. package/dist/overlays/element-details/ElementDetailsCard.css.d.ts +0 -1
  53. package/dist/overlays/element-details/ElementDetailsCard.css.js +1 -3
  54. package/dist/overlays/element-details/ElementDetailsCard.js +5 -30
  55. package/dist/overlays/element-details/TabPanelRelationships.js +2 -2
  56. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -4
  57. package/dist/overlays/relationship-details/custom/ElementActions.js +1 -3
  58. package/dist/overlays/relationship-details/custom/edgeTypes.d.ts +1 -1
  59. package/dist/overlays/relationship-details/hooks.d.ts +2 -2
  60. package/dist/overlays/relationship-details/hooks.js +7 -7
  61. package/dist/overlays/relationship-details/useLayoutedDetails.js +7 -10
  62. package/dist/overlays/relationships-browser/-useRelationshipsView.d.ts +4 -1
  63. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -11
  64. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +81 -66
  65. package/dist/overlays/relationships-browser/_types.d.ts +2 -1
  66. package/dist/overlays/relationships-browser/actor.d.ts +15 -2
  67. package/dist/overlays/relationships-browser/actor.js +262 -65
  68. package/dist/overlays/relationships-browser/const.d.ts +1 -0
  69. package/dist/overlays/relationships-browser/const.js +4 -0
  70. package/dist/overlays/relationships-browser/custom/ElementActions.js +13 -3
  71. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -2
  72. package/dist/overlays/relationships-browser/custom/edgeTypes.js +24 -6
  73. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -2
  74. package/dist/overlays/relationships-browser/hooks.d.ts +6 -3
  75. package/dist/overlays/relationships-browser/hooks.js +4 -2
  76. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +7 -2
  77. package/dist/style.css +1 -1
  78. package/dist/utils/xyflow.js +9 -1
  79. package/package.json +10 -10
  80. package/src/LikeC4Diagram.css.ts +8 -0
  81. package/src/base/BaseXYFlow.tsx +23 -14
  82. package/src/base/Link.css.ts +49 -0
  83. package/src/base/Link.tsx +66 -0
  84. package/src/base/const.ts +1 -0
  85. package/src/base/index.ts +2 -0
  86. package/src/base/primitives/compound/CompoundActionButton.tsx +2 -1
  87. package/src/base/primitives/compound/CompoundDetailsButton.tsx +2 -1
  88. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +16 -10
  89. package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -1
  90. package/src/base/primitives/customEdge.ts +2 -0
  91. package/src/base/primitives/customNode.ts +3 -0
  92. package/src/base/primitives/edge/EdgeLabel.tsx +3 -1
  93. package/src/base/primitives/edge/EdgePath.tsx +3 -0
  94. package/src/base/primitives/element/ElementActionButtons.tsx +2 -1
  95. package/src/base/primitives/element/ElementDetailsButton.tsx +2 -1
  96. package/src/base/primitives/element/ElementNodeContainer.css.ts +9 -4
  97. package/src/base/primitives/element/ElementNodeContainer.tsx +1 -2
  98. package/src/base/primitives/element/ElementShape.tsx +4 -3
  99. package/src/base/primitives/element/ElementTitle.tsx +2 -1
  100. package/src/base/updateEdges.ts +3 -2
  101. package/src/base/updateNodes.ts +3 -2
  102. package/src/context/DiagramFeatures.tsx +1 -0
  103. package/src/context/FramerMotionConfig.tsx +1 -1
  104. package/src/hooks/useDiagramActor.ts +7 -5
  105. package/src/hooks/useDiagramContext.ts +4 -1
  106. package/src/hooks/useXYFlow.ts +6 -1
  107. package/src/likec4diagram/XYFlow.tsx +20 -9
  108. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -2
  109. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  110. package/src/likec4diagram/types.ts +1 -0
  111. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +4 -3
  112. package/src/likec4diagram/useLayoutConstraints.ts +23 -19
  113. package/src/likec4diagram/useViewToNodesEdges.ts +1 -0
  114. package/src/overlays/element-details/ElementDetailsCard.css.ts +0 -28
  115. package/src/overlays/element-details/ElementDetailsCard.tsx +2 -50
  116. package/src/overlays/element-details/TabPanelRelationships.tsx +2 -2
  117. package/src/overlays/relationship-details/RelationshipDetails.tsx +4 -4
  118. package/src/overlays/relationship-details/custom/ElementActions.tsx +1 -2
  119. package/src/overlays/relationship-details/custom/edgeTypes.tsx +0 -5
  120. package/src/overlays/relationship-details/hooks.ts +6 -6
  121. package/src/overlays/relationship-details/useLayoutedDetails.ts +7 -10
  122. package/src/overlays/relationships-browser/-useRelationshipsView.ts +16 -12
  123. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +105 -68
  124. package/src/overlays/relationships-browser/_types.ts +2 -1
  125. package/src/overlays/relationships-browser/actor.ts +321 -62
  126. package/src/overlays/relationships-browser/const.ts +1 -0
  127. package/src/overlays/relationships-browser/custom/ElementActions.tsx +13 -3
  128. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +25 -6
  129. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +3 -2
  130. package/src/overlays/relationships-browser/hooks.ts +3 -1
  131. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +8 -2
  132. package/dist/likec4diagram/ui/diagram-title/Link.js +0 -36
  133. package/dist/ui/Link.d.ts +0 -4
  134. package/src/ui/Link.tsx +0 -40
@@ -0,0 +1,49 @@
1
+ import { style } from '@vanilla-extract/css'
2
+ import { mantine } from '../theme-vars'
3
+ import { easings, whereDark } from '../theme-vars.css'
4
+
5
+ export const elementLink = style({
6
+ display: 'flex',
7
+ overflow: 'hidden',
8
+ alignItems: 'center',
9
+ gap: 3,
10
+ justifyContent: 'stretch',
11
+ // transition: transitions.fast,
12
+ border: `1px dashed ${mantine.colors.defaultBorder}`,
13
+ borderRadius: mantine.radius.sm,
14
+ minHeight: 30,
15
+ padding: '3px 16px 3px 3px',
16
+ cursor: 'pointer',
17
+ color: mantine.colors.gray[7],
18
+ ':hover': {
19
+ // transitionTimingFunction: easings.out,
20
+ borderStyle: 'solid',
21
+ color: mantine.colors.defaultColor,
22
+ background: mantine.colors.defaultHover,
23
+ },
24
+ selectors: {
25
+ [`${whereDark} &`]: {
26
+ color: mantine.colors.dark[1],
27
+ },
28
+ [`&[data-size='sm']`]: {
29
+ minHeight: 22,
30
+ padding: '2px 8px 2px 2px',
31
+ },
32
+ },
33
+ })
34
+
35
+ export const linkIcon = style({
36
+ flex: 0,
37
+ })
38
+
39
+ export const linkTitleBox = style({
40
+ flex: '1 1 100%',
41
+ transition: `transform 130ms ${easings.inOut}`,
42
+ selectors: {
43
+ [`${elementLink}:hover &`]: {
44
+ transitionTimingFunction: easings.out,
45
+ transitionDelay: '50ms',
46
+ transform: 'translateX(1px)',
47
+ },
48
+ },
49
+ })
@@ -0,0 +1,66 @@
1
+ import type { Link as LinkData } from '@likec4/core'
2
+ import { ActionIcon, Anchor, Box, CopyButton, Text } from '@mantine/core'
3
+ import { IconCheck, IconCopy } from '@tabler/icons-react'
4
+ import { stopPropagation } from '../utils'
5
+ import * as css from './Link.css'
6
+
7
+ export function Link({
8
+ value,
9
+ size = 'md',
10
+ }: {
11
+ size?: 'sm' | 'md'
12
+ value: LinkData
13
+ }) {
14
+ const isNormalSize = size === 'md'
15
+ const url = new URL(value.url, window.location.href).toString()
16
+ return (
17
+ <CopyButton value={url}>
18
+ {({ copied, copy }) => (
19
+ <Anchor
20
+ href={url}
21
+ target="_blank"
22
+ underline="never"
23
+ className={css.elementLink}
24
+ data-size={size}
25
+ onClick={stopPropagation}>
26
+ <ActionIcon
27
+ className={css.linkIcon}
28
+ tabIndex={-1}
29
+ size={isNormalSize ? 24 : 20}
30
+ variant={copied ? 'light' : 'subtle'}
31
+ color={copied ? 'teal' : 'gray'}
32
+ onClick={e => {
33
+ e.stopPropagation()
34
+ e.preventDefault()
35
+ copy()
36
+ }}
37
+ >
38
+ {copied ? <IconCheck /> : <IconCopy style={{ width: '65%', opacity: 0.65 }} />}
39
+ </ActionIcon>
40
+ <Box className={css.linkTitleBox}>
41
+ <Text
42
+ component="div"
43
+ fz={isNormalSize ? 'xs' : 11}
44
+ truncate
45
+ lh={isNormalSize ? 1.3 : 1.2}
46
+ fw={value.title ? 500 : 400}>
47
+ {value.title || url}
48
+ </Text>
49
+ {value.title && (
50
+ <Text component="div" fz={isNormalSize ? 10 : 9} c={'dimmed'} lh={isNormalSize ? 1.2 : 1.1} truncate>
51
+ {url}
52
+ </Text>
53
+ )}
54
+ </Box>
55
+ </Anchor>
56
+ )}
57
+ </CopyButton>
58
+ )
59
+ // <Anchor href={value.url} fz={'sm'}>
60
+ // {value.title || url}
61
+ // </Anchor><Button variant='default' size='sm'>
62
+ // {value.title || value.url}
63
+ // </Button>
64
+ // </Box>
65
+ // )
66
+ }
package/src/base/const.ts CHANGED
@@ -2,6 +2,7 @@ export const ZIndexes = {
2
2
  Compound: 2,
3
3
  Edge: 4,
4
4
  Element: 6,
5
+ Max: 8,
5
6
  } as const
6
7
 
7
8
  export const MinZoom = 0.1
package/src/base/index.ts CHANGED
@@ -8,3 +8,5 @@ export {
8
8
  type ReactFlowEdge,
9
9
  type ReactFlowNode,
10
10
  } from './types'
11
+
12
+ export { Link } from './Link'
@@ -2,6 +2,7 @@ import { ActionIcon, Box } from '@mantine/core'
2
2
  import { IconZoomScan } from '@tabler/icons-react'
3
3
  import clsx from 'clsx'
4
4
  import { m } from 'framer-motion'
5
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
5
6
  import { stopPropagation } from '../../../utils/xyflow'
6
7
  import type { NodeProps } from '../../types'
7
8
  import * as css from './CompoundActionButton.css'
@@ -19,7 +20,7 @@ export function CompoundActionButton({
19
20
  onClick,
20
21
  }: CompoundActionButtonProps) {
21
22
  return (
22
- <Box className={clsx(css.container, 'compound-action')}>
23
+ <Box className={clsx(css.container, hiddenIfZoomTooSmall, 'compound-action')}>
23
24
  <Box
24
25
  className={clsx('nodrag nopan', css.actionButton)}
25
26
  component={m.div}
@@ -2,6 +2,7 @@ import { ActionIcon, Box } from '@mantine/core'
2
2
  import { IconId } from '@tabler/icons-react'
3
3
  import clsx from 'clsx'
4
4
  import { m } from 'framer-motion'
5
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
5
6
  import { stopPropagation } from '../../../utils/xyflow'
6
7
  import type { NodeProps } from '../../types'
7
8
  import * as css from './CompoundDetailsButton.css'
@@ -19,7 +20,7 @@ export function CompoundDetailsButton({
19
20
  onClick,
20
21
  }: CompoundDetailsButtonProps) {
21
22
  return (
22
- <Box className={clsx(css.container, 'details-button')} onClick={stopPropagation}>
23
+ <Box className={clsx(css.container, hiddenIfZoomTooSmall, 'details-button')} onClick={stopPropagation}>
23
24
  <Box
24
25
  component={m.div}
25
26
  className={clsx('nodrag nopan', css.actionIconWrapper)}
@@ -1,7 +1,7 @@
1
1
  import { rem } from '@mantine/core'
2
2
  import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
3
  import { calc } from '@vanilla-extract/css-utils'
4
- import { easings, mantine, transitions, vars, whereLight } from '../../../theme-vars'
4
+ import { easings, mantine, vars, whereLight } from '../../../theme-vars'
5
5
 
6
6
  export const varCompoundOpacity = createVar('opacity')
7
7
  export const varBorderRadius = createVar('borderRadius')
@@ -9,15 +9,6 @@ export const varBorderTransparency = createVar('borderTransparency')
9
9
  export const varBorderColor = createVar('border-color')
10
10
  const varBorderWidth = createVar('border-width')
11
11
 
12
- export const dimmed = style({})
13
- globalStyle(`.react-flow__node:has(${dimmed})`, {
14
- opacity: 0.25,
15
- transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
16
- transitionDelay: '200ms',
17
- filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
18
- willChange: 'opacity, filter',
19
- })
20
-
21
12
  export const container = style({
22
13
  position: 'relative',
23
14
  width: '100%',
@@ -31,6 +22,21 @@ export const container = style({
31
22
  },
32
23
  })
33
24
 
25
+ globalStyle(`.react-flow__node:has(${container}[data-likec4-dimmed="true"])`, {
26
+ opacity: 0.25,
27
+ transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
28
+ transitionDelay: '200ms',
29
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
30
+ willChange: 'opacity, filter',
31
+ })
32
+
33
+ globalStyle(`.react-flow__node:has(${container}[data-likec4-dimmed="immediate"])`, {
34
+ opacity: 0.25,
35
+ transition: 'opacity 100ms ease-in-out, filter 100ms ease-in-out',
36
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(1px)')}`,
37
+ willChange: 'opacity, filter',
38
+ })
39
+
34
40
  const outlineColor = fallbackVar(
35
41
  mantine.colors.primaryColors.outline,
36
42
  mantine.colors.primaryColors.filled,
@@ -54,7 +54,6 @@ export const CompoundNodeContainer = createPolymorphicComponent<'div', CompoundN
54
54
  component={m.div}
55
55
  className={clsx([
56
56
  css.container,
57
- isDimmed && css.dimmed,
58
57
  'likec4-compound-node',
59
58
  ])}
60
59
  initial={false}
@@ -62,6 +61,7 @@ export const CompoundNodeContainer = createPolymorphicComponent<'div', CompoundN
62
61
  data-likec4-color={data.color}
63
62
  data-compound-depth={data.depth ?? 1}
64
63
  data-compound-transparent={isTransparent}
64
+ data-likec4-dimmed={isDimmed}
65
65
  data-likec4-shape={data.shape}
66
66
  style={assignInlineVars({
67
67
  [css.varCompoundOpacity]: opacity.toFixed(2),
@@ -17,6 +17,8 @@ export const edgePropsEqual = <P extends Record<string, unknown> = Base.EdgeData
17
17
  && eq(prev.target, next.target)
18
18
  && eq(prev.targetHandleId ?? null, next.targetHandleId ?? null)
19
19
  && eq(prev.selected ?? false, next.selected ?? false)
20
+ && eq(prev.sourcePosition, next.sourcePosition)
21
+ && eq(prev.targetPosition, next.targetPosition)
20
22
  && isSame(prev.sourceX, next.sourceX)
21
23
  && isSame(prev.sourceY, next.sourceY)
22
24
  && isSame(prev.targetX, next.targetX)
@@ -11,6 +11,9 @@ export const nodePropsEqual = <P extends Record<string, unknown> = Base.NodeData
11
11
  && eq(prev.dragging ?? false, next.dragging ?? false)
12
12
  && eq(prev.width ?? 0, next.width ?? 0)
13
13
  && eq(prev.height ?? 0, next.height ?? 0)
14
+ && eq(prev.zIndex, next.zIndex)
15
+ && eq(prev.positionAbsoluteX, next.positionAbsoluteX)
16
+ && eq(prev.positionAbsoluteY, next.positionAbsoluteY)
14
17
  && eq(prev.data, next.data)
15
18
  )
16
19
 
@@ -6,6 +6,7 @@ import clsx from 'clsx'
6
6
  import { type PropsWithChildren, forwardRef } from 'react'
7
7
  import { isNumber, isTruthy } from 'remeda'
8
8
  import type { UndefinedOnPartialDeep } from 'type-fest'
9
+ import { useIsZoomTooSmall } from '../../../hooks/useXYFlow'
9
10
  import { ZIndexes } from '../../const'
10
11
  import type { EdgeProps } from '../../types'
11
12
  import * as css from './EdgeLabel.css'
@@ -59,6 +60,7 @@ export const EdgeLabel = createPolymorphicComponent<'div', EdgeLabelProps>(
59
60
  children,
60
61
  ...rest
61
62
  }, ref) => {
63
+ const isZoomTooSmall = useIsZoomTooSmall()
62
64
  const stepNum = isStepEdgeId(id) ? extractStep(id) : null
63
65
 
64
66
  let zIndex = ZIndexes.Edge
@@ -70,7 +72,7 @@ export const EdgeLabel = createPolymorphicComponent<'div', EdgeLabelProps>(
70
72
  let labelX = labelXY?.x ?? labelBBox?.x,
71
73
  labelY = labelXY?.y ?? labelBBox?.y
72
74
 
73
- if (labelX === undefined || labelY === undefined) {
75
+ if (labelX === undefined || labelY === undefined || isZoomTooSmall) {
74
76
  return null
75
77
  }
76
78
 
@@ -18,6 +18,7 @@ type Data = UndefinedOnPartialDeep<
18
18
 
19
19
  type EdgePathProps = EdgeProps<Data> & {
20
20
  svgPath: string
21
+ strokeWidth?: number
21
22
  onEdgePointerDown?: PointerEventHandler<SVGGElement> | undefined
22
23
  }
23
24
 
@@ -29,6 +30,7 @@ export const EdgePath = forwardRef<SVGPathElement, EdgePathProps>(({
29
30
  tail,
30
31
  head,
31
32
  },
33
+ strokeWidth,
32
34
  svgPath,
33
35
  style,
34
36
  interactionWidth,
@@ -78,6 +80,7 @@ export const EdgePath = forwardRef<SVGPathElement, EdgePathProps>(({
78
80
  className={clsx('react-flow__edge-path', css.cssEdgePath)}
79
81
  d={svgPath}
80
82
  style={style}
83
+ strokeWidth={strokeWidth}
81
84
  strokeLinecap={'round'}
82
85
  strokeDasharray={strokeDasharray}
83
86
  markerStart={MarkerStart ? `url(#start${id})` : undefined}
@@ -1,6 +1,7 @@
1
1
  import { ActionIcon, Box } from '@mantine/core'
2
2
  import clsx from 'clsx'
3
3
  import { m } from 'framer-motion'
4
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
4
5
  import { stopPropagation } from '../../../utils/xyflow'
5
6
  import type { NodeProps } from '../../types'
6
7
  import * as css from './ElementActionButtons.css'
@@ -21,7 +22,7 @@ export function ElementActionButtons({
21
22
  return null
22
23
  }
23
24
  return (
24
- <Box className={css.container}>
25
+ <Box className={clsx(css.container, hiddenIfZoomTooSmall)}>
25
26
  <Box
26
27
  component={m.div}
27
28
  layoutRoot
@@ -2,6 +2,7 @@ import { ActionIcon, Box } from '@mantine/core'
2
2
  import { IconId } from '@tabler/icons-react'
3
3
  import clsx from 'clsx'
4
4
  import { m } from 'framer-motion'
5
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
5
6
  import { stopPropagation } from '../../../utils/xyflow'
6
7
  import type { NodeProps } from '../../types'
7
8
  import * as css from './ElementDetailsButton.css'
@@ -20,7 +21,7 @@ export function ElementDetailsButton({
20
21
  onClick,
21
22
  }: ElementDetailsButtonProps) {
22
23
  return (
23
- <Box className={clsx(css.container, 'details-button')}>
24
+ <Box className={clsx(css.container, hiddenIfZoomTooSmall, 'details-button')}>
24
25
  <Box
25
26
  className={clsx('nodrag nopan', css.actionIconWrapper)}
26
27
  component={m.div}
@@ -1,5 +1,5 @@
1
1
  import { createVar, fallbackVar, globalStyle, style } from '@vanilla-extract/css'
2
- import { transitions, vars } from '../../../theme-vars'
2
+ import { vars } from '../../../theme-vars'
3
3
 
4
4
  export const stokeFillMix = createVar('stroke-fill-mix')
5
5
 
@@ -36,12 +36,17 @@ export const container = style({
36
36
  },
37
37
  })
38
38
 
39
- export const dimmed = style({})
40
-
41
- globalStyle(`.react-flow__node:has(${dimmed})`, {
39
+ globalStyle(`.react-flow__node:has(${container}[data-likec4-dimmed="true"])`, {
42
40
  opacity: 0.25,
43
41
  transition: 'opacity 400ms ease-in-out, filter 500ms ease-in-out',
44
42
  transitionDelay: '50ms',
45
43
  filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(2px)')}`,
46
44
  willChange: 'opacity, filter',
47
45
  })
46
+
47
+ globalStyle(`.react-flow__node:has(${container}[data-likec4-dimmed="immediate"])`, {
48
+ opacity: 0.25,
49
+ transition: 'opacity 100ms ease-in-out, filter 100ms ease-in-out',
50
+ filter: `grayscale(0.85) ${fallbackVar(vars.safariAnimationHook, 'blur(2px)')}`,
51
+ willChange: 'opacity, filter',
52
+ })
@@ -48,11 +48,9 @@ export const ElementNodeContainer = createPolymorphicComponent<'div', ElementNod
48
48
  ref={ref}
49
49
  className={clsx([
50
50
  css.container,
51
- isDimmed && css.dimmed,
52
51
  'likec4-element-node',
53
52
  ])}
54
53
  initial={false}
55
- layoutRoot
56
54
  {...selectable && {
57
55
  animate: {
58
56
  scale,
@@ -62,6 +60,7 @@ export const ElementNodeContainer = createPolymorphicComponent<'div', ElementNod
62
60
  data-hovered={isHovered}
63
61
  data-likec4-color={data.color}
64
62
  data-likec4-shape={data.shape}
63
+ data-likec4-dimmed={isDimmed}
65
64
  {...rest}
66
65
  >
67
66
  {children}
@@ -1,6 +1,7 @@
1
1
  import { type DiagramNode, type ElementShape, nonexhaustive } from '@likec4/core'
2
+ import clsx from 'clsx'
3
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
2
4
  import { toDomPrecision } from '../../../utils/xyflow'
3
- import type { NodeProps } from '../../types'
4
5
  import * as css from './ElementShape.css'
5
6
 
6
7
  export function cylinderSVGPath(diameter: number, height: number, tilt = 0.065) {
@@ -184,7 +185,7 @@ export function ElementShape(
184
185
  return (
185
186
  <>
186
187
  {isMultiple && (
187
- <svg className={css.shapeSvgMultiple} viewBox={`0 0 ${w} ${h}`}>
188
+ <svg className={clsx(css.shapeSvgMultiple, hiddenIfZoomTooSmall)} viewBox={`0 0 ${w} ${h}`}>
188
189
  <ElementShapeSvg shape={data.shape} w={w} h={h} />
189
190
  </svg>
190
191
  )}
@@ -222,5 +223,5 @@ export function SelectedIndicator({ shape, w, h }: ElementShapeProps) {
222
223
  break
223
224
  }
224
225
  }
225
- return <g className={css.indicator}>{svg}</g>
226
+ return <g className={clsx(css.indicator, hiddenIfZoomTooSmall)}>{svg}</g>
226
227
  }
@@ -4,6 +4,7 @@ import { assignInlineVars } from '@vanilla-extract/dynamic'
4
4
  import clsx from 'clsx'
5
5
  import { isNumber, isTruthy } from 'remeda'
6
6
  import { IconRenderer } from '../../../context/IconRenderer'
7
+ import { hiddenIfZoomTooSmall } from '../../../LikeC4Diagram.css'
7
8
  import type { NodeProps } from '../../types'
8
9
  import * as css from './ElementTitle.css'
9
10
 
@@ -57,7 +58,7 @@ export function ElementTitle({ id, data, iconSize }: ElementTitleProps) {
57
58
  {data.technology && (
58
59
  <Text
59
60
  component="div"
60
- className={clsx(css.technology, 'likec4-element-technology')}>
61
+ className={clsx(css.technology, hiddenIfZoomTooSmall, 'likec4-element-technology')}>
61
62
  {data.technology}
62
63
  </Text>
63
64
  )}
@@ -1,5 +1,5 @@
1
1
  import { deepEqual as eq } from 'fast-equals'
2
- import { isDefined } from 'remeda'
2
+ import { isDefined, omit } from 'remeda'
3
3
  import type { Base } from './types'
4
4
 
5
5
  function _update<E extends Base.Edge>(current: E[], update: E[]): E[] {
@@ -12,12 +12,13 @@ function _update<E extends Base.Edge>(current: E[], update: E[]): E[] {
12
12
  && eq(existing.sourceHandle ?? null, next.sourceHandle ?? null)
13
13
  && eq(existing.target, next.target)
14
14
  && eq(existing.targetHandle ?? null, next.targetHandle ?? null)
15
+ && eq(existing.zIndex ?? 0, next.zIndex ?? 0)
15
16
  && eq(existing.data, next.data)
16
17
  ) {
17
18
  return existing
18
19
  }
19
20
  return {
20
- ...existing,
21
+ ...omit(existing, ['hidden', 'zIndex']),
21
22
  ...next,
22
23
  data: {
23
24
  ...existing.data,
@@ -12,15 +12,16 @@ function _update<N extends Base.Node>(current: N[], updated: N[]): N[] {
12
12
  eq(existing.type, update.type)
13
13
  && eq(existingWidth, update.initialWidth)
14
14
  && eq(existingHeight, update.initialHeight)
15
+ && eq(existing.parentId ?? null, update.parentId ?? null)
15
16
  && eq(existing.hidden ?? false, update.hidden ?? false)
17
+ && eq(existing.zIndex ?? 0, update.zIndex ?? 0)
16
18
  && eq(existing.position, update.position)
17
19
  && eq(existing.data, update.data)
18
- && eq(existing.parentId ?? null, update.parentId ?? null)
19
20
  ) {
20
21
  return existing
21
22
  }
22
23
  return {
23
- ...omit(existing, ['measured', 'parentId']),
24
+ ...omit(existing, ['measured', 'parentId', 'hidden', 'zIndex']),
24
25
  ...update,
25
26
  // Force dimensions from update
26
27
  width: update.initialWidth,
@@ -67,6 +67,7 @@ const validate = (features: EnabledFeatures) => {
67
67
  enableSearch = false
68
68
  }
69
69
  }
70
+
70
71
  if (enableReadOnly) {
71
72
  if (rest.enableEdgeEditing) {
72
73
  console.warn('enableEdgeEditing is ignored because enabled ReadOnly')
@@ -6,7 +6,7 @@ export const FramerMotionConfig = ({ children }: PropsWithChildren) => {
6
6
  const nonce = useMantineStyleNonce()?.()
7
7
  return (
8
8
  <LazyMotion features={domMax} strict>
9
- <MotionConfig reducedMotion="user" {...nonce && { nonce }}>
9
+ <MotionConfig {...nonce && { nonce }}>
10
10
  {children}
11
11
  </MotionConfig>
12
12
  </LazyMotion>
@@ -1,8 +1,7 @@
1
1
  import { useCallbackRef } from '@mantine/hooks'
2
2
  import { useSelector as useXstateSelector } from '@xstate/react'
3
3
  import { shallowEqual } from 'fast-equals'
4
- import { useActorRef } from '../likec4diagram/state/actorContext'
5
- import { useSelector } from '../likec4diagram/state/actorContext'
4
+ import { useActorRef, useSelector } from '../likec4diagram/state/actorContext'
6
5
  import type { LikeC4ViewActorRef } from '../likec4diagram/state/machine'
7
6
  import { type MachineSnapshot } from '../likec4diagram/state/machine'
8
7
  import type { SyncLayoutActorSnapshot } from '../likec4diagram/state/syncManualLayoutActor'
@@ -10,14 +9,17 @@ import type { SyncLayoutActorSnapshot } from '../likec4diagram/state/syncManualL
10
9
  export function useDiagramActor(): LikeC4ViewActorRef {
11
10
  return useActorRef()
12
11
  }
13
- export function useDiagramActorState<T>(selector: (state: MachineSnapshot) => T, compare = shallowEqual): T {
12
+ export function useDiagramActorState<T = unknown>(
13
+ selector: (state: MachineSnapshot) => T,
14
+ compare: (a: NoInfer<T>, b: NoInfer<T>) => boolean = shallowEqual,
15
+ ): T {
14
16
  const select = useCallbackRef((s: MachineSnapshot) => selector(s))
15
17
  return useSelector(select, compare)
16
18
  }
17
19
 
18
- export function useDiagramSyncLayoutState<T>(
20
+ export function useDiagramSyncLayoutState<T = unknown>(
19
21
  selector: (state: SyncLayoutActorSnapshot) => T,
20
- compare = shallowEqual,
22
+ compare: (a: NoInfer<T>, b: NoInfer<T>) => boolean = shallowEqual,
21
23
  ): T {
22
24
  const syncLayoutActorRef = useDiagramActorState(s => s.context.syncLayoutActorRef)
23
25
  const select = useCallbackRef(selector)
@@ -3,7 +3,10 @@ import { shallowEqual } from 'fast-equals'
3
3
  import { useSelector } from '../likec4diagram/state/actorContext'
4
4
  import { type DiagramContext, type MachineSnapshot } from '../likec4diagram/state/machine'
5
5
 
6
- export function useDiagramContext<T>(selector: (state: DiagramContext) => T, compare = shallowEqual) {
6
+ export function useDiagramContext<T = unknown>(
7
+ selector: (state: DiagramContext) => T,
8
+ compare: (a: NoInfer<T>, b: NoInfer<T>) => boolean = shallowEqual,
9
+ ) {
7
10
  const select = useCallbackRef((s: MachineSnapshot) => selector(s.context))
8
11
  return useSelector(select, compare)
9
12
  }
@@ -7,7 +7,7 @@ export const useXYFlow = useReactFlow<Types.Node, Types.Edge>
7
7
 
8
8
  export function useXYStore<StateSlice = unknown>(
9
9
  selector: (state: ReactFlowState<Types.Node, Types.Edge>) => StateSlice,
10
- equalityFn?: (a: StateSlice, b: StateSlice) => boolean,
10
+ equalityFn?: (a: NoInfer<StateSlice>, b: NoInfer<StateSlice>) => boolean,
11
11
  ): StateSlice {
12
12
  return useStore(
13
13
  useCallbackRef(selector as any),
@@ -19,3 +19,8 @@ export const useXYStoreApi = useStoreApi<Types.Node, Types.Edge>
19
19
  export type XYStoreApi = ReturnType<typeof useXYStoreApi>
20
20
 
21
21
  export const useXYInternalNode = useInternalNode<Types.Node>
22
+
23
+ const selectZoom = (state: ReactFlowState) => state.transform[2] < 0.2
24
+ export function useIsZoomTooSmall() {
25
+ return useStore(selectZoom)
26
+ }
@@ -1,6 +1,7 @@
1
1
  import { type EdgeId, type NodeId, nonNullable } from '@likec4/core'
2
2
  import { useCallbackRef } from '@mantine/hooks'
3
3
  import clsx from 'clsx'
4
+ import { shallowEqual } from 'fast-equals'
4
5
  import type { EnforceOptional } from 'type-fest/source/enforce-optional'
5
6
  import { BaseXYFlow } from '../base'
6
7
  import { useDiagramEventHandlers, useEnabledFeature } from '../context'
@@ -19,7 +20,18 @@ const selectXYProps = (ctx: DiagramContext) => ({
19
20
  pannable: ctx.pannable,
20
21
  zoomable: ctx.zoomable,
21
22
  fitViewPadding: ctx.fitViewPadding,
23
+ enableFitView: ctx.features.enableFitView,
24
+ enableReadOnly: ctx.features.enableReadOnly,
22
25
  })
26
+ const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof selectXYProps>): boolean =>
27
+ a.initialized === b.initialized &&
28
+ a.pannable === b.pannable &&
29
+ a.zoomable === b.zoomable &&
30
+ a.fitViewPadding === b.fitViewPadding &&
31
+ a.enableFitView === b.enableFitView &&
32
+ a.enableReadOnly === b.enableReadOnly &&
33
+ shallowEqual(a.nodes, b.nodes) &&
34
+ shallowEqual(a.edges, b.edges)
23
35
 
24
36
  type Picked = EnforceOptional<
25
37
  Pick<
@@ -37,8 +49,10 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
37
49
  initialized,
38
50
  nodes,
39
51
  edges,
52
+ enableReadOnly,
53
+ enableFitView,
40
54
  ...props
41
- } = useDiagramContext(selectXYProps)
55
+ } = useDiagramContext(selectXYProps, equalsXYProps)
42
56
 
43
57
  const {
44
58
  onNodeContextMenu,
@@ -50,15 +64,14 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
50
64
  onCanvasDblClick,
51
65
  } = useDiagramEventHandlers()
52
66
 
53
- const {
54
- enableFitView,
55
- enableReadOnly,
56
- } = useEnabledFeature('ReadOnly', 'FitView')
57
-
58
67
  const notReadOnly = !enableReadOnly
59
68
 
60
69
  const layoutConstraints = useLayoutConstraints()
61
70
 
71
+ const onViewportResize = useCallbackRef(() => {
72
+ diagram.send({ type: 'xyflow.resized' })
73
+ })
74
+
62
75
  return (
63
76
  <BaseXYFlow<Types.Node, Types.Edge>
64
77
  nodes={nodes}
@@ -126,9 +139,7 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
126
139
  }),
127
140
  }}
128
141
  {...enableFitView && {
129
- onViewportResize: () => {
130
- diagram.send({ type: 'xyflow.resized' })
131
- },
142
+ onViewportResize,
132
143
  }}
133
144
  {...(notReadOnly && rest.nodesDraggable && layoutConstraints)}
134
145
  {...props}
@@ -27,12 +27,12 @@ import { IconArrowRight, IconFileSymlink, IconInfoCircle, IconZoomScan } from '@
27
27
  import clsx from 'clsx'
28
28
  import { type MouseEventHandler, type PropsWithChildren, forwardRef, Fragment, useCallback } from 'react'
29
29
  import { filter, isTruthy, map, partition, pipe } from 'remeda'
30
+ import { Link } from '../../../base'
30
31
  import { IfEnabled, useDiagramEventHandlers, useEnabledFeature } from '../../../context'
31
32
  import { useMantinePortalProps } from '../../../hooks'
32
33
  import { useDiagram } from '../../../hooks/useDiagram'
33
34
  import { DiagramContext, useDiagramContext } from '../../../hooks/useDiagramContext'
34
35
  import { useLikeC4Model } from '../../../likec4model'
35
- import { Link } from '../../ui/diagram-title/Link'
36
36
  import * as css from './RelationshipsDropdownMenu.css'
37
37
 
38
38
  const stopPropagation: MouseEventHandler = (e) => e.stopPropagation()
@@ -252,7 +252,7 @@ const Relationship = forwardRef<
252
252
  gap={3}
253
253
  justify="stretch"
254
254
  align="stretch">
255
- {links.map((link) => <Link key={link.url} link={link} />)}
255
+ {links.map((link) => <Link key={link.url} size="sm" value={link} />)}
256
256
  </Stack>
257
257
  )}
258
258
  </Stack>