@webpieces/nx-webpieces-rules 0.4.978 → 0.4.980

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 (32) hide show
  1. package/README.md +32 -21
  2. package/package.json +7 -7
  3. package/src/lib/graph-filter.client.d.ts +25 -11
  4. package/src/lib/graph-filter.client.js +65 -29
  5. package/src/lib/graph-filter.client.js.map +1 -1
  6. package/src/lib/graph-multi-select.client.d.ts +57 -0
  7. package/src/lib/graph-multi-select.client.js +222 -0
  8. package/src/lib/graph-multi-select.client.js.map +1 -0
  9. package/src/lib/graph-node-menu.d.ts +1 -1
  10. package/src/lib/graph-node-menu.js +1 -1
  11. package/src/lib/graph-node-menu.js.map +1 -1
  12. package/src/lib/graph-page-shell.d.ts +23 -2
  13. package/src/lib/graph-page-shell.js +63 -13
  14. package/src/lib/graph-page-shell.js.map +1 -1
  15. package/src/lib/graph-page-styles.d.ts +6 -0
  16. package/src/lib/graph-page-styles.js +38 -10
  17. package/src/lib/graph-page-styles.js.map +1 -1
  18. package/src/lib/graph-render-model.js +2 -2
  19. package/src/lib/graph-render-model.js.map +1 -1
  20. package/src/lib/graph-visualizer.client.d.ts +39 -19
  21. package/src/lib/graph-visualizer.client.js +96 -110
  22. package/src/lib/graph-visualizer.client.js.map +1 -1
  23. package/src/lib/graph-visualizer.d.ts +7 -11
  24. package/src/lib/graph-visualizer.js +14 -32
  25. package/src/lib/graph-visualizer.js.map +1 -1
  26. package/src/lib/runtime-html-page.d.ts +6 -1
  27. package/src/lib/runtime-html-page.js +13 -6
  28. package/src/lib/runtime-html-page.js.map +1 -1
  29. package/src/lib/runtime-visualizer.client.js +3 -1
  30. package/src/lib/runtime-visualizer.client.js.map +1 -1
  31. package/src/lib/runtime-visualizer.js +5 -3
  32. package/src/lib/runtime-visualizer.js.map +1 -1
package/README.md CHANGED
@@ -120,8 +120,9 @@ the goal's derived levels first. Details, comparison policy and the edit workflo
120
120
 
121
121
  `architecture/dependencies.html` opens with the graph filling the viewport and every control
122
122
  in a collapsible charcoal drawer on the left (violet accent): the **Color by** pulldown, the
123
- **Filter** button, a type-to-search **Focus** field (`/` focuses it, `Esc` unlocks), a separate **Lock** switch, a **Hide
124
- unconnected** toggle, the legend (with a pop-out button) and the edge key. Help is the **?**
123
+ **Filter** button, two multi-select pulldowns with search, **Keep connected to** and **Focus**
124
+ (`/` opens Focus's search; `Esc` closes an open pulldown, else clears Focus), the legend (with a
125
+ pop-out button) and the edge key. Help is the **?**
125
126
  popover, zoom floats bottom-right, and **Responsibilities** opens a right-hand panel from the
126
127
  drawer footer.
127
128
 
@@ -222,20 +223,30 @@ validator checks.
222
223
 
223
224
  ### Compact graph filtering
224
225
 
225
- Architecture and runtime node menus offer **Filter Unconnected** alongside
226
- Lock/Unlock. Filtering retains the selected node and its separate transitive
227
- incoming and outgoing chains, then reruns Graphviz. Architecture L-number rows keep
228
- their original identities; runtime chains include rendered queues, clocks, external
229
- APIs and systems. The anchor indicator and all surviving node menus offer **Turn off
230
- Filter**, restoring the full layout and independent Lock state. Responsibilities
231
- follow the retained set intersected with architecture Lock. Both viewers’ Lock pins
232
- its chain in the foreground; hover adds a temporary chain without dimming locked
233
- nodes or edges, and leaving restores the Lock chain. The locked anchor keeps a violet outline,
234
- and hover never changes responsibilities. A filtered-out Lock is suspended until
235
- the full graph returns. Hover and menu dismissal
236
- do not clear filtering, and no filter is persisted across reloads. Saved graph facts
226
+ Both viewers' drawers hold two independent multi-select pulldowns (#1385), one shared client
227
+ component (`graph-multi-select.client.ts`): checkbox rows in alphabetical order, a search box that
228
+ matches anywhere in a name (case-insensitive, the match highlighted), ↑/↓ and Enter (Space while
229
+ the query is empty) to toggle with the query kept for the next pick, the picks as removable chips
230
+ on the closed field (`None (off)` when empty), and an `N selected` footer with **Clear all**.
231
+
232
+ The page applies them in order, each working on what the previous stage left:
233
+
234
+ 1. **Filter** (change scope, runtime, role, product).
235
+ 2. **Keep connected to** keeps the UNION of every picked box plus its complete transitive incoming
236
+ and outgoing chains, then reruns Graphviz. Adding a pick only ever grows the graph. Architecture
237
+ L-number rows keep their original identities; runtime chains include rendered queues, clocks,
238
+ external APIs and systems. Node menus offer **Keep connected** / **Stop keeping connected** for
239
+ that one box; the top-bar indicator names the picks and clears them all.
240
+ 3. **Focus** pins the union of every picked box's chains in the foreground and dims the rest,
241
+ without a relayout. Every focused box keeps the violet outline, and hover adds a temporary chain.
242
+ Focus offers only the boxes drawn after stages 1–2; a focused box a later stage hides stays as a
243
+ chip marked "hidden" and relights when drawn again. Node menus offer **Add to Focus** /
244
+ **Remove from Focus**. Responsibilities follow the drawn set narrowed to the focused chains.
245
+
246
+ Clear all in a pulldown turns that stage off; Clear all in Filter leaves both alone. Hover and
247
+ menu dismissal change neither, and nothing is persisted across reloads. Saved graph facts
237
248
  and visibility options remain untouched. See
238
- [dependency graphs](../../../docs/architecture/dependency-graphs.md#filter-unconnected)
249
+ [dependency graphs](../../../docs/architecture/dependency-graphs.md#keep-connected-to-and-focus)
239
250
  for traversal examples and browser verification.
240
251
 
241
252
  The runtime viewer uses the same drawer, staged Filter popover, legend pop-out, help,
@@ -247,7 +258,7 @@ are explicitly unknown. Missing project facts disable Impact, leaving the runtim
247
258
 
248
259
  Filters combine OR within Runtime/Role/Product groups and AND between groups and change scope.
249
260
  Draft changes apply only with **Show N services**; closing cancels them. Clear all resets facets,
250
- leaving Lock and Hide unconnected alone. Counts describe services, with contextual nodes reported
261
+ leaving Keep connected to and Focus alone. Counts describe services, with contextual nodes reported
251
262
  separately. Products preserve saved runtime closure across calls and queues. Attached queues,
252
263
  clocks and external systems remain as context without resurrecting excluded services.
253
264
 
@@ -264,11 +275,11 @@ light grey for only-OFF membership, white for no product, neutral only while eve
264
275
  and the node carries them all. Runtime services use the
265
276
  established nested/striped framework palette and Architecture uses declared roles.
266
277
 
267
- Lock from Focus, the sidebar switch, or a box menu pins separate complete incoming/outgoing
268
- chains; hover temporarily adds its own chain. Lock does not change layout. Hide unconnected
269
- removes unrelated nodes and compacts the graph while preserving original runtime L-identities.
270
- Switching Lock off retains the explicit Focus selection so it can be pinned again; menu Unlock
271
- or clearing Focus clears that selection. A filtered-out lock is suspended and restored when visible.
278
+ Focus (the pulldown or a box menu's **Add to Focus**) pins each picked box's complete
279
+ incoming/outgoing chains; hover temporarily adds its own chain. Focus does not change layout.
280
+ Keep connected to removes every node off its picks' chains and compacts the graph while preserving
281
+ original runtime L-identities. Clear all in either pulldown turns it off. A focused box that is
282
+ not drawn is suspended, shown as a "hidden" chip, and restored when visible.
272
283
 
273
284
  **API details are click-only.** Every service shows plain **Implements (N)** summary text.
274
285
  Click anywhere in the box for its menu, then **Implements** for full contracts/provenance and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.978",
3
+ "version": "0.4.980",
4
4
  "description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -20,12 +20,12 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@webpieces/api-doc-model": "0.4.978",
24
- "@webpieces/core-util": "0.4.978",
25
- "@webpieces/eslint-rules": "0.4.978",
26
- "@webpieces/rules-config": "0.4.978",
23
+ "@webpieces/api-doc-model": "0.4.980",
24
+ "@webpieces/core-util": "0.4.980",
25
+ "@webpieces/eslint-rules": "0.4.980",
26
+ "@webpieces/rules-config": "0.4.980",
27
27
  "madge": "8.0.0",
28
- "@webpieces/rules-sdk": "0.4.978"
28
+ "@webpieces/rules-sdk": "0.4.980"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@nx/devkit": ">=18.0.0"
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "devDependencies": {
44
- "@webpieces/tooling-testkit": "0.4.978"
44
+ "@webpieces/tooling-testkit": "0.4.980"
45
45
  },
46
46
  "types": "./src/index.d.ts",
47
47
  "bin": {
@@ -26,20 +26,26 @@ declare class WpFilteredDot {
26
26
  render(retained: Set<string>, nodeDot?: (node: RenderNodeJson) => string, keepEmptyLevels?: boolean, edgeDot?: (edge: RenderEdgeJson) => string): string;
27
27
  private bandDot;
28
28
  }
29
- /** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */
29
+ /**
30
+ * Page-owned state survives replacements; rendering failures leave the usable SVG intact.
31
+ *
32
+ * Two independent multi-selects (#1385): `keep` ("Keep connected to") decides which boxes are DRAWN —
33
+ * the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the
34
+ * graph — and `focused` ("Focus") decides which drawn boxes are LIT, without a relayout.
35
+ */
30
36
  declare abstract class WpFilterPage {
31
37
  protected model: RenderModelJson;
32
38
  protected chain: WpGraphChain;
33
- protected locked: string | null;
34
- protected anchor: string | null;
39
+ protected readonly focused: Set<string>;
40
+ protected keep: ReadonlySet<string>;
35
41
  protected retained: Set<string>;
36
42
  private viz;
37
43
  constructor(model: RenderModelJson);
38
44
  /**
39
45
  * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —
40
46
  * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of
41
- * the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is
42
- * dropped rather than filtering everything away; a Lock survives and relights when its box exists.
47
+ * the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped
48
+ * rather than filtering everything away; a Focus pick survives and relights when its box exists.
43
49
  */
44
50
  protected useModel(model: RenderModelJson): void;
45
51
  render(): void;
@@ -54,19 +60,27 @@ declare abstract class WpFilterPage {
54
60
  /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */
55
61
  protected usesFullDot(): boolean;
56
62
  /**
57
- * The page's own filter on top of "Hide unconnected": the architecture page intersects the set
63
+ * The page's own filter on top of "Keep connected to": the architecture page intersects the set
58
64
  * with its Filter popover (changes, runtime, role). Everything, by default.
59
65
  */
60
66
  protected narrow(retained: Set<string>): Set<string>;
61
67
  /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */
62
68
  protected keepsEmptyLevels(): boolean;
69
+ /** What "Keep connected to" keeps: the UNION of every pick's complete chains; everything when empty. */
70
+ protected kept(keep: ReadonlySet<string>): Set<string>;
71
+ /** Draw again with the current picks — after a color-mode or filter change. */
72
+ redraw(): void;
63
73
  /**
64
- * Draw again with the current filter — after a color-mode switch. A control outside the graph
65
- * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.
74
+ * Draw with a "Keep connected to" set. A control outside the graph (a drawer button, a pulldown's
75
+ * search box) keeps keyboard focus; the redraw would otherwise move it into the graph.
66
76
  */
67
- redraw(): void;
68
- filterItem(name: string): WpNodeMenuItem;
69
- filter(anchor: string | null): void;
77
+ protected drawKeeping(keep: ReadonlySet<string>): void;
78
+ keepSelection(): ReadonlySet<string>;
79
+ /** A box's menu item: "Keep connected" adds the box to Keep connected to; the other removes it. */
80
+ keepItem(name: string): WpNodeMenuItem;
81
+ toggleKeep(id: string): void;
82
+ clearKeep(): void;
83
+ draw(keep: ReadonlySet<string>): void;
70
84
  private indicator;
71
85
  private error;
72
86
  }
@@ -114,12 +114,18 @@ class WpFilteredDot {
114
114
  return dot;
115
115
  }
116
116
  }
117
- /** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */
117
+ /**
118
+ * Page-owned state survives replacements; rendering failures leave the usable SVG intact.
119
+ *
120
+ * Two independent multi-selects (#1385): `keep` ("Keep connected to") decides which boxes are DRAWN —
121
+ * the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the
122
+ * graph — and `focused` ("Focus") decides which drawn boxes are LIT, without a relayout.
123
+ */
118
124
  class WpFilterPage {
119
125
  model;
120
126
  chain;
121
- locked = null;
122
- anchor = null;
127
+ focused = new Set();
128
+ keep = new Set();
123
129
  retained;
124
130
  viz = null;
125
131
  constructor(model) {
@@ -130,27 +136,27 @@ class WpFilterPage {
130
136
  /**
131
137
  * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —
132
138
  * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of
133
- * the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is
134
- * dropped rather than filtering everything away; a Lock survives and relights when its box exists.
139
+ * the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped
140
+ * rather than filtering everything away; a Focus pick survives and relights when its box exists.
135
141
  */
136
142
  useModel(model) {
137
143
  if (model === this.model)
138
144
  return;
139
145
  this.model = model;
140
146
  this.chain = new WpGraphChain(model);
141
- this.retained = new Set(model.nodes.map((node) => node.id));
142
- if (this.anchor !== null && !model.nodes.some((node) => node.id === this.anchor))
143
- this.anchor = null;
147
+ const ids = new Set(model.nodes.map((node) => node.id));
148
+ this.retained = new Set(ids);
149
+ this.keep = new Set([...this.keep].filter((id) => ids.has(id)));
144
150
  }
145
151
  render() {
146
152
  document
147
153
  .getElementById('wp-filter-off')
148
- ?.addEventListener('click', () => this.filter(null));
154
+ ?.addEventListener('click', () => this.draw(new Set()));
149
155
  this.wireControls();
150
156
  Viz.instance()
151
157
  .then((viz) => {
152
158
  this.viz = viz;
153
- this.filter(null);
159
+ this.draw(new Set());
154
160
  })
155
161
  // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value
156
162
  .catch((err) => this.error(err instanceof Error ? err : new Error(String(err))));
@@ -173,7 +179,7 @@ class WpFilterPage {
173
179
  return true;
174
180
  }
175
181
  /**
176
- * The page's own filter on top of "Hide unconnected": the architecture page intersects the set
182
+ * The page's own filter on top of "Keep connected to": the architecture page intersects the set
177
183
  * with its Filter popover (changes, runtime, role). Everything, by default.
178
184
  */
179
185
  narrow(retained) {
@@ -183,44 +189,74 @@ class WpFilterPage {
183
189
  keepsEmptyLevels() {
184
190
  return false;
185
191
  }
192
+ /** What "Keep connected to" keeps: the UNION of every pick's complete chains; everything when empty. */
193
+ kept(keep) {
194
+ if (keep.size === 0)
195
+ return new Set(this.model.nodes.map((node) => node.id));
196
+ const kept = new Set();
197
+ for (const id of keep) {
198
+ for (const node of this.chain.nodes(id))
199
+ kept.add(node);
200
+ }
201
+ return kept;
202
+ }
203
+ /** Draw again with the current picks — after a color-mode or filter change. */
204
+ redraw() {
205
+ this.drawKeeping(this.keep);
206
+ }
186
207
  /**
187
- * Draw again with the current filter — after a color-mode switch. A control outside the graph
188
- * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.
208
+ * Draw with a "Keep connected to" set. A control outside the graph (a drawer button, a pulldown's
209
+ * search box) keeps keyboard focus; the redraw would otherwise move it into the graph.
189
210
  */
190
- redraw() {
211
+ drawKeeping(keep) {
191
212
  const active = document.activeElement;
192
213
  const host = document.getElementById('graph');
193
214
  const outside = active instanceof HTMLElement && host !== null && !host.contains(active);
194
- this.filter(this.anchor);
215
+ this.draw(keep);
195
216
  if (outside && active.isConnected)
196
217
  active.focus({ preventScroll: true });
197
218
  }
198
- filterItem(name) {
199
- return new WpNodeMenuItem(this.anchor === null ? 'Filter Unconnected' : 'Turn off Filter', () => this.filter(this.anchor === null ? name : null));
219
+ keepSelection() {
220
+ return this.keep;
221
+ }
222
+ /** A box's menu item: "Keep connected" adds the box to Keep connected to; the other removes it. */
223
+ keepItem(name) {
224
+ const kept = this.keep.has(name);
225
+ return new WpNodeMenuItem(kept ? 'Stop keeping connected' : 'Keep connected', () => this.toggleKeep(name));
226
+ }
227
+ toggleKeep(id) {
228
+ const next = new Set(this.keep);
229
+ if (next.has(id))
230
+ next.delete(id);
231
+ else
232
+ next.add(id);
233
+ this.drawKeeping(next);
234
+ }
235
+ clearKeep() {
236
+ if (this.keep.size > 0)
237
+ this.drawKeeping(new Set());
200
238
  }
201
- filter(anchor) {
239
+ draw(keep) {
202
240
  if (this.viz === null)
203
241
  return;
204
242
  const host = document.getElementById('graph');
205
243
  if (host === null)
206
244
  return;
207
- const retained = this.narrow(anchor === null
208
- ? new Set(this.model.nodes.map((node) => node.id))
209
- : this.chain.nodes(anchor));
210
- const previousAnchor = this.anchor;
245
+ const retained = this.narrow(this.kept(keep));
246
+ const previousKeep = this.keep;
211
247
  const previousRetained = this.retained;
212
248
  const previousSvg = host.querySelector('svg');
213
249
  const focusName = WpNodeMenu.focusedName(previousSvg);
214
250
  const rollback = this.captureBinding(previousSvg);
215
251
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure
216
252
  try {
217
- const dot = anchor === null && this.usesFullDot()
253
+ const dot = keep.size === 0 && this.usesFullDot()
218
254
  ? this.model.fullDot
219
255
  : new WpFilteredDot(this.model).render(retained, (node) => this.nodeDot(node), this.keepsEmptyLevels(), (edge) => this.edgeDot(edge));
220
256
  const svg = this.viz.renderSVGElement(dot);
221
257
  this.prepareSvg(svg);
222
258
  WpNodeMenu.close();
223
- this.anchor = anchor;
259
+ this.keep = keep;
224
260
  this.retained = retained;
225
261
  host.replaceChildren(svg);
226
262
  this.wireSvg(svg);
@@ -231,7 +267,7 @@ class WpFilterPage {
231
267
  }
232
268
  catch (err) {
233
269
  //const error = toError(err);
234
- this.anchor = previousAnchor;
270
+ this.keep = previousKeep;
235
271
  this.retained = previousRetained;
236
272
  if (previousSvg !== null)
237
273
  host.replaceChildren(previousSvg);
@@ -247,10 +283,10 @@ class WpFilterPage {
247
283
  const status = document.getElementById('wp-filter-status');
248
284
  const label = document.getElementById('wp-filter-anchor');
249
285
  if (status !== null)
250
- status.hidden = this.anchor === null;
286
+ status.hidden = this.keep.size === 0;
251
287
  if (label !== null)
252
- label.textContent = this.anchor;
253
- if (this.anchor !== null)
288
+ label.textContent = [...this.keep].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).join(', ');
289
+ if (this.keep.size > 0)
254
290
  document.getElementById('wp-filter-off')?.focus();
255
291
  else
256
292
  document.querySelector('#graph g.wp-node-clickable')?.focus();
@@ -263,7 +299,7 @@ class WpFilterPage {
263
299
  el.textContent =
264
300
  err === null
265
301
  ? ''
266
- : `Graph rendering failed: ${err.message}. Try Turn off Filter or reload the page.`;
302
+ : `Graph rendering failed: ${err.message}. Try Clear all in Keep connected to, or reload the page.`;
267
303
  }
268
304
  }
269
305
  //# sourceMappingURL=graph-filter.client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */\nabstract class WpFilterPage {\n protected chain: WpGraphChain;\n protected locked: string | null = null;\n protected anchor: string | null = null;\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n /**\n * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —\n * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of\n * the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is\n * dropped rather than filtering everything away; a Lock survives and relights when its box exists.\n */\n protected useModel(model: RenderModelJson): void {\n if (model === this.model) return;\n this.model = model;\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node: RenderNodeJson): string => node.id));\n if (this.anchor !== null && !model.nodes.some((node: RenderNodeJson): boolean => node.id === this.anchor)) this.anchor = null;\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.filter(null));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.filter(null);\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Hide unconnected\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /**\n * Draw again with the current filter — after a color-mode switch. A control outside the graph\n * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n redraw(): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.filter(this.anchor);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n filterItem(name: string): WpNodeMenuItem {\n return new WpNodeMenuItem(\n this.anchor === null ? 'Filter Unconnected' : 'Turn off Filter',\n () => this.filter(this.anchor === null ? name : null),\n );\n }\n\n filter(anchor: string | null): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(\n anchor === null\n ? new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id))\n : this.chain.nodes(anchor),\n );\n const previousAnchor = this.anchor;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n anchor === null && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.anchor = anchor;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.anchor = previousAnchor;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.anchor === null;\n if (label !== null) label.textContent = this.anchor;\n if (this.anchor !== null) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Turn off Filter or reload the page.`;\n }\n}\n"]}
1
+ {"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/**\n * Page-owned state survives replacements; rendering failures leave the usable SVG intact.\n *\n * Two independent multi-selects (#1385): `keep` (\"Keep connected to\") decides which boxes are DRAWN —\n * the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the\n * graph — and `focused` (\"Focus\") decides which drawn boxes are LIT, without a relayout.\n */\nabstract class WpFilterPage {\n protected chain: WpGraphChain;\n protected readonly focused = new Set<string>();\n protected keep: ReadonlySet<string> = new Set<string>();\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n /**\n * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —\n * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of\n * the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped\n * rather than filtering everything away; a Focus pick survives and relights when its box exists.\n */\n protected useModel(model: RenderModelJson): void {\n if (model === this.model) return;\n this.model = model;\n this.chain = new WpGraphChain(model);\n const ids = new Set(model.nodes.map((node: RenderNodeJson): string => node.id));\n this.retained = new Set(ids);\n this.keep = new Set([...this.keep].filter((id: string): boolean => ids.has(id)));\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.draw(new Set<string>()));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.draw(new Set<string>());\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Keep connected to\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /** What \"Keep connected to\" keeps: the UNION of every pick's complete chains; everything when empty. */\n protected kept(keep: ReadonlySet<string>): Set<string> {\n if (keep.size === 0) return new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id));\n const kept = new Set<string>();\n for (const id of keep) {\n for (const node of this.chain.nodes(id)) kept.add(node);\n }\n return kept;\n }\n\n /** Draw again with the current picks — after a color-mode or filter change. */\n redraw(): void {\n this.drawKeeping(this.keep);\n }\n\n /**\n * Draw with a \"Keep connected to\" set. A control outside the graph (a drawer button, a pulldown's\n * search box) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n protected drawKeeping(keep: ReadonlySet<string>): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.draw(keep);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n keepSelection(): ReadonlySet<string> {\n return this.keep;\n }\n\n /** A box's menu item: \"Keep connected\" adds the box to Keep connected to; the other removes it. */\n keepItem(name: string): WpNodeMenuItem {\n const kept = this.keep.has(name);\n return new WpNodeMenuItem(kept ? 'Stop keeping connected' : 'Keep connected', () => this.toggleKeep(name));\n }\n\n toggleKeep(id: string): void {\n const next = new Set(this.keep);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n this.drawKeeping(next);\n }\n\n clearKeep(): void {\n if (this.keep.size > 0) this.drawKeeping(new Set<string>());\n }\n\n draw(keep: ReadonlySet<string>): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(this.kept(keep));\n const previousKeep = this.keep;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n keep.size === 0 && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.keep = keep;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.keep = previousKeep;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.keep.size === 0;\n if (label !== null) label.textContent = [...this.keep].sort((a: string, b: string): number => a.toLowerCase().localeCompare(b.toLowerCase())).join(', ');\n if (this.keep.size > 0) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Clear all in Keep connected to, or reload the page.`;\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What one multi-select pulldown reads and changes on the page: its picks, which options it may offer
3
+ * right now, and how a toggle or Clear all lands. Behaviour, so an interface.
4
+ */
5
+ interface WpMultiSource {
6
+ selected(): ReadonlySet<string>;
7
+ /** Whether `id` is drawn right now: an unavailable option is not offered, and its chip says "hidden". */
8
+ available(id: string): boolean;
9
+ toggle(id: string): void;
10
+ clear(): void;
11
+ }
12
+ /**
13
+ * One multi-select pulldown with search (#1385) — ONE implementation, two instances: "Keep connected
14
+ * to" and "Focus". Its markup is graph-page-shell.ts `multiSelect()`; this owns the behaviour:
15
+ * - one checkbox row per option, alphabetical (the shell's order);
16
+ * - the search matches ANYWHERE in the name, case-insensitively, and highlights the match;
17
+ * - ↑/↓ move, Enter toggles (Space too while the query is empty), and the query stays after a toggle
18
+ * so several matches can be picked in a row;
19
+ * - the closed field shows the picks as removable chips, or "None (off)";
20
+ * - the footer says "N selected", and Clear all turns the pulldown off.
21
+ * It holds no picks of its own: every change goes to the page, and `sync()` repaints from it.
22
+ */
23
+ declare class WpMultiSelect {
24
+ private readonly key;
25
+ private readonly source;
26
+ private activeId;
27
+ constructor(key: string, source: WpMultiSource);
28
+ wire(): void;
29
+ isOpen(): boolean;
30
+ /** Open with the cursor in an EMPTY search box: the query lasts while it is open, not across opens. */
31
+ open(): void;
32
+ /** Close; `returnFocus` puts the keyboard back on the pulldown's own button. */
33
+ close(returnFocus: boolean): void;
34
+ /**
35
+ * Whether a click happened inside this pulldown: an outside click closes it. Read from the event's
36
+ * PATH, not from the target's ancestors — a pick repaints the row it was clicked on, so by the time
37
+ * the click reaches the document its target (the old `<mark>`) is no longer in the page.
38
+ */
39
+ clicked(ev: Event): boolean;
40
+ /** Repaint the chips, the footer and (while open) the rows from the page's picks. */
41
+ sync(): void;
42
+ private onKey;
43
+ private move;
44
+ /** Show the rows the query and the page allow, highlight the match, and tick the picks. */
45
+ private paint;
46
+ /** The name with its matched part in a `<mark>`, built from text nodes so no name is parsed as HTML. */
47
+ private highlight;
48
+ /** One removable chip per pick, alphabetical; a pick that is not drawn right now says "hidden". */
49
+ private chips;
50
+ private chip;
51
+ private shownRows;
52
+ private rows;
53
+ private idOf;
54
+ private search;
55
+ private pop;
56
+ private byId;
57
+ }