@dryui/ui 0.1.2 → 0.1.4

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 (79) hide show
  1. package/dist/accordion/accordion-trigger.svelte +5 -6
  2. package/dist/alert-dialog/alert-dialog-action.svelte +42 -6
  3. package/dist/alert-dialog/alert-dialog-cancel.svelte +44 -5
  4. package/dist/alert-dialog/alert-dialog-footer.svelte +3 -2
  5. package/dist/aurora/aurora.svelte.d.ts +6 -0
  6. package/dist/beam/beam.svelte +17 -10
  7. package/dist/button/button.svelte +51 -1
  8. package/dist/button-group/button-group.svelte +7 -62
  9. package/dist/button-group/context.svelte.d.ts +5 -0
  10. package/dist/button-group/context.svelte.js +11 -0
  11. package/dist/chromatic-aberration/chromatic-aberration.svelte +60 -18
  12. package/dist/collapsible/collapsible-trigger.svelte +4 -7
  13. package/dist/color-picker/color-picker-eyedropper.svelte +4 -11
  14. package/dist/combobox/combobox-group.svelte +1 -1
  15. package/dist/data-grid/data-grid-pagination.svelte +20 -2
  16. package/dist/data-grid/data-grid-root.svelte +1 -0
  17. package/dist/date-field/date-field-root.svelte +66 -20
  18. package/dist/date-field/date-field-segment.svelte +11 -9
  19. package/dist/date-field/date-field-separator.svelte +9 -1
  20. package/dist/date-picker/datepicker-calendar.svelte +168 -13
  21. package/dist/date-picker/datepicker-trigger.svelte +3 -8
  22. package/dist/date-range-picker/date-range-picker-calendar.svelte +177 -13
  23. package/dist/date-range-picker/date-range-picker-root.svelte +0 -6
  24. package/dist/date-range-picker/date-range-picker-trigger.svelte +18 -12
  25. package/dist/dialog/dialog-content.svelte +1 -0
  26. package/dist/field/field-root.svelte +0 -1
  27. package/dist/file-select/file-select-clear.svelte +2 -8
  28. package/dist/file-upload/file-upload-item-delete.svelte +4 -7
  29. package/dist/file-upload/file-upload-root.svelte +0 -4
  30. package/dist/flip-card/flip-card-back.svelte +2 -2
  31. package/dist/flip-card/flip-card-front.svelte +2 -2
  32. package/dist/flip-card/flip-card-root.svelte +2 -0
  33. package/dist/float-button/float-button-root.svelte +2 -1
  34. package/dist/image-comparison/image-comparison.svelte +16 -24
  35. package/dist/input-group/input-group-action.svelte +5 -0
  36. package/dist/input-group/input-group-input.svelte +7 -2
  37. package/dist/input-group/input-group-prefix.svelte +5 -0
  38. package/dist/input-group/input-group-root.svelte +10 -2
  39. package/dist/input-group/input-group-select.svelte +5 -0
  40. package/dist/input-group/input-group-separator.svelte +10 -0
  41. package/dist/input-group/input-group-suffix.svelte +5 -0
  42. package/dist/list/list-item-icon.svelte +8 -0
  43. package/dist/list/list-item-text.svelte +19 -0
  44. package/dist/list/list-item.svelte +42 -0
  45. package/dist/list/list-root.svelte +0 -71
  46. package/dist/list/list-subheader.svelte +11 -0
  47. package/dist/map/map-marker.svelte +10 -0
  48. package/dist/map/map-popup.svelte +7 -0
  49. package/dist/map/map-root.svelte +0 -30
  50. package/dist/multi-select-combobox/multi-select-combobox-group.svelte +1 -1
  51. package/dist/option-swatch-group/option-swatch-group-item.svelte +46 -0
  52. package/dist/option-swatch-group/option-swatch-group-label.svelte +10 -0
  53. package/dist/option-swatch-group/option-swatch-group-meta.svelte +10 -0
  54. package/dist/option-swatch-group/option-swatch-group-root.svelte +0 -79
  55. package/dist/option-swatch-group/option-swatch-group-swatch.svelte +25 -6
  56. package/dist/pin-input/pin-input-cell.svelte +4 -1
  57. package/dist/radio-group/radio-group-item.svelte +90 -0
  58. package/dist/radio-group/radio-group.svelte +0 -89
  59. package/dist/range-calendar/range-calendar-grid.svelte +217 -179
  60. package/dist/range-calendar/range-calendar-root.svelte +24 -10
  61. package/dist/rich-text-editor/rich-text-editor-content.svelte +91 -3
  62. package/dist/rich-text-editor/rich-text-editor-root.svelte +168 -3
  63. package/dist/rich-text-editor/rich-text-editor-toolbar.svelte +318 -275
  64. package/dist/select/select-trigger.svelte +5 -8
  65. package/dist/shader-canvas/shader-canvas.svelte +0 -3
  66. package/dist/sidebar/sidebar-trigger.svelte +3 -2
  67. package/dist/system-map/system-map.svelte +120 -674
  68. package/dist/tabs/tabs-trigger.svelte +7 -4
  69. package/dist/tags-input/tags-input-input.svelte +3 -0
  70. package/dist/tags-input/tags-input-root.svelte +4 -13
  71. package/dist/tags-input/tags-input-tag.svelte +3 -0
  72. package/dist/themes/dark.css +6 -0
  73. package/dist/themes/default.css +3 -0
  74. package/dist/toast/toast-action.svelte +1 -0
  75. package/dist/toast/toast-close.svelte +4 -0
  76. package/dist/toast/toast-provider.svelte +5 -26
  77. package/dist/toast/toast-root.svelte +5 -10
  78. package/dist/virtual-list/virtual-list.svelte +187 -3
  79. package/package.json +3 -3
@@ -1,13 +1,9 @@
1
1
  <script lang="ts">
2
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
2
+ import { SvelteSet } from 'svelte/reactivity';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { Badge } from '../badge/index.js';
5
- import { Svg } from '../svg/index.js';
6
4
  import { Text } from '../text/index.js';
7
- import { Thumbnail } from '../thumbnail/index.js';
8
5
  import type {
9
6
  DolphinPackage,
10
- SystemMapEdge,
11
7
  SystemMapEdgeKind,
12
8
  SystemMapGraph,
13
9
  SystemMapLayer,
@@ -30,52 +26,19 @@
30
26
  showEdgeLabels?: boolean;
31
27
  }
32
28
 
33
- interface PositionedNode extends SystemMapNode {
34
- x: number;
35
- y: number;
36
- width: number;
37
- height: number;
38
- accent: string;
39
- isFocused: boolean;
40
- groupLabel: string;
41
- sublabel: string;
42
- helper: string;
43
- thumbnailName: string | null;
44
- }
45
-
46
- interface PositionedGroup {
29
+ interface ViewNode {
47
30
  id: string;
48
31
  label: string;
49
- x: number;
50
- y: number;
51
- nodes: PositionedNode[];
52
- }
53
-
54
- interface PositionedLayer {
55
- id: SystemMapLayer;
56
- label: string;
57
- x: number;
58
- y: number;
59
- width: number;
60
- height: number;
61
- accent: string;
62
- groups: PositionedGroup[];
63
- nodeCount: number;
64
- }
65
-
66
- interface PositionedEdge extends SystemMapEdge {
67
- path: string;
68
- labelX: number;
69
- labelY: number;
70
- color: string;
71
- dashed: boolean;
32
+ sublabel: string;
33
+ helper: string;
34
+ isFocused: boolean;
72
35
  }
73
36
 
74
- interface LegendItem {
37
+ interface EdgeRow {
75
38
  id: string;
39
+ source: ViewNode;
40
+ target: ViewNode;
76
41
  label: string;
77
- color: string;
78
- dashed?: boolean;
79
42
  }
80
43
 
81
44
  const layerOrder: SystemMapLayer[] = [
@@ -94,55 +57,15 @@
94
57
  audit: 'Audit'
95
58
  };
96
59
 
97
- const layerLabels: Record<SystemMapLayer, string> = {
98
- primitive: 'Primitives',
99
- 'ui-wrapper': 'UI wrappers',
100
- 'ui-composite': 'UI composites',
101
- part: 'Compound parts',
102
- catalog: 'Catalog',
103
- cluster: 'Audit clusters'
60
+ const edgeLabels: Record<SystemMapEdgeKind, string> = {
61
+ wraps: 'wraps',
62
+ composes: 'composes',
63
+ compound_part: 'part of',
64
+ related: 'related',
65
+ docs: 'docs',
66
+ duplication_cluster: 'duplicates'
104
67
  };
105
68
 
106
- const layerColors: Record<SystemMapLayer, string> = {
107
- primitive: 'var(--dry-color-fill-warning)',
108
- 'ui-wrapper': 'var(--dry-color-fill-brand)',
109
- 'ui-composite': 'var(--dry-color-fill-success)',
110
- part: 'var(--dry-color-fill-info)',
111
- catalog: 'var(--dry-color-fill-info)',
112
- cluster: 'var(--dry-color-fill-error)'
113
- };
114
-
115
- const edgeStyles: Record<SystemMapEdgeKind, { color: string; dashed: boolean }> = {
116
- wraps: { color: 'var(--dry-color-fill-brand)', dashed: false },
117
- composes: { color: 'var(--dry-color-fill-success)', dashed: false },
118
- compound_part: { color: 'var(--dry-color-fill-warning)', dashed: false },
119
- related: { color: 'var(--dry-color-text-strong)', dashed: true },
120
- docs: { color: 'var(--dry-color-fill-info)', dashed: true },
121
- duplication_cluster: { color: 'var(--dry-color-fill-error)', dashed: true }
122
- };
123
-
124
- const kindOrder = new Map<SystemMapNodeKind, number>([
125
- ['component', 0],
126
- ['catalog', 1],
127
- ['cluster', 2],
128
- ['part', 3]
129
- ]);
130
-
131
- const layoutConfig = {
132
- paddingX: 28,
133
- paddingTop: 70,
134
- paddingBottom: 24,
135
- laneWidth: 268,
136
- laneGap: 32,
137
- groupHeaderHeight: 26,
138
- groupGap: 18,
139
- nodeWidth: 232,
140
- nodeHeight: 86,
141
- nodeGap: 12,
142
- nodeInsetX: 18,
143
- thumbnailSize: 28
144
- } as const;
145
-
146
69
  let {
147
70
  graph,
148
71
  focusId = null,
@@ -171,79 +94,40 @@
171
94
  .replace(/\b\w/g, (match) => match.toUpperCase());
172
95
  }
173
96
 
174
- function trimText(value: string, maxLength = 76): string {
175
- return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1).trimEnd()}\u2026`;
176
- }
177
-
178
- function groupLabelFor(node: SystemMapNode): string {
179
- if (node.kind === 'part') {
180
- return node.name.split('.')[0] ?? 'Parts';
181
- }
182
- if (node.layer === 'catalog') {
183
- return formatTokenLabel(node.category || 'Catalog');
184
- }
185
- if (node.layer === 'cluster') {
186
- return formatTokenLabel(node.category || 'Audit');
187
- }
188
- return formatTokenLabel(node.category || packageLabels[node.package]);
189
- }
190
-
191
97
  function sublabelFor(node: SystemMapNode): string {
192
98
  const labels = [packageLabels[node.package], formatTokenLabel(node.kind)];
193
- if (node.visibility) {
194
- labels.push(formatTokenLabel(node.visibility));
195
- }
99
+ if (node.visibility) labels.push(formatTokenLabel(node.visibility));
196
100
  return labels.join(' \u2022 ');
197
101
  }
198
102
 
199
103
  function helperFor(node: SystemMapNode): string {
200
- if (node.kind === 'part') {
201
- return trimText(node.description || 'Public part export.');
202
- }
203
- if (node.layer === 'cluster') {
204
- return trimText(node.description || 'Audit cluster.');
205
- }
206
- if (node.layer === 'catalog') {
207
- return trimText(node.sourcePath || node.description || 'Catalog asset.');
208
- }
104
+ if (node.kind === 'part') return node.description || 'Public part export.';
105
+ if (node.layer === 'cluster') return node.description || 'Audit cluster.';
106
+ if (node.layer === 'catalog') return node.sourcePath || node.description || 'Catalog asset.';
209
107
  const metrics: string[] = [];
210
108
  if (node.parts.length > 0) metrics.push(`${node.parts.length} parts`);
211
109
  if (node.componentImportCount && node.componentImportCount > 0)
212
110
  metrics.push(`${node.componentImportCount} links`);
213
- if (metrics.length > 0) {
214
- return metrics.join(' \u2022 ');
215
- }
216
- return trimText(node.description || node.category || 'Public surface.');
217
- }
218
-
219
- function thumbnailNameFor(node: SystemMapNode): string | null {
220
- if (!showThumbnails) return null;
221
- if (node.kind !== 'component') return null;
222
- if (node.package === 'docs' || node.package === 'audit') return null;
223
- if (node.name.includes('.')) return null;
224
- return node.name;
225
- }
226
-
227
- function edgeLabel(kind: SystemMapEdgeKind): string {
228
- return formatTokenLabel(kind);
111
+ if (metrics.length > 0) return metrics.join(' \u2022 ');
112
+ return node.description || node.category || 'Public surface.';
229
113
  }
230
114
 
231
- function edgeStyle(kind: SystemMapEdgeKind): { color: string; dashed: boolean } {
232
- return edgeStyles[kind];
115
+ function toViewNode(node: SystemMapNode): ViewNode {
116
+ return {
117
+ id: node.id,
118
+ label: node.label,
119
+ sublabel: sublabelFor(node),
120
+ helper: helperFor(node),
121
+ isFocused: focusId === node.id
122
+ };
233
123
  }
234
124
 
235
125
  const normalizedNodes = $derived(
236
- (() => {
237
- return [...graph.nodes].sort((left, right) => {
238
- const layerDelta = layerOrder.indexOf(left.layer) - layerOrder.indexOf(right.layer);
239
- if (layerDelta !== 0) return layerDelta;
240
- const groupDelta = compareStrings(groupLabelFor(left), groupLabelFor(right));
241
- if (groupDelta !== 0) return groupDelta;
242
- const kindDelta = (kindOrder.get(left.kind) ?? 99) - (kindOrder.get(right.kind) ?? 99);
243
- if (kindDelta !== 0) return kindDelta;
244
- return compareStrings(left.label, right.label);
245
- });
246
- })()
126
+ [...graph.nodes].sort((left, right) => {
127
+ const layerDelta = layerOrder.indexOf(left.layer) - layerOrder.indexOf(right.layer);
128
+ if (layerDelta !== 0) return layerDelta;
129
+ return compareStrings(left.label, right.label);
130
+ })
247
131
  );
248
132
 
249
133
  const filteredNodes = $derived(
@@ -265,15 +149,15 @@
265
149
  })()
266
150
  );
267
151
 
268
- const filteredNodeIds = $derived(new SvelteSet(filteredNodes.map((node) => node.id)));
152
+ const filteredNodeIds = $derived(new SvelteSet(filteredNodes.map((n) => n.id)));
269
153
 
270
154
  const filteredEdges = $derived(
271
155
  (() => {
272
156
  const allowedEdges = edgeFilter ? new Set(edgeFilter) : null;
273
157
  return graph.edges
274
- .filter((edge) => filteredNodeIds.has(edge.from) && filteredNodeIds.has(edge.to))
275
- .filter((edge) => !allowedEdges || allowedEdges.has(edge.type))
276
- .sort((left, right) => compareStrings(left.id, right.id));
158
+ .filter((e) => filteredNodeIds.has(e.from) && filteredNodeIds.has(e.to))
159
+ .filter((e) => !allowedEdges || allowedEdges.has(e.type))
160
+ .sort((a, b) => compareStrings(a.id, b.id));
277
161
  })()
278
162
  );
279
163
 
@@ -291,575 +175,137 @@
291
175
  })()
292
176
  );
293
177
 
294
- const visibleNodes = $derived(filteredNodes.filter((node) => visibleNodeIds.has(node.id)));
178
+ const visibleNodes = $derived(filteredNodes.filter((n) => visibleNodeIds.has(n.id)));
295
179
  const visibleEdges = $derived(
296
- filteredEdges.filter((edge) => visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to))
297
- );
298
- const visibleLayers = $derived(
299
- layerOrder.filter((layer) => visibleNodes.some((node) => node.layer === layer))
180
+ filteredEdges.filter((e) => visibleNodeIds.has(e.from) && visibleNodeIds.has(e.to))
300
181
  );
301
182
 
302
- const layout = $derived(
303
- (() => {
304
- const nodeLookup = new SvelteMap<string, PositionedNode>();
305
- const layers: PositionedLayer[] = [];
306
- let maxContentBottom = layoutConfig.paddingTop + 80;
307
-
308
- visibleLayers.forEach((layer, layerIndex) => {
309
- const laneNodes = visibleNodes.filter((node) => node.layer === layer);
310
- const groups = new SvelteMap<string, SystemMapNode[]>();
311
- for (const node of laneNodes) {
312
- const label = groupLabelFor(node);
313
- groups.set(label, [...(groups.get(label) ?? []), node]);
314
- }
315
- let cursorY = layoutConfig.paddingTop;
316
- const positionedGroups: PositionedGroup[] = [];
317
- for (const [groupLabel, groupNodes] of [...groups.entries()].sort(([left], [right]) =>
318
- compareStrings(left, right)
319
- )) {
320
- const groupY = cursorY;
321
- cursorY += layoutConfig.groupHeaderHeight;
322
- const positionedNodes = groupNodes.map((node, nodeIndex) => {
323
- const positionedNode: PositionedNode = {
324
- ...node,
325
- x:
326
- layoutConfig.paddingX +
327
- layerIndex * (layoutConfig.laneWidth + layoutConfig.laneGap) +
328
- layoutConfig.nodeInsetX,
329
- y: cursorY + nodeIndex * (layoutConfig.nodeHeight + layoutConfig.nodeGap),
330
- width: layoutConfig.nodeWidth,
331
- height: layoutConfig.nodeHeight,
332
- accent: layerColors[node.layer],
333
- isFocused: focusId === node.id,
334
- groupLabel,
335
- sublabel: sublabelFor(node),
336
- helper: helperFor(node),
337
- thumbnailName: thumbnailNameFor(node)
338
- };
339
- nodeLookup.set(node.id, positionedNode);
340
- return positionedNode;
341
- });
342
- positionedGroups.push({
343
- id: `${layer}:${groupLabel}`,
344
- label: groupLabel,
345
- x:
346
- layoutConfig.paddingX +
347
- layerIndex * (layoutConfig.laneWidth + layoutConfig.laneGap) +
348
- layoutConfig.nodeInsetX,
349
- y: groupY,
350
- nodes: positionedNodes
351
- });
352
- cursorY += positionedNodes.length * layoutConfig.nodeHeight;
353
- cursorY += Math.max(positionedNodes.length - 1, 0) * layoutConfig.nodeGap;
354
- cursorY += layoutConfig.groupGap;
355
- }
356
- const layerHeight = Math.max(
357
- cursorY - layoutConfig.groupGap + layoutConfig.paddingBottom,
358
- layoutConfig.paddingTop + 80
359
- );
360
- maxContentBottom = Math.max(maxContentBottom, layerHeight);
361
- layers.push({
362
- id: layer,
363
- label: layerLabels[layer],
364
- x: layoutConfig.paddingX + layerIndex * (layoutConfig.laneWidth + layoutConfig.laneGap),
365
- y: 18,
366
- width: layoutConfig.laneWidth,
367
- height: layerHeight - 18,
368
- accent: layerColors[layer],
369
- groups: positionedGroups,
370
- nodeCount: laneNodes.length
371
- });
372
- });
373
-
374
- const edges: PositionedEdge[] = visibleEdges.flatMap((edge) => {
375
- const fromNode = nodeLookup.get(edge.from);
376
- const toNode = nodeLookup.get(edge.to);
377
- if (!fromNode || !toNode) return [];
378
- const fromCenterY = fromNode.y + fromNode.height / 2;
379
- const toCenterY = toNode.y + toNode.height / 2;
380
- const flowingForward = fromNode.x <= toNode.x;
381
- const startX = flowingForward ? fromNode.x + fromNode.width : fromNode.x;
382
- const endX = flowingForward ? toNode.x : toNode.x + toNode.width;
383
- const bend = Math.max(40, Math.abs(endX - startX) * 0.45);
384
- const style = edgeStyle(edge.type);
385
- return [
386
- {
387
- ...edge,
388
- path: `M ${startX} ${fromCenterY} C ${startX + (flowingForward ? bend : -bend)} ${fromCenterY}, ${endX - (flowingForward ? bend : -bend)} ${toCenterY}, ${endX} ${toCenterY}`,
389
- labelX: startX + (endX - startX) / 2,
390
- labelY: fromCenterY + (toCenterY - fromCenterY) / 2 - 10,
391
- color: style.color,
392
- dashed: style.dashed
393
- }
394
- ];
395
- });
396
-
397
- return {
398
- width:
399
- layers.length > 0
400
- ? layoutConfig.paddingX * 2 +
401
- layers.length * layoutConfig.laneWidth +
402
- (layers.length - 1) * layoutConfig.laneGap
403
- : 720,
404
- height: Math.max(maxContentBottom, 160),
405
- layers,
406
- edges
407
- };
408
- })()
183
+ const edgeRows = $derived(
184
+ visibleEdges
185
+ .map((edge) => {
186
+ const source = visibleNodes.find((n) => n.id === edge.from);
187
+ const target = visibleNodes.find((n) => n.id === edge.to);
188
+ if (!source || !target) return null;
189
+ return {
190
+ id: edge.id,
191
+ source: toViewNode(source),
192
+ target: toViewNode(target),
193
+ label: edgeLabels[edge.type]
194
+ } satisfies EdgeRow;
195
+ })
196
+ .filter((row): row is EdgeRow => row !== null)
409
197
  );
410
198
 
411
- const focusedNode = $derived(
412
- focusId ? (visibleNodes.find((node) => node.id === focusId) ?? null) : null
413
- );
199
+ const connectedNodeIds = $derived(new Set(visibleEdges.flatMap((e) => [e.from, e.to])));
414
200
 
415
- const layerLegend = $derived(
416
- visibleLayers.map(
417
- (layer) =>
418
- ({
419
- id: layer,
420
- label: layerLabels[layer],
421
- color: layerColors[layer]
422
- }) satisfies LegendItem
423
- )
424
- );
425
-
426
- const edgeLegend = $derived(
427
- (() => {
428
- return [...new Set(visibleEdges.map((edge) => edge.type))]
429
- .map((type) => {
430
- const style = edgeStyle(type);
431
- return {
432
- id: type,
433
- label: edgeLabel(type),
434
- color: style.color,
435
- dashed: style.dashed
436
- } satisfies LegendItem;
437
- })
438
- .sort((left, right) => compareStrings(left.label, right.label));
439
- })()
440
- );
441
-
442
- const summaryText = $derived(
443
- (() => {
444
- const bits = [
445
- `${visibleNodes.length} visible nodes`,
446
- `${visibleEdges.length} relationships`,
447
- `${graph.summary.mismatches} audit mismatches`
448
- ];
449
- if (focusedNode) {
450
- bits.push(`focus: ${focusedNode.label}`);
451
- }
452
- return bits.join(' \u2022 ');
453
- })()
201
+ const orphanNodes = $derived(
202
+ visibleNodes.filter((n) => !connectedNodeIds.has(n.id)).map(toViewNode)
454
203
  );
455
204
  </script>
456
205
 
457
206
  <div data-system-map class={className} {...rest}>
458
- <div data-header>
459
- <div data-header-text>
460
- <Text as="p" size="sm" color="muted" data-eyebrow>DolphinGraph</Text>
461
- <Text as="p" data-map-title>System Map</Text>
462
- <Text as="p" size="sm" color="muted" data-summary>{summaryText}</Text>
207
+ {#if edgeRows.length === 0 && orphanNodes.length === 0}
208
+ <div data-empty>
209
+ <Text as="p" color="muted">No nodes match the active filters.</Text>
463
210
  </div>
464
- <div data-header-badges>
465
- <Badge variant="soft" color="gray">{graph.summary.componentNodes} components</Badge>
466
- <Badge variant="soft" color="gray">{graph.summary.wrapEdges} wraps</Badge>
467
- <Badge variant="soft" color="gray">{graph.summary.composeEdges} composes</Badge>
468
- <Badge variant="soft" color="gray">{graph.summary.clusterNodes} clusters</Badge>
469
- </div>
470
- </div>
471
-
472
- <div data-canvas-shell>
473
- <Svg
474
- data-svg
475
- viewBox="0 0 {layout.width} {layout.height}"
476
- aria-label="DryUI system map"
477
- role="img"
478
- >
479
- <defs>
480
- <marker
481
- id="dry-system-map-arrow"
482
- markerWidth="8"
483
- markerHeight="8"
484
- refX="7"
485
- refY="4"
486
- orient="auto-start-reverse"
487
- markerUnits="strokeWidth"
488
- >
489
- <path
490
- d="M 1 1 L 7 4 L 1 7"
491
- fill="none"
492
- stroke="context-stroke"
493
- stroke-width="1.2"
494
- stroke-linejoin="round"
495
- stroke-linecap="round"
496
- />
497
- </marker>
498
- </defs>
499
-
500
- {#if layout.layers.length === 0}
501
- <rect
502
- x="22"
503
- y="22"
504
- width={layout.width - 44}
505
- height={layout.height - 44}
506
- rx="24"
507
- fill="color-mix(in srgb, var(--dry-color-bg-overlay) 76%, transparent)"
508
- stroke="var(--dry-color-stroke-weak)"
509
- stroke-dasharray="8 8"
510
- />
511
- <text data-empty-label x={layout.width / 2} y={layout.height / 2 - 8} text-anchor="middle">
512
- Nothing matches the active focus or filters
513
- </text>
514
- <text data-empty-hint x={layout.width / 2} y={layout.height / 2 + 18} text-anchor="middle">
515
- Relax the filters or clear the focus to restore the full map.
516
- </text>
517
- {:else}
518
- {#each layout.edges as edge (edge.id)}
519
- <g>
520
- <path
521
- d={edge.path}
522
- fill="none"
523
- stroke={edge.color}
524
- stroke-width="1.5"
525
- stroke-dasharray={edge.dashed ? '6 4' : undefined}
526
- opacity="0.7"
527
- stroke-linecap="round"
528
- stroke-linejoin="round"
529
- marker-end="url(#dry-system-map-arrow)"
530
- />
531
- {#if showEdgeLabels}
532
- <rect
533
- x={edge.labelX - 42}
534
- y={edge.labelY - 14}
535
- width="84"
536
- height="20"
537
- rx="999"
538
- fill="color-mix(in srgb, var(--dry-color-bg-base) 94%, white)"
539
- stroke="color-mix(in srgb, var(--dry-color-stroke-weak) 88%, transparent)"
540
- />
541
- <text data-edge-label x={edge.labelX} y={edge.labelY} text-anchor="middle">
542
- {edgeLabel(edge.type)}
543
- </text>
544
- {/if}
545
- </g>
546
- {/each}
211
+ {:else}
212
+ {#each edgeRows as row (row.id)}
213
+ <div data-edge-row>
214
+ <div data-node data-focused={row.source.isFocused || undefined}>
215
+ <Text as="p" data-node-name>{row.source.label}</Text>
216
+ <Text as="p" size="xs" color="muted">{row.source.sublabel}</Text>
217
+ <Text as="p" size="xs" color="muted" data-node-helper>{row.source.helper}</Text>
218
+ </div>
547
219
 
548
- {#each layout.layers as layer (layer.id)}
549
- <g>
550
- <rect
551
- x={layer.x}
552
- y={layer.y}
553
- width={layer.width}
554
- height={layer.height}
555
- rx="18"
556
- fill="color-mix(in srgb, {layer.accent} 5%, var(--dry-color-bg-overlay))"
557
- stroke="color-mix(in srgb, {layer.accent} 16%, var(--dry-color-stroke-weak))"
558
- stroke-width="0.75"
559
- />
560
- <rect
561
- x={layer.x + 18}
562
- y={layer.y + 16}
563
- width="32"
564
- height="3"
565
- rx="1.5"
566
- fill={layer.accent}
567
- opacity="0.7"
568
- />
569
- <text data-lane-title x={layer.x + 18} y={layer.y + 36}>{layer.label}</text>
570
- <text data-lane-meta x={layer.x + 18} y={layer.y + 52}>
571
- {layer.nodeCount} node{layer.nodeCount === 1 ? '' : 's'}
572
- </text>
573
-
574
- {#each layer.groups as group (group.id)}
575
- <g>
576
- <text data-group-label x={group.x} y={group.y + 16}>{group.label}</text>
577
- {#each group.nodes as node (node.id)}
578
- <g>
579
- <rect
580
- x={node.x}
581
- y={node.y}
582
- width={node.width}
583
- height={node.height}
584
- rx="14"
585
- fill="color-mix(in srgb, var(--dry-color-bg-base) 94%, white)"
586
- stroke={node.isFocused
587
- ? `color-mix(in srgb, ${node.accent} 70%, var(--dry-color-stroke-weak))`
588
- : 'color-mix(in srgb, var(--dry-color-stroke-weak) 60%, transparent)'}
589
- stroke-width={node.isFocused ? 1.5 : 0.75}
590
- />
591
- <rect
592
- x={node.x + 12}
593
- y={node.y + 14}
594
- width="3"
595
- height={node.height - 28}
596
- rx="1.5"
597
- fill={node.accent}
598
- opacity="0.8"
599
- />
600
- {#if node.thumbnailName}
601
- <foreignObject
602
- x={node.x + node.width - layoutConfig.thumbnailSize - 14}
603
- y={node.y + 12}
604
- width={layoutConfig.thumbnailSize}
605
- height={layoutConfig.thumbnailSize}
606
- >
607
- <div xmlns="http://www.w3.org/1999/xhtml" data-thumbnail-object>
608
- <Thumbnail.Root
609
- name={node.thumbnailName}
610
- size={layoutConfig.thumbnailSize}
611
- />
612
- </div>
613
- </foreignObject>
614
- {/if}
615
- <text data-node-label x={node.x + 28} y={node.y + 28}>
616
- {node.label}
617
- </text>
618
- <text data-node-description x={node.x + 28} y={node.y + 47}>
619
- {trimText(node.sublabel, 34)}
620
- </text>
621
- <text data-node-meta x={node.x + 28} y={node.y + 66}>
622
- {trimText(node.helper, 44)}
623
- </text>
624
- </g>
625
- {/each}
626
- </g>
627
- {/each}
628
- </g>
629
- {/each}
630
- {/if}
631
- </Svg>
632
- </div>
633
-
634
- {#if showLegend && (layerLegend.length > 0 || edgeLegend.length > 0)}
635
- <div data-legend>
636
- {#if layerLegend.length > 0}
637
- <div data-legend-section>
638
- <Badge variant="soft" color="gray">Layers</Badge>
639
- <div data-legend-items>
640
- {#each layerLegend as item (item.id)}
641
- <div data-legend-item>
642
- <span
643
- {@attach (node) => {
644
- node.style.setProperty('--swatch-color', item.color);
645
- }}
646
- data-swatch
647
- ></span>
648
- <Text as="span" size="sm">{item.label}</Text>
649
- </div>
650
- {/each}
651
- </div>
220
+ <div data-connector>
221
+ <span data-connector-line></span>
222
+ <Text as="span" size="xs" color="muted" data-connector-label>{row.label}</Text>
223
+ <span data-connector-line></span>
652
224
  </div>
653
- {/if}
654
- {#if edgeLegend.length > 0}
655
- <div data-legend-section>
656
- <Badge variant="soft" color="gray">Relationships</Badge>
657
- <div data-legend-items>
658
- {#each edgeLegend as item (item.id)}
659
- <div data-legend-item>
660
- <span
661
- data-line-swatch
662
- data-dashed={item.dashed ? 'true' : undefined}
663
- {@attach (node) => {
664
- node.style.setProperty('--swatch-color', item.color);
665
- }}
666
- ></span>
667
- <Text as="span" size="sm">{item.label}</Text>
668
- </div>
669
- {/each}
670
- </div>
225
+
226
+ <div data-node data-focused={row.target.isFocused || undefined}>
227
+ <Text as="p" data-node-name>{row.target.label}</Text>
228
+ <Text as="p" size="xs" color="muted">{row.target.sublabel}</Text>
229
+ <Text as="p" size="xs" color="muted" data-node-helper>{row.target.helper}</Text>
671
230
  </div>
672
- {/if}
673
- </div>
231
+ </div>
232
+ {/each}
233
+
234
+ {#if orphanNodes.length > 0}
235
+ <div data-orphans>
236
+ {#each orphanNodes as node (node.id)}
237
+ <div data-node data-focused={node.isFocused || undefined}>
238
+ <Text as="p" data-node-name>{node.label}</Text>
239
+ <Text as="p" size="xs" color="muted">{node.sublabel}</Text>
240
+ <Text as="p" size="xs" color="muted" data-node-helper>{node.helper}</Text>
241
+ </div>
242
+ {/each}
243
+ </div>
244
+ {/if}
674
245
  {/if}
675
246
  </div>
676
247
 
677
248
  <style>
678
249
  [data-system-map] {
679
- --dry-system-map-bg: var(--dry-color-bg-raised);
680
- --dry-system-map-surface: var(--dry-color-bg-base);
681
- --dry-system-map-border: var(--dry-color-stroke-weak);
682
- --dry-system-map-text: var(--dry-color-text-strong);
683
- --dry-system-map-text-muted: var(--dry-color-text-weak);
684
- --dry-system-map-shadow: 0 8px 32px
685
- color-mix(in srgb, var(--dry-color-text-strong) 8%, transparent);
686
-
687
250
  display: grid;
688
- grid-template-columns: minmax(0, 1fr);
689
251
  gap: var(--dry-space-3);
690
- padding: 0.875rem;
691
- border: 1px solid var(--dry-system-map-border);
692
- border-radius: var(--dry-radius-xl);
693
- background:
694
- radial-gradient(
695
- circle at top left,
696
- color-mix(in srgb, var(--dry-color-fill-brand) 6%, transparent),
697
- transparent 40%
698
- ),
699
- var(--dry-system-map-bg);
700
- color: var(--dry-system-map-text);
701
- box-shadow: var(--dry-system-map-shadow);
702
- overflow: hidden;
703
- }
704
-
705
- [data-header] {
706
- display: grid;
707
- grid-template-columns: 1fr auto;
708
- gap: var(--dry-space-4);
709
- align-items: start;
252
+ color: var(--dry-color-text-strong);
710
253
  }
711
254
 
712
- [data-header-text] {
255
+ [data-empty] {
713
256
  display: grid;
714
- gap: var(--dry-space-2);
257
+ place-items: center;
258
+ padding: var(--dry-space-8);
715
259
  }
716
260
 
717
- [data-header-badges] {
261
+ [data-edge-row] {
718
262
  display: grid;
719
- grid-auto-flow: column;
720
- gap: var(--dry-space-2);
263
+ grid-template-columns: 1fr auto 1fr;
264
+ gap: var(--dry-space-3);
721
265
  align-items: center;
722
266
  }
723
267
 
724
- [data-eyebrow] {
725
- margin: 0;
726
- font-size: var(--dry-text-xs-size, 0.75rem);
727
- font-weight: 700;
728
- line-height: 1.2;
729
- letter-spacing: 0.08em;
730
- text-transform: uppercase;
731
- color: var(--dry-system-map-text-muted);
732
- }
733
-
734
- [data-map-title] {
735
- margin: 0;
736
- font-size: var(--dry-text-lg-size, 1.125rem);
737
- font-weight: 700;
738
- line-height: 1.2;
739
- }
740
-
741
- [data-summary] {
742
- margin: 0;
743
- }
744
-
745
- [data-canvas-shell] {
268
+ [data-node] {
746
269
  display: grid;
747
- grid-template-columns: minmax(40rem, 1fr);
748
- overflow-x: auto;
749
- overflow-y: hidden;
750
- padding-bottom: 0.125rem;
751
- border-radius: calc(var(--dry-radius-xl) - 0.25rem);
752
- background: color-mix(in srgb, var(--dry-color-bg-overlay) 45%, transparent);
753
- border: 1px solid color-mix(in srgb, var(--dry-system-map-border) 85%, transparent);
270
+ gap: var(--dry-space-1);
271
+ padding: var(--dry-space-3);
272
+ border-radius: var(--dry-radius-lg);
273
+ background: var(--dry-color-bg-raised);
274
+ border: 1px solid var(--dry-color-stroke-weak);
754
275
  }
755
276
 
756
- [data-svg] {
757
- display: grid;
758
- height: auto;
277
+ [data-node][data-focused] {
278
+ border-color: var(--dry-color-stroke-strong);
759
279
  }
760
280
 
761
- [data-lane-title] {
762
- font-size: 0.8125rem;
763
- font-weight: 600;
764
- letter-spacing: 0.01em;
765
- fill: var(--dry-system-map-text);
766
- }
767
- [data-lane-meta] {
768
- font-size: 0.6875rem;
769
- fill: var(--dry-system-map-text-muted);
770
- }
771
- [data-group-label] {
772
- font-size: 0.6875rem;
281
+ [data-node-name] {
773
282
  font-weight: 600;
774
- letter-spacing: 0.04em;
775
- text-transform: uppercase;
776
- fill: var(--dry-system-map-text-muted);
777
- }
778
- [data-node-label] {
779
- font-size: 0.8125rem;
780
- font-weight: 600;
781
- fill: var(--dry-system-map-text);
782
- }
783
- [data-node-description] {
784
- font-size: 0.6875rem;
785
- fill: var(--dry-system-map-text-muted);
786
- }
787
- [data-node-meta] {
788
- font-size: 0.6875rem;
789
- fill: color-mix(in srgb, var(--dry-system-map-text-muted) 70%, transparent);
790
- }
791
- [data-edge-label] {
792
- font-size: 0.625rem;
793
- font-weight: 600;
794
- letter-spacing: 0.02em;
795
- fill: var(--dry-system-map-text);
796
- }
797
- [data-empty-label] {
798
- font-size: 0.875rem;
799
- font-weight: 600;
800
- fill: var(--dry-system-map-text);
801
- }
802
- [data-empty-hint] {
803
- font-size: 0.75rem;
804
- fill: var(--dry-system-map-text-muted);
805
- }
806
-
807
- [data-thumbnail-object] {
808
- display: grid;
809
- place-items: center;
810
- height: 100%;
811
283
  }
812
284
 
813
- [data-legend] {
814
- display: grid;
815
- grid-auto-flow: column;
816
- grid-auto-columns: minmax(18rem, 1fr);
817
- gap: var(--dry-space-6);
818
- align-items: start;
819
- padding: 0.875rem;
820
- border-radius: calc(var(--dry-radius-xl) - 0.375rem);
821
- background: color-mix(in srgb, var(--dry-system-map-surface) 82%, transparent);
822
- border: 1px solid color-mix(in srgb, var(--dry-system-map-border) 78%, transparent);
285
+ [data-node-helper] {
286
+ opacity: 0.7;
823
287
  }
824
288
 
825
- [data-legend-section] {
289
+ [data-connector] {
826
290
  display: grid;
827
- gap: var(--dry-space-2);
828
- }
829
-
830
- [data-legend-items] {
831
- display: grid;
832
- grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
833
- gap: var(--dry-space-4);
834
- row-gap: 0.5rem;
835
- align-items: start;
836
- }
837
-
838
- [data-legend-item] {
839
- display: grid;
840
- grid-template-columns: auto 1fr;
291
+ grid-template-columns: 1fr auto 1fr;
841
292
  gap: var(--dry-space-2);
842
293
  align-items: center;
843
- padding: 0.125rem 0;
844
294
  }
845
295
 
846
- [data-swatch] {
296
+ [data-connector-line] {
847
297
  display: grid;
848
- grid-template-columns: 0.875rem;
849
- aspect-ratio: 1;
850
- border-radius: 999px;
851
- border: 1px solid color-mix(in srgb, var(--dry-system-map-border) 80%, transparent);
852
- background: var(--swatch-color, var(--dry-color-fill-brand));
298
+ height: 0;
299
+ border-top: 1px solid var(--dry-color-stroke-weak);
853
300
  }
854
301
 
855
- [data-line-swatch] {
856
- display: grid;
857
- grid-template-columns: 1.5rem;
858
- height: 0;
859
- border-top: 2px solid var(--swatch-color, var(--dry-color-fill-brand));
302
+ [data-connector-label] {
303
+ white-space: nowrap;
860
304
  }
861
305
 
862
- [data-line-swatch][data-dashed='true'] {
863
- border-top-style: dashed;
306
+ [data-orphans] {
307
+ display: grid;
308
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
309
+ gap: var(--dry-space-3);
864
310
  }
865
311
  </style>