@likec4/diagram 1.40.0 → 1.41.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 (30) hide show
  1. package/bundle/{chunk-CDr-4Ymp.js → chunk-FGcODVxo.js} +232 -229
  2. package/bundle/custom.js +59 -59
  3. package/bundle/index.js +140 -100
  4. package/dist/base/primitives/MarkdownBlock.js +2 -0
  5. package/dist/components/Link.js +28 -20
  6. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +1 -1
  7. package/dist/navigationpanel/controls/ViewDetailsCard.js +22 -10
  8. package/dist/overlays/element-details/ElementDetailsCard.css.js +7 -1
  9. package/dist/overlays/element-details/ElementDetailsCard.js +17 -19
  10. package/dist/overlays/relationship-details/layout.d.ts +3 -2
  11. package/dist/overlays/relationship-details/layout.js +1 -2
  12. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +2 -1
  13. package/dist/overlays/relationships-browser/layout.js +1 -1
  14. package/dist/search/components/ElementsColumn.js +1 -1
  15. package/dist/state/DiagramActorProvider.js +23 -32
  16. package/dist/styles-min.css +1 -1
  17. package/dist/styles.css +1 -1
  18. package/dist/styles.css2.js +1 -1
  19. package/package.json +4 -4
  20. package/panda.buildinfo.json +15 -15
  21. package/src/base/primitives/MarkdownBlock.tsx +2 -0
  22. package/src/components/Link.tsx +21 -18
  23. package/src/navigationpanel/controls/ViewDetailsCard.tsx +24 -10
  24. package/src/overlays/element-details/ElementDetailsCard.css.ts +7 -1
  25. package/src/overlays/element-details/ElementDetailsCard.tsx +24 -25
  26. package/src/overlays/relationship-details/layout.ts +4 -3
  27. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +2 -1
  28. package/src/overlays/relationships-browser/layout.ts +1 -1
  29. package/src/search/components/ElementsColumn.tsx +1 -1
  30. package/src/state/DiagramActorProvider.tsx +29 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.40.0",
3
+ "version": "1.41.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -89,8 +89,8 @@
89
89
  "remeda": "^2.30.0",
90
90
  "type-fest": "^4.41.0",
91
91
  "xstate": "^5.20.2",
92
- "@likec4/core": "1.40.0",
93
- "@likec4/styles": "1.40.0"
92
+ "@likec4/core": "1.41.0",
93
+ "@likec4/styles": "1.41.0"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "@pandacss/dev": "^1.3.0",
@@ -120,7 +120,7 @@
120
120
  "vite-plugin-dts": "^4.5.4",
121
121
  "vitest": "3.2.4",
122
122
  "wireit": "0.14.12",
123
- "@likec4/tsconfig": "1.40.0"
123
+ "@likec4/tsconfig": "1.41.0"
124
124
  },
125
125
  "scripts": {
126
126
  "typecheck": "tsc --build --verbose",
@@ -37,16 +37,12 @@
37
37
  "color]___[value:mantine.colors.dimmed]___[cond:&:is([data-position=\"left\"])",
38
38
  "userSelect]___[value:none]___[cond:&:is([data-position=\"left\"])",
39
39
  "pointerEvents]___[value:none]___[cond:&:is([data-position=\"left\"])",
40
- "color]___[value:[inherit]]___[cond:&:is([data-position=\"left\"])<___>_groupHover",
40
+ "color]___[value:[var(--badge-color)]]___[cond:&:is([data-position=\"left\"])<___>_groupHover",
41
41
  "opacity]___[value:0.7]___[cond:&:is([data-position=\"left\"])<___>_groupHover",
42
- "color]___[value:[inherit]]___[cond:& > a",
43
- "cursor]___[value:pointer]___[cond:& > a",
44
- "transition]___[value:fast]___[cond:& > a",
45
- "opacity]___[value:0.7]___[cond:& > a",
46
- "opacity]___[value:1]___[cond:& > a<___>_hover",
47
- "textDecoration]___[value:none]___[cond:& > a",
48
- "textDecoration]___[value:underline]___[cond:& > a<___>_hover",
49
42
  "stroke]___[value:2.5",
43
+ "opacity]___[value:0.7",
44
+ "textDecoration]___[value:none",
45
+ "textDecoration]___[value:underline]___[cond:_hover",
50
46
  "height]___[value:12",
51
47
  "width]___[value:12",
52
48
  "height]___[value:30px",
@@ -249,16 +245,17 @@
249
245
  "gap]___[value:md",
250
246
  "margin]___[value:xs",
251
247
  "padding]___[value:md",
252
- "maxWidth]___[value:calc(100cqw - 32px)",
253
- "minWidth]___[value:calc(100cqw - 50px)",
248
+ "paddingBottom]___[value:lg",
249
+ "maxWidth]___[value:calc(100cqw - 52px)",
250
+ "minWidth]___[value:200px",
254
251
  "maxHeight]___[value:calc(100cqh - 100px)",
255
252
  "overflow]___[value:auto",
256
253
  "overscrollBehavior]___[value:contain",
257
254
  "minWidth]___[value:400]___[cond:@/sm",
258
255
  "maxWidth]___[value:550]___[cond:@/sm",
259
256
  "maxWidth]___[value:700]___[cond:@/lg",
260
- "marginBlock]___[value:md",
261
257
  "overflow]___[value:visible",
258
+ "color]___[value:mantine.colors.gray[4]]___[cond:_dark",
262
259
  "color]___[value:mantine.colors.gray[8]]___[cond:_light",
263
260
  "opacity]___[value:0.5",
264
261
  "marginInlineEnd]___[value:0.5",
@@ -331,6 +328,8 @@
331
328
  "--text-fz]___[value:{fontSizes.sm}",
332
329
  "--text-fz]___[value:{fontSizes.md}]___[cond:md",
333
330
  "paddingTop]___[value:xxs",
331
+ "maxWidth]___[value:calc(100cqw - 32px)",
332
+ "minWidth]___[value:calc(100cqw - 50px)",
334
333
  "boxSizing]___[value:border-box",
335
334
  "inset]___[value:0",
336
335
  "width]___[value:100vw",
@@ -341,6 +340,7 @@
341
340
  "backdropBlur]___[value:var(--_blur)]___[cond:_backdrop",
342
341
  "backgroundColor]___[value:[rgb(36 36 36 / var(--_opacity, 5%))]]___[cond:_backdrop",
343
342
  "gap]___[value:lg",
343
+ "color]___[value:mantine.colors.text",
344
344
  "backgroundColor]___[value:mantine.colors.body",
345
345
  "backgroundColor]___[value:mantine.colors.dark[6]]___[cond:_dark",
346
346
  "border]___[value:none",
@@ -424,13 +424,15 @@
424
424
  "cursor]___[value:se-resize",
425
425
  "borderWidth]___[value:4px]___[cond:_hover",
426
426
  "borderColor]___[value:mantine.colors.dark[1]]___[cond:_hover",
427
+ "alignItems]___[value:start",
428
+ "justifyContent]___[value:space-between",
427
429
  "marginBottom]___[value:sm",
428
- "gap]___[value:6",
429
430
  "lineClamp]___[value:1",
430
431
  "gap]___[value:4",
431
432
  "marginTop]___[value:6",
432
433
  "stroke]___[value:1.6",
433
434
  "paddingTop]___[value:xs",
435
+ "gap]___[value:6",
434
436
  "gap]___[value:[4px 4px]",
435
437
  "paddingRight]___[value:xxs",
436
438
  "transitionProperty]___[value:all",
@@ -441,7 +443,7 @@
441
443
  "color]___[value:mantine.colors.defaultColor]___[cond:_groupHover",
442
444
  "background]___[value:mantine.colors.defaultHover]___[cond:_groupHover",
443
445
  "whiteSpace]___[value:pre",
444
- "fontFamily]___[value:[var(--mantine-font-family-monospace)]",
446
+ "fontFamily]___[value:mono",
445
447
  "overscrollBehavior]___[value:none",
446
448
  "marginBottom]___[value:sm]___[cond:&[data-level='1']",
447
449
  "marginBottom]___[value:0",
@@ -665,7 +667,6 @@
665
667
  "top]___[value:[2rem]",
666
668
  "left]___[value:[50%]",
667
669
  "maxWidth]___[value:600px",
668
- "minWidth]___[value:200px",
669
670
  "transform]___[value:translateX(-50%)",
670
671
  "zIndex]___[value:903",
671
672
  "marginTop]___[value:[32px]]___[cond:& + &",
@@ -795,7 +796,6 @@
795
796
  "marginBottom]___[value:xs",
796
797
  "height]___[value:sm",
797
798
  "paddingBlock]___[value:2",
798
- "opacity]___[value:0.7",
799
799
  "flex]___[value:0 0 auto",
800
800
  "maxHeight]___[value:60",
801
801
  "right]___[value:0",
@@ -45,6 +45,7 @@ export const MarkdownBlock = forwardRef<HTMLDivElement, MarkdownBlockProps>(({
45
45
  hideIfEmpty = false,
46
46
  emptyText = 'no content',
47
47
  className,
48
+ style,
48
49
  ...props
49
50
  }, ref) => {
50
51
  if (value.isEmpty && hideIfEmpty) {
@@ -67,6 +68,7 @@ export const MarkdownBlock = forwardRef<HTMLDivElement, MarkdownBlockProps>(({
67
68
  className,
68
69
  )}
69
70
  style={{
71
+ ...style,
70
72
  ...props.fontSize && {
71
73
  '--text-fz': `var(--font-sizes-${props.fontSize}, var(--font-sizes-md))`,
72
74
  },
@@ -1,5 +1,6 @@
1
1
  import type { Link as LinkData } from '@likec4/core'
2
2
  import { css, cx } from '@likec4/styles/css'
3
+ import { styled } from '@likec4/styles/jsx'
3
4
  import { type BadgeProps, ActionIcon, Badge, CopyButton } from '@mantine/core'
4
5
  import { IconCheck, IconCopy } from '@tabler/icons-react'
5
6
  import { forwardRef } from 'react'
@@ -72,29 +73,31 @@ export const Link = forwardRef<HTMLDivElement, Omit<BadgeProps, 'children' | 'cl
72
73
  userSelect: 'none',
73
74
  pointerEvents: 'none',
74
75
  _groupHover: {
75
- color: '[inherit]',
76
+ color: '[var(--badge-color)]',
76
77
  opacity: .7,
77
78
  },
78
79
  },
79
80
  }),
80
- label: css({
81
- '& > a': {
82
- color: '[inherit]',
83
- cursor: 'pointer',
84
- transition: 'fast',
85
- opacity: {
86
- base: 0.7,
87
- _hover: 1,
88
- },
89
- textDecoration: {
90
- base: 'none',
91
- _hover: 'underline',
92
- },
93
- },
94
- }),
95
81
  }}
96
82
  >
97
- <a href={url} target="_blank">
83
+ <styled.a
84
+ href={url}
85
+ target="_blank"
86
+ style={{
87
+ color: 'var(--badge-color)',
88
+ cursor: 'pointer',
89
+ }}
90
+ css={{
91
+ transition: 'fast',
92
+ opacity: {
93
+ base: 0.7,
94
+ _hover: 1,
95
+ },
96
+ textDecoration: {
97
+ base: 'none',
98
+ _hover: 'underline',
99
+ },
100
+ }}>
98
101
  {isGithub && (
99
102
  <GithubIcon
100
103
  height="12"
@@ -102,7 +105,7 @@ export const Link = forwardRef<HTMLDivElement, Omit<BadgeProps, 'children' | 'cl
102
105
  style={{ verticalAlign: 'middle', marginRight: '4px' }} />
103
106
  )}
104
107
  {isGithub ? url.replace(GITHUB_PREFIX, '') : url}
105
- </a>
108
+ </styled.a>
106
109
  </Badge>
107
110
  )
108
111
  },
@@ -10,9 +10,10 @@ import {
10
10
  } from '@mantine/core'
11
11
  import { IconId, IconLink } from '@tabler/icons-react'
12
12
  import * as m from 'motion/react-m'
13
+ import { useState } from 'react'
13
14
  import { ElementTag, MarkdownBlock } from '../../base/primitives'
14
15
  import { Link } from '../../components/Link'
15
- import { useDiagram } from '../../hooks/useDiagram'
16
+ import { useDiagram, useOnDiagramEvent } from '../../hooks/useDiagram'
16
17
  import { useMantinePortalProps } from '../../hooks/useMantinePortalProps'
17
18
  import type { NavigationPanelActorSnapshot } from '../actor'
18
19
  import { useNavigationActorSnapshot } from '../hooks'
@@ -32,6 +33,7 @@ const selector = ({ context }: NavigationPanelActorSnapshot) => {
32
33
  type ViewDetailsCardData = ReturnType<typeof selector>
33
34
 
34
35
  export const ViewDetailsCard = (props: PopoverProps) => {
36
+ const [opened, setOpened] = useState(false)
35
37
  const data = useNavigationActorSnapshot(selector)
36
38
  const portalProps = useMantinePortalProps()
37
39
 
@@ -40,20 +42,21 @@ export const ViewDetailsCard = (props: PopoverProps) => {
40
42
  position="bottom-end"
41
43
  shadow="xl"
42
44
  clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
43
- trapFocus
44
45
  offset={{
45
46
  mainAxis: 4,
46
47
  }}
48
+ opened={opened}
49
+ onChange={setOpened}
47
50
  {...portalProps}
48
51
  {...props}
49
52
  >
50
- <ViewDetailsCardTrigger linksCount={data.links.length} />
51
- <ViewDetailsCardDropdown data={data} />
53
+ <ViewDetailsCardTrigger linksCount={data.links.length} onOpen={() => setOpened(true)} />
54
+ {opened && <ViewDetailsCardDropdown data={data} onClose={() => setOpened(false)} />}
52
55
  </Popover>
53
56
  )
54
57
  }
55
58
 
56
- const ViewDetailsCardTrigger = ({ linksCount }: { linksCount: number }) => (
59
+ const ViewDetailsCardTrigger = ({ linksCount, onOpen }: { linksCount: number; onOpen: () => void }) => (
57
60
  <Popover.Target>
58
61
  <UnstyledButton
59
62
  component={m.button}
@@ -62,6 +65,10 @@ const ViewDetailsCardTrigger = ({ linksCount }: { linksCount: number }) => (
62
65
  scale: 0.95,
63
66
  translateY: 1,
64
67
  }}
68
+ onClick={e => {
69
+ e.stopPropagation()
70
+ onOpen()
71
+ }}
65
72
  className={cx(
66
73
  'group',
67
74
  hstack({
@@ -123,8 +130,13 @@ const ViewDetailsCardDropdown = ({
123
130
  links,
124
131
  relativePath,
125
132
  },
126
- }: { data: ViewDetailsCardData }) => {
133
+ onClose,
134
+ }: { data: ViewDetailsCardData; onClose: () => void }) => {
127
135
  const diagram = useDiagram()
136
+
137
+ useOnDiagramEvent('paneClick', onClose)
138
+ useOnDiagramEvent('nodeClick', onClose)
139
+
128
140
  return (
129
141
  <Popover.Dropdown
130
142
  className={cx(
@@ -134,9 +146,10 @@ const ViewDetailsCardDropdown = ({
134
146
  layerStyle: 'likec4.dropdown',
135
147
  gap: 'md',
136
148
  padding: 'md',
149
+ paddingBottom: 'lg',
137
150
  pointerEvents: 'all',
138
- maxWidth: 'calc(100cqw - 32px)',
139
- minWidth: 'calc(100cqw - 50px)',
151
+ maxWidth: 'calc(100cqw - 52px)',
152
+ minWidth: '200px',
140
153
  maxHeight: 'calc(100cqh - 100px)',
141
154
  width: 'max-content',
142
155
  cursor: 'default',
@@ -152,7 +165,7 @@ const ViewDetailsCardDropdown = ({
152
165
  }),
153
166
  )}>
154
167
  <section>
155
- <Text component="div" fw={500} size="xl">{title}</Text>
168
+ <Text component="div" fw={500} size="xl" lh={'sm'}>{title}</Text>
156
169
  <HStack alignItems={'flex-start'} mt="1">
157
170
  <ViewBadge label="id" value={id} />
158
171
  {/* {relativePath && <ViewBadge label="source" value={relativePath} />} */}
@@ -179,7 +192,7 @@ const ViewDetailsCardDropdown = ({
179
192
  </section>
180
193
  )}
181
194
  {description.isEmpty && (
182
- <Text component="div" fw={500} size="xs" c="dimmed" my="md" style={{ userSelect: 'none' }}>No description</Text>
195
+ <Text component="div" fw={500} size="xs" c="dimmed" style={{ userSelect: 'none' }}>No description</Text>
183
196
  )}
184
197
  {description.nonEmpty && (
185
198
  <section>
@@ -221,6 +234,7 @@ const ViewBadge = ({
221
234
  overflow: 'visible',
222
235
  px: '1',
223
236
  color: {
237
+ _dark: 'mantine.colors.gray[4]',
224
238
  _light: 'mantine.colors.gray[8]',
225
239
  },
226
240
  }),
@@ -25,13 +25,19 @@ export const dialog = css({
25
25
  export const card = css({
26
26
  position: 'absolute',
27
27
  pointerEvents: 'all',
28
+ display: 'flex',
29
+ flexDirection: 'column',
30
+ padding: '4',
28
31
  gap: 'lg',
29
32
  justifyContent: 'stretch',
33
+ color: 'mantine.colors.text',
30
34
  backgroundColor: {
31
35
  base: 'mantine.colors.body',
32
36
  _dark: 'mantine.colors.dark[6]',
33
37
  },
34
- border: `none`,
38
+ boxShadow: 'md',
39
+ overflow: 'hidden',
40
+ border: 'none',
35
41
  backgroundImage: `
36
42
  linear-gradient(180deg,
37
43
  color-mix(in srgb, var(--likec4-palette-fill) 60%, transparent),
@@ -16,7 +16,6 @@ import {
16
16
  ActionIconGroup,
17
17
  Badge,
18
18
  Box,
19
- Card,
20
19
  CloseButton,
21
20
  Divider as MantineDivider,
22
21
  Flex,
@@ -48,7 +47,7 @@ import { DiagramFeatures, IconRenderer, IfEnabled } from '../../context'
48
47
  import { useUpdateEffect } from '../../hooks'
49
48
  import { useDiagram } from '../../hooks/useDiagram'
50
49
  import type { OnNavigateTo } from '../../LikeC4Diagram.props'
51
- import { useLikeC4Model } from '../../likec4model'
50
+ import { useCurrentViewModel } from '../../likec4model'
52
51
  import { stopPropagation } from '../../utils'
53
52
  import * as styles from './ElementDetailsCard.css'
54
53
  import { TabPanelDeployments } from './TabPanelDeployments'
@@ -115,8 +114,7 @@ export function ElementDetailsCard({
115
114
  defaultValue: 'Properties',
116
115
  })
117
116
  const diagram = useDiagram()
118
- const likec4model = useLikeC4Model()
119
- const viewModel = likec4model.view(viewId)
117
+ const viewModel = useCurrentViewModel()
120
118
  const nodeModel = fromNode ? viewModel.findNode(fromNode) : viewModel.findNodeWithElement(fqn)
121
119
 
122
120
  const elementModel = viewModel.$model.element(fqn)
@@ -221,7 +219,7 @@ export function ElementDetailsCard({
221
219
  */
222
220
  useTimeoutEffect(() => {
223
221
  setOpened(true)
224
- }, 200)
222
+ }, 220)
225
223
 
226
224
  return (
227
225
  <m.dialog
@@ -257,9 +255,7 @@ export function ElementDetailsCard({
257
255
  }}
258
256
  >
259
257
  <RemoveScroll forwardProps removeScrollBar={false}>
260
- <Card
261
- shadow="md"
262
- component={m.div}
258
+ <m.div
263
259
  layout
264
260
  layoutRoot
265
261
  drag
@@ -292,13 +288,12 @@ export function ElementDetailsCard({
292
288
  },
293
289
  }}
294
290
  style={{
295
- // `style` prop in Mantine doesn't accept motion values
296
- width: width as any,
297
- height: height as any,
291
+ width,
292
+ height,
298
293
  }}>
299
294
  <div className={styles.cardHeader} onPointerDown={e => controls.start(e)}>
300
- <Group align="start" justify="space-between" gap={'sm'} mb={'sm'} wrap="nowrap">
301
- <Group align="start" gap={'sm'} style={{ cursor: 'default' }} wrap="nowrap">
295
+ <HStack alignItems="start" justify="space-between" gap={'sm'} mb={'sm'} flexWrap="nowrap">
296
+ <HStack alignItems="start" gap={'sm'} style={{ cursor: 'default' }} flexWrap="nowrap">
302
297
  {elementIcon}
303
298
  <div>
304
299
  <Text
@@ -312,15 +307,15 @@ export function ElementDetailsCard({
312
307
  </Text>
313
308
  )}
314
309
  </div>
315
- </Group>
310
+ </HStack>
316
311
  <CloseButton
317
312
  size={'lg'}
318
313
  onClick={e => {
319
314
  e.stopPropagation()
320
315
  close()
321
316
  }} />
322
- </Group>
323
- <Group align="baseline" gap={'sm'} wrap="nowrap">
317
+ </HStack>
318
+ <HStack alignItems="baseline" gap={'sm'} flexWrap="nowrap">
324
319
  <div>
325
320
  <SmallLabel>kind</SmallLabel>
326
321
  <Badge
@@ -403,7 +398,7 @@ export function ElementDetailsCard({
403
398
  </Tooltip>
404
399
  )}
405
400
  </ActionIconGroup>
406
- </Group>
401
+ </HStack>
407
402
  </div>
408
403
  <Tabs
409
404
  value={activeTab}
@@ -426,6 +421,12 @@ export function ElementDetailsCard({
426
421
  <TabsPanel value="Properties">
427
422
  <ScrollArea scrollbars="y" type="scroll" offsetScrollbars>
428
423
  <Box className={styles.propertiesGrid} pt={'xs'}>
424
+ {elementModel.hasSummary && (
425
+ <>
426
+ <PropertyLabel>summary</PropertyLabel>
427
+ <MarkdownBlock value={elementModel.summary} />
428
+ </>
429
+ )}
429
430
  <>
430
431
  <PropertyLabel>description</PropertyLabel>
431
432
  <MarkdownBlock
@@ -517,7 +518,7 @@ export function ElementDetailsCard({
517
518
  dragMomentum={false}
518
519
  onDrag={handleDrag}
519
520
  dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }} />
520
- </Card>
521
+ </m.div>
521
522
  </RemoveScroll>
522
523
  </m.dialog>
523
524
  )
@@ -609,12 +610,10 @@ function ElementMetata({
609
610
  {entries(metadata).map(([key, value]) => (
610
611
  <div
611
612
  key={key}
612
- className={cx(
613
- 'group',
614
- css({
615
- display: 'contents',
616
- }),
617
- )}
613
+ style={{
614
+ display: 'contents',
615
+ }}
616
+ className={'group'}
618
617
  >
619
618
  <div
620
619
  className={css({
@@ -651,7 +650,7 @@ function ElementMetata({
651
650
  fontSize: 'sm',
652
651
  padding: 'xxs',
653
652
  whiteSpace: 'pre',
654
- fontFamily: '[var(--mantine-font-family-monospace)]',
653
+ fontFamily: 'mono',
655
654
  userSelect: 'all',
656
655
  })}>
657
656
  {value}
@@ -34,6 +34,7 @@ import {
34
34
  sortBy,
35
35
  tap,
36
36
  } from 'remeda'
37
+ import type { Except } from 'type-fest'
37
38
  import type { RelationshipDetailsTypes } from './_types'
38
39
  import type { RelationshipDetailsViewData } from './compute'
39
40
 
@@ -218,14 +219,14 @@ export type LayoutResult = {
218
219
  }
219
220
 
220
221
  export namespace LayoutResult {
221
- export type Node = Omit<DiagramNode, 'modelRef' | 'description' | 'deploymentRef' | 'inEdges' | 'outEdges'> & {
222
+ export type Node = Except<DiagramNode, 'modelRef' | 'description' | 'deploymentRef' | 'inEdges' | 'outEdges'> & {
222
223
  description: RichTextOrEmpty
223
224
  modelRef: Fqn
224
225
  column: RelationshipDetailsTypes.Column
225
226
  ports: RelationshipDetailsTypes.Ports
226
227
  // existsInCurrentView: boolean
227
228
  }
228
- export type Edge = Omit<DiagramEdge, 'relations' | 'description'> & {
229
+ export type Edge = Except<DiagramEdge, 'relations' | 'description'> & {
229
230
  relationId: RelationId
230
231
  sourceHandle: string
231
232
  targetHandle: string
@@ -402,7 +403,7 @@ export function layoutRelationshipDetails(
402
403
  y: position.y,
403
404
  position: [position.x, position.y],
404
405
  title: element.title,
405
- description: RichText.from(element.description),
406
+ description: element.summary,
406
407
  technology: element.technology,
407
408
  tags: [...element.tags],
408
409
  links: null,
@@ -1,6 +1,7 @@
1
1
  import { cx } from '@likec4/styles/css'
2
2
  import { ActionIcon, Group } from '@mantine/core'
3
3
  import { useCallbackRef, useStateHistory } from '@mantine/hooks'
4
+ import { useRafEffect } from '@react-hookz/web'
4
5
  import { IconChevronLeft, IconChevronRight, IconX } from '@tabler/icons-react'
5
6
  import { Panel, ReactFlowProvider, useReactFlow, useStoreApi } from '@xyflow/react'
6
7
  import { shallowEqual } from 'fast-equals'
@@ -183,7 +184,7 @@ const RelationshipsBrowserInner = memo(() => {
183
184
  }
184
185
  }, [historySubjectId, browser])
185
186
 
186
- useEffect(() => {
187
+ useRafEffect(() => {
187
188
  browser.updateView(layouted)
188
189
  }, [layouted, browser])
189
190
 
@@ -518,7 +518,7 @@ export function layoutRelationshipsView(
518
518
  y: position.y,
519
519
  position: [position.x, position.y],
520
520
  title: element.title,
521
- description: RichText.from(element.description),
521
+ description: element.summary,
522
522
  technology: element.technology,
523
523
  tags: [...element.tags],
524
524
  links: null,
@@ -279,7 +279,7 @@ function ElementTreeNode(
279
279
  </Tooltip>
280
280
  </Group>
281
281
  <Highlight component="div" highlight={searchTerms} className={btn.description!} lineClamp={1}>
282
- {element.description.text || 'No description'}
282
+ {element.summary.text || 'No description'}
283
283
  </Highlight>
284
284
  </Box>
285
285
 
@@ -1,15 +1,14 @@
1
1
  import type { DiagramView, DynamicViewDisplayVariant, WhereOperator } from '@likec4/core/types'
2
- import { useCustomCompareEffect } from '@react-hookz/web'
2
+ import { useCustomCompareEffect, useRafEffect } from '@react-hookz/web'
3
3
  import { useActorRef, useSelector } from '@xstate/react'
4
4
  import { useStoreApi } from '@xyflow/react'
5
5
  import { deepEqual, shallowEqual } from 'fast-equals'
6
- import { type PropsWithChildren, useEffect, useMemo, useRef } from 'react'
6
+ import { type PropsWithChildren, useMemo, useRef } from 'react'
7
7
  import { ErrorBoundary } from '../components/ErrorFallback'
8
8
  import { useDiagramEventHandlers } from '../context/DiagramEventHandlers'
9
9
  import { DiagramFeatures, useEnabledFeatures } from '../context/DiagramFeatures'
10
10
  import { useOnDiagramEvent } from '../custom'
11
11
  import { DiagramActorContextProvider } from '../hooks/safeContext'
12
- import { useCurrentViewId } from '../hooks/useCurrentViewId'
13
12
  import type { ViewPadding } from '../LikeC4Diagram.props'
14
13
  import { convertToXYFlow } from '../likec4diagram/convert-to-xyflow'
15
14
  import type { Types } from '../likec4diagram/types'
@@ -19,16 +18,6 @@ import { type DiagramMachine, diagramMachine } from './diagram-machine'
19
18
  import { syncManualLayoutActorLogic } from './syncManualLayoutActor'
20
19
  import type { DiagramActorRef, DiagramActorSnapshot } from './types'
21
20
 
22
- const selectToggledFeatures = (state: DiagramActorSnapshot) => {
23
- if (state.context.features.enableReadOnly || state.context.activeWalkthrough) {
24
- return {
25
- ...state.context.toggledFeatures,
26
- enableReadOnly: true,
27
- }
28
- }
29
- return state.context.toggledFeatures
30
- }
31
-
32
21
  const selectDynamicViewVariant = (state: DiagramActorSnapshot) => {
33
22
  return state.context.dynamicViewVariant
34
23
  }
@@ -111,36 +100,49 @@ export function DiagramActorProvider({
111
100
  [view, where, nodesSelectable, dynamicViewVariant],
112
101
  )
113
102
 
114
- useEffect(() => {
103
+ useRafEffect(() => {
115
104
  actorRef.send({
116
105
  type: 'update.view',
117
106
  ...update,
118
107
  })
119
108
  }, [actorRef, update])
120
109
 
121
- const toggledFeatures = useSelector(actorRef, selectToggledFeatures, shallowEqual)
122
-
123
110
  return (
124
111
  <DiagramActorContextProvider value={actorRef}>
125
- <DiagramFeatures overrides={toggledFeatures}>
126
- <ErrorBoundary>
127
- <CurrentViewModelProvider>
128
- {children}
129
- </CurrentViewModelProvider>
130
- </ErrorBoundary>
131
- </DiagramFeatures>
112
+ <ErrorBoundary>
113
+ <CurrentViewModelProvider actorRef={actorRef}>
114
+ {children}
115
+ </CurrentViewModelProvider>
116
+ </ErrorBoundary>
132
117
  <DiagramActorEventListener actorRef={actorRef} />
133
118
  </DiagramActorContextProvider>
134
119
  )
135
120
  }
136
121
 
137
- function CurrentViewModelProvider({ children }: PropsWithChildren) {
138
- const viewId = useCurrentViewId()
122
+ const selectViewIdAndToggledFeatures = (state: DiagramActorSnapshot) => {
123
+ if (state.context.features.enableReadOnly || state.context.activeWalkthrough) {
124
+ return {
125
+ viewId: state.context.view.id,
126
+ toggledFeatures: {
127
+ ...state.context.toggledFeatures,
128
+ enableReadOnly: true,
129
+ },
130
+ }
131
+ }
132
+ return {
133
+ viewId: state.context.view.id,
134
+ toggledFeatures: state.context.toggledFeatures,
135
+ }
136
+ }
137
+ function CurrentViewModelProvider({ children, actorRef }: PropsWithChildren<{ actorRef: DiagramActorRef }>) {
138
+ const { viewId, toggledFeatures } = useSelector(actorRef, selectViewIdAndToggledFeatures, deepEqual)
139
139
  const likec4model = useLikeC4Model()
140
140
  const viewmodel = likec4model.findView(viewId)
141
141
  return (
142
142
  <CurrentViewModelContext.Provider value={viewmodel}>
143
- {children}
143
+ <DiagramFeatures overrides={toggledFeatures}>
144
+ {children}
145
+ </DiagramFeatures>
144
146
  </CurrentViewModelContext.Provider>
145
147
  )
146
148
  }
@@ -152,25 +154,7 @@ function DiagramActorEventListener({ actorRef }: { actorRef: DiagramActorRef })
152
154
  } = useDiagramEventHandlers()
153
155
 
154
156
  useOnDiagramEvent('openSource', ({ params }) => onOpenSource?.(params))
155
-
156
- // onNavigateTo we defer the callback for better responsiveness
157
- // (allowing animation to finish)
158
- useEffect(() => {
159
- if (!onNavigateTo) return
160
- let frame: number
161
-
162
- const subscription = actorRef.on('navigateTo', ({ viewId }) => {
163
- cancelAnimationFrame(frame)
164
- // Slightly defer callback for better responsiveness
165
- frame = requestAnimationFrame(() => {
166
- onNavigateTo(viewId)
167
- })
168
- })
169
- return () => {
170
- cancelAnimationFrame(frame)
171
- subscription.unsubscribe()
172
- }
173
- }, [actorRef, onNavigateTo])
157
+ useOnDiagramEvent('navigateTo', ({ viewId }) => onNavigateTo?.(viewId))
174
158
 
175
159
  return null
176
160
  }