@lifecorner/angular-ui-kit 3.23.0 → 3.23.1
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.
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +6 -6
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_theme-dark.scss +4 -0
- package/src/styles/_theme-light.scss +4 -0
- package/src/styles/theme-contrast.spec.ts +291 -8
|
@@ -136,7 +136,7 @@ function measureNodes(nodes, measure) {
|
|
|
136
136
|
}
|
|
137
137
|
const RELATION_STYLES = {
|
|
138
138
|
depends: {
|
|
139
|
-
color: "var(--color-
|
|
139
|
+
color: "var(--color-text-secondary)",
|
|
140
140
|
dashed: false,
|
|
141
141
|
label: "depends on"
|
|
142
142
|
},
|
|
@@ -244,7 +244,7 @@ function layoutTreeH(measure, x, y, parentId, nodes, edges, allNodes, nodeMap, m
|
|
|
244
244
|
path: `M ${sx} ${sy} C ${mx} ${sy}, ${mx} ${ty}, ${tx} ${ty}`,
|
|
245
245
|
labelX: mx,
|
|
246
246
|
labelY: (sy + ty) / 2,
|
|
247
|
-
color: "var(--color-
|
|
247
|
+
color: "var(--color-field-border)",
|
|
248
248
|
dashed: false,
|
|
249
249
|
isCrossRef: false,
|
|
250
250
|
arrow: false,
|
|
@@ -276,7 +276,7 @@ function layoutTreeV(measure, x, y, parentId, nodes, edges, allNodes, nodeMap, m
|
|
|
276
276
|
path: `M ${sx} ${sy} C ${sx} ${my}, ${tx} ${my}, ${tx} ${ty}`,
|
|
277
277
|
labelX: (sx + tx) / 2,
|
|
278
278
|
labelY: my,
|
|
279
|
-
color: "var(--color-
|
|
279
|
+
color: "var(--color-field-border)",
|
|
280
280
|
dashed: false,
|
|
281
281
|
isCrossRef: false,
|
|
282
282
|
arrow: false,
|
|
@@ -857,7 +857,7 @@ function layoutLayered(roots, dir, allNodes, metrics) {
|
|
|
857
857
|
labelX: geo.labelX,
|
|
858
858
|
labelY: geo.labelY,
|
|
859
859
|
labelAnchor: geo.labelAnchor,
|
|
860
|
-
color: edge.structural ? "var(--color-
|
|
860
|
+
color: edge.structural ? "var(--color-text-secondary)" : style.color,
|
|
861
861
|
dashed: edge.back || !edge.structural && style.dashed,
|
|
862
862
|
isCrossRef: edge.back,
|
|
863
863
|
arrow: true,
|
|
@@ -1532,7 +1532,7 @@ var DependencyViewerComponent = class DependencyViewerComponent {
|
|
|
1532
1532
|
isSignal: true
|
|
1533
1533
|
}],
|
|
1534
1534
|
ngImport: i0,
|
|
1535
|
-
template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
|
|
1535
|
+
template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-secondary)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-secondary)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
|
|
1536
1536
|
styles: [":host{display:flex;flex-direction:column;position:relative;overflow:hidden;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface)}.dep-viewer__toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);-webkit-user-select:none;user-select:none}.dep-viewer__btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:4px;background:var(--color-surface);color:var(--color-text-secondary);cursor:pointer;font-size:14px;line-height:1}.dep-viewer__btn:hover{background:var(--color-surface-hover)}.dep-viewer__btn--reset{margin-left:4px}.dep-viewer__zoom{font-size:12px;min-width:40px;text-align:center;color:var(--color-text-secondary)}.dep-viewer__direction-label{margin-left:8px;font-size:14px;color:var(--color-text-tertiary)}.dep-viewer__canvas{cursor:grab;overflow:hidden;width:100%;flex:1 1 auto;min-height:0}.dep-viewer__canvas:active{cursor:grabbing}.dep-viewer__canvas--static,.dep-viewer__canvas--static:active{cursor:default}.dep-viewer__canvas--sized{flex:0 0 auto}.dep-viewer__svg{transform-origin:0 0;transition:transform .1s ease-out;overflow:visible}.dep-viewer__edge{pointer-events:none;transition:opacity .15s}.dep-viewer__edge--cross-ref{opacity:.8}.dep-viewer__edge-label{font-size:9px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none;dominant-baseline:central;paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round}.dep-viewer__node{cursor:pointer;transition:filter .15s}.dep-viewer__node:hover{filter:brightness(.96)}.dep-viewer__node--selected rect{stroke-width:2.5}.dep-viewer__node-backdrop{fill:var(--color-surface)}.dep-viewer__node-label{font-size:12px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__toggle{cursor:pointer}.dep-viewer__toggle:hover circle{fill:var(--color-surface-hover)}.dep-viewer__more{pointer-events:none}.dep-viewer__more rect{fill:var(--color-surface-2);stroke:var(--color-border-strong);stroke-width:1}.dep-viewer__more text{font-size:9px;font-weight:600;fill:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.dep-viewer__toggle-text{font-size:11px;font-weight:600;fill:var(--color-text-secondary);pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__legend{display:flex;flex-wrap:wrap;gap:12px;padding:6px 12px;border-top:1px solid var(--color-border);background:var(--color-surface-2)}.dep-viewer__legend-item{display:flex;align-items:center;gap:4px}.dep-viewer__legend-text{font-size:11px;color:var(--color-text-secondary)}.dep-viewer__detail{position:absolute;bottom:12px;right:12px;width:240px;padding:12px;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:8px;box-shadow:var(--elevation-1);font-size:12px;z-index:10}.dep-viewer__detail-header{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:13px}.dep-viewer__detail-type{margin-left:auto;padding:1px 6px;border-radius:3px;background:var(--color-surface-hover);color:var(--color-text-secondary);font-size:10px;font-weight:500}.dep-viewer__detail-status{font-size:10px}.dep-viewer__detail-status--default{color:var(--color-text-tertiary)}.dep-viewer__detail-status--active{color:var(--color-primary-400)}.dep-viewer__detail-status--success{color:var(--color-on-success-subtle)}.dep-viewer__detail-status--warning{color:var(--color-on-warning-subtle)}.dep-viewer__detail-status--error{color:var(--color-on-error-subtle)}.dep-viewer__detail-status--muted{color:var(--color-text-disabled)}.dep-viewer__detail-desc{color:var(--color-text-secondary);margin:0 0 6px;line-height:1.4}.dep-viewer__detail-meta{display:flex;flex-wrap:wrap;gap:8px;color:var(--color-text-tertiary);font-size:11px}.dep-viewer__detail-deps{margin-top:8px;padding-top:8px;border-top:1px solid var(--color-border)}.dep-viewer__detail-deps ul{list-style:none;margin:4px 0 0;padding:0}.dep-viewer__detail-deps li{padding:2px 0;color:var(--color-text-secondary)}.dep-viewer__dep-relation{display:inline-block;padding:1px 4px;background:var(--color-surface-hover);border-radius:3px;font-size:10px;font-weight:500;color:var(--color-text-secondary)}\n"],
|
|
1537
1537
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
1538
1538
|
});
|
|
@@ -1552,7 +1552,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1552
1552
|
"[class]": "\"dep-viewer\"",
|
|
1553
1553
|
"[style.height]": "hostHeight()"
|
|
1554
1554
|
},
|
|
1555
|
-
template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
|
|
1555
|
+
template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-secondary)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-secondary)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
|
|
1556
1556
|
styles: [":host{display:flex;flex-direction:column;position:relative;overflow:hidden;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface)}.dep-viewer__toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);-webkit-user-select:none;user-select:none}.dep-viewer__btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:4px;background:var(--color-surface);color:var(--color-text-secondary);cursor:pointer;font-size:14px;line-height:1}.dep-viewer__btn:hover{background:var(--color-surface-hover)}.dep-viewer__btn--reset{margin-left:4px}.dep-viewer__zoom{font-size:12px;min-width:40px;text-align:center;color:var(--color-text-secondary)}.dep-viewer__direction-label{margin-left:8px;font-size:14px;color:var(--color-text-tertiary)}.dep-viewer__canvas{cursor:grab;overflow:hidden;width:100%;flex:1 1 auto;min-height:0}.dep-viewer__canvas:active{cursor:grabbing}.dep-viewer__canvas--static,.dep-viewer__canvas--static:active{cursor:default}.dep-viewer__canvas--sized{flex:0 0 auto}.dep-viewer__svg{transform-origin:0 0;transition:transform .1s ease-out;overflow:visible}.dep-viewer__edge{pointer-events:none;transition:opacity .15s}.dep-viewer__edge--cross-ref{opacity:.8}.dep-viewer__edge-label{font-size:9px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none;dominant-baseline:central;paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round}.dep-viewer__node{cursor:pointer;transition:filter .15s}.dep-viewer__node:hover{filter:brightness(.96)}.dep-viewer__node--selected rect{stroke-width:2.5}.dep-viewer__node-backdrop{fill:var(--color-surface)}.dep-viewer__node-label{font-size:12px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__toggle{cursor:pointer}.dep-viewer__toggle:hover circle{fill:var(--color-surface-hover)}.dep-viewer__more{pointer-events:none}.dep-viewer__more rect{fill:var(--color-surface-2);stroke:var(--color-border-strong);stroke-width:1}.dep-viewer__more text{font-size:9px;font-weight:600;fill:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.dep-viewer__toggle-text{font-size:11px;font-weight:600;fill:var(--color-text-secondary);pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__legend{display:flex;flex-wrap:wrap;gap:12px;padding:6px 12px;border-top:1px solid var(--color-border);background:var(--color-surface-2)}.dep-viewer__legend-item{display:flex;align-items:center;gap:4px}.dep-viewer__legend-text{font-size:11px;color:var(--color-text-secondary)}.dep-viewer__detail{position:absolute;bottom:12px;right:12px;width:240px;padding:12px;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:8px;box-shadow:var(--elevation-1);font-size:12px;z-index:10}.dep-viewer__detail-header{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:13px}.dep-viewer__detail-type{margin-left:auto;padding:1px 6px;border-radius:3px;background:var(--color-surface-hover);color:var(--color-text-secondary);font-size:10px;font-weight:500}.dep-viewer__detail-status{font-size:10px}.dep-viewer__detail-status--default{color:var(--color-text-tertiary)}.dep-viewer__detail-status--active{color:var(--color-primary-400)}.dep-viewer__detail-status--success{color:var(--color-on-success-subtle)}.dep-viewer__detail-status--warning{color:var(--color-on-warning-subtle)}.dep-viewer__detail-status--error{color:var(--color-on-error-subtle)}.dep-viewer__detail-status--muted{color:var(--color-text-disabled)}.dep-viewer__detail-desc{color:var(--color-text-secondary);margin:0 0 6px;line-height:1.4}.dep-viewer__detail-meta{display:flex;flex-wrap:wrap;gap:8px;color:var(--color-text-tertiary);font-size:11px}.dep-viewer__detail-deps{margin-top:8px;padding-top:8px;border-top:1px solid var(--color-border)}.dep-viewer__detail-deps ul{list-style:none;margin:4px 0 0;padding:0}.dep-viewer__detail-deps li{padding:2px 0;color:var(--color-text-secondary)}.dep-viewer__dep-relation{display:inline-block;padding:1px 4px;background:var(--color-surface-hover);border-radius:3px;font-size:10px;font-weight:500;color:var(--color-text-secondary)}\n"]
|
|
1557
1557
|
}]
|
|
1558
1558
|
}],
|