@tldiagram/core-ui 1.95.0 → 2.0.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 (102) hide show
  1. package/dist/api/client.d.ts +184 -3
  2. package/dist/components/ConnectorPanel.d.ts +5 -1
  3. package/dist/components/CrossBranchControls.d.ts +4 -3
  4. package/dist/components/ElementNode.d.ts +5 -0
  5. package/dist/components/ElementPanel.d.ts +6 -1
  6. package/dist/components/LayoutSection.d.ts +2 -1
  7. package/dist/components/MergeDialog.d.ts +16 -0
  8. package/dist/components/MiniZoomOnboarding.d.ts +2 -1
  9. package/dist/components/NodeContainer.d.ts +2 -0
  10. package/dist/components/ProxyConnectorPanel.d.ts +4 -1
  11. package/dist/components/ViewExplorer/index.d.ts +1 -1
  12. package/dist/components/ViewFloatingMenu-vscode.d.ts +5 -0
  13. package/dist/components/ViewFloatingMenu.d.ts +8 -1
  14. package/dist/components/ViewGridNode.d.ts +3 -0
  15. package/dist/components/ViewPanel.d.ts +2 -1
  16. package/dist/components/WorkspacePanel.d.ts +2 -0
  17. package/dist/components/ZUI/ZUICanvas.d.ts +5 -0
  18. package/dist/components/ZUI/focus.d.ts +32 -0
  19. package/dist/components/ZUI/focus.test.d.ts +1 -0
  20. package/dist/components/ZUI/layout.d.ts +2 -2
  21. package/dist/components/ZUI/proxy.d.ts +20 -4
  22. package/dist/components/ZUI/renderer.d.ts +35 -1
  23. package/dist/components/ZUI/types.d.ts +6 -0
  24. package/dist/components/ZUI/useZUIInteraction.d.ts +1 -0
  25. package/dist/context/WorkspaceVersionContext.d.ts +49 -0
  26. package/dist/crossBranch/resolve.d.ts +39 -2
  27. package/dist/crossBranch/resolve.test.d.ts +1 -0
  28. package/dist/crossBranch/settings.d.ts +6 -1
  29. package/dist/crossBranch/types.d.ts +8 -0
  30. package/dist/hooks/useElementSearch.d.ts +8 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +14597 -12083
  33. package/dist/pages/InfiniteZoom.d.ts +1 -0
  34. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +6 -1
  35. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +2 -0
  36. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +4 -2
  37. package/dist/pages/ViewEditor/hooks/useViewEditHistory.d.ts +13 -0
  38. package/dist/pages/viewsJumpSearch.d.ts +22 -0
  39. package/dist/pages/viewsJumpSearch.test.d.ts +1 -0
  40. package/dist/store/useStore.d.ts +3 -0
  41. package/dist/types/index.d.ts +9 -0
  42. package/dist/utils/elementIcon.d.ts +2 -0
  43. package/dist/utils/elementIcon.test.d.ts +1 -0
  44. package/dist/utils/sourceEditor.d.ts +7 -0
  45. package/dist/utils/watchDiffSummary.d.ts +34 -0
  46. package/package.json +2 -2
  47. package/src/App.tsx +12 -8
  48. package/src/api/client.ts +488 -26
  49. package/src/components/CodePreviewPanel.tsx +90 -16
  50. package/src/components/ConnectorPanel.tsx +34 -3
  51. package/src/components/ContextNeighborElement.tsx +2 -5
  52. package/src/components/CrossBranchControls.tsx +46 -17
  53. package/src/components/ElementNode.tsx +98 -47
  54. package/src/components/ElementPanel.tsx +62 -25
  55. package/src/components/InlineElementAdder.tsx +8 -3
  56. package/src/components/LayoutSection.tsx +4 -1
  57. package/src/components/MergeDialog.tsx +269 -0
  58. package/src/components/MiniZoomOnboarding.tsx +29 -22
  59. package/src/components/NodeContainer.tsx +55 -17
  60. package/src/components/ProxyConnectorPanel.tsx +58 -16
  61. package/src/components/ViewBezierConnector.tsx +116 -21
  62. package/src/components/ViewExplorer/index.tsx +1 -1
  63. package/src/components/ViewFloatingMenu-vscode.tsx +5 -0
  64. package/src/components/ViewFloatingMenu.tsx +110 -1
  65. package/src/components/ViewGridNode.tsx +59 -8
  66. package/src/components/ViewPanel.tsx +3 -2
  67. package/src/components/WorkspacePanel.tsx +938 -0
  68. package/src/components/ZUI/ZUICanvas.tsx +226 -127
  69. package/src/components/ZUI/focus.test.ts +534 -0
  70. package/src/components/ZUI/focus.ts +293 -0
  71. package/src/components/ZUI/layout.ts +7 -11
  72. package/src/components/ZUI/proxy.ts +470 -114
  73. package/src/components/ZUI/renderer.ts +510 -134
  74. package/src/components/ZUI/types.ts +6 -0
  75. package/src/components/ZUI/useZUIInteraction.ts +66 -29
  76. package/src/context/WorkspaceVersionContext.tsx +126 -0
  77. package/src/crossBranch/resolve.test.ts +342 -0
  78. package/src/crossBranch/resolve.ts +368 -68
  79. package/src/crossBranch/settings.ts +49 -3
  80. package/src/crossBranch/types.ts +9 -0
  81. package/src/hooks/useElementSearch.ts +45 -0
  82. package/src/index.css +11 -0
  83. package/src/index.ts +7 -0
  84. package/src/pages/AppearanceSettings.tsx +24 -1
  85. package/src/pages/Dependencies.tsx +231 -65
  86. package/src/pages/InfiniteZoom.tsx +76 -27
  87. package/src/pages/Settings.tsx +1 -1
  88. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +103 -24
  89. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +102 -6
  90. package/src/pages/ViewEditor/hooks/useViewData.ts +42 -26
  91. package/src/pages/ViewEditor/hooks/useViewEditHistory.ts +62 -0
  92. package/src/pages/ViewEditor/index.tsx +549 -59
  93. package/src/pages/Views.tsx +112 -41
  94. package/src/pages/ViewsGrid.tsx +332 -113
  95. package/src/pages/viewsJumpSearch.test.ts +193 -0
  96. package/src/pages/viewsJumpSearch.ts +111 -0
  97. package/src/store/useStore.ts +58 -0
  98. package/src/types/index.ts +10 -0
  99. package/src/utils/elementIcon.test.ts +28 -0
  100. package/src/utils/elementIcon.ts +20 -0
  101. package/src/utils/sourceEditor.ts +46 -0
  102. package/src/utils/watchDiffSummary.ts +159 -0
@@ -0,0 +1,293 @@
1
+ import type { DiagramGroupLayout, LayoutNode, ZUIViewState } from './types'
2
+ import { getExpandThresholds } from './renderer'
3
+
4
+ interface Rect {
5
+ x: number
6
+ y: number
7
+ w: number
8
+ h: number
9
+ }
10
+
11
+ export interface ZUIFocusTarget {
12
+ id: string
13
+ label: string
14
+ type: 'group' | 'node'
15
+ isCircular?: boolean
16
+ absX: number
17
+ absY: number
18
+ absW: number
19
+ absH: number
20
+ absScale: number
21
+ node?: LayoutNode
22
+ contentRect?: Rect
23
+ }
24
+
25
+ export interface ZUIFocusViewportOptions {
26
+ preferContent?: boolean
27
+ minTargetScreenW?: number
28
+ minChildScreenW?: number
29
+ keepParentVisible?: boolean
30
+ }
31
+
32
+ function boundsForRects(rects: Rect[]): Rect | null {
33
+ if (rects.length === 0) return null
34
+
35
+ let minX = Infinity
36
+ let minY = Infinity
37
+ let maxX = -Infinity
38
+ let maxY = -Infinity
39
+
40
+ for (const rect of rects) {
41
+ minX = Math.min(minX, rect.x)
42
+ minY = Math.min(minY, rect.y)
43
+ maxX = Math.max(maxX, rect.x + rect.w)
44
+ maxY = Math.max(maxY, rect.y + rect.h)
45
+ }
46
+
47
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
48
+ return null
49
+ }
50
+
51
+ return { x: minX, y: minY, w: positiveSize(maxX - minX), h: positiveSize(maxY - minY) }
52
+ }
53
+
54
+ function positiveSize(value: number): number {
55
+ return Number.isFinite(value) && value > 0 ? value : 0.0001
56
+ }
57
+
58
+ function childContentRect(node: LayoutNode, absX: number, absY: number, absScale: number): Rect | null {
59
+ if (node.children.length === 0) return null
60
+
61
+ const childAbsScale = absScale * node.childScale
62
+ return boundsForRects(node.children.map((child) => ({
63
+ x: absX + (child.worldX - node.childOffsetX) * childAbsScale,
64
+ y: absY + (child.worldY - node.childOffsetY) * childAbsScale,
65
+ w: child.worldW * childAbsScale,
66
+ h: child.worldH * childAbsScale,
67
+ })))
68
+ }
69
+
70
+ function nodeTarget(
71
+ node: LayoutNode,
72
+ parentAbsX: number,
73
+ parentAbsY: number,
74
+ parentAbsScale: number,
75
+ parentChildOffsetX: number,
76
+ parentChildOffsetY: number,
77
+ ): ZUIFocusTarget {
78
+ const absX = parentAbsX + (node.worldX - parentChildOffsetX) * parentAbsScale
79
+ const absY = parentAbsY + (node.worldY - parentChildOffsetY) * parentAbsScale
80
+ const absW = node.worldW * parentAbsScale
81
+ const absH = node.worldH * parentAbsScale
82
+
83
+ return {
84
+ id: node.id,
85
+ label: node.linkedDiagramLabel || node.label,
86
+ type: 'node',
87
+ isCircular: node.isCircular,
88
+ absX,
89
+ absY,
90
+ absW,
91
+ absH,
92
+ absScale: parentAbsScale,
93
+ node,
94
+ contentRect: childContentRect(node, absX, absY, parentAbsScale) ?? undefined,
95
+ }
96
+ }
97
+
98
+ function findLinkedDiagramInNodes(
99
+ viewId: number,
100
+ nodes: DiagramGroupLayout['nodes'],
101
+ parentAbsX: number,
102
+ parentAbsY: number,
103
+ parentAbsScale: number,
104
+ parentChildOffsetX: number,
105
+ parentChildOffsetY: number,
106
+ ): ZUIFocusTarget | null {
107
+ for (const node of nodes) {
108
+ const target = nodeTarget(node, parentAbsX, parentAbsY, parentAbsScale, parentChildOffsetX, parentChildOffsetY)
109
+
110
+ if (node.linkedDiagramId === viewId) {
111
+ return target
112
+ }
113
+
114
+ if (node.children.length > 0) {
115
+ const found = findLinkedDiagramInNodes(
116
+ viewId,
117
+ node.children,
118
+ target.absX,
119
+ target.absY,
120
+ parentAbsScale * node.childScale,
121
+ node.childOffsetX,
122
+ node.childOffsetY,
123
+ )
124
+ if (found) return found
125
+ }
126
+ }
127
+
128
+ return null
129
+ }
130
+
131
+ function findElementInNodes(
132
+ viewId: number,
133
+ elementId: number,
134
+ nodes: DiagramGroupLayout['nodes'],
135
+ parentAbsX: number,
136
+ parentAbsY: number,
137
+ parentAbsScale: number,
138
+ parentChildOffsetX: number,
139
+ parentChildOffsetY: number,
140
+ ): ZUIFocusTarget | null {
141
+ for (const node of nodes) {
142
+ const target = nodeTarget(node, parentAbsX, parentAbsY, parentAbsScale, parentChildOffsetX, parentChildOffsetY)
143
+
144
+ if (node.diagramId === viewId && node.elementId === elementId) {
145
+ return target
146
+ }
147
+
148
+ if (node.children.length > 0) {
149
+ const found = findElementInNodes(
150
+ viewId,
151
+ elementId,
152
+ node.children,
153
+ target.absX,
154
+ target.absY,
155
+ parentAbsScale * node.childScale,
156
+ node.childOffsetX,
157
+ node.childOffsetY,
158
+ )
159
+ if (found) return found
160
+ }
161
+ }
162
+
163
+ return null
164
+ }
165
+
166
+ export function findDiagramFocusTarget(groups: DiagramGroupLayout[], viewId: number): ZUIFocusTarget | null {
167
+ for (const group of groups) {
168
+ if (group.diagramId === viewId) {
169
+ return {
170
+ id: `g-${group.diagramId}`,
171
+ label: group.label,
172
+ type: 'group',
173
+ absX: group.worldX,
174
+ absY: group.worldY,
175
+ absW: group.worldW,
176
+ absH: group.worldH,
177
+ absScale: 1,
178
+ }
179
+ }
180
+
181
+ const found = findLinkedDiagramInNodes(viewId, group.nodes, 0, 0, 1, 0, 0)
182
+ if (found) return found
183
+ }
184
+
185
+ return null
186
+ }
187
+
188
+ export function findElementFocusTarget(groups: DiagramGroupLayout[], viewId: number, elementId: number): ZUIFocusTarget | null {
189
+ for (const group of groups) {
190
+ const found = findElementInNodes(viewId, elementId, group.nodes, 0, 0, 1, 0, 0)
191
+ if (found) return found
192
+ }
193
+
194
+ return null
195
+ }
196
+
197
+ export function viewportForFocusTarget(
198
+ target: ZUIFocusTarget,
199
+ canvasW: number,
200
+ canvasH: number,
201
+ maxZoom: number,
202
+ padding: number,
203
+ options: ZUIFocusViewportOptions = {},
204
+ ): ZUIViewState | null {
205
+ const rect = options.preferContent && target.contentRect ? target.contentRect : {
206
+ x: target.absX,
207
+ y: target.absY,
208
+ w: target.absW,
209
+ h: target.absH,
210
+ }
211
+
212
+ const bboxW = positiveSize(rect.w)
213
+ const bboxH = positiveSize(rect.h)
214
+ const fitZoom = Math.min(
215
+ (canvasW * (1 - padding * 2)) / bboxW,
216
+ (canvasH * (1 - padding * 2)) / bboxH,
217
+ )
218
+ const minZooms: number[] = []
219
+
220
+ if (options.minTargetScreenW && target.absW > 0) {
221
+ minZooms.push(options.minTargetScreenW / target.absW)
222
+ }
223
+
224
+ if (target.node?.children.length && options.minChildScreenW && target.node.childScale > 0) {
225
+ const childAbsW = target.node.worldW * target.absScale * target.node.childScale
226
+ if (childAbsW > 0) {
227
+ minZooms.push(options.minChildScreenW / childAbsW)
228
+ }
229
+ }
230
+
231
+ const finiteMinZooms = minZooms.filter((value) => Number.isFinite(value) && value > 0)
232
+ const zoomLimit = Math.max(maxZoom, ...finiteMinZooms)
233
+ let zoom = Math.min(fitZoom, zoomLimit)
234
+
235
+ for (const minZoom of finiteMinZooms) {
236
+ zoom = Math.max(zoom, Math.min(minZoom, zoomLimit))
237
+ }
238
+
239
+ if (target.node?.children.length && options.keepParentVisible) {
240
+ const thresholds = getExpandThresholds(canvasW)
241
+ const maxParentScreenW = thresholds.start + (thresholds.end - thresholds.start) * 0.78
242
+ zoom = Math.min(zoom, maxParentScreenW / positiveSize(target.absW))
243
+ }
244
+
245
+ if (!Number.isFinite(zoom) || zoom <= 0) return null
246
+
247
+ return {
248
+ x: (canvasW - bboxW * zoom) / 2 - rect.x * zoom,
249
+ y: (canvasH - bboxH * zoom) / 2 - rect.y * zoom,
250
+ zoom,
251
+ }
252
+ }
253
+
254
+ export function viewportForDiagramFocusTarget(
255
+ target: ZUIFocusTarget,
256
+ canvasW: number,
257
+ canvasH: number,
258
+ maxZoom: number,
259
+ isMobileLayout: boolean,
260
+ ): ZUIViewState | null {
261
+ return viewportForFocusTarget(
262
+ target,
263
+ canvasW,
264
+ canvasH,
265
+ maxZoom,
266
+ isMobileLayout ? 0.18 : 0.16,
267
+ {
268
+ preferContent: true,
269
+ minTargetScreenW: isMobileLayout ? 180 : 260,
270
+ minChildScreenW: isMobileLayout ? 76 : 104,
271
+ },
272
+ )
273
+ }
274
+
275
+ export function viewportForElementFocusTarget(
276
+ target: ZUIFocusTarget,
277
+ canvasW: number,
278
+ canvasH: number,
279
+ maxZoom: number,
280
+ isMobileLayout: boolean,
281
+ ): ZUIViewState | null {
282
+ return viewportForFocusTarget(
283
+ target,
284
+ canvasW,
285
+ canvasH,
286
+ maxZoom,
287
+ isMobileLayout ? 0.2 : 0.18,
288
+ {
289
+ minTargetScreenW: isMobileLayout ? 220 : 320,
290
+ keepParentVisible: true,
291
+ },
292
+ )
293
+ }
@@ -10,15 +10,15 @@ import type {
10
10
  ExploreData,
11
11
  ViewConnector,
12
12
  } from '../../types'
13
- import { resolveIconPath } from '../../utils/url'
13
+ import { resolveElementIconUrl } from '../../utils/elementIcon'
14
14
 
15
15
  // ── Constants ──────────────────────────────────────────────────────
16
16
 
17
- export const NODE_W = 200
18
- export const NODE_H = 100
17
+ export const NODE_W = 180
18
+ export const NODE_H = 85
19
19
  const GROUP_PAD = 80 // padding inside a diagram group box
20
20
  const GROUP_SPACING = 400 // horizontal gap between root diagrams
21
- const CHILD_PAD = 1 // padding inside a node when rendering children
21
+ const CHILD_PAD = 4 // padding inside a node when rendering children
22
22
 
23
23
  // ── Helpers ────────────────────────────────────────────────────────
24
24
 
@@ -159,6 +159,7 @@ function buildNodes(
159
159
  const edgesOut = (views[String(diagramId)]?.connectors ?? [])
160
160
  .filter((e) => e.source_element_id === obj.element_id)
161
161
  .map((e) => ({
162
+ id: e.id,
162
163
  targetId: nodeId(diagramId, e.target_element_id),
163
164
  label: e.label ?? '',
164
165
  direction: e.direction ?? 'forward',
@@ -167,12 +168,6 @@ function buildNodes(
167
168
  type: e.style || 'bezier',
168
169
  }))
169
170
 
170
- const derivedPrimaryIconPath = (() => {
171
- const selected = obj.technology_connectors?.find((link) => link.type === 'catalog' && !!(link.is_primary_icon ?? (link as any).isPrimaryIcon) && !!link.slug)
172
- if (!selected?.slug) return null
173
- return resolveIconPath(`/icons/${selected.slug}.png`)
174
- })()
175
-
176
171
  return {
177
172
  id: nodeId(diagramId, obj.element_id),
178
173
  elementId: obj.element_id,
@@ -183,7 +178,7 @@ function buildNodes(
183
178
  worldH: NODE_H,
184
179
  label: obj.name,
185
180
  type: obj.kind ?? 'system',
186
- logoUrl: obj.logo_url ? resolveIconPath(obj.logo_url) : derivedPrimaryIconPath,
181
+ logoUrl: resolveElementIconUrl(obj.logo_url, obj.technology_connectors),
187
182
  description: obj.description ?? null,
188
183
  technology: obj.technology ?? null,
189
184
  tags: obj.tags ?? [],
@@ -253,6 +248,7 @@ export function computeLayout(data: ExploreData): ZUILayout {
253
248
 
254
249
  // Edges within the same diagram (world-level, not children)
255
250
  const edges = (diagData.connectors ?? []).map((e) => ({
251
+ id: e.id,
256
252
  sourceId: nodeId(diag.id, e.source_element_id),
257
253
  targetId: nodeId(diag.id, e.target_element_id),
258
254
  label: e.label ?? '',