@reqlan/analytical 0.6.3 → 0.7.0
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 +11 -0
- package/out/analysis-api.d.ts +2 -2
- package/out/analysis-api.js +4 -3
- package/out/analysis-api.js.map +1 -1
- package/out/core/filter-specials.d.ts +35 -0
- package/out/core/filter-specials.js +98 -0
- package/out/core/filter-specials.js.map +1 -0
- package/out/core/types.d.ts +10 -0
- package/out/core/types.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-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/html-export-assets.d.ts +1 -1
- package/out/export/html-export-assets.js +537 -15
- package/out/export/html-export-assets.js.map +1 -1
- package/out/export/html-export-template.js +71 -14
- package/out/export/html-export-template.js.map +1 -1
- package/out/export/html-export-utils.js +15 -5
- package/out/export/html-export-utils.js.map +1 -1
- package/out/export/types.d.ts +19 -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/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/index-diagnostics-store.js +1 -2
- package/out/index-store/index-diagnostics-store.js.map +1 -1
- package/out/index-store/sqlite-store.js +4 -2
- package/out/index-store/sqlite-store.js.map +1 -1
- package/out/index-store/webview-graph-queries.d.ts +8 -2
- package/out/index-store/webview-graph-queries.js +77 -14
- package/out/index-store/webview-graph-queries.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 +3 -1
- package/out/index.js +1 -0
- 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 {
|
|
@@ -127,6 +132,165 @@ a.pill:hover { color: var(--accent-strong); border-color: color-mix(in srgb, var
|
|
|
127
132
|
flex: 1 1 180px;
|
|
128
133
|
min-width: 160px;
|
|
129
134
|
}
|
|
135
|
+
.scd {
|
|
136
|
+
position: relative;
|
|
137
|
+
flex: 1 1 180px;
|
|
138
|
+
min-width: 160px;
|
|
139
|
+
}
|
|
140
|
+
.scd-trigger {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: space-between;
|
|
144
|
+
gap: 0.55rem;
|
|
145
|
+
width: 100%;
|
|
146
|
+
min-height: 2.35rem;
|
|
147
|
+
padding: 0.4rem 0.75rem;
|
|
148
|
+
border: 1px solid var(--line);
|
|
149
|
+
border-radius: 8px;
|
|
150
|
+
background: var(--bg-soft);
|
|
151
|
+
color: var(--fg);
|
|
152
|
+
font: inherit;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
text-align: left;
|
|
155
|
+
}
|
|
156
|
+
.scd.is-open .scd-trigger,
|
|
157
|
+
.scd-trigger:hover {
|
|
158
|
+
border-color: var(--accent-dim);
|
|
159
|
+
}
|
|
160
|
+
.scd.has-selection .scd-trigger {
|
|
161
|
+
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
|
162
|
+
}
|
|
163
|
+
.scd.is-searching .scd-trigger {
|
|
164
|
+
border-color: var(--line);
|
|
165
|
+
}
|
|
166
|
+
.scd.is-loading .scd-trigger {
|
|
167
|
+
opacity: 0.72;
|
|
168
|
+
}
|
|
169
|
+
.scd-trigger-label {
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
text-overflow: ellipsis;
|
|
172
|
+
white-space: nowrap;
|
|
173
|
+
}
|
|
174
|
+
.scd-chevron {
|
|
175
|
+
width: 0;
|
|
176
|
+
height: 0;
|
|
177
|
+
border-left: 4px solid transparent;
|
|
178
|
+
border-right: 4px solid transparent;
|
|
179
|
+
border-top: 5px solid currentColor;
|
|
180
|
+
opacity: 0.7;
|
|
181
|
+
flex: 0 0 auto;
|
|
182
|
+
}
|
|
183
|
+
.scd.is-open .scd-chevron { transform: rotate(180deg); }
|
|
184
|
+
.scd-panel {
|
|
185
|
+
position: absolute;
|
|
186
|
+
z-index: 50;
|
|
187
|
+
top: calc(100% + 0.35rem);
|
|
188
|
+
left: 0;
|
|
189
|
+
right: auto;
|
|
190
|
+
width: max(100%, 15rem);
|
|
191
|
+
max-width: min(20rem, 80vw);
|
|
192
|
+
max-height: 18rem;
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
border: 1px solid var(--line);
|
|
196
|
+
border-radius: 10px;
|
|
197
|
+
background: var(--bg-elev);
|
|
198
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
|
|
199
|
+
overflow: hidden;
|
|
200
|
+
}
|
|
201
|
+
/* Author display:flex overrides the UA [hidden] rule; keep closed panels invisible. */
|
|
202
|
+
.scd-panel[hidden] {
|
|
203
|
+
display: none;
|
|
204
|
+
}
|
|
205
|
+
.scd-search-row {
|
|
206
|
+
display: flex;
|
|
207
|
+
gap: 0.45rem;
|
|
208
|
+
padding: 0.65rem;
|
|
209
|
+
border-bottom: 1px solid var(--line);
|
|
210
|
+
opacity: 0.72;
|
|
211
|
+
}
|
|
212
|
+
.scd-search-row.is-active { opacity: 1; }
|
|
213
|
+
.scd-search {
|
|
214
|
+
flex: 1;
|
|
215
|
+
min-width: 0;
|
|
216
|
+
padding: 0.4rem 0.55rem;
|
|
217
|
+
border: 1px solid transparent;
|
|
218
|
+
border-radius: 6px;
|
|
219
|
+
background: color-mix(in srgb, var(--bg) 88%, transparent);
|
|
220
|
+
color: var(--muted);
|
|
221
|
+
font: inherit;
|
|
222
|
+
}
|
|
223
|
+
.scd.is-searching .scd-search,
|
|
224
|
+
.scd-search:focus {
|
|
225
|
+
border-color: var(--accent-dim);
|
|
226
|
+
background: var(--bg);
|
|
227
|
+
color: var(--fg);
|
|
228
|
+
outline: none;
|
|
229
|
+
}
|
|
230
|
+
.scd-clear {
|
|
231
|
+
border: none;
|
|
232
|
+
background: transparent;
|
|
233
|
+
color: var(--accent-strong);
|
|
234
|
+
font: inherit;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
}
|
|
237
|
+
.scd-list {
|
|
238
|
+
overflow: auto;
|
|
239
|
+
padding: 0.25rem 0 0.55rem;
|
|
240
|
+
}
|
|
241
|
+
.scd-group-label {
|
|
242
|
+
padding: 0.55rem 0.85rem 0.25rem;
|
|
243
|
+
font-size: 0.72rem;
|
|
244
|
+
letter-spacing: 0.06em;
|
|
245
|
+
text-transform: uppercase;
|
|
246
|
+
color: var(--muted);
|
|
247
|
+
}
|
|
248
|
+
.scd-option {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: 0.55rem;
|
|
252
|
+
padding: 0.45rem 0.85rem;
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
color: var(--fg);
|
|
255
|
+
}
|
|
256
|
+
.scd-option:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
|
|
257
|
+
.scd-option input { margin: 0; }
|
|
258
|
+
.scd-option-label {
|
|
259
|
+
flex: 1;
|
|
260
|
+
min-width: 0;
|
|
261
|
+
overflow: hidden;
|
|
262
|
+
text-overflow: ellipsis;
|
|
263
|
+
white-space: nowrap;
|
|
264
|
+
}
|
|
265
|
+
.scd-option-count {
|
|
266
|
+
flex: 0 0 auto;
|
|
267
|
+
font-variant-numeric: tabular-nums;
|
|
268
|
+
font-size: 0.85em;
|
|
269
|
+
color: var(--muted);
|
|
270
|
+
opacity: 0.65;
|
|
271
|
+
}
|
|
272
|
+
.scd-option.is-special .scd-option-label { font-style: italic; }
|
|
273
|
+
.scd-option.is-not-present .scd-option-label { color: var(--faint); }
|
|
274
|
+
.scd-option.is-empty .scd-option-label { color: var(--accent-strong); }
|
|
275
|
+
.scd-option.is-unspecified .scd-option-label { color: var(--rust-muted); }
|
|
276
|
+
.scd-empty {
|
|
277
|
+
margin: 0;
|
|
278
|
+
padding: 0.85rem;
|
|
279
|
+
color: var(--muted);
|
|
280
|
+
font-size: 0.9rem;
|
|
281
|
+
}
|
|
282
|
+
.rollup-list .rollup-special dt {
|
|
283
|
+
font-style: italic;
|
|
284
|
+
}
|
|
285
|
+
.rollup-list .rollup-not-present dt {
|
|
286
|
+
color: var(--faint);
|
|
287
|
+
}
|
|
288
|
+
.rollup-list .rollup-empty dt {
|
|
289
|
+
color: var(--accent-strong);
|
|
290
|
+
}
|
|
291
|
+
.rollup-list .rollup-unspecified dt {
|
|
292
|
+
color: var(--rust-muted);
|
|
293
|
+
}
|
|
130
294
|
.graph-action {
|
|
131
295
|
border: 1px solid var(--line);
|
|
132
296
|
background: var(--bg-soft);
|
|
@@ -269,6 +433,37 @@ body[data-runtime-mode="interactive"] .scroll-window thead .column-filter-row.is
|
|
|
269
433
|
background: linear-gradient(90deg, var(--accent-dim), var(--accent-strong));
|
|
270
434
|
min-width: 0;
|
|
271
435
|
}
|
|
436
|
+
.graph-root {
|
|
437
|
+
position: relative;
|
|
438
|
+
min-height: 620px;
|
|
439
|
+
}
|
|
440
|
+
.graph-root.is-booting {
|
|
441
|
+
display: grid;
|
|
442
|
+
place-items: center;
|
|
443
|
+
border: 1px solid var(--line);
|
|
444
|
+
border-radius: 10px;
|
|
445
|
+
background: var(--bg);
|
|
446
|
+
}
|
|
447
|
+
.graph-boot {
|
|
448
|
+
display: grid;
|
|
449
|
+
justify-items: center;
|
|
450
|
+
gap: 0.75rem;
|
|
451
|
+
padding: 1.5rem;
|
|
452
|
+
text-align: center;
|
|
453
|
+
color: var(--muted);
|
|
454
|
+
}
|
|
455
|
+
.graph-boot p { margin: 0; }
|
|
456
|
+
.graph-boot-spinner {
|
|
457
|
+
width: 1.35rem;
|
|
458
|
+
height: 1.35rem;
|
|
459
|
+
border: 2px solid color-mix(in srgb, var(--muted) 35%, transparent);
|
|
460
|
+
border-top-color: var(--accent-strong);
|
|
461
|
+
border-radius: 50%;
|
|
462
|
+
animation: graph-boot-spin 0.7s linear infinite;
|
|
463
|
+
}
|
|
464
|
+
@keyframes graph-boot-spin {
|
|
465
|
+
to { transform: rotate(360deg); }
|
|
466
|
+
}
|
|
272
467
|
.graph-root canvas {
|
|
273
468
|
display: block;
|
|
274
469
|
width: 100%;
|
|
@@ -391,6 +586,267 @@ function lower(value) {
|
|
|
391
586
|
return String(value || '').toLowerCase();
|
|
392
587
|
}
|
|
393
588
|
|
|
589
|
+
function mountSearchableCheckboxDropdown(host, onChange) {
|
|
590
|
+
if (!host) {
|
|
591
|
+
return {
|
|
592
|
+
getSelected: () => [],
|
|
593
|
+
clear: () => undefined,
|
|
594
|
+
setOnChange: () => undefined
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
if (host.__scdApi) {
|
|
598
|
+
host.__scdApi.setOnChange(onChange);
|
|
599
|
+
return host.__scdApi;
|
|
600
|
+
}
|
|
601
|
+
let options = [];
|
|
602
|
+
try {
|
|
603
|
+
options = JSON.parse(host.dataset.options || '[]');
|
|
604
|
+
} catch {
|
|
605
|
+
options = [];
|
|
606
|
+
}
|
|
607
|
+
const label = host.dataset.label || 'Filter';
|
|
608
|
+
const placeholder = host.dataset.placeholder || 'Search…';
|
|
609
|
+
let selected = new Set();
|
|
610
|
+
let open = false;
|
|
611
|
+
let searching = false;
|
|
612
|
+
let query = '';
|
|
613
|
+
let panel = null;
|
|
614
|
+
let search = null;
|
|
615
|
+
let searchRow = null;
|
|
616
|
+
let clearBtn = null;
|
|
617
|
+
let list = null;
|
|
618
|
+
let listenersBound = false;
|
|
619
|
+
let changeHandler = onChange;
|
|
620
|
+
|
|
621
|
+
let trigger = host.querySelector('.scd-trigger');
|
|
622
|
+
let triggerLabel = host.querySelector('.scd-trigger-label');
|
|
623
|
+
if (!trigger) {
|
|
624
|
+
trigger = document.createElement('button');
|
|
625
|
+
trigger.type = 'button';
|
|
626
|
+
trigger.className = 'scd-trigger';
|
|
627
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
628
|
+
triggerLabel = document.createElement('span');
|
|
629
|
+
triggerLabel.className = 'scd-trigger-label';
|
|
630
|
+
const chevron = document.createElement('span');
|
|
631
|
+
chevron.className = 'scd-chevron';
|
|
632
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
633
|
+
trigger.append(triggerLabel, chevron);
|
|
634
|
+
host.appendChild(trigger);
|
|
635
|
+
}
|
|
636
|
+
if (!trigger.querySelector('.scd-chevron')) {
|
|
637
|
+
const chevron = document.createElement('span');
|
|
638
|
+
chevron.className = 'scd-chevron';
|
|
639
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
640
|
+
trigger.appendChild(chevron);
|
|
641
|
+
}
|
|
642
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
643
|
+
trigger.setAttribute('aria-busy', 'false');
|
|
644
|
+
host.classList.remove('is-loading');
|
|
645
|
+
host.dataset.scdReady = '1';
|
|
646
|
+
|
|
647
|
+
function emitChange() {
|
|
648
|
+
changeHandler?.([...selected]);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function syncTrigger() {
|
|
652
|
+
const values = [...selected];
|
|
653
|
+
host.dataset.selected = JSON.stringify(values);
|
|
654
|
+
host.classList.toggle('has-selection', values.length > 0);
|
|
655
|
+
if (!triggerLabel) return;
|
|
656
|
+
if (values.length === 0) triggerLabel.textContent = label;
|
|
657
|
+
else if (values.length === 1) {
|
|
658
|
+
const match = options.find(option => option.value === values[0]);
|
|
659
|
+
triggerLabel.textContent = match?.label || values[0];
|
|
660
|
+
} else triggerLabel.textContent = \`\${values.length} selected\`;
|
|
661
|
+
if (clearBtn) clearBtn.hidden = values.length === 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function setSearching(next) {
|
|
665
|
+
searching = next;
|
|
666
|
+
host.classList.toggle('is-searching', searching);
|
|
667
|
+
if (searchRow) searchRow.classList.toggle('is-active', searching);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function endSearching() {
|
|
671
|
+
query = '';
|
|
672
|
+
if (search) search.value = '';
|
|
673
|
+
setSearching(false);
|
|
674
|
+
if (search && document.activeElement === search) search.blur();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function ensurePanel() {
|
|
678
|
+
if (panel) return;
|
|
679
|
+
panel = document.createElement('div');
|
|
680
|
+
panel.className = 'scd-panel';
|
|
681
|
+
panel.hidden = true;
|
|
682
|
+
panel.setAttribute('role', 'listbox');
|
|
683
|
+
panel.setAttribute('aria-multiselectable', 'true');
|
|
684
|
+
panel.setAttribute('aria-label', label);
|
|
685
|
+
|
|
686
|
+
searchRow = document.createElement('div');
|
|
687
|
+
searchRow.className = 'scd-search-row';
|
|
688
|
+
search = document.createElement('input');
|
|
689
|
+
search.type = 'search';
|
|
690
|
+
search.className = 'scd-search';
|
|
691
|
+
search.placeholder = placeholder;
|
|
692
|
+
search.setAttribute('aria-label', \`Search \${label}\`);
|
|
693
|
+
clearBtn = document.createElement('button');
|
|
694
|
+
clearBtn.type = 'button';
|
|
695
|
+
clearBtn.className = 'scd-clear';
|
|
696
|
+
clearBtn.textContent = 'Clear';
|
|
697
|
+
clearBtn.hidden = true;
|
|
698
|
+
searchRow.append(search, clearBtn);
|
|
699
|
+
|
|
700
|
+
list = document.createElement('div');
|
|
701
|
+
list.className = 'scd-list';
|
|
702
|
+
panel.append(searchRow, list);
|
|
703
|
+
host.appendChild(panel);
|
|
704
|
+
|
|
705
|
+
search.addEventListener('focus', () => setSearching(true));
|
|
706
|
+
search.addEventListener('blur', () => {
|
|
707
|
+
if (!query.trim()) setSearching(false);
|
|
708
|
+
});
|
|
709
|
+
search.addEventListener('input', () => {
|
|
710
|
+
query = search.value;
|
|
711
|
+
if (query.trim()) setSearching(true);
|
|
712
|
+
renderList();
|
|
713
|
+
});
|
|
714
|
+
clearBtn.addEventListener('click', () => {
|
|
715
|
+
selected.clear();
|
|
716
|
+
syncTrigger();
|
|
717
|
+
renderList();
|
|
718
|
+
emitChange();
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function renderList() {
|
|
723
|
+
if (!list) return;
|
|
724
|
+
const needle = lower(query.trim());
|
|
725
|
+
const visible = options.filter(option => !needle || lower(option.label).includes(needle));
|
|
726
|
+
list.innerHTML = '';
|
|
727
|
+
if (visible.length === 0) {
|
|
728
|
+
const empty = document.createElement('p');
|
|
729
|
+
empty.className = 'scd-empty';
|
|
730
|
+
empty.textContent = 'No matches';
|
|
731
|
+
list.appendChild(empty);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
let lastGroup = '';
|
|
735
|
+
for (const option of visible) {
|
|
736
|
+
const group = option.special ? 'Special' : label;
|
|
737
|
+
if (group !== lastGroup) {
|
|
738
|
+
const groupLabel = document.createElement('div');
|
|
739
|
+
groupLabel.className = 'scd-group-label';
|
|
740
|
+
groupLabel.textContent = group;
|
|
741
|
+
list.appendChild(groupLabel);
|
|
742
|
+
lastGroup = group;
|
|
743
|
+
}
|
|
744
|
+
const row = document.createElement('label');
|
|
745
|
+
row.className = 'scd-option';
|
|
746
|
+
if (option.kind) row.classList.add(\`is-\${option.kind}\`);
|
|
747
|
+
if (option.special) row.classList.add('is-special');
|
|
748
|
+
const box = document.createElement('input');
|
|
749
|
+
box.type = 'checkbox';
|
|
750
|
+
box.checked = selected.has(option.value);
|
|
751
|
+
box.addEventListener('change', () => {
|
|
752
|
+
if (box.checked) selected.add(option.value);
|
|
753
|
+
else selected.delete(option.value);
|
|
754
|
+
syncTrigger();
|
|
755
|
+
emitChange();
|
|
756
|
+
});
|
|
757
|
+
const text = document.createElement('span');
|
|
758
|
+
text.className = 'scd-option-label';
|
|
759
|
+
text.textContent = option.label;
|
|
760
|
+
row.append(box, text);
|
|
761
|
+
if (typeof option.count === 'number') {
|
|
762
|
+
const count = document.createElement('span');
|
|
763
|
+
count.className = 'scd-option-count';
|
|
764
|
+
count.textContent = String(option.count);
|
|
765
|
+
row.appendChild(count);
|
|
766
|
+
}
|
|
767
|
+
list.appendChild(row);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function onDocPointerDown(event) {
|
|
772
|
+
if (!open) return;
|
|
773
|
+
if (!host.contains(event.target)) setOpen(false);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function onKeyDown(event) {
|
|
777
|
+
if (event.key !== 'Escape' || !open) return;
|
|
778
|
+
if (searching || query.trim()) {
|
|
779
|
+
endSearching();
|
|
780
|
+
renderList();
|
|
781
|
+
event.stopPropagation();
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
setOpen(false);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function onPeerOpen(event) {
|
|
788
|
+
if (!open) return;
|
|
789
|
+
if (event.detail?.source && event.detail.source !== host) setOpen(false);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function bindListeners() {
|
|
793
|
+
if (listenersBound) return;
|
|
794
|
+
document.addEventListener('pointerdown', onDocPointerDown);
|
|
795
|
+
document.addEventListener('keydown', onKeyDown);
|
|
796
|
+
document.addEventListener('reqlan-scd-open', onPeerOpen);
|
|
797
|
+
listenersBound = true;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function unbindListeners() {
|
|
801
|
+
if (!listenersBound) return;
|
|
802
|
+
document.removeEventListener('pointerdown', onDocPointerDown);
|
|
803
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
804
|
+
document.removeEventListener('reqlan-scd-open', onPeerOpen);
|
|
805
|
+
listenersBound = false;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function setOpen(next) {
|
|
809
|
+
open = next;
|
|
810
|
+
host.classList.toggle('is-open', open);
|
|
811
|
+
if (open) {
|
|
812
|
+
ensurePanel();
|
|
813
|
+
panel.hidden = false;
|
|
814
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
815
|
+
setSearching(false);
|
|
816
|
+
renderList();
|
|
817
|
+
bindListeners();
|
|
818
|
+
document.dispatchEvent(new CustomEvent('reqlan-scd-open', { detail: { source: host } }));
|
|
819
|
+
} else {
|
|
820
|
+
if (panel) panel.hidden = true;
|
|
821
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
822
|
+
endSearching();
|
|
823
|
+
unbindListeners();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
trigger.addEventListener('click', () => setOpen(!open));
|
|
828
|
+
syncTrigger();
|
|
829
|
+
const api = {
|
|
830
|
+
getSelected: () => [...selected],
|
|
831
|
+
clear: () => {
|
|
832
|
+
selected.clear();
|
|
833
|
+
syncTrigger();
|
|
834
|
+
if (open) renderList();
|
|
835
|
+
},
|
|
836
|
+
setOnChange: (fn) => {
|
|
837
|
+
changeHandler = fn;
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
host.__scdApi = api;
|
|
841
|
+
return api;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function enhanceScdPlaceholders(root) {
|
|
845
|
+
for (const host of root.querySelectorAll('[data-graph-status-scd], [data-graph-tag-scd]')) {
|
|
846
|
+
mountSearchableCheckboxDropdown(host, null);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
394
850
|
function scoreDocument(doc, query) {
|
|
395
851
|
const haystack = [
|
|
396
852
|
doc.title,
|
|
@@ -635,6 +1091,10 @@ const GRAPH_NODE_RADIUS = 7;
|
|
|
635
1091
|
const GRAPH_SUBJECT_RADIUS = 10;
|
|
636
1092
|
const GRAPH_LABEL_MAX_WIDTH = 132;
|
|
637
1093
|
const GRAPH_HIT_PAD = 6;
|
|
1094
|
+
// Auto-mode opacity ramp: fully hidden below START, fully opaque at END (brief muted band between).
|
|
1095
|
+
const GRAPH_LABEL_FADE_START = 0.62;
|
|
1096
|
+
const GRAPH_LABEL_FADE_END = 0.82;
|
|
1097
|
+
const GRAPH_LABEL_MODES = ['auto', 'on', 'off'];
|
|
638
1098
|
|
|
639
1099
|
function wireGraph(root) {
|
|
640
1100
|
const graphRoots = root.querySelectorAll('[data-graph-json]');
|
|
@@ -653,21 +1113,37 @@ function wireGraph(root) {
|
|
|
653
1113
|
controls = document.createElement('div');
|
|
654
1114
|
controls.className = 'graph-controls-bar';
|
|
655
1115
|
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>';
|
|
1116
|
+
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
1117
|
element.parentElement?.insertBefore(controls, element);
|
|
658
1118
|
}
|
|
659
1119
|
const searchInput = controls.querySelector('[data-graph-search]');
|
|
660
1120
|
const pathInput = controls.querySelector('[data-graph-path]');
|
|
661
|
-
const
|
|
662
|
-
const
|
|
1121
|
+
const statusHost = controls.querySelector('[data-graph-status-scd]');
|
|
1122
|
+
const tagHost = controls.querySelector('[data-graph-tag-scd]');
|
|
663
1123
|
const toggleExternal = controls.querySelector('[data-graph-toggle-external]');
|
|
664
1124
|
const toggleIdeasets = controls.querySelector('[data-graph-toggle-ideasets]');
|
|
1125
|
+
const toggleLabels = controls.querySelector('[data-graph-toggle-labels]');
|
|
665
1126
|
const togglePhysics = controls.querySelector('[data-graph-toggle-physics]');
|
|
666
1127
|
const fitButton = controls.querySelector('[data-graph-fit]');
|
|
667
1128
|
const resetButton = controls.querySelector('[data-graph-reset]');
|
|
668
1129
|
const statusText = controls.querySelector('[data-graph-status-text]');
|
|
1130
|
+
if (statusText) statusText.textContent = 'Initialising graph…';
|
|
1131
|
+
element.classList.add('is-booting');
|
|
669
1132
|
let hideExternal = false;
|
|
670
1133
|
let hideIdeasets = false;
|
|
1134
|
+
let labelMode = 'auto';
|
|
1135
|
+
let statusSelected = [];
|
|
1136
|
+
let tagSelected = [];
|
|
1137
|
+
const statusFilter = mountSearchableCheckboxDropdown(statusHost, (values) => {
|
|
1138
|
+
statusSelected = values;
|
|
1139
|
+
refresh();
|
|
1140
|
+
});
|
|
1141
|
+
const tagFilter = mountSearchableCheckboxDropdown(tagHost, (values) => {
|
|
1142
|
+
tagSelected = values;
|
|
1143
|
+
refresh();
|
|
1144
|
+
});
|
|
1145
|
+
statusSelected = statusFilter.getSelected();
|
|
1146
|
+
tagSelected = tagFilter.getSelected();
|
|
671
1147
|
let livePhysics = false;
|
|
672
1148
|
let animationFrame = 0;
|
|
673
1149
|
let calmTicks = 0;
|
|
@@ -788,7 +1264,8 @@ function wireGraph(root) {
|
|
|
788
1264
|
minX = Math.min(minX, pos.x - r);
|
|
789
1265
|
minY = Math.min(minY, pos.y - r);
|
|
790
1266
|
maxX = Math.max(maxX, pos.x + r);
|
|
791
|
-
|
|
1267
|
+
// Reserve label space only when labels are forced on; auto hides when fitted/zoomed out.
|
|
1268
|
+
maxY = Math.max(maxY, pos.y + r + (labelMode === 'on' ? 54 : 0));
|
|
792
1269
|
}
|
|
793
1270
|
if (!Number.isFinite(minX)) {
|
|
794
1271
|
return { minX: 0, minY: 0, maxX: 1200, maxY: 640 };
|
|
@@ -796,6 +1273,23 @@ function wireGraph(root) {
|
|
|
796
1273
|
return { minX, minY, maxX, maxY };
|
|
797
1274
|
}
|
|
798
1275
|
|
|
1276
|
+
function syncLabelsButton() {
|
|
1277
|
+
if (!toggleLabels) return;
|
|
1278
|
+
const label = labelMode === 'auto' ? 'Labels: auto' : labelMode === 'on' ? 'Labels: on' : 'Labels: off';
|
|
1279
|
+
toggleLabels.textContent = label;
|
|
1280
|
+
toggleLabels.dataset.labelMode = labelMode;
|
|
1281
|
+
toggleLabels.classList.toggle('is-active', labelMode !== 'off');
|
|
1282
|
+
toggleLabels.setAttribute('aria-pressed', labelMode === 'auto' ? 'mixed' : labelMode === 'on' ? 'true' : 'false');
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function labelOpacityAtScale(scale) {
|
|
1286
|
+
if (labelMode === 'on') return 1;
|
|
1287
|
+
if (labelMode === 'off') return 0;
|
|
1288
|
+
if (scale <= GRAPH_LABEL_FADE_START) return 0;
|
|
1289
|
+
if (scale >= GRAPH_LABEL_FADE_END) return 1;
|
|
1290
|
+
return (scale - GRAPH_LABEL_FADE_START) / (GRAPH_LABEL_FADE_END - GRAPH_LABEL_FADE_START);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
799
1293
|
function fitView(force = false) {
|
|
800
1294
|
if (userViewport && !force) return;
|
|
801
1295
|
const bounds = contentBounds();
|
|
@@ -892,6 +1386,7 @@ function wireGraph(root) {
|
|
|
892
1386
|
ctx.fillRect(0, 0, cssWidth, cssHeight);
|
|
893
1387
|
|
|
894
1388
|
const { scale, offsetX, offsetY } = viewportTransform();
|
|
1389
|
+
const ambientLabelOpacity = labelOpacityAtScale(scale);
|
|
895
1390
|
ctx.save();
|
|
896
1391
|
ctx.translate(offsetX, offsetY);
|
|
897
1392
|
ctx.scale(scale, scale);
|
|
@@ -933,6 +1428,9 @@ function wireGraph(root) {
|
|
|
933
1428
|
ctx.strokeStyle = nodeStroke(node, hover);
|
|
934
1429
|
ctx.stroke();
|
|
935
1430
|
|
|
1431
|
+
// Auto: continuous opacity from zoom; hovered/dragged nodes stay fully opaque.
|
|
1432
|
+
const labelOpacity = hover && labelMode === 'auto' ? 1 : ambientLabelOpacity;
|
|
1433
|
+
if (labelOpacity <= 0) continue;
|
|
936
1434
|
const meta = formatNodeMeta(node);
|
|
937
1435
|
const attrs = formatNodeAttrs(node);
|
|
938
1436
|
const nameFont = isSubject ? '600 11px Inter, system-ui, sans-serif' : '600 10px Inter, system-ui, sans-serif';
|
|
@@ -941,6 +1439,8 @@ function wireGraph(root) {
|
|
|
941
1439
|
const metaLines = wrapLines(meta, GRAPH_LABEL_MAX_WIDTH, metaFont);
|
|
942
1440
|
const attrLines = wrapLines(attrs, GRAPH_LABEL_MAX_WIDTH, metaFont);
|
|
943
1441
|
let ty = pos.y + r + 12;
|
|
1442
|
+
ctx.save();
|
|
1443
|
+
ctx.globalAlpha = labelOpacity;
|
|
944
1444
|
ctx.textAlign = 'center';
|
|
945
1445
|
ctx.textBaseline = 'top';
|
|
946
1446
|
ctx.fillStyle = isSubject ? '#0bbefb' : '#ebe4de';
|
|
@@ -963,6 +1463,7 @@ function wireGraph(root) {
|
|
|
963
1463
|
ctx.fillText(line, pos.x, ty);
|
|
964
1464
|
ty += 11;
|
|
965
1465
|
}
|
|
1466
|
+
ctx.restore();
|
|
966
1467
|
}
|
|
967
1468
|
ctx.restore();
|
|
968
1469
|
}
|
|
@@ -1055,6 +1556,7 @@ function wireGraph(root) {
|
|
|
1055
1556
|
canvas.setAttribute('aria-label', \`Requirement graph with \${filteredNodes.length} nodes\`);
|
|
1056
1557
|
ctx = canvas.getContext('2d');
|
|
1057
1558
|
element.innerHTML = '';
|
|
1559
|
+
element.classList.remove('is-booting');
|
|
1058
1560
|
element.appendChild(canvas);
|
|
1059
1561
|
resizeCanvas();
|
|
1060
1562
|
wireViewport(canvas);
|
|
@@ -1189,17 +1691,24 @@ function wireGraph(root) {
|
|
|
1189
1691
|
}
|
|
1190
1692
|
|
|
1191
1693
|
function matches(node) {
|
|
1694
|
+
const FILTER_NOT_PRESENT = '__not_present__';
|
|
1192
1695
|
const query = lower(searchInput?.value?.trim());
|
|
1193
1696
|
const pathQuery = lower(pathInput?.value?.trim());
|
|
1194
|
-
const statusQuery = lower(statusSelect?.value?.trim());
|
|
1195
|
-
const tagQuery = lower(tagSelect?.value?.trim());
|
|
1196
1697
|
if (hideExternal && node.isExternal) return false;
|
|
1197
1698
|
if (hideIdeasets && node.kind === 'ideaset') return false;
|
|
1198
1699
|
const haystack = [node.name, node.kind, node.fileUri, node.status || '', ...(node.tags || [])].map(lower).join(' ');
|
|
1199
1700
|
if (query && !haystack.includes(query)) return false;
|
|
1200
1701
|
if (pathQuery && !lower(node.fileUri).includes(pathQuery)) return false;
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
1702
|
+
if (statusSelected.length) {
|
|
1703
|
+
const key = node.statusKey || (String(node.status || '').trim() ? String(node.status).trim() : FILTER_NOT_PRESENT);
|
|
1704
|
+
if (!statusSelected.includes(key)) return false;
|
|
1705
|
+
}
|
|
1706
|
+
if (tagSelected.length) {
|
|
1707
|
+
const keys = Array.isArray(node.tagsKeys) && node.tagsKeys.length
|
|
1708
|
+
? node.tagsKeys
|
|
1709
|
+
: (Array.isArray(node.tags) && node.tags.length ? node.tags : [FILTER_NOT_PRESENT]);
|
|
1710
|
+
if (!tagSelected.some(value => keys.includes(value))) return false;
|
|
1711
|
+
}
|
|
1203
1712
|
return true;
|
|
1204
1713
|
}
|
|
1205
1714
|
|
|
@@ -1212,7 +1721,7 @@ function wireGraph(root) {
|
|
|
1212
1721
|
mountGraph(visibleNodes, visibleEdges);
|
|
1213
1722
|
}
|
|
1214
1723
|
|
|
1215
|
-
for (const control of [searchInput, pathInput
|
|
1724
|
+
for (const control of [searchInput, pathInput]) {
|
|
1216
1725
|
control?.addEventListener('input', refresh);
|
|
1217
1726
|
control?.addEventListener('change', refresh);
|
|
1218
1727
|
}
|
|
@@ -1228,6 +1737,12 @@ function wireGraph(root) {
|
|
|
1228
1737
|
toggleIdeasets.textContent = hideIdeasets ? 'Show ideasets' : 'Hide ideasets';
|
|
1229
1738
|
refresh();
|
|
1230
1739
|
});
|
|
1740
|
+
toggleLabels?.addEventListener('click', () => {
|
|
1741
|
+
const index = GRAPH_LABEL_MODES.indexOf(labelMode);
|
|
1742
|
+
labelMode = GRAPH_LABEL_MODES[(index + 1) % GRAPH_LABEL_MODES.length];
|
|
1743
|
+
syncLabelsButton();
|
|
1744
|
+
paint();
|
|
1745
|
+
});
|
|
1231
1746
|
togglePhysics?.addEventListener('click', () => {
|
|
1232
1747
|
livePhysics = !livePhysics;
|
|
1233
1748
|
togglePhysics.classList.toggle('is-active', livePhysics);
|
|
@@ -1246,12 +1761,16 @@ function wireGraph(root) {
|
|
|
1246
1761
|
resetButton?.addEventListener('click', () => {
|
|
1247
1762
|
if (searchInput) searchInput.value = '';
|
|
1248
1763
|
if (pathInput) pathInput.value = '';
|
|
1249
|
-
|
|
1250
|
-
|
|
1764
|
+
statusSelected = [];
|
|
1765
|
+
tagSelected = [];
|
|
1766
|
+
statusFilter.clear();
|
|
1767
|
+
tagFilter.clear();
|
|
1251
1768
|
hideExternal = false;
|
|
1252
1769
|
hideIdeasets = false;
|
|
1770
|
+
labelMode = 'auto';
|
|
1253
1771
|
toggleExternal?.classList.remove('is-active');
|
|
1254
1772
|
toggleIdeasets?.classList.remove('is-active');
|
|
1773
|
+
syncLabelsButton();
|
|
1255
1774
|
if (toggleExternal) toggleExternal.textContent = 'Hide external';
|
|
1256
1775
|
if (toggleIdeasets) toggleIdeasets.textContent = 'Hide ideasets';
|
|
1257
1776
|
positions.clear();
|
|
@@ -1260,12 +1779,15 @@ function wireGraph(root) {
|
|
|
1260
1779
|
userViewport = false;
|
|
1261
1780
|
refresh();
|
|
1262
1781
|
});
|
|
1782
|
+
syncLabelsButton();
|
|
1263
1783
|
refresh();
|
|
1264
1784
|
}
|
|
1265
1785
|
}
|
|
1266
1786
|
|
|
1267
1787
|
function boot() {
|
|
1268
1788
|
const root = document.body;
|
|
1789
|
+
// Interactive Status/Tags triggers before heavy table/graph init.
|
|
1790
|
+
enhanceScdPlaceholders(root);
|
|
1269
1791
|
wireTables(root);
|
|
1270
1792
|
wireGraph(root);
|
|
1271
1793
|
void wireGlobalSearch(root);
|