@webpieces/nx-webpieces-rules 0.4.873 → 0.4.874

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 (35) hide show
  1. package/README.md +31 -18
  2. package/package.json +7 -7
  3. package/src/executors/visualize/executor.d.ts +17 -3
  4. package/src/executors/visualize/executor.js +53 -33
  5. package/src/executors/visualize/executor.js.map +1 -1
  6. package/src/lib/graph-color-modes.d.ts +56 -34
  7. package/src/lib/graph-color-modes.js +111 -100
  8. package/src/lib/graph-color-modes.js.map +1 -1
  9. package/src/lib/graph-filter.client.d.ts +13 -2
  10. package/src/lib/graph-filter.client.js +27 -7
  11. package/src/lib/graph-filter.client.js.map +1 -1
  12. package/src/lib/graph-impact.d.ts +37 -3
  13. package/src/lib/graph-impact.js +42 -11
  14. package/src/lib/graph-impact.js.map +1 -1
  15. package/src/lib/graph-legend.js +1 -1
  16. package/src/lib/graph-legend.js.map +1 -1
  17. package/src/lib/graph-level-bands.d.ts +8 -0
  18. package/src/lib/graph-level-bands.js +13 -1
  19. package/src/lib/graph-level-bands.js.map +1 -1
  20. package/src/lib/graph-node-menu.js +10 -108
  21. package/src/lib/graph-node-menu.js.map +1 -1
  22. package/src/lib/graph-page-shell.d.ts +31 -1
  23. package/src/lib/graph-page-shell.js +75 -7
  24. package/src/lib/graph-page-shell.js.map +1 -1
  25. package/src/lib/graph-page-styles.d.ts +14 -3
  26. package/src/lib/graph-page-styles.js +89 -18
  27. package/src/lib/graph-page-styles.js.map +1 -1
  28. package/src/lib/graph-render-model.d.ts +14 -6
  29. package/src/lib/graph-render-model.js +22 -8
  30. package/src/lib/graph-render-model.js.map +1 -1
  31. package/src/lib/graph-visualizer.client.d.ts +82 -5
  32. package/src/lib/graph-visualizer.client.js +351 -23
  33. package/src/lib/graph-visualizer.client.js.map +1 -1
  34. package/src/lib/graph-visualizer.js +1 -1
  35. package/src/lib/graph-visualizer.js.map +1 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GraphPageStyles = void 0;
4
+ const graph_color_modes_1 = require("./graph-color-modes");
4
5
  /**
5
6
  * The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating
6
7
  * popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so
@@ -8,14 +9,21 @@ exports.GraphPageStyles = void 0;
8
9
  */
9
10
  class GraphPageStyles {
10
11
  css() {
11
- return this.base() + this.drawer() + this.controls() + this.main() + this.overlays() + this.graph() + this.cards();
12
+ return (this.base() +
13
+ this.drawer() +
14
+ this.controls() +
15
+ this.main() +
16
+ this.overlays() +
17
+ this.filterPopover() +
18
+ this.graph() +
19
+ this.cards());
12
20
  }
13
21
  base() {
14
22
  return `
15
23
  :root {
16
- --wp-side-bg: #0c0d12; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;
17
- --wp-side-hover: #1f2129; --wp-side-line: #2a2c36; --wp-accent: #3559d6; --wp-accent-dark: #8aa4ff;
18
- --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #fbfbfd;
24
+ --wp-side-bg: #15161b; --wp-side-control: #1e1f25; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;
25
+ --wp-side-hover: #26272f; --wp-side-line: #3a3c46; --wp-accent: #8b3cf0; --wp-accent-hover: #a46bf7;
26
+ --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #faf9f5;
19
27
  }
20
28
  html, body { height: 100%; }
21
29
  /* A class that sets display (the legend lists are grids) must not un-hide a [hidden] element. */
@@ -46,13 +54,27 @@ class GraphPageStyles {
46
54
  .wp-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--wp-side-muted);
47
55
  font-weight: 600; padding-left: 4px; }
48
56
  .wp-label-row { display: flex; justify-content: space-between; align-items: center; }
49
- .wp-modes { display: grid; gap: 1px; }
50
- .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 5px 10px; border-radius: 8px; }
57
+ .wp-pulldown { position: relative; }
58
+ .wp-pill-trigger { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px;
59
+ width: 100%; padding: 7px 16px; border-radius: 999px; border: 1px solid var(--wp-side-line);
60
+ background: var(--wp-side-control); }
61
+ .wp-pill-trigger:hover, .wp-pill-trigger[aria-expanded="true"] { border-color: var(--wp-accent-hover); }
62
+ .wp-pill-text { flex: 1 1 auto; min-width: 0; }
63
+ .wp-caret { color: var(--wp-side-muted); }
64
+ .wp-menu { position: absolute; z-index: 35; left: 0; right: 0; top: calc(100% + 6px); display: grid; gap: 2px; padding: 6px;
65
+ background: var(--wp-side-control); border: 1px solid var(--wp-side-line); border-radius: 12px; box-shadow: 0 10px 28px #0008; }
66
+ .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 6px 10px; border-radius: 8px; }
51
67
  .wp-mode:hover { background: var(--wp-side-hover); }
52
- .wp-mode[aria-pressed="true"] { background: #262833; box-shadow: inset 3px 0 0 var(--wp-accent-dark); }
68
+ .wp-mode[aria-checked="true"] { background: #2a2333; box-shadow: inset 3px 0 0 var(--wp-accent); }
53
69
  .wp-mode:disabled { cursor: not-allowed; opacity: 0.45; }
54
70
  .wp-mode-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }
55
71
  .wp-mode-sub { display: block; font-size: 12px; color: var(--wp-side-muted); line-height: 1.3; }
72
+ .wp-filter-btn { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;
73
+ align-items: center; padding: 7px 12px; border-radius: 8px; border: 1px solid var(--wp-side-line);
74
+ background: var(--wp-side-control); font-size: 13px; font-weight: 600; }
75
+ .wp-filter-btn:hover, .wp-filter-btn[aria-expanded="true"] { border-color: var(--wp-accent-hover); }
76
+ .wp-badge { min-width: 18px; padding: 1px 6px; border-radius: 999px; background: var(--wp-accent); color: #fff;
77
+ font-size: 11.5px; text-align: center; box-sizing: border-box; }
56
78
  .wp-mode-reason { margin: 2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--wp-side-muted); }
57
79
  .wp-legend-list { display: grid; gap: 5px; padding-left: 4px; }
58
80
  .wp-legend-row { display: flex; align-items: center; gap: 10px; line-height: 1.25; }
@@ -64,7 +86,7 @@ class GraphPageStyles {
64
86
  }
65
87
  controls() {
66
88
  return `
67
- .wp-side input[type="search"] { width: 100%; box-sizing: border-box; background: #17181f; color: var(--wp-side-fg);
89
+ .wp-side input[type="search"] { width: 100%; box-sizing: border-box; background: var(--wp-side-control); color: var(--wp-side-fg);
68
90
  border: 1px solid var(--wp-side-line); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
69
91
  .wp-toggle { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;
70
92
  align-items: center; padding: 6px 10px; border-radius: 8px; font-size: 13px; }
@@ -83,8 +105,9 @@ class GraphPageStyles {
83
105
  .wp-icon:hover { border-color: var(--wp-accent); }
84
106
  .wp-icon-mini { width: 24px; height: 24px; font-size: 12px; }
85
107
  .wp-icon-dark { background: transparent; border-color: var(--wp-side-line); color: var(--wp-side-fg); }
86
- .wp-mode:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,
87
- .wp-link:focus-visible { outline: 2px solid var(--wp-accent-dark); outline-offset: 2px; }`;
108
+ .wp-mode:focus-visible, .wp-pill-trigger:focus-visible, .wp-filter-btn:focus-visible, .wp-chip:focus-visible,
109
+ .wp-primary:focus-visible, .wp-pill button:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,
110
+ .wp-link:focus-visible { outline: 2px solid var(--wp-accent-hover); outline-offset: 2px; }`;
88
111
  }
89
112
  main() {
90
113
  return `
@@ -94,7 +117,41 @@ class GraphPageStyles {
94
117
  .wp-crumb { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--wp-muted); overflow: hidden;
95
118
  text-overflow: ellipsis; white-space: nowrap; }
96
119
  .wp-crumb b { color: var(--wp-fg); }
97
- .wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }`;
120
+ .wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }
121
+ .wp-pills { display: flex; gap: 6px; flex-wrap: wrap; }
122
+ .wp-pills:empty { display: none; }
123
+ .wp-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px;
124
+ background: #f1e8fe; color: #4a1d8a; border: 1px solid #d6bcfa; font-size: 12.5px; }
125
+ .wp-pill button { all: unset; cursor: pointer; width: 18px; height: 18px; display: inline-grid; place-items: center;
126
+ border-radius: 50%; color: #4a1d8a; }
127
+ .wp-pill button:hover { background: #e2cffc; }`;
128
+ }
129
+ /** The Filter popover: three groups, chips, and the "Show N projects" footer. */
130
+ filterPopover() {
131
+ return `
132
+ .wp-filter-pop { left: 12px; top: 56px; width: min(380px, calc(100% - 24px)); max-height: calc(100% - 80px); overflow: auto; }
133
+ .wp-fgroup { border: 0; margin: 0 0 12px; padding: 0; display: grid; gap: 4px; }
134
+ .wp-fgroup legend { padding: 0; margin-bottom: 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;
135
+ color: var(--wp-muted); font-weight: 600; }
136
+ .wp-fgroup legend small { text-transform: none; letter-spacing: 0; font-weight: 400; }
137
+ .wp-fgroup:disabled .wp-scope { opacity: 0.45; cursor: not-allowed; }
138
+ .wp-scope { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }
139
+ .wp-scope:hover { background: #f4f1fb; }
140
+ .wp-scope input { accent-color: var(--wp-accent); margin: 0; }
141
+ .wp-scope-name { flex: 1 1 auto; }
142
+ .wp-count { color: var(--wp-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
143
+ .wp-scope-reason { margin: 2px 6px 0; font-size: 12px; color: var(--wp-muted); }
144
+ .wp-chips { display: flex; flex-wrap: wrap; gap: 6px; }
145
+ .wp-chip { all: unset; box-sizing: border-box; cursor: pointer; padding: 3px 11px; border-radius: 999px;
146
+ border: 1px solid var(--wp-line); background: var(--wp-surface); font-size: 12.5px; }
147
+ .wp-chip:hover { border-color: var(--wp-accent-hover); }
148
+ .wp-chip[aria-pressed="true"] { background: var(--wp-accent); border-color: var(--wp-accent); color: #fff; }
149
+ .wp-filter-pop footer { display: flex; justify-content: space-between; align-items: center; gap: 8px;
150
+ padding-top: 10px; border-top: 1px solid var(--wp-line); }
151
+ .wp-text-btn { all: unset; cursor: pointer; color: var(--wp-muted); font-size: 12.5px; text-decoration: underline; }
152
+ .wp-primary { all: unset; box-sizing: border-box; cursor: pointer; padding: 7px 14px; border-radius: 8px;
153
+ background: var(--wp-accent); color: #fff; font-weight: 600; font-size: 13px; }
154
+ .wp-primary:hover { background: var(--wp-accent-hover); }`;
98
155
  }
99
156
  overlays() {
100
157
  return `
@@ -113,18 +170,32 @@ class GraphPageStyles {
113
170
  padding: 12px 0; background: #f5f6f9; font-weight: 600; font-size: 15px; }`;
114
171
  }
115
172
  /**
116
- * The graph fills what the topbar leaves. A Runtime box with a specialization is an HTML-like
117
- * table, so its cells are extra polygons inside the node: the hover glow and the lock outline are
118
- * kept on the box's own outline (the FIRST polygon) instead of stroking every cell.
173
+ * The graph fills what the topbar leaves.
174
+ *
175
+ * The hover glow and the lock outline belong to the OUTER box only. A nested Runtime box (one
176
+ * runtime plus a specialization) is an HTML-like table, so after the node's own outline Graphviz
177
+ * emits a `<polygon>` for the base-colored cell and a `<path>` for the inner `STYLE="rounded"`
178
+ * table — matching only `polygon` here is what let the glow stroke the inner box (#1158). So the
179
+ * glow is suppressed on EVERY shape after the node's first, whatever its element type.
180
+ *
181
+ * A striped (multi-runtime) box is the one exception to "the first shape is the outline":
182
+ * Graphviz draws one filled polygon per stripe first and the outline LAST, as an unfilled polygon.
183
+ * graph-color-modes.ts stamps those boxes `wp-striped`, and for them the outline is that last,
184
+ * `fill="none"` polygon; the stripes keep their thin dividers.
119
185
  */
120
186
  graph() {
187
+ const shape = ':is(polygon, ellipse, path)';
188
+ const later = `${shape} ~ ${shape}`;
189
+ const first = `${shape}:not(${later})`;
190
+ const glow = 'stroke: var(--wp-accent); stroke-width: 5; filter: drop-shadow(0 0 6px rgba(139, 60, 240, 0.85));';
121
191
  return `
122
192
  #graph { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; padding: 16px 16px 64px;
123
193
  box-sizing: border-box; background: var(--wp-canvas); }
124
- #graph g.wp-node-clickable:hover polygon:not(:first-of-type) { stroke: transparent; stroke-width: 0; filter: none; }
125
- #graph g.node.wp-locked > polygon:first-of-type,
126
- #graph g.node.wp-locked > ellipse,
127
- #graph g.node.wp-locked > path { stroke: #b26a00; stroke-width: 3; }`;
194
+ #graph g.wp-node-clickable:hover > ${later} { stroke: none; stroke-width: 0; filter: none; }
195
+ #graph g.wp-node-clickable.${graph_color_modes_1.STRIPED_CLASS}:hover > polygon:not([fill="none"]) { stroke: #000000; stroke-width: 0.5; filter: none; }
196
+ #graph g.wp-node-clickable.${graph_color_modes_1.STRIPED_CLASS}:hover > polygon[fill="none"] { ${glow} }
197
+ #graph g.node.wp-locked:not(.${graph_color_modes_1.STRIPED_CLASS}) > ${first},
198
+ #graph g.node.wp-locked.${graph_color_modes_1.STRIPED_CLASS} > polygon[fill="none"] { stroke: var(--wp-accent); stroke-width: 3; }`;
128
199
  }
129
200
  cards() {
130
201
  return `
@@ -1 +1 @@
1
- {"version":3,"file":"graph-page-styles.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-styles.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,MAAa,eAAe;IACxB,GAAG;QACC,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IACvH,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;;;;;;;;;;uFAuBwE,CAAC;IACpF,CAAC;IAEO,MAAM;QACV,OAAO;;;;;;;;;;;;;;;;;;;;;;;mJAuBoI,CAAC;IAChJ,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;;;;;;;;;kGAqBmF,CAAC;IAC/F,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;iGAOkF,CAAC;IAC9F,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;uFAawE,CAAC;IACpF,CAAC;IAED;;;;OAIG;IACK,KAAK;QACT,OAAO;;;;;;6EAM8D,CAAC;IAC1E,CAAC;IAEO,KAAK;QACT,OAAO;;;;;;;;;;sCAUuB,CAAC;IACnC,CAAC;CACJ;AA5ID,0CA4IC","sourcesContent":["/**\n * The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating\n * popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so\n * its `#graph` rules (fill the viewport, no 75vh cap) win.\n */\nexport class GraphPageStyles {\n css(): string {\n return this.base() + this.drawer() + this.controls() + this.main() + this.overlays() + this.graph() + this.cards();\n }\n\n private base(): string {\n return `\n :root {\n --wp-side-bg: #0c0d12; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;\n --wp-side-hover: #1f2129; --wp-side-line: #2a2c36; --wp-accent: #3559d6; --wp-accent-dark: #8aa4ff;\n --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #fbfbfd;\n }\n html, body { height: 100%; }\n /* A class that sets display (the legend lists are grids) must not un-hide a [hidden] element. */\n [hidden] { display: none !important; }\n body { margin: 0; padding: 0; overflow: hidden; background: var(--wp-canvas); color: var(--wp-fg);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Arial, sans-serif; font-size: 14px; }\n kbd { font-family: ui-monospace, Menlo, monospace; font-size: 12px; border: 1px solid var(--wp-line);\n border-radius: 4px; padding: 0 5px; background: #f3f4f7; }\n .wp-shell { display: grid; grid-template-columns: 272px minmax(0, 1fr); height: 100vh; }\n .wp-shell.wp-collapsed { grid-template-columns: 0 minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { visibility: hidden; }\n @media (max-width: 760px) {\n .wp-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, auto) minmax(0, 1fr); }\n .wp-side { max-height: 42vh; }\n .wp-shell.wp-collapsed { grid-template-columns: minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { display: none; }\n .wp-main { height: auto; min-height: 0; }\n }\n @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }`;\n }\n\n private drawer(): string {\n return `\n .wp-side { background: var(--wp-side-bg); color: var(--wp-side-fg); overflow-y: auto; overflow-x: hidden;\n display: flex; flex-direction: column; padding: 12px 0 10px; min-width: 0; font-size: 13px; }\n .wp-brand { display: flex; align-items: center; gap: 10px; padding: 0 16px 10px; font-weight: 600; font-size: 14px; }\n .wp-brand svg { flex: none; }\n .wp-group { padding: 8px 14px; border-top: 1px solid var(--wp-side-line); display: grid; gap: 6px; }\n .wp-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--wp-side-muted);\n font-weight: 600; padding-left: 4px; }\n .wp-label-row { display: flex; justify-content: space-between; align-items: center; }\n .wp-modes { display: grid; gap: 1px; }\n .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 5px 10px; border-radius: 8px; }\n .wp-mode:hover { background: var(--wp-side-hover); }\n .wp-mode[aria-pressed=\"true\"] { background: #262833; box-shadow: inset 3px 0 0 var(--wp-accent-dark); }\n .wp-mode:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-mode-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }\n .wp-mode-sub { display: block; font-size: 12px; color: var(--wp-side-muted); line-height: 1.3; }\n .wp-mode-reason { margin: 2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--wp-side-muted); }\n .wp-legend-list { display: grid; gap: 5px; padding-left: 4px; }\n .wp-legend-row { display: flex; align-items: center; gap: 10px; line-height: 1.25; }\n .wp-legend-row svg { flex: none; }\n .wp-legend-row small { display: block; color: var(--wp-side-muted); font-size: 11.5px; }\n .wp-impact-note { margin: 2px 0 0; font-size: 11.5px; color: var(--wp-side-muted); }\n .wp-impact-note:empty { display: none; }\n .wp-foot { margin-top: auto; padding: 10px 18px 0; border-top: 1px solid var(--wp-side-line); display: flex; gap: 14px; flex-wrap: wrap; }`;\n }\n\n private controls(): string {\n return `\n .wp-side input[type=\"search\"] { width: 100%; box-sizing: border-box; background: #17181f; color: var(--wp-side-fg);\n border: 1px solid var(--wp-side-line); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }\n .wp-toggle { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 6px 10px; border-radius: 8px; font-size: 13px; }\n .wp-toggle:hover { background: var(--wp-side-hover); }\n .wp-toggle:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-sw { width: 30px; height: 18px; border-radius: 9px; background: #3a3d4a; position: relative; transition: background 0.15s; }\n .wp-sw::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;\n background: #fff; transition: transform 0.15s; }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw { background: var(--wp-accent); }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw::after { transform: translateX(12px); }\n .wp-link { all: unset; cursor: pointer; color: var(--wp-side-muted); font-size: 12px; text-decoration: underline; }\n .wp-link:hover { color: var(--wp-side-fg); }\n .wp-icon { all: unset; box-sizing: border-box; cursor: pointer; display: inline-grid; place-items: center; flex: none;\n width: 34px; height: 34px; border-radius: 8px; background: var(--wp-surface); border: 1px solid var(--wp-line);\n color: var(--wp-fg); font-size: 15px; }\n .wp-icon:hover { border-color: var(--wp-accent); }\n .wp-icon-mini { width: 24px; height: 24px; font-size: 12px; }\n .wp-icon-dark { background: transparent; border-color: var(--wp-side-line); color: var(--wp-side-fg); }\n .wp-mode:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,\n .wp-link:focus-visible { outline: 2px solid var(--wp-accent-dark); outline-offset: 2px; }`;\n }\n\n private main(): string {\n return `\n .wp-main { position: relative; min-width: 0; height: 100vh; display: flex; flex-direction: column; }\n .wp-topbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 12px;\n border-bottom: 1px solid var(--wp-line); background: var(--wp-surface); }\n .wp-crumb { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--wp-muted); overflow: hidden;\n text-overflow: ellipsis; white-space: nowrap; }\n .wp-crumb b { color: var(--wp-fg); }\n .wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }`;\n }\n\n private overlays(): string {\n return `\n .wp-pop { position: absolute; z-index: 30; background: var(--wp-surface); border: 1px solid var(--wp-line);\n border-radius: 10px; box-shadow: 0 8px 24px #0002; padding: 12px 14px; font-size: 13px; box-sizing: border-box; }\n .wp-pop header { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 8px; }\n .wp-legend-popout { left: 12px; bottom: 12px; width: 270px; max-height: 60vh; overflow: auto; }\n .wp-legend-popout .wp-legend-row small, .wp-legend-popout .wp-impact-note { color: var(--wp-muted); }\n .wp-help { right: 12px; top: 56px; width: min(380px, calc(100% - 24px)); line-height: 1.5; }\n .wp-help p { margin: 0 0 8px; }\n .wp-snapshot { left: 12px; bottom: 12px; width: min(440px, calc(100% - 24px)); }\n .wp-snapshot .hint { margin: 0; }\n .wp-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 40; width: min(540px, 100%); box-sizing: border-box;\n overflow-y: auto; padding: 0 16px 16px; background: #f5f6f9; border-left: 1px solid var(--wp-line); box-shadow: -8px 0 24px #0002; }\n .wp-panel > header { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: center;\n padding: 12px 0; background: #f5f6f9; font-weight: 600; font-size: 15px; }`;\n }\n\n /**\n * The graph fills what the topbar leaves. A Runtime box with a specialization is an HTML-like\n * table, so its cells are extra polygons inside the node: the hover glow and the lock outline are\n * kept on the box's own outline (the FIRST polygon) instead of stroking every cell.\n */\n private graph(): string {\n return `\n #graph { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; padding: 16px 16px 64px;\n box-sizing: border-box; background: var(--wp-canvas); }\n #graph g.wp-node-clickable:hover polygon:not(:first-of-type) { stroke: transparent; stroke-width: 0; filter: none; }\n #graph g.node.wp-locked > polygon:first-of-type,\n #graph g.node.wp-locked > ellipse,\n #graph g.node.wp-locked > path { stroke: #b26a00; stroke-width: 3; }`;\n }\n\n private cards(): string {\n return `\n #wp-responsibilities { margin: 0; }\n #wp-responsibilities > h2 { display: none; }\n #wp-responsibilities > .hint { color: var(--wp-muted); margin: 0 0 8px; }\n .wp-resp-card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 0; padding: 10px 15px; }\n .wp-resp-card > summary { cursor: pointer; color: #333; }\n .wp-resp-level { display: inline-block; min-width: 26px; padding: 1px 6px; margin-right: 6px; border-radius: 4px;\n background: #eef; font-size: 12px; font-weight: bold; text-align: center; }\n .wp-resp-body { margin-top: 8px; color: #444; }\n .wp-resp-body code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\n .wp-hidden { display: none; }`;\n }\n}\n"]}
1
+ {"version":3,"file":"graph-page-styles.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-styles.ts"],"names":[],"mappings":";;;AAAA,2DAAoD;AAEpD;;;;GAIG;AACH,MAAa,eAAe;IACxB,GAAG;QACC,OAAO,CACH,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,MAAM,EAAE;YACb,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,aAAa,EAAE;YACpB,IAAI,CAAC,KAAK,EAAE;YACZ,IAAI,CAAC,KAAK,EAAE,CACf,CAAC;IACN,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;;;;;;;;;;uFAuBwE,CAAC;IACpF,CAAC;IAEO,MAAM;QACV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mJAqCoI,CAAC;IAChJ,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;;;;;;;;;;mGAsBoF,CAAC;IAChG,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;uDAcwC,CAAC;IACpD,CAAC;IAED,iFAAiF;IACzE,aAAa;QACjB,OAAO;;;;;;;;;;;;;;;;;;;;;;;kEAuBmD,CAAC;IAC/D,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;uFAawE,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,KAAK;QACT,MAAM,KAAK,GAAG,6BAA6B,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,KAAK,MAAM,KAAK,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,KAAK,QAAQ,KAAK,GAAG,CAAC;QACvC,MAAM,IAAI,GAAG,mGAAmG,CAAC;QACjH,OAAO;;;6CAG8B,KAAK;qCACb,iCAAa;qCACb,iCAAa,mCAAmC,IAAI;uCAClD,iCAAa,OAAO,KAAK;kCAC9B,iCAAa,wEAAwE,CAAC;IACpH,CAAC;IAEO,KAAK;QACT,OAAO;;;;;;;;;;sCAUuB,CAAC;IACnC,CAAC;CACJ;AArND,0CAqNC","sourcesContent":["import { STRIPED_CLASS } from './graph-color-modes';\n\n/**\n * The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating\n * popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so\n * its `#graph` rules (fill the viewport, no 75vh cap) win.\n */\nexport class GraphPageStyles {\n css(): string {\n return (\n this.base() +\n this.drawer() +\n this.controls() +\n this.main() +\n this.overlays() +\n this.filterPopover() +\n this.graph() +\n this.cards()\n );\n }\n\n private base(): string {\n return `\n :root {\n --wp-side-bg: #15161b; --wp-side-control: #1e1f25; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;\n --wp-side-hover: #26272f; --wp-side-line: #3a3c46; --wp-accent: #8b3cf0; --wp-accent-hover: #a46bf7;\n --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #faf9f5;\n }\n html, body { height: 100%; }\n /* A class that sets display (the legend lists are grids) must not un-hide a [hidden] element. */\n [hidden] { display: none !important; }\n body { margin: 0; padding: 0; overflow: hidden; background: var(--wp-canvas); color: var(--wp-fg);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Arial, sans-serif; font-size: 14px; }\n kbd { font-family: ui-monospace, Menlo, monospace; font-size: 12px; border: 1px solid var(--wp-line);\n border-radius: 4px; padding: 0 5px; background: #f3f4f7; }\n .wp-shell { display: grid; grid-template-columns: 272px minmax(0, 1fr); height: 100vh; }\n .wp-shell.wp-collapsed { grid-template-columns: 0 minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { visibility: hidden; }\n @media (max-width: 760px) {\n .wp-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, auto) minmax(0, 1fr); }\n .wp-side { max-height: 42vh; }\n .wp-shell.wp-collapsed { grid-template-columns: minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { display: none; }\n .wp-main { height: auto; min-height: 0; }\n }\n @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }`;\n }\n\n private drawer(): string {\n return `\n .wp-side { background: var(--wp-side-bg); color: var(--wp-side-fg); overflow-y: auto; overflow-x: hidden;\n display: flex; flex-direction: column; padding: 12px 0 10px; min-width: 0; font-size: 13px; }\n .wp-brand { display: flex; align-items: center; gap: 10px; padding: 0 16px 10px; font-weight: 600; font-size: 14px; }\n .wp-brand svg { flex: none; }\n .wp-group { padding: 8px 14px; border-top: 1px solid var(--wp-side-line); display: grid; gap: 6px; }\n .wp-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--wp-side-muted);\n font-weight: 600; padding-left: 4px; }\n .wp-label-row { display: flex; justify-content: space-between; align-items: center; }\n .wp-pulldown { position: relative; }\n .wp-pill-trigger { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px;\n width: 100%; padding: 7px 16px; border-radius: 999px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); }\n .wp-pill-trigger:hover, .wp-pill-trigger[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-pill-text { flex: 1 1 auto; min-width: 0; }\n .wp-caret { color: var(--wp-side-muted); }\n .wp-menu { position: absolute; z-index: 35; left: 0; right: 0; top: calc(100% + 6px); display: grid; gap: 2px; padding: 6px;\n background: var(--wp-side-control); border: 1px solid var(--wp-side-line); border-radius: 12px; box-shadow: 0 10px 28px #0008; }\n .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 6px 10px; border-radius: 8px; }\n .wp-mode:hover { background: var(--wp-side-hover); }\n .wp-mode[aria-checked=\"true\"] { background: #2a2333; box-shadow: inset 3px 0 0 var(--wp-accent); }\n .wp-mode:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-mode-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }\n .wp-mode-sub { display: block; font-size: 12px; color: var(--wp-side-muted); line-height: 1.3; }\n .wp-filter-btn { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 7px 12px; border-radius: 8px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); font-size: 13px; font-weight: 600; }\n .wp-filter-btn:hover, .wp-filter-btn[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-badge { min-width: 18px; padding: 1px 6px; border-radius: 999px; background: var(--wp-accent); color: #fff;\n font-size: 11.5px; text-align: center; box-sizing: border-box; }\n .wp-mode-reason { margin: 2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--wp-side-muted); }\n .wp-legend-list { display: grid; gap: 5px; padding-left: 4px; }\n .wp-legend-row { display: flex; align-items: center; gap: 10px; line-height: 1.25; }\n .wp-legend-row svg { flex: none; }\n .wp-legend-row small { display: block; color: var(--wp-side-muted); font-size: 11.5px; }\n .wp-impact-note { margin: 2px 0 0; font-size: 11.5px; color: var(--wp-side-muted); }\n .wp-impact-note:empty { display: none; }\n .wp-foot { margin-top: auto; padding: 10px 18px 0; border-top: 1px solid var(--wp-side-line); display: flex; gap: 14px; flex-wrap: wrap; }`;\n }\n\n private controls(): string {\n return `\n .wp-side input[type=\"search\"] { width: 100%; box-sizing: border-box; background: var(--wp-side-control); color: var(--wp-side-fg);\n border: 1px solid var(--wp-side-line); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }\n .wp-toggle { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 6px 10px; border-radius: 8px; font-size: 13px; }\n .wp-toggle:hover { background: var(--wp-side-hover); }\n .wp-toggle:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-sw { width: 30px; height: 18px; border-radius: 9px; background: #3a3d4a; position: relative; transition: background 0.15s; }\n .wp-sw::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;\n background: #fff; transition: transform 0.15s; }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw { background: var(--wp-accent); }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw::after { transform: translateX(12px); }\n .wp-link { all: unset; cursor: pointer; color: var(--wp-side-muted); font-size: 12px; text-decoration: underline; }\n .wp-link:hover { color: var(--wp-side-fg); }\n .wp-icon { all: unset; box-sizing: border-box; cursor: pointer; display: inline-grid; place-items: center; flex: none;\n width: 34px; height: 34px; border-radius: 8px; background: var(--wp-surface); border: 1px solid var(--wp-line);\n color: var(--wp-fg); font-size: 15px; }\n .wp-icon:hover { border-color: var(--wp-accent); }\n .wp-icon-mini { width: 24px; height: 24px; font-size: 12px; }\n .wp-icon-dark { background: transparent; border-color: var(--wp-side-line); color: var(--wp-side-fg); }\n .wp-mode:focus-visible, .wp-pill-trigger:focus-visible, .wp-filter-btn:focus-visible, .wp-chip:focus-visible,\n .wp-primary:focus-visible, .wp-pill button:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,\n .wp-link:focus-visible { outline: 2px solid var(--wp-accent-hover); outline-offset: 2px; }`;\n }\n\n private main(): string {\n return `\n .wp-main { position: relative; min-width: 0; height: 100vh; display: flex; flex-direction: column; }\n .wp-topbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 12px;\n border-bottom: 1px solid var(--wp-line); background: var(--wp-surface); }\n .wp-crumb { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--wp-muted); overflow: hidden;\n text-overflow: ellipsis; white-space: nowrap; }\n .wp-crumb b { color: var(--wp-fg); }\n .wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }\n .wp-pills { display: flex; gap: 6px; flex-wrap: wrap; }\n .wp-pills:empty { display: none; }\n .wp-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px;\n background: #f1e8fe; color: #4a1d8a; border: 1px solid #d6bcfa; font-size: 12.5px; }\n .wp-pill button { all: unset; cursor: pointer; width: 18px; height: 18px; display: inline-grid; place-items: center;\n border-radius: 50%; color: #4a1d8a; }\n .wp-pill button:hover { background: #e2cffc; }`;\n }\n\n /** The Filter popover: three groups, chips, and the \"Show N projects\" footer. */\n private filterPopover(): string {\n return `\n .wp-filter-pop { left: 12px; top: 56px; width: min(380px, calc(100% - 24px)); max-height: calc(100% - 80px); overflow: auto; }\n .wp-fgroup { border: 0; margin: 0 0 12px; padding: 0; display: grid; gap: 4px; }\n .wp-fgroup legend { padding: 0; margin-bottom: 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;\n color: var(--wp-muted); font-weight: 600; }\n .wp-fgroup legend small { text-transform: none; letter-spacing: 0; font-weight: 400; }\n .wp-fgroup:disabled .wp-scope { opacity: 0.45; cursor: not-allowed; }\n .wp-scope { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }\n .wp-scope:hover { background: #f4f1fb; }\n .wp-scope input { accent-color: var(--wp-accent); margin: 0; }\n .wp-scope-name { flex: 1 1 auto; }\n .wp-count { color: var(--wp-muted); font-size: 12px; font-variant-numeric: tabular-nums; }\n .wp-scope-reason { margin: 2px 6px 0; font-size: 12px; color: var(--wp-muted); }\n .wp-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n .wp-chip { all: unset; box-sizing: border-box; cursor: pointer; padding: 3px 11px; border-radius: 999px;\n border: 1px solid var(--wp-line); background: var(--wp-surface); font-size: 12.5px; }\n .wp-chip:hover { border-color: var(--wp-accent-hover); }\n .wp-chip[aria-pressed=\"true\"] { background: var(--wp-accent); border-color: var(--wp-accent); color: #fff; }\n .wp-filter-pop footer { display: flex; justify-content: space-between; align-items: center; gap: 8px;\n padding-top: 10px; border-top: 1px solid var(--wp-line); }\n .wp-text-btn { all: unset; cursor: pointer; color: var(--wp-muted); font-size: 12.5px; text-decoration: underline; }\n .wp-primary { all: unset; box-sizing: border-box; cursor: pointer; padding: 7px 14px; border-radius: 8px;\n background: var(--wp-accent); color: #fff; font-weight: 600; font-size: 13px; }\n .wp-primary:hover { background: var(--wp-accent-hover); }`;\n }\n\n private overlays(): string {\n return `\n .wp-pop { position: absolute; z-index: 30; background: var(--wp-surface); border: 1px solid var(--wp-line);\n border-radius: 10px; box-shadow: 0 8px 24px #0002; padding: 12px 14px; font-size: 13px; box-sizing: border-box; }\n .wp-pop header { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 8px; }\n .wp-legend-popout { left: 12px; bottom: 12px; width: 270px; max-height: 60vh; overflow: auto; }\n .wp-legend-popout .wp-legend-row small, .wp-legend-popout .wp-impact-note { color: var(--wp-muted); }\n .wp-help { right: 12px; top: 56px; width: min(380px, calc(100% - 24px)); line-height: 1.5; }\n .wp-help p { margin: 0 0 8px; }\n .wp-snapshot { left: 12px; bottom: 12px; width: min(440px, calc(100% - 24px)); }\n .wp-snapshot .hint { margin: 0; }\n .wp-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 40; width: min(540px, 100%); box-sizing: border-box;\n overflow-y: auto; padding: 0 16px 16px; background: #f5f6f9; border-left: 1px solid var(--wp-line); box-shadow: -8px 0 24px #0002; }\n .wp-panel > header { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: center;\n padding: 12px 0; background: #f5f6f9; font-weight: 600; font-size: 15px; }`;\n }\n\n /**\n * The graph fills what the topbar leaves.\n *\n * The hover glow and the lock outline belong to the OUTER box only. A nested Runtime box (one\n * runtime plus a specialization) is an HTML-like table, so after the node's own outline Graphviz\n * emits a `<polygon>` for the base-colored cell and a `<path>` for the inner `STYLE=\"rounded\"`\n * table — matching only `polygon` here is what let the glow stroke the inner box (#1158). So the\n * glow is suppressed on EVERY shape after the node's first, whatever its element type.\n *\n * A striped (multi-runtime) box is the one exception to \"the first shape is the outline\":\n * Graphviz draws one filled polygon per stripe first and the outline LAST, as an unfilled polygon.\n * graph-color-modes.ts stamps those boxes `wp-striped`, and for them the outline is that last,\n * `fill=\"none\"` polygon; the stripes keep their thin dividers.\n */\n private graph(): string {\n const shape = ':is(polygon, ellipse, path)';\n const later = `${shape} ~ ${shape}`;\n const first = `${shape}:not(${later})`;\n const glow = 'stroke: var(--wp-accent); stroke-width: 5; filter: drop-shadow(0 0 6px rgba(139, 60, 240, 0.85));';\n return `\n #graph { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; padding: 16px 16px 64px;\n box-sizing: border-box; background: var(--wp-canvas); }\n #graph g.wp-node-clickable:hover > ${later} { stroke: none; stroke-width: 0; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon:not([fill=\"none\"]) { stroke: #000000; stroke-width: 0.5; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon[fill=\"none\"] { ${glow} }\n #graph g.node.wp-locked:not(.${STRIPED_CLASS}) > ${first},\n #graph g.node.wp-locked.${STRIPED_CLASS} > polygon[fill=\"none\"] { stroke: var(--wp-accent); stroke-width: 3; }`;\n }\n\n private cards(): string {\n return `\n #wp-responsibilities { margin: 0; }\n #wp-responsibilities > h2 { display: none; }\n #wp-responsibilities > .hint { color: var(--wp-muted); margin: 0 0 8px; }\n .wp-resp-card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 0; padding: 10px 15px; }\n .wp-resp-card > summary { cursor: pointer; color: #333; }\n .wp-resp-level { display: inline-block; min-width: 26px; padding: 1px 6px; margin-right: 6px; border-radius: 4px;\n background: #eef; font-size: 12px; font-weight: bold; text-align: center; }\n .wp-resp-body { margin-top: 8px; color: #444; }\n .wp-resp-body code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\n .wp-hidden { display: none; }`;\n }\n}\n"]}
@@ -1,17 +1,25 @@
1
1
  import { LevelBand } from './graph-level-bands';
2
- import { LegendFacts, NodeModeDots } from './graph-color-modes';
2
+ import { LegendFacts, NodeFacts, NodeModeDots } from './graph-color-modes';
3
3
  /**
4
4
  * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
5
5
  *
6
- * `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page
7
- * switches color modes by re-rendering each box from its per-mode statement; the runtime graph has
8
- * one look and leaves it null.
6
+ * `dot` is the statement `fullDot` carries. `modes` and `tags` are set only by the architecture
7
+ * graph, whose page switches color modes by re-rendering each box from its per-mode statement and
8
+ * filters boxes by their role and framework tags; the runtime graph has one look and leaves both null.
9
9
  */
10
10
  export declare class RenderNode {
11
11
  readonly id: string;
12
12
  readonly dot: string;
13
13
  readonly modes: NodeModeDots | null;
14
- constructor(id: string, dot: string, modes: NodeModeDots | null);
14
+ readonly tags: NodeTags | null;
15
+ constructor(id: string, dot: string, modes: NodeModeDots | null, tags: NodeTags | null);
16
+ }
17
+ /** What the architecture page's Filter matches a box on: its level, role and framework tags. */
18
+ export declare class NodeTags {
19
+ readonly level: number;
20
+ readonly role: string;
21
+ readonly frameworks: string[];
22
+ constructor(level: number, role: string, frameworks: string[]);
15
23
  }
16
24
  export declare class RenderEdge {
17
25
  readonly from: string;
@@ -32,7 +40,7 @@ export declare class GraphRenderModel {
32
40
  legend: LegendFacts;
33
41
  node(id: string, dot: string): string;
34
42
  /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */
35
- styledNode(id: string, modes: NodeModeDots): string;
43
+ styledNode(facts: NodeFacts, modes: NodeModeDots): string;
36
44
  completeEndpoints(): void;
37
45
  edge(from: string, to: string, dot: string): string;
38
46
  }
@@ -1,26 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GraphFilterAssets = exports.GraphRenderModel = exports.RenderEdge = exports.RenderNode = void 0;
3
+ exports.GraphFilterAssets = exports.GraphRenderModel = exports.RenderEdge = exports.NodeTags = exports.RenderNode = void 0;
4
4
  const dot_syntax_1 = require("./dot-syntax");
5
5
  const graph_color_modes_1 = require("./graph-color-modes");
6
6
  /**
7
7
  * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
8
8
  *
9
- * `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page
10
- * switches color modes by re-rendering each box from its per-mode statement; the runtime graph has
11
- * one look and leaves it null.
9
+ * `dot` is the statement `fullDot` carries. `modes` and `tags` are set only by the architecture
10
+ * graph, whose page switches color modes by re-rendering each box from its per-mode statement and
11
+ * filters boxes by their role and framework tags; the runtime graph has one look and leaves both null.
12
12
  */
13
13
  class RenderNode {
14
14
  id;
15
15
  dot;
16
16
  modes;
17
- constructor(id, dot, modes) {
17
+ tags;
18
+ constructor(id, dot, modes, tags) {
18
19
  this.id = id;
19
20
  this.dot = dot;
20
21
  this.modes = modes;
22
+ this.tags = tags;
21
23
  }
22
24
  }
23
25
  exports.RenderNode = RenderNode;
26
+ /** What the architecture page's Filter matches a box on: its level, role and framework tags. */
27
+ class NodeTags {
28
+ level;
29
+ role;
30
+ frameworks;
31
+ constructor(level, role, frameworks) {
32
+ this.level = level;
33
+ this.role = role;
34
+ this.frameworks = frameworks;
35
+ }
36
+ }
37
+ exports.NodeTags = NodeTags;
24
38
  class RenderEdge {
25
39
  from;
26
40
  to;
@@ -45,12 +59,12 @@ class GraphRenderModel {
45
59
  /** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
46
60
  legend = new graph_color_modes_1.LegendFacts();
47
61
  node(id, dot) {
48
- this.nodes.push(new RenderNode(id, dot, null));
62
+ this.nodes.push(new RenderNode(id, dot, null, null));
49
63
  return dot;
50
64
  }
51
65
  /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */
52
- styledNode(id, modes) {
53
- this.nodes.push(new RenderNode(id, modes.runtime, modes));
66
+ styledNode(facts, modes) {
67
+ this.nodes.push(new RenderNode(facts.nodeId, modes.runtime, modes, new NodeTags(facts.level, facts.role, facts.frameworks)));
54
68
  return modes.runtime;
55
69
  }
56
70
  completeEndpoints() {
@@ -1 +1 @@
1
- {"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { dotValue } from './dot-syntax';\nimport { LevelBand } from './graph-level-bands';\nimport { LegendFacts, NodeModeDots } from './graph-color-modes';\n\n/**\n * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.\n *\n * `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page\n * switches color modes by re-rendering each box from its per-mode statement; the runtime graph has\n * one look and leaves it null.\n */\nexport class RenderNode {\n constructor(\n public readonly id: string,\n public readonly dot: string,\n public readonly modes: NodeModeDots | null,\n ) {}\n}\n\nexport class RenderEdge {\n constructor(\n public readonly from: string,\n public readonly to: string,\n public readonly dot: string,\n public readonly id: string,\n ) {}\n}\n\n/** The immutable drawable snapshot carried by a generated page. */\nexport class GraphRenderModel {\n readonly nodes: RenderNode[] = [];\n readonly edges: RenderEdge[] = [];\n bands: LevelBand[] = [];\n fullDot = '';\n header = '';\n footer = '';\n /** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */\n legend = new LegendFacts();\n\n node(id: string, dot: string): string {\n this.nodes.push(new RenderNode(id, dot, null));\n return dot;\n }\n\n /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */\n styledNode(id: string, modes: NodeModeDots): string {\n this.nodes.push(new RenderNode(id, modes.runtime, modes));\n return modes.runtime;\n }\n\n completeEndpoints(): void {\n const ids = new Set(this.nodes.map((node) => node.id));\n for (const edge of this.edges) {\n for (const id of [edge.from, edge.to]) {\n if (ids.has(id)) continue;\n ids.add(id);\n this.node(id, ` \"${dotValue(id)}\";\\n`);\n }\n }\n }\n\n edge(from: string, to: string, dot: string): string {\n const id = `wp-real-edge-${this.edges.length}`;\n const statement = `${dot.slice(0, -2)} [id=\"${id}\"];\\n`;\n this.edges.push(new RenderEdge(from, to, statement, id));\n return statement;\n }\n}\n\n/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */\nexport class GraphFilterAssets {\n json<T>(value: T): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n }\n\n html(): string {\n return (\n '<p id=\"wp-filter-status\" hidden>Filtering: <strong id=\"wp-filter-anchor\"></strong> ' +\n '<button id=\"wp-filter-off\" type=\"button\">Turn off Filter</button></p>' +\n '<p id=\"wp-graph-error\" role=\"alert\" hidden></p>'\n );\n }\n\n styles(): string {\n return (\n '#wp-filter-status { text-align:center; } #wp-graph-error { color:#b71c1c; } ' +\n '#graph .wp-filter-anchor polygon, #graph .wp-filter-anchor path, ' +\n '#graph .wp-filter-anchor ellipse { stroke:#1565c0; stroke-width:3; }'\n );\n }\n}\n"]}
1
+ {"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { dotValue } from './dot-syntax';\nimport { LevelBand } from './graph-level-bands';\nimport { LegendFacts, NodeFacts, NodeModeDots } from './graph-color-modes';\n\n/**\n * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.\n *\n * `dot` is the statement `fullDot` carries. `modes` and `tags` are set only by the architecture\n * graph, whose page switches color modes by re-rendering each box from its per-mode statement and\n * filters boxes by their role and framework tags; the runtime graph has one look and leaves both null.\n */\nexport class RenderNode {\n constructor(\n public readonly id: string,\n public readonly dot: string,\n public readonly modes: NodeModeDots | null,\n public readonly tags: NodeTags | null,\n ) {}\n}\n\n/** What the architecture page's Filter matches a box on: its level, role and framework tags. */\nexport class NodeTags {\n constructor(\n public readonly level: number,\n public readonly role: string,\n public readonly frameworks: string[],\n ) {}\n}\n\nexport class RenderEdge {\n constructor(\n public readonly from: string,\n public readonly to: string,\n public readonly dot: string,\n public readonly id: string,\n ) {}\n}\n\n/** The immutable drawable snapshot carried by a generated page. */\nexport class GraphRenderModel {\n readonly nodes: RenderNode[] = [];\n readonly edges: RenderEdge[] = [];\n bands: LevelBand[] = [];\n fullDot = '';\n header = '';\n footer = '';\n /** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */\n legend = new LegendFacts();\n\n node(id: string, dot: string): string {\n this.nodes.push(new RenderNode(id, dot, null, null));\n return dot;\n }\n\n /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */\n styledNode(facts: NodeFacts, modes: NodeModeDots): string {\n this.nodes.push(\n new RenderNode(facts.nodeId, modes.runtime, modes, new NodeTags(facts.level, facts.role, facts.frameworks)),\n );\n return modes.runtime;\n }\n\n completeEndpoints(): void {\n const ids = new Set(this.nodes.map((node) => node.id));\n for (const edge of this.edges) {\n for (const id of [edge.from, edge.to]) {\n if (ids.has(id)) continue;\n ids.add(id);\n this.node(id, ` \"${dotValue(id)}\";\\n`);\n }\n }\n }\n\n edge(from: string, to: string, dot: string): string {\n const id = `wp-real-edge-${this.edges.length}`;\n const statement = `${dot.slice(0, -2)} [id=\"${id}\"];\\n`;\n this.edges.push(new RenderEdge(from, to, statement, id));\n return statement;\n }\n}\n\n/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */\nexport class GraphFilterAssets {\n json<T>(value: T): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n }\n\n html(): string {\n return (\n '<p id=\"wp-filter-status\" hidden>Filtering: <strong id=\"wp-filter-anchor\"></strong> ' +\n '<button id=\"wp-filter-off\" type=\"button\">Turn off Filter</button></p>' +\n '<p id=\"wp-graph-error\" role=\"alert\" hidden></p>'\n );\n }\n\n styles(): string {\n return (\n '#wp-filter-status { text-align:center; } #wp-graph-error { color:#b71c1c; } ' +\n '#graph .wp-filter-anchor polygon, #graph .wp-filter-anchor path, ' +\n '#graph .wp-filter-anchor ellipse { stroke:#1565c0; stroke-width:3; }'\n );\n }\n}\n"]}
@@ -9,14 +9,16 @@
9
9
  declare class WpModeState {
10
10
  static readonly MODES: string[];
11
11
  static readonly NAMES: string[];
12
+ static readonly SUBTITLES: string[];
12
13
  static readonly STORAGE_KEY = "wp-architecture-graph-mode";
13
14
  readonly impact: ImpactJson | null;
14
15
  mode: string;
15
16
  constructor();
16
- /** '' when Impact can be shown, else the one line the drawer and the menu say instead. */
17
+ /** '' when Impact can be shown, else the one line the drawer and the Filter popover say instead. */
17
18
  impactReason(): string;
18
19
  available(mode: string): boolean;
19
20
  name(mode: string): string;
21
+ subtitle(mode: string): string;
20
22
  /** False (and nothing changes) for an unknown or unavailable mode. */
21
23
  set(mode: string): boolean;
22
24
  /** Which impact shade a box gets. */
@@ -24,6 +26,36 @@ declare class WpModeState {
24
26
  private initial;
25
27
  private stored;
26
28
  }
29
+ /**
30
+ * One state of the Filter popover: a change scope plus the selected Runtime and Role chips. The page
31
+ * holds the APPLIED one; the popover edits a copy until "Show N projects".
32
+ */
33
+ declare class WpFilterChoice {
34
+ scope: string;
35
+ readonly runtimes: Set<string>;
36
+ readonly roles: Set<string>;
37
+ copy(): WpFilterChoice;
38
+ /** How many filters are active: the drawer button's badge. */
39
+ count(): number;
40
+ }
41
+ /**
42
+ * The Filter popover's three groups, INTERSECTED: a box stays when it is in the change scope, AND has
43
+ * any selected runtime chip (when one is selected), AND has any selected role chip (likewise).
44
+ */
45
+ declare class WpProjectFilter {
46
+ private readonly model;
47
+ private readonly modes;
48
+ static readonly SCOPE_NAMES: Map<string, string>;
49
+ constructor(model: RenderModelJson, modes: WpModeState);
50
+ /** The node ids a change scope keeps; null for "everything" (and whenever Impact is unavailable). */
51
+ scopeSet(scope: string): Set<string> | null;
52
+ /** How many drawn projects a scope keeps on its own: the count beside each option. */
53
+ scopeCount(scope: string): number;
54
+ /** `base` (everything, or the Hide-unconnected chain) narrowed by `choice`. */
55
+ apply(base: Set<string>, choice: WpFilterChoice): Set<string>;
56
+ /** An empty chip group matches every box; otherwise the box must carry one of the chips. */
57
+ private anyOf;
58
+ }
27
59
  /** Architecture interactions bind to each new SVG; logical Lock/Filter state belongs to the page. */
28
60
  declare class GraphHighlighter {
29
61
  private readonly svg;
@@ -39,51 +71,96 @@ declare class GraphHighlighter {
39
71
  private wireHover;
40
72
  private wireMenu;
41
73
  }
74
+ /**
75
+ * The Filter popover: edits a COPY of the page's applied filter, shows what each choice would keep,
76
+ * and hands the copy to the page on "Show N projects". Closing it any other way discards the edit.
77
+ */
78
+ declare class GraphFilterPopover {
79
+ private readonly page;
80
+ private draft;
81
+ constructor(page: GraphPage);
82
+ wire(): void;
83
+ isOpen(): boolean;
84
+ open(): void;
85
+ close(): void;
86
+ /** Repaint the controls from the draft: checked scope, pressed chips, counts and the footer. */
87
+ private paint;
88
+ private byId;
89
+ }
42
90
  /**
43
91
  * The drawer and its floating panels: every control outside the graph itself. Holds no graph state —
44
92
  * each control calls back into the page, and `sync()` repaints the controls from the page's state.
45
93
  */
46
94
  declare class GraphDrawer {
47
95
  private readonly page;
96
+ readonly popover: GraphFilterPopover;
48
97
  constructor(page: GraphPage);
49
98
  wire(): void;
99
+ /** "Color by": the pill trigger opens the menu of three; picking one switches and closes it. */
100
+ private wireModeMenu;
101
+ private closeModeMenu;
50
102
  /** Type-to-search lock: an exact project id (or a unique short name) locks, empty or Esc unlocks. */
51
103
  private wireLock;
52
104
  /**
53
105
  * Page keys, in the CAPTURE phase so the open/closed state of the node menu is read before the
54
- * menu's own Escape handler closes it: Esc with a menu open only closes the menu.
106
+ * menu's own Escape handler closes it: Esc with a menu open only closes the menu. Esc closes an
107
+ * open Color-by menu or Filter popover first, too.
55
108
  */
56
109
  private onKey;
57
110
  /** `button` shows/hides `target`; for the drawer itself it toggles the collapsed class instead. */
58
111
  private toggles;
59
112
  /** Repaint every control from the page's state. */
60
- sync(modes: WpModeState, locked: string | null, filtering: boolean): void;
113
+ sync(modes: WpModeState, locked: string | null, filtering: boolean, filter: WpFilterChoice): void;
61
114
  private syncFilterToggle;
115
+ /** The drawer button's count badge, and one removable pill per active filter in the top bar. */
116
+ private syncFilter;
117
+ /** A pill, built with text nodes so no value is parsed as HTML. */
118
+ private pill;
62
119
  private impactNote;
120
+ /**
121
+ * When files changed but no project owns one, everything nx affected is affected by a
122
+ * workspace-global input (a lockfile, the workspace manifest). That is correct for CI, and
123
+ * baffling on a graph, so the note names the cause.
124
+ */
125
+ private globalCause;
63
126
  /** "Mode: <b>Runtime</b> · Locked: <b>x</b>", built with text nodes so no name is parsed as HTML. */
64
127
  private crumb;
128
+ private text;
65
129
  private lockInput;
66
130
  private byId;
67
131
  }
68
132
  declare class GraphPage extends WpFilterPage {
69
133
  private highlighter;
70
134
  private readonly modes;
135
+ private readonly projects;
136
+ private applied;
71
137
  private readonly drawer;
72
138
  protected captureBinding(_svg: SVGSVGElement | null): () => void;
73
139
  protected wireControls(): void;
74
140
  protected nodeDot(node: RenderNodeJson): string;
75
141
  protected usesFullDot(): boolean;
142
+ protected narrow(retained: Set<string>): Set<string>;
143
+ /** Filtering keeps every box in its own L-row: an emptied level stays as a thin labeled band. */
144
+ protected keepsEmptyLevels(): boolean;
76
145
  protected wireSvg(svg: SVGSVGElement): void;
77
146
  lockSelection(): string | null;
78
147
  hasNode(id: string): boolean;
148
+ projectCount(): number;
79
149
  /** An exact id, else the one id whose short name (after any scope) is `text`; null if none or several. */
80
150
  resolveName(text: string): string | null;
81
151
  setLock(name: string | null): void;
82
152
  setMode(mode: string): void;
83
153
  /** The drawer's "Hide unconnected": filter to the locked box's chain, or turn the filter off. */
84
154
  toggleFilter(): void;
85
- /** The node menu's "Mode ▸" submenu: one radio item per mode, Impact disabled with its reason. */
86
- modeItem(): WpNodeMenuItem;
155
+ appliedFilter(): WpFilterChoice;
156
+ /** '' when the change-scope group can be used, else Impact's reason (the group is disabled). */
157
+ scopeReason(): string;
158
+ scopeCount(scope: string): number;
159
+ /** How many boxes `choice` would show, with the current Hide-unconnected chain. */
160
+ preview(choice: WpFilterChoice): number;
161
+ applyFilter(choice: WpFilterChoice): void;
162
+ /** A top-bar pill's ✕. */
163
+ removeFilter(group: string, value: string): void;
87
164
  private syncControls;
88
165
  private filterCards;
89
166
  }