@reqlan/analytical 0.6.3 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/out/analysis/fuzzy-search-analyser.d.ts +17 -0
- package/out/analysis/fuzzy-search-analyser.js +25 -0
- package/out/analysis/fuzzy-search-analyser.js.map +1 -0
- package/out/analysis/fuzzy-search-worker-client.d.ts +39 -0
- package/out/analysis/fuzzy-search-worker-client.js +178 -0
- package/out/analysis/fuzzy-search-worker-client.js.map +1 -0
- package/out/analysis/fuzzy-search-worker.d.ts +32 -0
- package/out/analysis/fuzzy-search-worker.js +81 -0
- package/out/analysis/fuzzy-search-worker.js.map +1 -0
- package/out/analysis/fuzzy-search.d.ts +38 -0
- package/out/analysis/fuzzy-search.js +217 -0
- package/out/analysis/fuzzy-search.js.map +1 -0
- package/out/analysis/git-dates-analyser.js +12 -3
- package/out/analysis/git-dates-analyser.js.map +1 -1
- package/out/analysis-api.d.ts +16 -2
- package/out/analysis-api.js +36 -3
- package/out/analysis-api.js.map +1 -1
- package/out/core/barrel-page.d.ts +46 -0
- package/out/core/barrel-page.js +162 -0
- package/out/core/barrel-page.js.map +1 -0
- package/out/core/filter-specials.d.ts +37 -0
- package/out/core/filter-specials.js +113 -0
- package/out/core/filter-specials.js.map +1 -0
- package/out/core/types.d.ts +27 -1
- package/out/core/types.js +1 -0
- package/out/core/types.js.map +1 -1
- package/out/create-runtime.js +2 -0
- package/out/create-runtime.js.map +1 -1
- package/out/export/build-export-snapshot.d.ts +2 -2
- package/out/export/build-export-snapshot.js +43 -16
- package/out/export/build-export-snapshot.js.map +1 -1
- package/out/export/export-csv.d.ts +4 -0
- package/out/export/export-csv.js +13 -0
- package/out/export/export-csv.js.map +1 -0
- package/out/export/export-html.d.ts +2 -2
- package/out/export/export-html.js +7 -3
- package/out/export/export-html.js.map +1 -1
- package/out/export/export-json.d.ts +6 -0
- package/out/export/export-json.js +28 -0
- package/out/export/export-json.js.map +1 -0
- package/out/export/export-markdown.d.ts +4 -0
- package/out/export/export-markdown.js +23 -0
- package/out/export/export-markdown.js.map +1 -0
- package/out/export/html-export-assets.d.ts +1 -1
- package/out/export/html-export-assets.js +663 -20
- package/out/export/html-export-assets.js.map +1 -1
- package/out/export/html-export-template.js +160 -61
- package/out/export/html-export-template.js.map +1 -1
- package/out/export/html-export-utils.d.ts +33 -3
- package/out/export/html-export-utils.js +121 -13
- package/out/export/html-export-utils.js.map +1 -1
- package/out/export/types.d.ts +20 -1
- package/out/export/write-csv-export.d.ts +5 -0
- package/out/export/write-csv-export.js +132 -0
- package/out/export/write-csv-export.js.map +1 -0
- package/out/export/write-html-export.d.ts +2 -2
- package/out/export/write-html-export.js +61 -29
- package/out/export/write-html-export.js.map +1 -1
- package/out/export/write-json-export.d.ts +3 -0
- package/out/export/write-json-export.js +92 -0
- package/out/export/write-json-export.js.map +1 -0
- package/out/export/write-markdown-export.d.ts +5 -0
- package/out/export/write-markdown-export.js +177 -0
- package/out/export/write-markdown-export.js.map +1 -0
- package/out/graph/physics-core-source.d.ts +5 -0
- package/out/graph/physics-core-source.js +8 -0
- package/out/graph/physics-core-source.js.map +1 -0
- package/out/index-store/idea-extractor.d.ts +6 -1
- package/out/index-store/idea-extractor.js +56 -11
- package/out/index-store/idea-extractor.js.map +1 -1
- package/out/index-store/index-diagnostics-store.js +1 -2
- package/out/index-store/index-diagnostics-store.js.map +1 -1
- package/out/index-store/sqlite-store.d.ts +16 -3
- package/out/index-store/sqlite-store.js +107 -9
- package/out/index-store/sqlite-store.js.map +1 -1
- package/out/index-store/webview-graph-queries.d.ts +13 -2
- package/out/index-store/webview-graph-queries.js +98 -18
- package/out/index-store/webview-graph-queries.js.map +1 -1
- package/out/index-store/webview-table-queries.js +1 -0
- package/out/index-store/webview-table-queries.js.map +1 -1
- package/out/index-store/workspace-index-file.js +22 -1
- package/out/index-store/workspace-index-file.js.map +1 -1
- package/out/index-store/workspace-index.d.ts +2 -1
- package/out/index-store/workspace-index.js +4 -1
- package/out/index-store/workspace-index.js.map +1 -1
- package/out/index.d.ts +17 -2
- package/out/index.js +10 -1
- package/out/index.js.map +1 -1
- package/package.json +7 -3
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
1
|
+
import { PHYSICS_CORE_CLASSIC_SOURCE } from '../graph/physics-core-source.js';
|
|
2
|
+
/**
|
|
3
|
+
* physics-core.js with `export` keywords stripped, inlined at build time by
|
|
4
|
+
* scripts/generate-physics-core-source.mjs. Using a pre-generated string
|
|
5
|
+
* constant rather than readFileSync(import.meta.url) keeps this bundler-safe:
|
|
6
|
+
* esbuild with format cjs + target es2017 empties import.meta.url, and the
|
|
7
|
+
* source file does not exist on disk relative to the bundled main.cjs anyway.
|
|
8
|
+
*/
|
|
3
9
|
function embedPhysicsCoreSource() {
|
|
4
|
-
return
|
|
5
|
-
.replace(/^export\s+/gm, '');
|
|
10
|
+
return PHYSICS_CORE_CLASSIC_SOURCE;
|
|
6
11
|
}
|
|
7
12
|
export const SHARED_STYLES = `
|
|
8
13
|
:root {
|
|
@@ -26,6 +31,10 @@ export const SHARED_STYLES = `
|
|
|
26
31
|
font-family: Inter, system-ui, sans-serif;
|
|
27
32
|
}
|
|
28
33
|
* { box-sizing: border-box; }
|
|
34
|
+
html {
|
|
35
|
+
/* Keep fragment targets below the sticky .topbar (nav + padding ≈ 4.5–5rem). */
|
|
36
|
+
scroll-padding-top: 5.5rem;
|
|
37
|
+
}
|
|
29
38
|
body {
|
|
30
39
|
margin: 0;
|
|
31
40
|
background: var(--bg);
|
|
@@ -127,6 +136,165 @@ a.pill:hover { color: var(--accent-strong); border-color: color-mix(in srgb, var
|
|
|
127
136
|
flex: 1 1 180px;
|
|
128
137
|
min-width: 160px;
|
|
129
138
|
}
|
|
139
|
+
.scd {
|
|
140
|
+
position: relative;
|
|
141
|
+
flex: 1 1 180px;
|
|
142
|
+
min-width: 160px;
|
|
143
|
+
}
|
|
144
|
+
.scd-trigger {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: space-between;
|
|
148
|
+
gap: 0.55rem;
|
|
149
|
+
width: 100%;
|
|
150
|
+
min-height: 2.35rem;
|
|
151
|
+
padding: 0.4rem 0.75rem;
|
|
152
|
+
border: 1px solid var(--line);
|
|
153
|
+
border-radius: 8px;
|
|
154
|
+
background: var(--bg-soft);
|
|
155
|
+
color: var(--fg);
|
|
156
|
+
font: inherit;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
text-align: left;
|
|
159
|
+
}
|
|
160
|
+
.scd.is-open .scd-trigger,
|
|
161
|
+
.scd-trigger:hover {
|
|
162
|
+
border-color: var(--accent-dim);
|
|
163
|
+
}
|
|
164
|
+
.scd.has-selection .scd-trigger {
|
|
165
|
+
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
|
166
|
+
}
|
|
167
|
+
.scd.is-searching .scd-trigger {
|
|
168
|
+
border-color: var(--line);
|
|
169
|
+
}
|
|
170
|
+
.scd.is-loading .scd-trigger {
|
|
171
|
+
opacity: 0.72;
|
|
172
|
+
}
|
|
173
|
+
.scd-trigger-label {
|
|
174
|
+
overflow: hidden;
|
|
175
|
+
text-overflow: ellipsis;
|
|
176
|
+
white-space: nowrap;
|
|
177
|
+
}
|
|
178
|
+
.scd-chevron {
|
|
179
|
+
width: 0;
|
|
180
|
+
height: 0;
|
|
181
|
+
border-left: 4px solid transparent;
|
|
182
|
+
border-right: 4px solid transparent;
|
|
183
|
+
border-top: 5px solid currentColor;
|
|
184
|
+
opacity: 0.7;
|
|
185
|
+
flex: 0 0 auto;
|
|
186
|
+
}
|
|
187
|
+
.scd.is-open .scd-chevron { transform: rotate(180deg); }
|
|
188
|
+
.scd-panel {
|
|
189
|
+
position: absolute;
|
|
190
|
+
z-index: 50;
|
|
191
|
+
top: calc(100% + 0.35rem);
|
|
192
|
+
left: 0;
|
|
193
|
+
right: auto;
|
|
194
|
+
width: max(100%, 15rem);
|
|
195
|
+
max-width: min(20rem, 80vw);
|
|
196
|
+
max-height: 18rem;
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
border: 1px solid var(--line);
|
|
200
|
+
border-radius: 10px;
|
|
201
|
+
background: var(--bg-elev);
|
|
202
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
|
|
203
|
+
overflow: hidden;
|
|
204
|
+
}
|
|
205
|
+
/* Author display:flex overrides the UA [hidden] rule; keep closed panels invisible. */
|
|
206
|
+
.scd-panel[hidden] {
|
|
207
|
+
display: none;
|
|
208
|
+
}
|
|
209
|
+
.scd-search-row {
|
|
210
|
+
display: flex;
|
|
211
|
+
gap: 0.45rem;
|
|
212
|
+
padding: 0.65rem;
|
|
213
|
+
border-bottom: 1px solid var(--line);
|
|
214
|
+
opacity: 0.72;
|
|
215
|
+
}
|
|
216
|
+
.scd-search-row.is-active { opacity: 1; }
|
|
217
|
+
.scd-search {
|
|
218
|
+
flex: 1;
|
|
219
|
+
min-width: 0;
|
|
220
|
+
padding: 0.4rem 0.55rem;
|
|
221
|
+
border: 1px solid transparent;
|
|
222
|
+
border-radius: 6px;
|
|
223
|
+
background: color-mix(in srgb, var(--bg) 88%, transparent);
|
|
224
|
+
color: var(--muted);
|
|
225
|
+
font: inherit;
|
|
226
|
+
}
|
|
227
|
+
.scd.is-searching .scd-search,
|
|
228
|
+
.scd-search:focus {
|
|
229
|
+
border-color: var(--accent-dim);
|
|
230
|
+
background: var(--bg);
|
|
231
|
+
color: var(--fg);
|
|
232
|
+
outline: none;
|
|
233
|
+
}
|
|
234
|
+
.scd-clear {
|
|
235
|
+
border: none;
|
|
236
|
+
background: transparent;
|
|
237
|
+
color: var(--accent-strong);
|
|
238
|
+
font: inherit;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
.scd-list {
|
|
242
|
+
overflow: auto;
|
|
243
|
+
padding: 0.25rem 0 0.55rem;
|
|
244
|
+
}
|
|
245
|
+
.scd-group-label {
|
|
246
|
+
padding: 0.55rem 0.85rem 0.25rem;
|
|
247
|
+
font-size: 0.72rem;
|
|
248
|
+
letter-spacing: 0.06em;
|
|
249
|
+
text-transform: uppercase;
|
|
250
|
+
color: var(--muted);
|
|
251
|
+
}
|
|
252
|
+
.scd-option {
|
|
253
|
+
display: flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 0.55rem;
|
|
256
|
+
padding: 0.45rem 0.85rem;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
color: var(--fg);
|
|
259
|
+
}
|
|
260
|
+
.scd-option:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
|
|
261
|
+
.scd-option input { margin: 0; }
|
|
262
|
+
.scd-option-label {
|
|
263
|
+
flex: 1;
|
|
264
|
+
min-width: 0;
|
|
265
|
+
overflow: hidden;
|
|
266
|
+
text-overflow: ellipsis;
|
|
267
|
+
white-space: nowrap;
|
|
268
|
+
}
|
|
269
|
+
.scd-option-count {
|
|
270
|
+
flex: 0 0 auto;
|
|
271
|
+
font-variant-numeric: tabular-nums;
|
|
272
|
+
font-size: 0.85em;
|
|
273
|
+
color: var(--muted);
|
|
274
|
+
opacity: 0.65;
|
|
275
|
+
}
|
|
276
|
+
.scd-option.is-special .scd-option-label { font-style: italic; }
|
|
277
|
+
.scd-option.is-not-present .scd-option-label { color: var(--faint); }
|
|
278
|
+
.scd-option.is-empty .scd-option-label { color: var(--accent-strong); }
|
|
279
|
+
.scd-option.is-unspecified .scd-option-label { color: var(--rust-muted); }
|
|
280
|
+
.scd-empty {
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0.85rem;
|
|
283
|
+
color: var(--muted);
|
|
284
|
+
font-size: 0.9rem;
|
|
285
|
+
}
|
|
286
|
+
.rollup-list .rollup-special dt {
|
|
287
|
+
font-style: italic;
|
|
288
|
+
}
|
|
289
|
+
.rollup-list .rollup-not-present dt {
|
|
290
|
+
color: var(--faint);
|
|
291
|
+
}
|
|
292
|
+
.rollup-list .rollup-empty dt {
|
|
293
|
+
color: var(--accent-strong);
|
|
294
|
+
}
|
|
295
|
+
.rollup-list .rollup-unspecified dt {
|
|
296
|
+
color: var(--rust-muted);
|
|
297
|
+
}
|
|
130
298
|
.graph-action {
|
|
131
299
|
border: 1px solid var(--line);
|
|
132
300
|
background: var(--bg-soft);
|
|
@@ -239,14 +407,68 @@ body[data-runtime-mode="interactive"] .scroll-window thead .column-filter-row.is
|
|
|
239
407
|
background: var(--bg-raised);
|
|
240
408
|
box-shadow: 0 1px 0 var(--line);
|
|
241
409
|
}
|
|
242
|
-
.entity-list {
|
|
410
|
+
.entity-list {
|
|
411
|
+
display: grid;
|
|
412
|
+
grid-template-columns: minmax(0, 1fr);
|
|
413
|
+
gap: 1.5rem;
|
|
414
|
+
}
|
|
243
415
|
.entity-card {
|
|
244
416
|
border: 1px solid var(--line); border-radius: 10px; padding: 0.9rem 1rem; background: var(--bg);
|
|
245
417
|
color: inherit;
|
|
418
|
+
min-width: 0;
|
|
419
|
+
max-width: 100%;
|
|
420
|
+
overflow-wrap: anywhere;
|
|
421
|
+
word-break: break-word;
|
|
246
422
|
}
|
|
247
423
|
.entity-card:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
|
|
248
424
|
.entity-card h3, .entity-card p { margin: 0; }
|
|
249
425
|
.entity-card p + p, .entity-card strong + p, .entity-card h3 + p { margin-top: 0.35rem; }
|
|
426
|
+
.print-card {
|
|
427
|
+
min-width: 0;
|
|
428
|
+
max-width: 100%;
|
|
429
|
+
overflow-wrap: anywhere;
|
|
430
|
+
word-break: break-word;
|
|
431
|
+
scroll-margin-top: 5.5rem;
|
|
432
|
+
}
|
|
433
|
+
.print-card > h3 { overflow-wrap: anywhere; }
|
|
434
|
+
.print-attrs {
|
|
435
|
+
display: grid;
|
|
436
|
+
gap: 0.45rem;
|
|
437
|
+
margin: 0.75rem 0 0;
|
|
438
|
+
}
|
|
439
|
+
.print-attrs div {
|
|
440
|
+
display: grid;
|
|
441
|
+
grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
|
|
442
|
+
gap: 0.65rem;
|
|
443
|
+
border-bottom: 1px solid var(--line);
|
|
444
|
+
padding-bottom: 0.35rem;
|
|
445
|
+
}
|
|
446
|
+
.print-attrs dt {
|
|
447
|
+
margin: 0;
|
|
448
|
+
color: var(--muted);
|
|
449
|
+
font-weight: 600;
|
|
450
|
+
overflow-wrap: anywhere;
|
|
451
|
+
}
|
|
452
|
+
.print-attrs dd {
|
|
453
|
+
margin: 0;
|
|
454
|
+
overflow-wrap: anywhere;
|
|
455
|
+
word-break: break-word;
|
|
456
|
+
}
|
|
457
|
+
.print-button {
|
|
458
|
+
appearance: none;
|
|
459
|
+
border: 1px solid var(--line);
|
|
460
|
+
border-radius: 8px;
|
|
461
|
+
background: color-mix(in srgb, var(--accent) 18%, var(--bg-raised));
|
|
462
|
+
color: var(--fg);
|
|
463
|
+
font: inherit;
|
|
464
|
+
font-weight: 600;
|
|
465
|
+
padding: 0.45rem 0.9rem;
|
|
466
|
+
cursor: pointer;
|
|
467
|
+
}
|
|
468
|
+
.print-button:hover {
|
|
469
|
+
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
|
470
|
+
background: color-mix(in srgb, var(--accent) 28%, var(--bg-raised));
|
|
471
|
+
}
|
|
250
472
|
.panel > h2, .table-shell > h2, .graph-shell > h2, .print-card > h3 { margin-top: 0; margin-bottom: 0.75rem; }
|
|
251
473
|
.toolbar h2 { margin: 0; }
|
|
252
474
|
.rollup-list { display: grid; gap: 0.5rem; }
|
|
@@ -254,6 +476,14 @@ body[data-runtime-mode="interactive"] .scroll-window thead .column-filter-row.is
|
|
|
254
476
|
display: flex; justify-content: space-between; gap: 1rem; border-bottom: 1px solid var(--line); padding-bottom: 0.4rem;
|
|
255
477
|
}
|
|
256
478
|
.rollup-list dd { margin: 0; }
|
|
479
|
+
.rollup-list a.rollup-link {
|
|
480
|
+
color: inherit;
|
|
481
|
+
text-decoration: none;
|
|
482
|
+
}
|
|
483
|
+
.rollup-list a.rollup-link:hover {
|
|
484
|
+
color: var(--accent);
|
|
485
|
+
text-decoration: underline;
|
|
486
|
+
}
|
|
257
487
|
.distribution-track {
|
|
258
488
|
height: 0.7rem;
|
|
259
489
|
min-width: 6rem;
|
|
@@ -269,6 +499,37 @@ body[data-runtime-mode="interactive"] .scroll-window thead .column-filter-row.is
|
|
|
269
499
|
background: linear-gradient(90deg, var(--accent-dim), var(--accent-strong));
|
|
270
500
|
min-width: 0;
|
|
271
501
|
}
|
|
502
|
+
.graph-root {
|
|
503
|
+
position: relative;
|
|
504
|
+
min-height: 620px;
|
|
505
|
+
}
|
|
506
|
+
.graph-root.is-booting {
|
|
507
|
+
display: grid;
|
|
508
|
+
place-items: center;
|
|
509
|
+
border: 1px solid var(--line);
|
|
510
|
+
border-radius: 10px;
|
|
511
|
+
background: var(--bg);
|
|
512
|
+
}
|
|
513
|
+
.graph-boot {
|
|
514
|
+
display: grid;
|
|
515
|
+
justify-items: center;
|
|
516
|
+
gap: 0.75rem;
|
|
517
|
+
padding: 1.5rem;
|
|
518
|
+
text-align: center;
|
|
519
|
+
color: var(--muted);
|
|
520
|
+
}
|
|
521
|
+
.graph-boot p { margin: 0; }
|
|
522
|
+
.graph-boot-spinner {
|
|
523
|
+
width: 1.35rem;
|
|
524
|
+
height: 1.35rem;
|
|
525
|
+
border: 2px solid color-mix(in srgb, var(--muted) 35%, transparent);
|
|
526
|
+
border-top-color: var(--accent-strong);
|
|
527
|
+
border-radius: 50%;
|
|
528
|
+
animation: graph-boot-spin 0.7s linear infinite;
|
|
529
|
+
}
|
|
530
|
+
@keyframes graph-boot-spin {
|
|
531
|
+
to { transform: rotate(360deg); }
|
|
532
|
+
}
|
|
272
533
|
.graph-root canvas {
|
|
273
534
|
display: block;
|
|
274
535
|
width: 100%;
|
|
@@ -299,7 +560,8 @@ body[data-runtime-mode="interactive"] .scroll-window thead .column-filter-row.is
|
|
|
299
560
|
color: var(--accent-strong);
|
|
300
561
|
font-weight: 600;
|
|
301
562
|
text-decoration: none;
|
|
302
|
-
white-space:
|
|
563
|
+
white-space: normal;
|
|
564
|
+
overflow-wrap: anywhere;
|
|
303
565
|
}
|
|
304
566
|
a.idea-ref:hover {
|
|
305
567
|
color: var(--fg);
|
|
@@ -329,6 +591,9 @@ pre.code-like {
|
|
|
329
591
|
@media (max-width: 900px) {
|
|
330
592
|
.hero, .topbar-inner { grid-template-columns: 1fr; }
|
|
331
593
|
}
|
|
594
|
+
@media (max-width: 520px) {
|
|
595
|
+
.print-attrs div { grid-template-columns: 1fr; }
|
|
596
|
+
}
|
|
332
597
|
@media print {
|
|
333
598
|
:root { color-scheme: light; }
|
|
334
599
|
body { background: white; color: black; }
|
|
@@ -391,6 +656,267 @@ function lower(value) {
|
|
|
391
656
|
return String(value || '').toLowerCase();
|
|
392
657
|
}
|
|
393
658
|
|
|
659
|
+
function mountSearchableCheckboxDropdown(host, onChange) {
|
|
660
|
+
if (!host) {
|
|
661
|
+
return {
|
|
662
|
+
getSelected: () => [],
|
|
663
|
+
clear: () => undefined,
|
|
664
|
+
setOnChange: () => undefined
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (host.__scdApi) {
|
|
668
|
+
host.__scdApi.setOnChange(onChange);
|
|
669
|
+
return host.__scdApi;
|
|
670
|
+
}
|
|
671
|
+
let options = [];
|
|
672
|
+
try {
|
|
673
|
+
options = JSON.parse(host.dataset.options || '[]');
|
|
674
|
+
} catch {
|
|
675
|
+
options = [];
|
|
676
|
+
}
|
|
677
|
+
const label = host.dataset.label || 'Filter';
|
|
678
|
+
const placeholder = host.dataset.placeholder || 'Search…';
|
|
679
|
+
let selected = new Set();
|
|
680
|
+
let open = false;
|
|
681
|
+
let searching = false;
|
|
682
|
+
let query = '';
|
|
683
|
+
let panel = null;
|
|
684
|
+
let search = null;
|
|
685
|
+
let searchRow = null;
|
|
686
|
+
let clearBtn = null;
|
|
687
|
+
let list = null;
|
|
688
|
+
let listenersBound = false;
|
|
689
|
+
let changeHandler = onChange;
|
|
690
|
+
|
|
691
|
+
let trigger = host.querySelector('.scd-trigger');
|
|
692
|
+
let triggerLabel = host.querySelector('.scd-trigger-label');
|
|
693
|
+
if (!trigger) {
|
|
694
|
+
trigger = document.createElement('button');
|
|
695
|
+
trigger.type = 'button';
|
|
696
|
+
trigger.className = 'scd-trigger';
|
|
697
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
698
|
+
triggerLabel = document.createElement('span');
|
|
699
|
+
triggerLabel.className = 'scd-trigger-label';
|
|
700
|
+
const chevron = document.createElement('span');
|
|
701
|
+
chevron.className = 'scd-chevron';
|
|
702
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
703
|
+
trigger.append(triggerLabel, chevron);
|
|
704
|
+
host.appendChild(trigger);
|
|
705
|
+
}
|
|
706
|
+
if (!trigger.querySelector('.scd-chevron')) {
|
|
707
|
+
const chevron = document.createElement('span');
|
|
708
|
+
chevron.className = 'scd-chevron';
|
|
709
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
710
|
+
trigger.appendChild(chevron);
|
|
711
|
+
}
|
|
712
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
713
|
+
trigger.setAttribute('aria-busy', 'false');
|
|
714
|
+
host.classList.remove('is-loading');
|
|
715
|
+
host.dataset.scdReady = '1';
|
|
716
|
+
|
|
717
|
+
function emitChange() {
|
|
718
|
+
changeHandler?.([...selected]);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function syncTrigger() {
|
|
722
|
+
const values = [...selected];
|
|
723
|
+
host.dataset.selected = JSON.stringify(values);
|
|
724
|
+
host.classList.toggle('has-selection', values.length > 0);
|
|
725
|
+
if (!triggerLabel) return;
|
|
726
|
+
if (values.length === 0) triggerLabel.textContent = label;
|
|
727
|
+
else if (values.length === 1) {
|
|
728
|
+
const match = options.find(option => option.value === values[0]);
|
|
729
|
+
triggerLabel.textContent = match?.label || values[0];
|
|
730
|
+
} else triggerLabel.textContent = \`\${values.length} selected\`;
|
|
731
|
+
if (clearBtn) clearBtn.hidden = values.length === 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function setSearching(next) {
|
|
735
|
+
searching = next;
|
|
736
|
+
host.classList.toggle('is-searching', searching);
|
|
737
|
+
if (searchRow) searchRow.classList.toggle('is-active', searching);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function endSearching() {
|
|
741
|
+
query = '';
|
|
742
|
+
if (search) search.value = '';
|
|
743
|
+
setSearching(false);
|
|
744
|
+
if (search && document.activeElement === search) search.blur();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function ensurePanel() {
|
|
748
|
+
if (panel) return;
|
|
749
|
+
panel = document.createElement('div');
|
|
750
|
+
panel.className = 'scd-panel';
|
|
751
|
+
panel.hidden = true;
|
|
752
|
+
panel.setAttribute('role', 'listbox');
|
|
753
|
+
panel.setAttribute('aria-multiselectable', 'true');
|
|
754
|
+
panel.setAttribute('aria-label', label);
|
|
755
|
+
|
|
756
|
+
searchRow = document.createElement('div');
|
|
757
|
+
searchRow.className = 'scd-search-row';
|
|
758
|
+
search = document.createElement('input');
|
|
759
|
+
search.type = 'search';
|
|
760
|
+
search.className = 'scd-search';
|
|
761
|
+
search.placeholder = placeholder;
|
|
762
|
+
search.setAttribute('aria-label', \`Search \${label}\`);
|
|
763
|
+
clearBtn = document.createElement('button');
|
|
764
|
+
clearBtn.type = 'button';
|
|
765
|
+
clearBtn.className = 'scd-clear';
|
|
766
|
+
clearBtn.textContent = 'Clear';
|
|
767
|
+
clearBtn.hidden = true;
|
|
768
|
+
searchRow.append(search, clearBtn);
|
|
769
|
+
|
|
770
|
+
list = document.createElement('div');
|
|
771
|
+
list.className = 'scd-list';
|
|
772
|
+
panel.append(searchRow, list);
|
|
773
|
+
host.appendChild(panel);
|
|
774
|
+
|
|
775
|
+
search.addEventListener('focus', () => setSearching(true));
|
|
776
|
+
search.addEventListener('blur', () => {
|
|
777
|
+
if (!query.trim()) setSearching(false);
|
|
778
|
+
});
|
|
779
|
+
search.addEventListener('input', () => {
|
|
780
|
+
query = search.value;
|
|
781
|
+
if (query.trim()) setSearching(true);
|
|
782
|
+
renderList();
|
|
783
|
+
});
|
|
784
|
+
clearBtn.addEventListener('click', () => {
|
|
785
|
+
selected.clear();
|
|
786
|
+
syncTrigger();
|
|
787
|
+
renderList();
|
|
788
|
+
emitChange();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function renderList() {
|
|
793
|
+
if (!list) return;
|
|
794
|
+
const needle = lower(query.trim());
|
|
795
|
+
const visible = options.filter(option => !needle || lower(option.label).includes(needle));
|
|
796
|
+
list.innerHTML = '';
|
|
797
|
+
if (visible.length === 0) {
|
|
798
|
+
const empty = document.createElement('p');
|
|
799
|
+
empty.className = 'scd-empty';
|
|
800
|
+
empty.textContent = 'No matches';
|
|
801
|
+
list.appendChild(empty);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
let lastGroup = '';
|
|
805
|
+
for (const option of visible) {
|
|
806
|
+
const group = option.special ? 'Special' : label;
|
|
807
|
+
if (group !== lastGroup) {
|
|
808
|
+
const groupLabel = document.createElement('div');
|
|
809
|
+
groupLabel.className = 'scd-group-label';
|
|
810
|
+
groupLabel.textContent = group;
|
|
811
|
+
list.appendChild(groupLabel);
|
|
812
|
+
lastGroup = group;
|
|
813
|
+
}
|
|
814
|
+
const row = document.createElement('label');
|
|
815
|
+
row.className = 'scd-option';
|
|
816
|
+
if (option.kind) row.classList.add(\`is-\${option.kind}\`);
|
|
817
|
+
if (option.special) row.classList.add('is-special');
|
|
818
|
+
const box = document.createElement('input');
|
|
819
|
+
box.type = 'checkbox';
|
|
820
|
+
box.checked = selected.has(option.value);
|
|
821
|
+
box.addEventListener('change', () => {
|
|
822
|
+
if (box.checked) selected.add(option.value);
|
|
823
|
+
else selected.delete(option.value);
|
|
824
|
+
syncTrigger();
|
|
825
|
+
emitChange();
|
|
826
|
+
});
|
|
827
|
+
const text = document.createElement('span');
|
|
828
|
+
text.className = 'scd-option-label';
|
|
829
|
+
text.textContent = option.label;
|
|
830
|
+
row.append(box, text);
|
|
831
|
+
if (typeof option.count === 'number') {
|
|
832
|
+
const count = document.createElement('span');
|
|
833
|
+
count.className = 'scd-option-count';
|
|
834
|
+
count.textContent = String(option.count);
|
|
835
|
+
row.appendChild(count);
|
|
836
|
+
}
|
|
837
|
+
list.appendChild(row);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function onDocPointerDown(event) {
|
|
842
|
+
if (!open) return;
|
|
843
|
+
if (!host.contains(event.target)) setOpen(false);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function onKeyDown(event) {
|
|
847
|
+
if (event.key !== 'Escape' || !open) return;
|
|
848
|
+
if (searching || query.trim()) {
|
|
849
|
+
endSearching();
|
|
850
|
+
renderList();
|
|
851
|
+
event.stopPropagation();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
setOpen(false);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function onPeerOpen(event) {
|
|
858
|
+
if (!open) return;
|
|
859
|
+
if (event.detail?.source && event.detail.source !== host) setOpen(false);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function bindListeners() {
|
|
863
|
+
if (listenersBound) return;
|
|
864
|
+
document.addEventListener('pointerdown', onDocPointerDown);
|
|
865
|
+
document.addEventListener('keydown', onKeyDown);
|
|
866
|
+
document.addEventListener('reqlan-scd-open', onPeerOpen);
|
|
867
|
+
listenersBound = true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function unbindListeners() {
|
|
871
|
+
if (!listenersBound) return;
|
|
872
|
+
document.removeEventListener('pointerdown', onDocPointerDown);
|
|
873
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
874
|
+
document.removeEventListener('reqlan-scd-open', onPeerOpen);
|
|
875
|
+
listenersBound = false;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function setOpen(next) {
|
|
879
|
+
open = next;
|
|
880
|
+
host.classList.toggle('is-open', open);
|
|
881
|
+
if (open) {
|
|
882
|
+
ensurePanel();
|
|
883
|
+
panel.hidden = false;
|
|
884
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
885
|
+
setSearching(false);
|
|
886
|
+
renderList();
|
|
887
|
+
bindListeners();
|
|
888
|
+
document.dispatchEvent(new CustomEvent('reqlan-scd-open', { detail: { source: host } }));
|
|
889
|
+
} else {
|
|
890
|
+
if (panel) panel.hidden = true;
|
|
891
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
892
|
+
endSearching();
|
|
893
|
+
unbindListeners();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
trigger.addEventListener('click', () => setOpen(!open));
|
|
898
|
+
syncTrigger();
|
|
899
|
+
const api = {
|
|
900
|
+
getSelected: () => [...selected],
|
|
901
|
+
clear: () => {
|
|
902
|
+
selected.clear();
|
|
903
|
+
syncTrigger();
|
|
904
|
+
if (open) renderList();
|
|
905
|
+
},
|
|
906
|
+
setOnChange: (fn) => {
|
|
907
|
+
changeHandler = fn;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
host.__scdApi = api;
|
|
911
|
+
return api;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function enhanceScdPlaceholders(root) {
|
|
915
|
+
for (const host of root.querySelectorAll('[data-graph-status-scd], [data-graph-tag-scd]')) {
|
|
916
|
+
mountSearchableCheckboxDropdown(host, null);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
394
920
|
function scoreDocument(doc, query) {
|
|
395
921
|
const haystack = [
|
|
396
922
|
doc.title,
|
|
@@ -501,6 +1027,7 @@ function ensureTableFilterToggle(table, filterRow, filterInputs, syncToggleState
|
|
|
501
1027
|
function wireTables(root) {
|
|
502
1028
|
const tables = root.querySelectorAll('table');
|
|
503
1029
|
const tableControllers = [];
|
|
1030
|
+
const params = new URLSearchParams(typeof location !== 'undefined' ? location.search : '');
|
|
504
1031
|
|
|
505
1032
|
for (const table of tables) {
|
|
506
1033
|
const thead = table.tHead;
|
|
@@ -550,8 +1077,12 @@ function wireTables(root) {
|
|
|
550
1077
|
|
|
551
1078
|
headers.forEach((th, index) => {
|
|
552
1079
|
const label = String(th.textContent || '').trim() || \`Column \${index + 1}\`;
|
|
1080
|
+
const filterKey = String(th.getAttribute('data-filter-key') || '').trim();
|
|
553
1081
|
th.classList.add('sortable-th');
|
|
554
1082
|
th.replaceChildren();
|
|
1083
|
+
if (filterKey) {
|
|
1084
|
+
th.setAttribute('data-filter-key', filterKey);
|
|
1085
|
+
}
|
|
555
1086
|
const button = document.createElement('button');
|
|
556
1087
|
button.type = 'button';
|
|
557
1088
|
button.className = 'sort-button';
|
|
@@ -597,12 +1128,16 @@ function wireTables(root) {
|
|
|
597
1128
|
filterRow.className = 'column-filter-row';
|
|
598
1129
|
headers.forEach((th, index) => {
|
|
599
1130
|
const label = String(th.querySelector('.sort-button span')?.textContent || \`Column \${index + 1}\`);
|
|
1131
|
+
const filterKey = String(th.getAttribute('data-filter-key') || '').trim().toLowerCase();
|
|
600
1132
|
const cell = document.createElement('th');
|
|
601
1133
|
const input = document.createElement('input');
|
|
602
1134
|
input.type = 'search';
|
|
603
1135
|
input.className = 'column-filter';
|
|
604
1136
|
input.placeholder = 'Filter…';
|
|
605
1137
|
input.dataset.colFilter = String(index);
|
|
1138
|
+
if (filterKey) {
|
|
1139
|
+
input.dataset.filterKey = filterKey;
|
|
1140
|
+
}
|
|
606
1141
|
input.setAttribute('aria-label', \`Filter \${label}\`);
|
|
607
1142
|
input.addEventListener('input', () => controller.applyFilters());
|
|
608
1143
|
filterInputs.push(input);
|
|
@@ -612,6 +1147,32 @@ function wireTables(root) {
|
|
|
612
1147
|
thead.appendChild(filterRow);
|
|
613
1148
|
filterToggle = ensureTableFilterToggle(table, filterRow, filterInputs, syncToggleState);
|
|
614
1149
|
tableControllers.push(controller);
|
|
1150
|
+
|
|
1151
|
+
let seeded = false;
|
|
1152
|
+
for (const input of filterInputs) {
|
|
1153
|
+
const key = String(input.dataset.filterKey || '').trim().toLowerCase();
|
|
1154
|
+
if (!key) continue;
|
|
1155
|
+
const aliases = [key];
|
|
1156
|
+
if (key === 'tags') aliases.push('tag');
|
|
1157
|
+
if (key === 'status') aliases.push('statuses');
|
|
1158
|
+
if (key === 'cluster') aliases.push('label');
|
|
1159
|
+
let value = '';
|
|
1160
|
+
for (const alias of aliases) {
|
|
1161
|
+
if (params.has(alias)) {
|
|
1162
|
+
value = params.get(alias) || '';
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (!value) continue;
|
|
1167
|
+
input.value = value;
|
|
1168
|
+
seeded = true;
|
|
1169
|
+
}
|
|
1170
|
+
if (seeded) {
|
|
1171
|
+
filterRow.classList.add('is-open');
|
|
1172
|
+
filterToggle.classList.add('is-active', 'has-filters');
|
|
1173
|
+
filterToggle.setAttribute('aria-expanded', 'true');
|
|
1174
|
+
controller.applyFilters();
|
|
1175
|
+
}
|
|
615
1176
|
}
|
|
616
1177
|
|
|
617
1178
|
const inputs = root.querySelectorAll('[data-filter-input]');
|
|
@@ -635,6 +1196,10 @@ const GRAPH_NODE_RADIUS = 7;
|
|
|
635
1196
|
const GRAPH_SUBJECT_RADIUS = 10;
|
|
636
1197
|
const GRAPH_LABEL_MAX_WIDTH = 132;
|
|
637
1198
|
const GRAPH_HIT_PAD = 6;
|
|
1199
|
+
// Auto-mode opacity ramp: fully hidden below START, fully opaque at END (brief muted band between).
|
|
1200
|
+
const GRAPH_LABEL_FADE_START = 0.62;
|
|
1201
|
+
const GRAPH_LABEL_FADE_END = 0.82;
|
|
1202
|
+
const GRAPH_LABEL_MODES = ['auto', 'on', 'off'];
|
|
638
1203
|
|
|
639
1204
|
function wireGraph(root) {
|
|
640
1205
|
const graphRoots = root.querySelectorAll('[data-graph-json]');
|
|
@@ -653,21 +1218,39 @@ function wireGraph(root) {
|
|
|
653
1218
|
controls = document.createElement('div');
|
|
654
1219
|
controls.className = 'graph-controls-bar';
|
|
655
1220
|
controls.dataset.graphControls = scriptId;
|
|
656
|
-
controls.innerHTML = '<button type="button" class="graph-action" data-graph-toggle-physics aria-pressed="false">Live physics</button><button type="button" class="graph-action" data-graph-fit>Fit</button><span class="graph-status" data-graph-status-text></span>';
|
|
1221
|
+
controls.innerHTML = '<button type="button" class="graph-action is-active" data-graph-toggle-labels data-label-mode="auto" aria-pressed="mixed">Labels: auto</button><button type="button" class="graph-action" data-graph-toggle-physics aria-pressed="false">Live physics</button><button type="button" class="graph-action" data-graph-fit>Fit</button><span class="graph-status" data-graph-status-text></span>';
|
|
657
1222
|
element.parentElement?.insertBefore(controls, element);
|
|
658
1223
|
}
|
|
659
1224
|
const searchInput = controls.querySelector('[data-graph-search]');
|
|
660
1225
|
const pathInput = controls.querySelector('[data-graph-path]');
|
|
661
|
-
const
|
|
662
|
-
const
|
|
1226
|
+
const statusHost = controls.querySelector('[data-graph-status-scd]');
|
|
1227
|
+
const tagHost = controls.querySelector('[data-graph-tag-scd]');
|
|
663
1228
|
const toggleExternal = controls.querySelector('[data-graph-toggle-external]');
|
|
664
1229
|
const toggleIdeasets = controls.querySelector('[data-graph-toggle-ideasets]');
|
|
1230
|
+
const toggleWildcard = controls.querySelector('[data-graph-toggle-wildcard]');
|
|
1231
|
+
const toggleLabels = controls.querySelector('[data-graph-toggle-labels]');
|
|
665
1232
|
const togglePhysics = controls.querySelector('[data-graph-toggle-physics]');
|
|
666
1233
|
const fitButton = controls.querySelector('[data-graph-fit]');
|
|
667
1234
|
const resetButton = controls.querySelector('[data-graph-reset]');
|
|
668
1235
|
const statusText = controls.querySelector('[data-graph-status-text]');
|
|
1236
|
+
if (statusText) statusText.textContent = 'Initialising graph…';
|
|
1237
|
+
element.classList.add('is-booting');
|
|
669
1238
|
let hideExternal = false;
|
|
670
1239
|
let hideIdeasets = false;
|
|
1240
|
+
let includeWildcardRefs = true;
|
|
1241
|
+
let labelMode = 'auto';
|
|
1242
|
+
let statusSelected = [];
|
|
1243
|
+
let tagSelected = [];
|
|
1244
|
+
const statusFilter = mountSearchableCheckboxDropdown(statusHost, (values) => {
|
|
1245
|
+
statusSelected = values;
|
|
1246
|
+
refresh();
|
|
1247
|
+
});
|
|
1248
|
+
const tagFilter = mountSearchableCheckboxDropdown(tagHost, (values) => {
|
|
1249
|
+
tagSelected = values;
|
|
1250
|
+
refresh();
|
|
1251
|
+
});
|
|
1252
|
+
statusSelected = statusFilter.getSelected();
|
|
1253
|
+
tagSelected = tagFilter.getSelected();
|
|
671
1254
|
let livePhysics = false;
|
|
672
1255
|
let animationFrame = 0;
|
|
673
1256
|
let calmTicks = 0;
|
|
@@ -788,7 +1371,8 @@ function wireGraph(root) {
|
|
|
788
1371
|
minX = Math.min(minX, pos.x - r);
|
|
789
1372
|
minY = Math.min(minY, pos.y - r);
|
|
790
1373
|
maxX = Math.max(maxX, pos.x + r);
|
|
791
|
-
|
|
1374
|
+
// Reserve label space only when labels are forced on; auto hides when fitted/zoomed out.
|
|
1375
|
+
maxY = Math.max(maxY, pos.y + r + (labelMode === 'on' ? 54 : 0));
|
|
792
1376
|
}
|
|
793
1377
|
if (!Number.isFinite(minX)) {
|
|
794
1378
|
return { minX: 0, minY: 0, maxX: 1200, maxY: 640 };
|
|
@@ -796,6 +1380,23 @@ function wireGraph(root) {
|
|
|
796
1380
|
return { minX, minY, maxX, maxY };
|
|
797
1381
|
}
|
|
798
1382
|
|
|
1383
|
+
function syncLabelsButton() {
|
|
1384
|
+
if (!toggleLabels) return;
|
|
1385
|
+
const label = labelMode === 'auto' ? 'Labels: auto' : labelMode === 'on' ? 'Labels: on' : 'Labels: off';
|
|
1386
|
+
toggleLabels.textContent = label;
|
|
1387
|
+
toggleLabels.dataset.labelMode = labelMode;
|
|
1388
|
+
toggleLabels.classList.toggle('is-active', labelMode !== 'off');
|
|
1389
|
+
toggleLabels.setAttribute('aria-pressed', labelMode === 'auto' ? 'mixed' : labelMode === 'on' ? 'true' : 'false');
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function labelOpacityAtScale(scale) {
|
|
1393
|
+
if (labelMode === 'on') return 1;
|
|
1394
|
+
if (labelMode === 'off') return 0;
|
|
1395
|
+
if (scale <= GRAPH_LABEL_FADE_START) return 0;
|
|
1396
|
+
if (scale >= GRAPH_LABEL_FADE_END) return 1;
|
|
1397
|
+
return (scale - GRAPH_LABEL_FADE_START) / (GRAPH_LABEL_FADE_END - GRAPH_LABEL_FADE_START);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
799
1400
|
function fitView(force = false) {
|
|
800
1401
|
if (userViewport && !force) return;
|
|
801
1402
|
const bounds = contentBounds();
|
|
@@ -892,6 +1493,7 @@ function wireGraph(root) {
|
|
|
892
1493
|
ctx.fillRect(0, 0, cssWidth, cssHeight);
|
|
893
1494
|
|
|
894
1495
|
const { scale, offsetX, offsetY } = viewportTransform();
|
|
1496
|
+
const ambientLabelOpacity = labelOpacityAtScale(scale);
|
|
895
1497
|
ctx.save();
|
|
896
1498
|
ctx.translate(offsetX, offsetY);
|
|
897
1499
|
ctx.scale(scale, scale);
|
|
@@ -904,11 +1506,14 @@ function wireGraph(root) {
|
|
|
904
1506
|
const source = positions.get(edge.sourceId);
|
|
905
1507
|
const target = positions.get(edge.targetId);
|
|
906
1508
|
if (!source || !target) continue;
|
|
1509
|
+
const isWildcard = edge.kind === 'wildcard_reference';
|
|
1510
|
+
ctx.setLineDash(isWildcard ? [2 / scale, 3.5 / scale] : []);
|
|
907
1511
|
ctx.beginPath();
|
|
908
1512
|
ctx.moveTo(source.x, source.y);
|
|
909
1513
|
ctx.lineTo(target.x, target.y);
|
|
910
1514
|
ctx.stroke();
|
|
911
1515
|
}
|
|
1516
|
+
ctx.setLineDash([]);
|
|
912
1517
|
ctx.globalAlpha = 1;
|
|
913
1518
|
|
|
914
1519
|
pulsePhase = (pulsePhase + 0.035) % (Math.PI * 2);
|
|
@@ -933,6 +1538,9 @@ function wireGraph(root) {
|
|
|
933
1538
|
ctx.strokeStyle = nodeStroke(node, hover);
|
|
934
1539
|
ctx.stroke();
|
|
935
1540
|
|
|
1541
|
+
// Auto: continuous opacity from zoom; hovered/dragged nodes stay fully opaque.
|
|
1542
|
+
const labelOpacity = hover && labelMode === 'auto' ? 1 : ambientLabelOpacity;
|
|
1543
|
+
if (labelOpacity <= 0) continue;
|
|
936
1544
|
const meta = formatNodeMeta(node);
|
|
937
1545
|
const attrs = formatNodeAttrs(node);
|
|
938
1546
|
const nameFont = isSubject ? '600 11px Inter, system-ui, sans-serif' : '600 10px Inter, system-ui, sans-serif';
|
|
@@ -941,6 +1549,8 @@ function wireGraph(root) {
|
|
|
941
1549
|
const metaLines = wrapLines(meta, GRAPH_LABEL_MAX_WIDTH, metaFont);
|
|
942
1550
|
const attrLines = wrapLines(attrs, GRAPH_LABEL_MAX_WIDTH, metaFont);
|
|
943
1551
|
let ty = pos.y + r + 12;
|
|
1552
|
+
ctx.save();
|
|
1553
|
+
ctx.globalAlpha = labelOpacity;
|
|
944
1554
|
ctx.textAlign = 'center';
|
|
945
1555
|
ctx.textBaseline = 'top';
|
|
946
1556
|
ctx.fillStyle = isSubject ? '#0bbefb' : '#ebe4de';
|
|
@@ -963,6 +1573,7 @@ function wireGraph(root) {
|
|
|
963
1573
|
ctx.fillText(line, pos.x, ty);
|
|
964
1574
|
ty += 11;
|
|
965
1575
|
}
|
|
1576
|
+
ctx.restore();
|
|
966
1577
|
}
|
|
967
1578
|
ctx.restore();
|
|
968
1579
|
}
|
|
@@ -1055,6 +1666,7 @@ function wireGraph(root) {
|
|
|
1055
1666
|
canvas.setAttribute('aria-label', \`Requirement graph with \${filteredNodes.length} nodes\`);
|
|
1056
1667
|
ctx = canvas.getContext('2d');
|
|
1057
1668
|
element.innerHTML = '';
|
|
1669
|
+
element.classList.remove('is-booting');
|
|
1058
1670
|
element.appendChild(canvas);
|
|
1059
1671
|
resizeCanvas();
|
|
1060
1672
|
wireViewport(canvas);
|
|
@@ -1189,30 +1801,39 @@ function wireGraph(root) {
|
|
|
1189
1801
|
}
|
|
1190
1802
|
|
|
1191
1803
|
function matches(node) {
|
|
1804
|
+
const FILTER_NOT_PRESENT = '__not_present__';
|
|
1192
1805
|
const query = lower(searchInput?.value?.trim());
|
|
1193
1806
|
const pathQuery = lower(pathInput?.value?.trim());
|
|
1194
|
-
const statusQuery = lower(statusSelect?.value?.trim());
|
|
1195
|
-
const tagQuery = lower(tagSelect?.value?.trim());
|
|
1196
1807
|
if (hideExternal && node.isExternal) return false;
|
|
1197
1808
|
if (hideIdeasets && node.kind === 'ideaset') return false;
|
|
1198
1809
|
const haystack = [node.name, node.kind, node.fileUri, node.status || '', ...(node.tags || [])].map(lower).join(' ');
|
|
1199
1810
|
if (query && !haystack.includes(query)) return false;
|
|
1200
1811
|
if (pathQuery && !lower(node.fileUri).includes(pathQuery)) return false;
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
1812
|
+
if (statusSelected.length) {
|
|
1813
|
+
const key = node.statusKey || (String(node.status || '').trim() ? String(node.status).trim() : FILTER_NOT_PRESENT);
|
|
1814
|
+
if (!statusSelected.includes(key)) return false;
|
|
1815
|
+
}
|
|
1816
|
+
if (tagSelected.length) {
|
|
1817
|
+
const keys = Array.isArray(node.tagsKeys) && node.tagsKeys.length
|
|
1818
|
+
? node.tagsKeys
|
|
1819
|
+
: (Array.isArray(node.tags) && node.tags.length ? node.tags : [FILTER_NOT_PRESENT]);
|
|
1820
|
+
if (!tagSelected.some(value => keys.includes(value))) return false;
|
|
1821
|
+
}
|
|
1203
1822
|
return true;
|
|
1204
1823
|
}
|
|
1205
1824
|
|
|
1206
1825
|
function refresh() {
|
|
1207
1826
|
const visibleNodes = (graph.nodes || []).filter(matches);
|
|
1208
1827
|
const visibleIds = new Set(visibleNodes.map(node => node.id));
|
|
1209
|
-
const visibleEdges = (graph.edges || []).filter(edge =>
|
|
1210
|
-
visibleIds.has(edge.sourceId)
|
|
1211
|
-
|
|
1828
|
+
const visibleEdges = (graph.edges || []).filter(edge => {
|
|
1829
|
+
if (!visibleIds.has(edge.sourceId) || !visibleIds.has(edge.targetId)) return false;
|
|
1830
|
+
if (!includeWildcardRefs && edge.kind === 'wildcard_reference') return false;
|
|
1831
|
+
return true;
|
|
1832
|
+
});
|
|
1212
1833
|
mountGraph(visibleNodes, visibleEdges);
|
|
1213
1834
|
}
|
|
1214
1835
|
|
|
1215
|
-
for (const control of [searchInput, pathInput
|
|
1836
|
+
for (const control of [searchInput, pathInput]) {
|
|
1216
1837
|
control?.addEventListener('input', refresh);
|
|
1217
1838
|
control?.addEventListener('change', refresh);
|
|
1218
1839
|
}
|
|
@@ -1228,6 +1849,18 @@ function wireGraph(root) {
|
|
|
1228
1849
|
toggleIdeasets.textContent = hideIdeasets ? 'Show ideasets' : 'Hide ideasets';
|
|
1229
1850
|
refresh();
|
|
1230
1851
|
});
|
|
1852
|
+
toggleWildcard?.addEventListener('click', () => {
|
|
1853
|
+
includeWildcardRefs = !includeWildcardRefs;
|
|
1854
|
+
toggleWildcard.classList.toggle('is-active', includeWildcardRefs);
|
|
1855
|
+
toggleWildcard.setAttribute('aria-pressed', includeWildcardRefs ? 'true' : 'false');
|
|
1856
|
+
refresh();
|
|
1857
|
+
});
|
|
1858
|
+
toggleLabels?.addEventListener('click', () => {
|
|
1859
|
+
const index = GRAPH_LABEL_MODES.indexOf(labelMode);
|
|
1860
|
+
labelMode = GRAPH_LABEL_MODES[(index + 1) % GRAPH_LABEL_MODES.length];
|
|
1861
|
+
syncLabelsButton();
|
|
1862
|
+
paint();
|
|
1863
|
+
});
|
|
1231
1864
|
togglePhysics?.addEventListener('click', () => {
|
|
1232
1865
|
livePhysics = !livePhysics;
|
|
1233
1866
|
togglePhysics.classList.toggle('is-active', livePhysics);
|
|
@@ -1246,12 +1879,19 @@ function wireGraph(root) {
|
|
|
1246
1879
|
resetButton?.addEventListener('click', () => {
|
|
1247
1880
|
if (searchInput) searchInput.value = '';
|
|
1248
1881
|
if (pathInput) pathInput.value = '';
|
|
1249
|
-
|
|
1250
|
-
|
|
1882
|
+
statusSelected = [];
|
|
1883
|
+
tagSelected = [];
|
|
1884
|
+
statusFilter.clear();
|
|
1885
|
+
tagFilter.clear();
|
|
1251
1886
|
hideExternal = false;
|
|
1252
1887
|
hideIdeasets = false;
|
|
1888
|
+
includeWildcardRefs = true;
|
|
1889
|
+
labelMode = 'auto';
|
|
1253
1890
|
toggleExternal?.classList.remove('is-active');
|
|
1254
1891
|
toggleIdeasets?.classList.remove('is-active');
|
|
1892
|
+
toggleWildcard?.classList.add('is-active');
|
|
1893
|
+
toggleWildcard?.setAttribute('aria-pressed', 'true');
|
|
1894
|
+
syncLabelsButton();
|
|
1255
1895
|
if (toggleExternal) toggleExternal.textContent = 'Hide external';
|
|
1256
1896
|
if (toggleIdeasets) toggleIdeasets.textContent = 'Hide ideasets';
|
|
1257
1897
|
positions.clear();
|
|
@@ -1260,12 +1900,15 @@ function wireGraph(root) {
|
|
|
1260
1900
|
userViewport = false;
|
|
1261
1901
|
refresh();
|
|
1262
1902
|
});
|
|
1903
|
+
syncLabelsButton();
|
|
1263
1904
|
refresh();
|
|
1264
1905
|
}
|
|
1265
1906
|
}
|
|
1266
1907
|
|
|
1267
1908
|
function boot() {
|
|
1268
1909
|
const root = document.body;
|
|
1910
|
+
// Interactive Status/Tags triggers before heavy table/graph init.
|
|
1911
|
+
enhanceScdPlaceholders(root);
|
|
1269
1912
|
wireTables(root);
|
|
1270
1913
|
wireGraph(root);
|
|
1271
1914
|
void wireGlobalSearch(root);
|