@likec4/diagram 1.17.0 → 1.18.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 (53) hide show
  1. package/package.json +24 -18
  2. package/src/LikeC4Diagram.props.ts +27 -12
  3. package/src/LikeC4Diagram.tsx +14 -9
  4. package/src/LikeC4Search.tsx +149 -0
  5. package/src/StaticLikeC4Diagram.tsx +1 -0
  6. package/src/hooks/useDiagramState.ts +8 -3
  7. package/src/hooks/useXYFlow.ts +2 -1
  8. package/src/likec4model/useLikeC4ElementsTree.ts +6 -13
  9. package/src/likec4model/useLikeC4Model.ts +21 -11
  10. package/src/overlays/Overlays.tsx +96 -63
  11. package/src/overlays/edge-details/EdgeDetailsXYFlow.tsx +3 -1
  12. package/src/overlays/edge-details/_types.ts +7 -3
  13. package/src/overlays/edge-details/use-layouted-edge-details.ts +21 -14
  14. package/src/overlays/edge-details/xyflow/ElementNode.tsx +4 -2
  15. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +4 -4
  16. package/src/overlays/element-details/ElementDetailsCard.tsx +94 -81
  17. package/src/overlays/element-details/TabPanelDeployments.css.ts +69 -0
  18. package/src/overlays/element-details/TabPanelDeployments.tsx +217 -0
  19. package/src/overlays/element-details/TabPanelRelationships.tsx +8 -9
  20. package/src/overlays/element-details/TabPanelStructure.tsx +10 -10
  21. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +1 -1
  22. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +3 -11
  23. package/src/overlays/relationships-of/SelectElement.tsx +2 -2
  24. package/src/overlays/relationships-of/_types.ts +2 -2
  25. package/src/overlays/relationships-of/use-layouted-relationships.ts +18 -19
  26. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +1 -3
  27. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +12 -6
  28. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +0 -1
  29. package/src/state/aligners.ts +473 -0
  30. package/src/state/diagramStore.layout.ts +67 -0
  31. package/src/state/diagramStore.ts +31 -77
  32. package/src/ui/DiagramTitlePanel.tsx +2 -29
  33. package/src/ui/Link.tsx +40 -0
  34. package/src/ui/notation/NotationPanel.tsx +4 -2
  35. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +2 -2
  36. package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
  37. package/src/ui/top-left/TopLeftPanel.tsx +16 -3
  38. package/src/xyflow/edges/EdgeLabel.tsx +4 -4
  39. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +20 -7
  40. package/src/xyflow/nodes/compound/CompoundNode.css.ts +28 -13
  41. package/src/xyflow/nodes/compound/CompoundNode.tsx +51 -19
  42. package/src/xyflow/nodes/element/ElementNode.tsx +153 -92
  43. package/src/xyflow/nodes/element/element.css.ts +53 -11
  44. package/src/xyflow/nodes/shared/ElementIcon.tsx +1 -1
  45. package/src/xyflow/nodes/shared/Toolbar.tsx +24 -8
  46. package/src/xyflow/useLayoutConstraints.ts +1 -1
  47. package/src/xyflow/useXYFlowEvents.ts +18 -5
  48. package/src/ui/OptionsPanel.css.ts +0 -9
  49. package/src/ui/OptionsPanel.tsx +0 -70
  50. package/src/ui/options/EdgeOptions.tsx +0 -142
  51. package/src/ui/options/NodeOptions.tsx +0 -362
  52. package/src/xyflow/nodes/element/ElementDetails.css.ts +0 -101
  53. package/src/xyflow/nodes/element/ElementDetails.tsx +0 -393
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -23,44 +23,49 @@
23
23
  "exports": {
24
24
  ".": "./src/index.ts"
25
25
  },
26
- "packageManager": "yarn@4.5.1",
26
+ "packageManager": "yarn@4.5.3",
27
27
  "scripts": {
28
28
  "typecheck": "tsc --noEmit",
29
+ "build": "",
29
30
  "lint": "run -T eslint src/ --fix",
30
- "clean": "rm -r -f -g 'dist/*' .tsbuildinfo"
31
+ "clean": "rm -r -f -g 'dist/*' .tsbuildinfo",
32
+ "test": "vitest run --no-isolate"
31
33
  },
32
34
  "dependencies": {
33
35
  "@dagrejs/dagre": "^1.1.4",
34
- "@likec4/core": "1.17.0",
35
- "@mantine/vanilla-extract": "7.13.4",
36
+ "@likec4/core": "1.18.0",
37
+ "@mantine/vanilla-extract": "7.15.1",
36
38
  "@react-hookz/web": "^24.0.4",
37
39
  "@tabler/icons-react": "3.17.0",
38
- "@vanilla-extract/css": "^1.16.0",
40
+ "@vanilla-extract/css": "^1.16.1",
39
41
  "@vanilla-extract/css-utils": "^0.1.4",
40
42
  "@vanilla-extract/dynamic": "^2.1.2",
41
- "@xyflow/react": "12.3.4",
42
- "@xyflow/system": "0.0.45",
43
+ "@xyflow/react": "12.3.5",
44
+ "@xyflow/system": "0.0.46",
43
45
  "bezier-js": "^6.1.4",
44
46
  "clsx": "^2.1.1",
45
47
  "d3-shape": "^3.2.0",
46
- "esm-env": "^1.1.4",
48
+ "esm-env": "^1.2.1",
47
49
  "fast-equals": "^5.0.1",
48
- "framer-motion": "^11.11.11",
50
+ "framer-motion": "^11.14.1",
49
51
  "khroma": "^2.1.0",
50
- "remeda": "^2.16.0",
51
- "type-fest": "4.26.1",
52
+ "react-error-boundary": "^4.1.2",
53
+ "remeda": "^2.17.4",
54
+ "type-fest": "4.28.1",
52
55
  "zustand": "^4.5.5"
53
56
  },
54
57
  "peerDependencies": {
55
- "@mantine/core": "^7.13.4",
56
- "@mantine/hooks": "^7.13.4",
58
+ "@mantine/core": "^7.15.1",
59
+ "@mantine/hooks": "^7.15.1",
60
+ "@mantine/spotlight": "^7.15.1",
57
61
  "react": "^18.3.1",
58
62
  "react-dom": "^18.3.1"
59
63
  },
60
64
  "devDependencies": {
61
- "@likec4/tsconfig": "1.17.0",
62
- "@mantine/core": "7.13.4",
63
- "@mantine/hooks": "7.13.4",
65
+ "@likec4/tsconfig": "1.18.0",
66
+ "@mantine/core": "7.15.1",
67
+ "@mantine/hooks": "7.15.1",
68
+ "@mantine/spotlight": "7.15.1",
64
69
  "@types/bezier-js": "^4.1.3",
65
70
  "@types/d3-shape": "^3.1.6",
66
71
  "@types/react": "^18.3.11",
@@ -68,6 +73,7 @@
68
73
  "execa": "^9.3.1",
69
74
  "react": "^18.3.1",
70
75
  "react-dom": "^18.3.1",
71
- "typescript": "^5.6.3"
76
+ "typescript": "^5.7.2",
77
+ "vitest": "^2.1.8"
72
78
  }
73
79
  }
@@ -3,9 +3,9 @@ import type {
3
3
  DiagramNode,
4
4
  DiagramView,
5
5
  Fqn,
6
- RelationID,
6
+ RelationId,
7
7
  ViewChange,
8
- ViewID,
8
+ ViewId,
9
9
  WhereOperator
10
10
  } from '@likec4/core'
11
11
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'
@@ -13,7 +13,7 @@ import type { XYBackground } from './xyflow/XYFlowBackground'
13
13
 
14
14
  export type { WhereOperator }
15
15
 
16
- export type DiagramNodeWithNavigate<ID extends string = ViewID> = Omit<DiagramNode, 'navigateTo'> & {
16
+ export type DiagramNodeWithNavigate<ID extends string = ViewId> = Omit<DiagramNode, 'navigateTo'> & {
17
17
  navigateTo: ID
18
18
  }
19
19
 
@@ -27,7 +27,7 @@ export type ElementIconRenderer = (props: {
27
27
  node: ElementIconNodeProps
28
28
  }) => ReactNode
29
29
 
30
- export type OnNavigateTo<ID extends string = ViewID> = (
30
+ export type OnNavigateTo<ID extends string = ViewId> = (
31
31
  to: ID,
32
32
  // These fields present if navigateTo triggered by a node click
33
33
  event?: ReactMouseEvent,
@@ -148,6 +148,12 @@ export interface LikeC4DiagramProperties {
148
148
  */
149
149
  enableFocusMode?: boolean | undefined
150
150
 
151
+ /**
152
+ * Enable search popup for elements and views
153
+ * @default true
154
+ */
155
+ enableSearch?: boolean | undefined
156
+
151
157
  /**
152
158
  * Enable modal with element details
153
159
  * @default false
@@ -185,6 +191,22 @@ export interface LikeC4DiagramProperties {
185
191
  where?: WhereOperator<string, string> | undefined
186
192
  }
187
193
 
194
+ export type OpenSourceParams =
195
+ | {
196
+ element: Fqn
197
+ property?: string
198
+ }
199
+ | {
200
+ relation: RelationId
201
+ }
202
+ | {
203
+ deployment: Fqn
204
+ property?: string
205
+ }
206
+ | {
207
+ view: ViewId
208
+ }
209
+
188
210
  export interface LikeC4DiagramEventHandlers {
189
211
  onChange?: OnChange | null | undefined
190
212
  onNavigateTo?: OnNavigateTo | null | undefined
@@ -199,12 +221,5 @@ export interface LikeC4DiagramEventHandlers {
199
221
  // if set, will render a burger menu icon in the top left corner
200
222
  onBurgerMenuClick?: null | undefined | (() => void)
201
223
 
202
- // if set, will render a source code icon in the top left corner
203
- onOpenSourceView?: null | undefined | (() => void)
204
-
205
- // if set, will render an icon in properties webview for each element
206
- onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void)
207
-
208
- // if set, will be called on edge click
209
- onOpenSourceRelation?: null | undefined | ((id: RelationID) => void)
224
+ onOpenSource?: null | undefined | ((params: OpenSourceParams) => void)
210
225
  }
@@ -1,6 +1,7 @@
1
+ import { ComputedView, isDeploymentView } from '@likec4/core'
1
2
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
2
3
  import clsx from 'clsx'
3
- import { deepEqual, shallowEqual } from 'fast-equals'
4
+ import { deepEqual } from 'fast-equals'
4
5
  import { memo, useEffect, useRef } from 'react'
5
6
  import { isEmpty } from 'remeda'
6
7
  import { rootClassName } from './globals.css'
@@ -9,6 +10,7 @@ import { LikeC4CustomColors } from './LikeC4CustomColors'
9
10
  import * as css from './LikeC4Diagram.css'
10
11
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
11
12
  import { useLikeC4Model } from './likec4model'
13
+ import { LikeC4Search } from './LikeC4Search'
12
14
  import { Overlays } from './overlays'
13
15
  import { DiagramContextProvider } from './state/DiagramContext'
14
16
  import { EnsureMantine } from './ui/EnsureMantine'
@@ -39,6 +41,7 @@ export function LikeC4Diagram({
39
41
  enableElementDetails = false,
40
42
  enableRelationshipDetails = enableElementDetails,
41
43
  enableRelationshipBrowser = enableRelationshipDetails,
44
+ enableSearch = true,
42
45
  initialWidth,
43
46
  initialHeight,
44
47
  keepAspectRatio = false,
@@ -52,9 +55,7 @@ export function LikeC4Diagram({
52
55
  onNavigateTo,
53
56
  onNodeClick,
54
57
  onNodeContextMenu,
55
- onOpenSourceElement,
56
- onOpenSourceView,
57
- onOpenSourceRelation,
58
+ onOpenSource,
58
59
  onBurgerMenuClick,
59
60
  renderIcon,
60
61
  where,
@@ -110,10 +111,12 @@ export function LikeC4Diagram({
110
111
  showNavigationButtons={showNavigationButtons && !!onNavigateTo}
111
112
  showNotations={showNotations}
112
113
  enableFocusMode={enableFocusMode}
113
- enableRelationshipDetails={enableRelationshipDetails && hasLikec4model}
114
+ enableSearch={hasLikec4model && enableSearch}
114
115
  enableElementDetails={enableElementDetails && hasLikec4model}
115
116
  enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
116
117
  enableRelationshipBrowser={enableRelationshipBrowser && hasLikec4model}
118
+ // TODO: temporary disable relationship details for deployment views
119
+ enableRelationshipDetails={enableRelationshipDetails && hasLikec4model && !isDeploymentView(view)}
117
120
  // Apply where filter only in readonly mode
118
121
  whereFilter={readonly ? (where ?? null) : null}
119
122
  renderIcon={renderIcon ?? null}
@@ -126,14 +129,13 @@ export function LikeC4Diagram({
126
129
  onChange={onChange ?? null}
127
130
  onNavigateTo={onNavigateTo ?? null}
128
131
  onCanvasDblClick={onCanvasDblClick ?? null}
129
- onOpenSourceView={onOpenSourceView ?? null}
130
- onOpenSourceElement={onOpenSourceElement ?? null}
131
- onOpenSourceRelation={onOpenSourceRelation ?? null}
132
+ onOpenSource={onOpenSource ?? null}
132
133
  onBurgerMenuClick={onBurgerMenuClick ?? null}
133
134
  >
134
135
  <LikeC4DiagramInnerMemo
135
136
  background={background}
136
137
  showDiagramTitle={showDiagramTitle}
138
+ enableSearch={hasLikec4model && enableSearch}
137
139
  />
138
140
  </DiagramContextProvider>
139
141
  </XYFlowProvider>
@@ -146,10 +148,12 @@ LikeC4Diagram.displayName = 'LikeC4Diagram'
146
148
  type LikeC4DiagramInnerProps = {
147
149
  background: NonNullable<LikeC4DiagramProperties['background']>
148
150
  showDiagramTitle: boolean
151
+ enableSearch: boolean
149
152
  }
150
153
  const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
151
154
  background,
152
- showDiagramTitle
155
+ showDiagramTitle,
156
+ enableSearch
153
157
  }) {
154
158
  const {
155
159
  isInitialized,
@@ -183,6 +187,7 @@ const LikeC4DiagramInnerMemo = /* @__PURE__ */ memo<LikeC4DiagramInnerProps>(fun
183
187
  />
184
188
  </XYFlow>
185
189
  {enableOverlays && <Overlays />}
190
+ {enableSearch && <LikeC4Search />}
186
191
  {isInitialized && (
187
192
  <>
188
193
  {fitView && <FitViewOnDiagramChange />}
@@ -0,0 +1,149 @@
1
+ import { Center, Group, Highlight, Text } from '@mantine/core'
2
+ import { Spotlight, type SpotlightActionData, SpotlightActionsGroup } from '@mantine/spotlight'
3
+ import { IconRectangularPrism, IconSearch, IconSitemap } from '@tabler/icons-react'
4
+ import { memo, useMemo, useState } from 'react'
5
+ import { filter, map, pipe } from 'remeda'
6
+ import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from './hooks'
7
+ import { useLikeC4Model } from './likec4model'
8
+
9
+ interface QueryMatch {
10
+ data: SpotlightActionData
11
+ keyword: string
12
+ match: string
13
+ }
14
+
15
+ function filterAction(actionData: SpotlightActionData, query: string): QueryMatch | undefined {
16
+ const queryLower = query.toLowerCase()
17
+ const keywords = Array.isArray(actionData.keywords) ? actionData.keywords : []
18
+ const keywordMatch = keywords
19
+ .map(keyword => {
20
+ const index = keyword.toLowerCase().indexOf(queryLower)
21
+ return index !== -1
22
+ ? {
23
+ keyword: keyword,
24
+ match: keyword.substring(index, index + query.length)
25
+ }
26
+ : null
27
+ })
28
+ .find(match => !!match)
29
+ return !!keywordMatch
30
+ ? {
31
+ data: actionData,
32
+ keyword: keywordMatch.keyword,
33
+ match: keywordMatch.match
34
+ }
35
+ : undefined
36
+ }
37
+
38
+ function buildFilteredActions(actionsData: SpotlightActionData[], query: string): JSX.Element[] {
39
+ return pipe(
40
+ actionsData,
41
+ map(a => filterAction(a, query)),
42
+ filter(qm => !!qm),
43
+ map(qm => buildSpotlightAction(qm))
44
+ )
45
+ }
46
+
47
+ function highlightMatch(match: QueryMatch): JSX.Element {
48
+ return <Highlight highlight={match.match}>{match.keyword}</Highlight>
49
+ }
50
+
51
+ function buildSpotlightAction(matchedAction: QueryMatch): JSX.Element {
52
+ const isMatchInLabel = matchedAction.keyword == matchedAction.data.label
53
+ return (
54
+ <Spotlight.Action key={matchedAction.data.id} onClick={matchedAction.data.onClick}>
55
+ <Center style={{ marginRight: '8px ' }}>
56
+ {matchedAction.data.leftSection}
57
+ </Center>
58
+ <Group wrap="nowrap" w="100%">
59
+ <div style={{ flex: 1 }}>
60
+ <Text>
61
+ {isMatchInLabel ? highlightMatch(matchedAction) : matchedAction.data.label}
62
+ </Text>
63
+ {!isMatchInLabel && <Text opacity={0.6} size="xs">{highlightMatch(matchedAction)}</Text>}
64
+ </div>
65
+ </Group>
66
+ </Spotlight.Action>
67
+ )
68
+ }
69
+
70
+ export const LikeC4Search = memo(() => {
71
+ const view = useDiagramState(s => s.view)
72
+ const model = useLikeC4Model(true)
73
+ const store = useDiagramStoreApi()
74
+ const [query, setQuery] = useState('')
75
+
76
+ const getNodeActionsData = (): SpotlightActionData[] => {
77
+ const { focusOnNode } = store.getState()
78
+
79
+ return pipe(
80
+ view.nodes,
81
+ filter(n => !!n.title),
82
+ map(n => ({
83
+ id: n.id,
84
+ label: n.title,
85
+ keywords: [
86
+ n.title,
87
+ ...(n.tags ?? []).map(t => `#${t}`),
88
+ ...(n.description ? [n.description] : [])
89
+ ].filter(k => k.toLowerCase()),
90
+ onClick: () => focusOnNode(n.id),
91
+ leftSection: <IconRectangularPrism />
92
+ }))
93
+ )
94
+ }
95
+
96
+ const getViewActionsData = (): SpotlightActionData[] => {
97
+ const { onNavigateTo } = store.getState()
98
+ const views = model.views().toArray()
99
+
100
+ return map(views, v => ({
101
+ id: v.id,
102
+ label: v.title ?? v.id,
103
+ keywords: [
104
+ v.title ?? v.id,
105
+ ...(v.tags ?? []),
106
+ ...(v.$view.description ? [v.$view.description] : [])
107
+ ],
108
+ onClick: () => {
109
+ store.setState({
110
+ hoveredNodeId: null,
111
+ lastOnNavigate: {
112
+ fromView: view.id,
113
+ toView: v.id,
114
+ fromNode: null
115
+ }
116
+ })
117
+ onNavigateTo?.(v.id)
118
+ },
119
+ leftSection: <IconSitemap />
120
+ }))
121
+ }
122
+
123
+ const { nodeActions, viewActions } = useMemo(() => ({
124
+ nodeActions: buildFilteredActions(getNodeActionsData(), query),
125
+ viewActions: buildFilteredActions(getViewActionsData(), query)
126
+ }), [model, store, view, query])
127
+
128
+ const portalProps = useMantinePortalProps()
129
+ return (
130
+ <Spotlight.Root
131
+ {...portalProps}
132
+ shortcut={['mod + f', 'mod + k']}
133
+ query={query}
134
+ onQueryChange={setQuery}
135
+ scrollable
136
+ maxHeight={'calc(100vh - 100px)'}
137
+ >
138
+ <Spotlight.Search
139
+ placeholder="Search elements in current view and other views..."
140
+ leftSection={<IconSearch stroke={1.5} />}
141
+ />
142
+ <Spotlight.ActionsList>
143
+ {nodeActions.length > 0 && <SpotlightActionsGroup label="Elements">{nodeActions}</SpotlightActionsGroup>}
144
+ {viewActions.length > 0 && <SpotlightActionsGroup label="Views">{viewActions}</SpotlightActionsGroup>}
145
+ {nodeActions.length == 0 && viewActions.length == 0 && <Spotlight.Empty>Nothing found...</Spotlight.Empty>}
146
+ </Spotlight.ActionsList>
147
+ </Spotlight.Root>
148
+ )
149
+ })
@@ -48,6 +48,7 @@ export function StaticLikeC4Diagram({
48
48
  enableFocusMode={false}
49
49
  nodesSelectable={false}
50
50
  nodesDraggable={false}
51
+ enableSearch={false}
51
52
  {...rest}
52
53
  />
53
54
  )
@@ -2,10 +2,16 @@ import { shallowEqual } from 'fast-equals'
2
2
  import { useContext } from 'react'
3
3
  import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'
4
4
 
5
+ import type { ViewId } from '@likec4/core'
5
6
  import { DiagramContext } from '../state/DiagramContext'
6
7
  import type { DiagramState, DiagramStoreApi } from '../state/diagramStore'
7
8
 
8
- function useDiagramState<StateSlice = unknown>(
9
+ const selectViewId = (state: DiagramState) => state.view.id
10
+ export function useCurrentViewId(): ViewId {
11
+ return useDiagramState(selectViewId)
12
+ }
13
+
14
+ export function useDiagramState<StateSlice = unknown>(
9
15
  selector: (state: DiagramState) => StateSlice,
10
16
  equalityFn?: (a: StateSlice, b: StateSlice) => boolean
11
17
  ): StateSlice {
@@ -18,7 +24,7 @@ function useDiagramState<StateSlice = unknown>(
18
24
  return useZustandStore(store, selector, equalityFn ?? shallowEqual)
19
25
  }
20
26
 
21
- function useDiagramStoreApi(): DiagramStoreApi {
27
+ export function useDiagramStoreApi(): DiagramStoreApi {
22
28
  const store = useContext(DiagramContext)
23
29
 
24
30
  if (store === null) {
@@ -28,4 +34,3 @@ function useDiagramStoreApi(): DiagramStoreApi {
28
34
  return store
29
35
  }
30
36
  export type { DiagramState, DiagramStoreApi }
31
- export { useDiagramState, useDiagramStoreApi }
@@ -1,4 +1,4 @@
1
- import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
1
+ import { useInternalNode, useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
2
2
  import { deepEqual, shallowEqual } from 'fast-equals'
3
3
  import { useCallback } from 'react'
4
4
  import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../xyflow/types'
@@ -6,6 +6,7 @@ import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../xyflow/types'
6
6
  export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
7
7
 
8
8
  export const useXYNodesData = useNodesData<XYFlowNode>
9
+ export const useXYInternalNode = useInternalNode<XYFlowNode>
9
10
 
10
11
  export function useXYStore<StateSlice = unknown>(
11
12
  selector: (state: XYFlowState) => StateSlice,
@@ -1,11 +1,4 @@
1
- import {
2
- compareNatural,
3
- type Fqn,
4
- type LikeC4DiagramModel,
5
- type LikeC4Model,
6
- type LikeC4ViewModel,
7
- type ViewID
8
- } from '@likec4/core'
1
+ import { compareNatural, type Fqn, type LikeC4Model, type ViewId } from '@likec4/core'
9
2
  import { useMemo } from 'react'
10
3
  import { useLikeC4Model } from './useLikeC4Model'
11
4
 
@@ -18,12 +11,12 @@ interface LikeC4ModelTreeNodeData {
18
11
  const sortByLabel = (a: LikeC4ModelTreeNodeData, b: LikeC4ModelTreeNodeData) => compareNatural(a.label, b.label)
19
12
 
20
13
  function buildNode(
21
- element: LikeC4Model.ElementModel | LikeC4DiagramModel.Element | LikeC4ViewModel.Element
14
+ element: LikeC4Model.Node | LikeC4Model.Element
22
15
  ): LikeC4ModelTreeNodeData {
23
16
  return {
24
17
  label: element.title,
25
18
  value: element.id,
26
- children: element.children().map(buildNode).sort(sortByLabel)
19
+ children: [...element.children()].map(buildNode).sort(sortByLabel)
27
20
  }
28
21
  }
29
22
 
@@ -31,14 +24,14 @@ function buildNode(
31
24
  * Returns a tree of elements in the model.
32
25
  * If `viewId` is provided, returns the tree of elements in the view.
33
26
  */
34
- export function useLikeC4ElementsTree(viewId?: ViewID): LikeC4ModelTreeNodeData[] {
27
+ export function useLikeC4ElementsTree(viewId?: ViewId): LikeC4ModelTreeNodeData[] {
35
28
  const model = useLikeC4Model(true)
36
29
  return useMemo(() => {
37
30
  if (viewId) {
38
31
  const view = model.view(viewId)
39
- return view.roots().map(buildNode).sort(sortByLabel)
32
+ return [...view.roots()].map(buildNode).sort(sortByLabel)
40
33
  } else {
41
- return model.roots().map(buildNode).sort(sortByLabel)
34
+ return [...model.roots()].map(buildNode).sort(sortByLabel)
42
35
  }
43
36
  }, [model, viewId ?? null])
44
37
  }
@@ -1,7 +1,8 @@
1
- import { type ComputedView, type DiagramView, type LikeC4Model, type ViewID } from '@likec4/core'
1
+ import { type ComputedView, type DiagramView, type Fqn, LikeC4Model, type ViewId } from '@likec4/core'
2
2
  import { useContext } from 'react'
3
3
  import { isDefined, isNonNullish, isString } from 'remeda'
4
4
  import type { LiteralUnion } from 'type-fest'
5
+ import { useCurrentViewId } from '../hooks'
5
6
  import { LikeC4ModelContext } from './LikeC4ModelContext'
6
7
 
7
8
  export function useLikeC4Model(): LikeC4Model | null
@@ -9,11 +10,11 @@ export function useLikeC4Model(strict: true): LikeC4Model
9
10
  export function useLikeC4Model(strict: boolean): LikeC4Model | null
10
11
  export function useLikeC4Model(strict: true, type: 'layouted'): LikeC4Model.Layouted
11
12
  export function useLikeC4Model(strict: true, type: 'computed'): LikeC4Model.Computed
12
- export function useLikeC4Model(strict: true, type: LikeC4Model['type'] | undefined): LikeC4Model
13
+ export function useLikeC4Model(strict: true, type: 'layouted' | 'computed' | undefined): LikeC4Model
13
14
  export function useLikeC4Model(strict: boolean, type: 'layouted'): LikeC4Model.Layouted | null
14
15
  export function useLikeC4Model(strict: boolean, type: 'computed'): LikeC4Model.Computed | null
15
- export function useLikeC4Model(strict: boolean, type: LikeC4Model['type'] | undefined): LikeC4Model | null
16
- export function useLikeC4Model(strict?: boolean, type?: LikeC4Model['type']) {
16
+ export function useLikeC4Model(strict: boolean, type: 'layouted' | 'computed' | undefined): LikeC4Model | null
17
+ export function useLikeC4Model(strict?: boolean, type?: 'layouted' | 'computed') {
17
18
  const model = useContext(LikeC4ModelContext)
18
19
 
19
20
  if (isString(type) && isNonNullish(model) && model.type !== type) {
@@ -26,33 +27,42 @@ export function useLikeC4Model(strict?: boolean, type?: LikeC4Model['type']) {
26
27
  return model
27
28
  }
28
29
 
29
- export function useLikeC4Views(): LikeC4Model.SourceModel['views'] {
30
- return useLikeC4Model(true).sourcemodel.views
30
+ export function useLikeC4Views(): Record<ViewId, ComputedView | DiagramView> {
31
+ return useLikeC4Model(true).$model.views
31
32
  }
32
33
 
33
- export function useLikeC4ViewModel(viewId: LiteralUnion<ViewID, string>): LikeC4Model.ViewModel {
34
+ export function useLikeC4ViewModel(viewId: LiteralUnion<ViewId, string>): LikeC4Model.View {
35
+ return useLikeC4Model(true).view(viewId)
36
+ }
37
+
38
+ export function useLikeC4CurrentViewModel(): LikeC4Model.View {
39
+ const viewId = useCurrentViewId()
34
40
  return useLikeC4Model(true).view(viewId)
35
41
  }
36
42
 
37
43
  /**
38
44
  * Parsed view, computed or layouted
39
45
  */
40
- export function useLikeC4View(viewId: LiteralUnion<ViewID, string>): ComputedView | DiagramView | null {
46
+ export function useLikeC4View(viewId: LiteralUnion<ViewId, string>): ComputedView | DiagramView | null {
41
47
  const model = useLikeC4Model(true)
42
48
  try {
43
- return model.view(viewId).view
49
+ return model.view(viewId).$view
44
50
  } catch (error) {
45
51
  console.warn(error)
46
52
  return null
47
53
  }
48
54
  }
49
55
 
50
- export function useLikeC4DiagramView(viewId: LiteralUnion<ViewID, string>): DiagramView | null {
56
+ export function useLikeC4DiagramView(viewId: LiteralUnion<ViewId, string>): DiagramView | null {
51
57
  const model = useLikeC4Model(true, 'layouted')
52
58
  try {
53
- return model.view(viewId).view
59
+ return model.view(viewId).$view
54
60
  } catch (error) {
55
61
  console.warn(error)
56
62
  return null
57
63
  }
58
64
  }
65
+
66
+ export function useLikeC4ElementModel(fqn: LiteralUnion<Fqn, string>): LikeC4Model.Element {
67
+ return useLikeC4Model(true).element(fqn)
68
+ }