@likec4/diagram 1.16.0 → 1.17.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 (47) hide show
  1. package/package.json +7 -7
  2. package/src/LikeC4CustomColors.tsx +24 -21
  3. package/src/LikeC4Diagram.props.ts +9 -9
  4. package/src/LikeC4Diagram.tsx +25 -26
  5. package/src/StaticLikeC4Diagram.tsx +9 -5
  6. package/src/overlays/OverlayContext.tsx +1 -1
  7. package/src/overlays/Overlays.css.ts +28 -23
  8. package/src/overlays/Overlays.tsx +116 -64
  9. package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
  10. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
  11. package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
  12. package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
  13. package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
  14. package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
  15. package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
  16. package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
  17. package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
  18. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
  19. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
  20. package/src/overlays/relationships-of/_types.ts +15 -4
  21. package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
  22. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
  23. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
  24. package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
  25. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
  26. package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
  27. package/src/state/DiagramContext.tsx +15 -8
  28. package/src/state/diagramStore.ts +14 -6
  29. package/src/theme-vars.ts +9 -0
  30. package/src/ui/FramerMotionConfig.tsx +18 -0
  31. package/src/ui/notation/NotationPanel.tsx +3 -3
  32. package/src/xyflow/XYFlow.tsx +18 -35
  33. package/src/xyflow/XYFlowInner.tsx +2 -2
  34. package/src/xyflow/edges/EdgeLabel.tsx +3 -3
  35. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
  36. package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
  37. package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
  38. package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
  39. package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
  40. package/src/xyflow/nodes/element/ElementNode.tsx +110 -116
  41. package/src/xyflow/nodes/element/element.css.ts +38 -18
  42. package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
  43. package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
  44. package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
  45. package/src/xyflow/utils.ts +12 -3
  46. package/src/LikeC4CustomColors.props.ts +0 -5
  47. /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -31,21 +31,21 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@dagrejs/dagre": "^1.1.4",
34
- "@likec4/core": "1.16.0",
34
+ "@likec4/core": "1.17.0",
35
35
  "@mantine/vanilla-extract": "7.13.4",
36
36
  "@react-hookz/web": "^24.0.4",
37
37
  "@tabler/icons-react": "3.17.0",
38
38
  "@vanilla-extract/css": "^1.16.0",
39
39
  "@vanilla-extract/css-utils": "^0.1.4",
40
40
  "@vanilla-extract/dynamic": "^2.1.2",
41
- "@xyflow/react": "12.3.2",
42
- "@xyflow/system": "0.0.43",
41
+ "@xyflow/react": "12.3.4",
42
+ "@xyflow/system": "0.0.45",
43
43
  "bezier-js": "^6.1.4",
44
44
  "clsx": "^2.1.1",
45
45
  "d3-shape": "^3.2.0",
46
- "esm-env": "^1.0.0",
46
+ "esm-env": "^1.1.4",
47
47
  "fast-equals": "^5.0.1",
48
- "framer-motion": "^11.11.10",
48
+ "framer-motion": "^11.11.11",
49
49
  "khroma": "^2.1.0",
50
50
  "remeda": "^2.16.0",
51
51
  "type-fest": "4.26.1",
@@ -58,7 +58,7 @@
58
58
  "react-dom": "^18.3.1"
59
59
  },
60
60
  "devDependencies": {
61
- "@likec4/tsconfig": "1.16.0",
61
+ "@likec4/tsconfig": "1.17.0",
62
62
  "@mantine/core": "7.13.4",
63
63
  "@mantine/hooks": "7.13.4",
64
64
  "@types/bezier-js": "^4.1.3",
@@ -1,33 +1,36 @@
1
- import { type ThemeColorValues } from '@likec4/core'
1
+ import { type CustomColorDefinitions, type ThemeColorValues } from '@likec4/core'
2
2
  import { useMantineStyleNonce } from '@mantine/core'
3
3
  import { deepEqual } from 'fast-equals'
4
4
  import { memo } from 'react'
5
5
  import { entries } from 'remeda'
6
- import type { LikeC4CustomColorsProperties } from './LikeC4CustomColors.props'
7
6
  import { vars } from './theme-vars'
8
7
 
9
8
  type CSSVarFunction = `var(--${string})` | `var(--${string}, ${string | number} )`
10
9
 
11
- export const LikeC4CustomColors = memo<LikeC4CustomColorsProperties>(({ customColors }) => {
12
- function toStyle(name: String, colorValues: ThemeColorValues): String {
13
- const rules = new Array<String>(
14
- ...entries(colorValues.elements).map(([key, value]) => `${stripCssVarReference(vars.element[key])}: ${value};`),
15
- ...entries(colorValues.relationships).map(([key, value]) =>
16
- `${stripCssVarReference(vars.relation[key])}: ${value};`
17
- )
18
- )
19
- .join('\n')
20
-
21
- return `:where([data-likec4-color=${name}]) {
22
- ${rules}
23
- }`
24
- }
25
-
26
- function stripCssVarReference(ref: CSSVarFunction): String {
27
- const end = ref.indexOf(',')
28
- return ref.substring(4, end == -1 ? ref.length - 1 : end)
29
- }
10
+ interface LikeC4CustomColorsProperties {
11
+ customColors: CustomColorDefinitions
12
+ }
13
+
14
+ function toStyle(name: String, colorValues: ThemeColorValues): String {
15
+ const rules = [
16
+ ...entries(colorValues.elements)
17
+ .map(([key, value]) => `${stripCssVarReference(vars.element[key])}: ${value};`),
18
+ ...entries(colorValues.relationships)
19
+ .map(([key, value]) => `${stripCssVarReference(vars.relation[key])}: ${value};`)
20
+ ]
21
+ .join('\n')
30
22
 
23
+ return `:where([data-likec4-color=${name}]) {
24
+ ${rules}
25
+ }`
26
+ }
27
+
28
+ function stripCssVarReference(ref: CSSVarFunction): String {
29
+ const end = ref.indexOf(',')
30
+ return ref.substring(4, end == -1 ? ref.length - 1 : end)
31
+ }
32
+
33
+ export const LikeC4CustomColors = memo<LikeC4CustomColorsProperties>(({ customColors }) => {
31
34
  const styles = entries(customColors)
32
35
  .map(([name, color]) => toStyle(name, color))
33
36
  .join('\n')
@@ -118,12 +118,6 @@ export interface LikeC4DiagramProperties {
118
118
  */
119
119
  background?: 'transparent' | 'solid' | XYBackground | undefined
120
120
 
121
- /**
122
- * Display hovercards with element links
123
- * @default true
124
- */
125
- showElementLinks?: boolean | undefined
126
-
127
121
  /**
128
122
  * Display webview with diagram title / description
129
123
  * @default true
@@ -144,9 +138,9 @@ export interface LikeC4DiagramProperties {
144
138
 
145
139
  /**
146
140
  * Display dropdown with details on relationship's label click
147
- * @default true
141
+ * @default false
148
142
  */
149
- showRelationshipDetails?: boolean | undefined
143
+ enableRelationshipDetails?: boolean | undefined
150
144
 
151
145
  /**
152
146
  * If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
@@ -154,12 +148,18 @@ export interface LikeC4DiagramProperties {
154
148
  */
155
149
  enableFocusMode?: boolean | undefined
156
150
 
151
+ /**
152
+ * Enable modal with element details
153
+ * @default false
154
+ */
155
+ enableElementDetails?: boolean | undefined
156
+
157
157
  /**
158
158
  * Experimental feature to browse relationships
159
159
  *
160
160
  * @default false
161
161
  */
162
- enableRelationshipsBrowser?: boolean | undefined
162
+ enableRelationshipBrowser?: boolean | undefined
163
163
 
164
164
  /**
165
165
  * If Walkthrough for dynamic views should be enabled
@@ -1,18 +1,18 @@
1
- import type { DiagramView } from '@likec4/core'
2
1
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
3
2
  import clsx from 'clsx'
4
- import { shallowEqual } from 'fast-equals'
5
- import { domMax, LazyMotion } from 'framer-motion'
3
+ import { deepEqual, shallowEqual } from 'fast-equals'
6
4
  import { memo, useEffect, useRef } from 'react'
5
+ import { isEmpty } from 'remeda'
7
6
  import { rootClassName } from './globals.css'
8
7
  import { useDiagramState } from './hooks/useDiagramState'
9
8
  import { LikeC4CustomColors } from './LikeC4CustomColors'
10
9
  import * as css from './LikeC4Diagram.css'
11
10
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
12
11
  import { useLikeC4Model } from './likec4model'
13
- import { EnsureMantine } from './mantine/EnsureMantine'
14
12
  import { Overlays } from './overlays'
15
13
  import { DiagramContextProvider } from './state/DiagramContext'
14
+ import { EnsureMantine } from './ui/EnsureMantine'
15
+ import { FramerMotionConfig } from './ui/FramerMotionConfig'
16
16
  import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
17
17
  import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
18
18
  import type { XYFlowEdge, XYFlowNode } from './xyflow/types'
@@ -32,13 +32,13 @@ export function LikeC4Diagram({
32
32
  nodesDraggable = !readonly,
33
33
  background = 'dots',
34
34
  controls = !readonly,
35
- showElementLinks = true,
36
35
  showDiagramTitle = true,
37
36
  showNotations = true,
38
- showRelationshipDetails = true,
39
37
  enableDynamicViewWalkthrough = false,
40
38
  enableFocusMode = false,
41
- enableRelationshipsBrowser = false,
39
+ enableElementDetails = false,
40
+ enableRelationshipDetails = enableElementDetails,
41
+ enableRelationshipBrowser = enableRelationshipDetails,
42
42
  initialWidth,
43
43
  initialHeight,
44
44
  keepAspectRatio = false,
@@ -74,43 +74,46 @@ export function LikeC4Diagram({
74
74
  if (hasLikec4model) {
75
75
  return
76
76
  }
77
- if (showRelationshipDetails) {
77
+ if (enableRelationshipDetails) {
78
78
  console.warn('Invalid showRelationshipDetails=true, requires LikeC4ModelProvider')
79
79
  }
80
- if (enableRelationshipsBrowser) {
81
- console.warn('Invalid enableRelationshipsBrowser=true, requires LikeC4ModelProvider')
80
+ if (enableElementDetails) {
81
+ console.warn('Invalid enableElementDetails=true, requires LikeC4ModelProvider')
82
+ }
83
+ if (enableRelationshipBrowser) {
84
+ console.warn('Invalid enableRelationshipBrowser=true, requires LikeC4ModelProvider')
82
85
  }
83
86
  })
84
87
 
85
88
  return (
86
89
  <EnsureMantine>
87
- <LazyMotion features={domMax} strict>
90
+ <FramerMotionConfig>
88
91
  <XYFlowProvider
89
92
  fitView={fitView}
90
93
  {...initialRef.current}
91
94
  >
92
- {customColorsDefined(view) && <LikeC4CustomColors customColors={view.customColorDefinitions} />}
95
+ {!isEmpty(view.customColorDefinitions) && <LikeC4CustomColors customColors={view.customColorDefinitions} />}
93
96
  <DiagramContextProvider
94
97
  view={view}
95
98
  keepAspectRatio={keepAspectRatio}
96
99
  className={clsx(rootClassName, className)}
97
100
  readonly={readonly}
101
+ experimentalEdgeEditing={!readonly && experimentalEdgeEditing}
98
102
  pannable={pannable}
99
103
  zoomable={zoomable}
104
+ nodesDraggable={nodesDraggable}
105
+ nodesSelectable={nodesSelectable || enableFocusMode}
100
106
  hasLikeC4Model={hasLikec4model}
101
107
  fitViewEnabled={fitView}
102
108
  fitViewPadding={fitViewPadding}
103
109
  controls={controls}
104
- showElementLinks={showElementLinks}
105
110
  showNavigationButtons={showNavigationButtons && !!onNavigateTo}
106
111
  showNotations={showNotations}
107
- showRelationshipDetails={showRelationshipDetails && hasLikec4model}
108
- nodesDraggable={nodesDraggable}
109
- nodesSelectable={nodesSelectable}
110
- experimentalEdgeEditing={experimentalEdgeEditing}
111
- enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
112
112
  enableFocusMode={enableFocusMode}
113
- enableRelationshipsBrowser={enableRelationshipsBrowser && hasLikec4model}
113
+ enableRelationshipDetails={enableRelationshipDetails && hasLikec4model}
114
+ enableElementDetails={enableElementDetails && hasLikec4model}
115
+ enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
116
+ enableRelationshipBrowser={enableRelationshipBrowser && hasLikec4model}
114
117
  // Apply where filter only in readonly mode
115
118
  whereFilter={readonly ? (where ?? null) : null}
116
119
  renderIcon={renderIcon ?? null}
@@ -134,7 +137,7 @@ export function LikeC4Diagram({
134
137
  />
135
138
  </DiagramContextProvider>
136
139
  </XYFlowProvider>
137
- </LazyMotion>
140
+ </FramerMotionConfig>
138
141
  </EnsureMantine>
139
142
  )
140
143
  }
@@ -159,7 +162,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
159
162
  pannable: s.pannable,
160
163
  fitView: s.fitViewEnabled,
161
164
  enableFocusMode: s.enableFocusMode,
162
- enableOverlays: s.hasLikeC4Model && (s.enableRelationshipsBrowser || s.showRelationshipDetails)
165
+ enableOverlays: s.hasLikeC4Model && (s.enableRelationshipBrowser || s.enableElementDetails)
163
166
  }))
164
167
 
165
168
  return (
@@ -188,9 +191,5 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
188
191
  )}
189
192
  </>
190
193
  )
191
- }, shallowEqual)
194
+ }, deepEqual)
192
195
  LikeC4DiagramInnerMemo.displayName = 'LikeC4DiagramInnerMemo'
193
-
194
- function customColorsDefined(view: DiagramView) {
195
- return Object.keys(view.customColorDefinitions).length > 0
196
- }
@@ -9,7 +9,9 @@ export type StaticLikeC4DiagramProps = Pick<
9
9
  | 'fitView'
10
10
  | 'fitViewPadding'
11
11
  | 'background'
12
- | 'showElementLinks'
12
+ | 'enableElementDetails'
13
+ | 'enableRelationshipDetails'
14
+ | 'enableRelationshipBrowser'
13
15
  | 'initialWidth'
14
16
  | 'initialHeight'
15
17
  | 'renderIcon'
@@ -20,7 +22,9 @@ export function StaticLikeC4Diagram({
20
22
  view,
21
23
  fitView = true,
22
24
  fitViewPadding = 0,
23
- showElementLinks = true,
25
+ enableElementDetails = false,
26
+ enableRelationshipDetails = false,
27
+ enableRelationshipBrowser = enableRelationshipDetails,
24
28
  background = 'transparent',
25
29
  ...rest
26
30
  }: StaticLikeC4DiagramProps) {
@@ -34,12 +38,12 @@ export function StaticLikeC4Diagram({
34
38
  zoomable={false}
35
39
  controls={false}
36
40
  background={background}
37
- showElementLinks={showElementLinks}
38
41
  showDiagramTitle={false}
39
42
  showNotations={false}
40
- showRelationshipDetails={false}
43
+ enableElementDetails={enableElementDetails}
44
+ enableRelationshipDetails={enableRelationshipDetails}
45
+ enableRelationshipBrowser={enableRelationshipBrowser}
41
46
  enableDynamicViewWalkthrough={false}
42
- enableRelationshipsBrowser={false}
43
47
  experimentalEdgeEditing={false}
44
48
  enableFocusMode={false}
45
49
  nodesSelectable={false}
@@ -3,7 +3,7 @@ import type { DiagramState } from '../hooks'
3
3
 
4
4
  export const OverlayContext = createContext({} as {
5
5
  openOverlay: DiagramState['openOverlay']
6
- close: () => void
6
+ close: (onClosed?: () => void) => void
7
7
  })
8
8
 
9
9
  export function useOverlayDialog() {
@@ -1,7 +1,7 @@
1
- import { createVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
2
- import { mantine, vars, whereDark, xyvars } from '../theme-vars'
1
+ import { createVar, globalStyle, style } from '@vanilla-extract/css'
2
+ import { mantine, vars, whereDark, whereLight, xyvars } from '../theme-vars'
3
3
 
4
- const mixColor = createVar('mix-color')
4
+ export const mixColor = createVar('mix-color')
5
5
  export const container = style({
6
6
  position: 'absolute',
7
7
  top: 0,
@@ -16,41 +16,46 @@ export const container = style({
16
16
  isolation: 'isolate',
17
17
  WebkitBackdropFilter: 'blur(var(--backdrop-blur))',
18
18
  backdropFilter: 'blur(var(--backdrop-blur))',
19
- backgroundColor: `color-mix(in srgb, ${mantine.colors.body}, transparent var(--backdrop-opacity))`,
19
+ backgroundColor: `rgb(200 200 200 / var(--backdrop-opacity))`,
20
20
  vars: {
21
- '--backdrop-blur': '3px',
22
- '--backdrop-opacity': '50%',
23
- [mixColor]: `black`,
24
- [xyvars.edge.stroke]: vars.relation.lineColor,
25
- [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
26
- [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 50%)`,
27
- [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 30%)`
21
+ '--backdrop-blur': '0px',
22
+ '--backdrop-opacity': '0%',
23
+ [mixColor]: `black`
28
24
  },
29
25
  selectors: {
30
26
  [`${whereDark} &`]: {
27
+ backgroundColor: `rgb(36 36 36 / var(--backdrop-opacity))`,
31
28
  vars: {
32
- [mixColor]: `white`,
33
- [xyvars.edge.labelColor]: vars.relation.labelColor,
34
- [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
29
+ [mixColor]: `white`
35
30
  }
36
31
  }
37
32
  }
38
33
  })
39
34
 
40
- globalStyle(`:where(${container}) .react-flow__attribution`, {
41
- display: 'none'
35
+ export const cssReactflowMarker = style({})
36
+ globalStyle(`.react-flow${cssReactflowMarker}`, {
37
+ vars: {
38
+ // [xyvars.background.color]: vars.likec4.background.color,
39
+ [xyvars.background.pattern.color]: vars.likec4.background.pattern.color
40
+ }
42
41
  })
43
- globalStyle(`:where(${container}) .react-flow__edge-text`, {
44
- fontSize: 14
42
+
43
+ globalStyle(`${whereDark} .react-flow${cssReactflowMarker}`, {
44
+ vars: {
45
+ [xyvars.edge.labelColor]: vars.relation.labelColor,
46
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
47
+ }
48
+ })
49
+ globalStyle(`:where(${cssReactflowMarker}) .react-flow__attribution`, {
50
+ display: 'none'
45
51
  })
46
- globalStyle(`:where(${container}) .react-flow__node-compound`, {
47
- mixBlendMode: 'hard-light'
52
+ globalStyle(`:where(${cssReactflowMarker}) .react-flow__edge-text`, {
53
+ fontSize: 16
48
54
  })
49
-
50
- globalStyle(`${container} .react-flow__edges > svg`, {
55
+ globalStyle(`${cssReactflowMarker} .react-flow__edges > svg`, {
51
56
  mixBlendMode: 'plus-lighter'
52
57
  })
53
- globalStyle(`:where([data-mantine-color-scheme="light"]) ${container} .react-flow__edges > svg`, {
58
+ globalStyle(`${whereLight} ${cssReactflowMarker} .react-flow__edges > svg`, {
54
59
  mixBlendMode: 'screen'
55
60
  })
56
61
 
@@ -1,96 +1,118 @@
1
- import { ActionIcon, Box, FocusTrap } from '@mantine/core'
1
+ import { ActionIcon, Box, FocusTrap, RemoveScroll } from '@mantine/core'
2
2
  import { useHotkeys } from '@mantine/hooks'
3
3
  import { IconX } from '@tabler/icons-react'
4
4
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
5
5
  import { AnimatePresence, m } from 'framer-motion'
6
- import { memo, useMemo } from 'react'
6
+ import { memo, type PropsWithChildren, useEffect, useMemo, useRef } from 'react'
7
+ import { isNullish } from 'remeda'
7
8
  import { type DiagramState, useDiagramState, useDiagramStoreApi } from '../hooks/useDiagramState'
8
9
  import { EdgeDetailsXYFlow } from './edge-details/EdgeDetailsXYFlow'
9
- import { OverlayContext } from './OverlayContext'
10
+ import { ElementDetailsCard } from './element-details/ElementDetailsCard'
11
+ import { OverlayContext, useOverlayDialog } from './OverlayContext'
10
12
  import * as css from './Overlays.css'
11
- import { RelationshipsXYFlow } from './relationships-of/RelationshipsXYFlow'
13
+ import { RelationshipsOverlay } from './relationships-of/RelationshipsOverlay'
14
+
12
15
  export const Overlays = memo(() => {
13
16
  const diagramStore = useDiagramStoreApi()
14
- const activeOverlay = useDiagramState(s => s.activeOverlay)
17
+ const {
18
+ activeOverlay,
19
+ viewId
20
+ } = useDiagramState(s => ({
21
+ activeOverlay: s.activeOverlay,
22
+ viewId: s.view.id
23
+ }))
24
+
25
+ const onCloseCbRef = useRef<(() => void)>()
15
26
 
16
27
  const ctxValue = useMemo(() => ({
17
28
  openOverlay: ((overlay) => {
18
29
  diagramStore.getState().openOverlay(overlay)
19
30
  }) as DiagramState['openOverlay'],
20
- close: () => {
31
+ close: (cb?: () => void) => {
32
+ onCloseCbRef.current = cb
21
33
  diagramStore.getState().closeOverlay()
22
34
  }
23
35
  }), [diagramStore])
24
36
 
37
+ const onExitComplete = () => {
38
+ onCloseCbRef.current?.()
39
+ onCloseCbRef.current = undefined
40
+ }
41
+
25
42
  const isActive = !!activeOverlay
26
43
  useHotkeys(
27
44
  isActive
28
45
  ? [
29
46
  ['Escape', (e) => {
30
- e.stopImmediatePropagation()
31
- diagramStore.getState().closeOverlay()
47
+ e.stopPropagation()
48
+ ctxValue.close()
32
49
  }, { preventDefault: true }]
33
50
  ]
34
51
  : []
35
52
  )
53
+
36
54
  return (
37
55
  <OverlayContext.Provider value={ctxValue}>
38
- <AnimatePresence>
39
- {activeOverlay && (
40
- <Box
41
- component={m.div}
42
- className={css.container}
43
- data-likec4-color="gray"
44
- initial={{
45
- '--backdrop-blur': '0px',
46
- '--backdrop-opacity': '60%',
47
- opacity: 0,
48
- translateY: -15
49
- }}
50
- animate={{
51
- '--backdrop-blur': '10px',
52
- '--backdrop-opacity': '25%',
53
- opacity: 1,
54
- translateY: 0
55
- }}
56
- exit={{
57
- '--backdrop-blur': '1px',
58
- '--backdrop-opacity': '90%',
59
- translateY: -5,
60
- opacity: 0
61
- }}
62
- >
63
- <FocusTrap>
64
- {activeOverlay.relationshipsOf && (
65
- <XYFlowProvider
66
- defaultNodes={[]}
67
- defaultEdges={[]}>
68
- <RelationshipsXYFlow subjectId={activeOverlay.relationshipsOf} />
69
- </XYFlowProvider>
70
- )}
71
- {activeOverlay.edgeDetails && (
72
- <XYFlowProvider
73
- defaultNodes={[]}
74
- defaultEdges={[]}>
75
- <EdgeDetailsXYFlow edgeId={activeOverlay.edgeDetails} />
76
- </XYFlowProvider>
77
- )}
78
- <Box pos={'absolute'} top={'1rem'} right={'1rem'}>
79
- <ActionIcon
80
- variant="default"
81
- // color="gray"
82
- size={'lg'}
83
- data-autofocus
84
- autoFocus
85
- onClick={(e) => {
86
- e.stopPropagation()
87
- ctxValue.close()
88
- }}>
89
- <IconX />
90
- </ActionIcon>
91
- </Box>
92
- </FocusTrap>
93
- </Box>
56
+ <AnimatePresence initial={false} key={viewId} onExitComplete={onExitComplete}>
57
+ {activeOverlay?.elementDetails && (
58
+ <ElementDetailsCard key={'details card'} fqn={activeOverlay.elementDetails} />
59
+ )}
60
+ </AnimatePresence>
61
+ <AnimatePresence initial={false} onExitComplete={onExitComplete}>
62
+ {activeOverlay && isNullish(activeOverlay.elementDetails) && (
63
+ <RemoveScroll forwardProps>
64
+ <Box
65
+ component={m.div}
66
+ className={css.container}
67
+ data-likec4-color="gray"
68
+ initial={{
69
+ '--backdrop-blur': '0px',
70
+ '--backdrop-opacity': '0%',
71
+ opacity: 0,
72
+ translateY: -15
73
+ }}
74
+ animate={{
75
+ '--backdrop-blur': '10px',
76
+ '--backdrop-opacity': '70%',
77
+ opacity: 1,
78
+ translateY: 0
79
+ }}
80
+ exit={{
81
+ '--backdrop-blur': '1px',
82
+ '--backdrop-opacity': '0%',
83
+ translateY: -5,
84
+ opacity: 0,
85
+ transition: {
86
+ duration: .2
87
+ }
88
+ }}
89
+ >
90
+ <FocusTrap>
91
+ {activeOverlay.relationshipsOf && <RelationshipsOverlay subjectId={activeOverlay.relationshipsOf} />}
92
+ {activeOverlay.edgeDetails && (
93
+ <XYFlowProvider
94
+ defaultNodes={[]}
95
+ defaultEdges={[]}>
96
+ <EdgeDetailsXYFlow edgeId={activeOverlay.edgeDetails} />
97
+ </XYFlowProvider>
98
+ )}
99
+ <Box pos={'absolute'} top={'1rem'} right={'1rem'}>
100
+ <ActionIcon
101
+ variant="default"
102
+ // color="gray"
103
+ size={'lg'}
104
+ // data-autofocus
105
+ // autoFocus
106
+ onClick={(e) => {
107
+ e.stopPropagation()
108
+ ctxValue.close()
109
+ }}>
110
+ <IconX />
111
+ </ActionIcon>
112
+ </Box>
113
+ </FocusTrap>
114
+ </Box>
115
+ </RemoveScroll>
94
116
  )}
95
117
  </AnimatePresence>
96
118
  </OverlayContext.Provider>
@@ -208,3 +230,33 @@ export const Overlays = memo(() => {
208
230
  // </Box>
209
231
  // )
210
232
  // }
233
+ const OverlayDialog = ({ children }: PropsWithChildren) => {
234
+ const overlay = useOverlayDialog()
235
+ const ref = useRef<HTMLDialogElement>(null)
236
+ useEffect(() => {
237
+ ref.current?.showModal()
238
+ }, [])
239
+ return (
240
+ <m.dialog
241
+ ref={ref}
242
+ exit={{
243
+ opacity: 0,
244
+ transition: {
245
+ duration: .15
246
+ }
247
+ }}
248
+ onClick={e => {
249
+ if ((e.target as any)?.nodeName?.toUpperCase() === 'DIALOG') {
250
+ e.stopPropagation()
251
+ ref.current?.close()
252
+ }
253
+ }}
254
+ onClose={e => {
255
+ e.stopPropagation()
256
+ overlay.close()
257
+ }}
258
+ >
259
+ {children}
260
+ </m.dialog>
261
+ )
262
+ }
@@ -27,6 +27,7 @@ type ElementNodeProps = NodeProps<XYFlowTypes.ElementNode>
27
27
  function selector(s: DiagramState) {
28
28
  return {
29
29
  currentViewId: s.view.id,
30
+ enableRelationshipBrowser: s.enableRelationshipBrowser,
30
31
  onNavigateTo: s.onNavigateTo,
31
32
  onOpenSource: s.onOpenSourceElement
32
33
  }
@@ -47,7 +48,8 @@ export function ElementNode({
47
48
  const {
48
49
  currentViewId,
49
50
  onNavigateTo,
50
- onOpenSource
51
+ onOpenSource,
52
+ enableRelationshipBrowser
51
53
  } = useDiagramState(selector)
52
54
  return (
53
55
  <>
@@ -97,21 +99,22 @@ export function ElementNode({
97
99
  <Action
98
100
  onClick={(event) => {
99
101
  event.stopPropagation()
100
- setTimeout(() => onNavigateTo(navigateTo), 100)
101
- overlay.close()
102
+ overlay.close(() => onNavigateTo(navigateTo))
102
103
  }}>
103
104
  <IconZoomScan stroke={1.8} style={{ width: '75%' }} />
104
105
  </Action>
105
106
  )}
106
- <Action
107
- onClick={(event) => {
108
- event.stopPropagation()
109
- overlay.openOverlay({
110
- relationshipsOf: data.fqn
111
- })
112
- }}>
113
- <IconTransform stroke={1.8} style={{ width: '72%' }} />
114
- </Action>
107
+ {enableRelationshipBrowser && (
108
+ <Action
109
+ onClick={(event) => {
110
+ event.stopPropagation()
111
+ overlay.openOverlay({
112
+ relationshipsOf: data.fqn
113
+ })
114
+ }}>
115
+ <IconTransform stroke={1.8} style={{ width: '72%' }} />
116
+ </Action>
117
+ )}
115
118
  {onOpenSource && (
116
119
  <Action
117
120
  onClick={(event) => {