@pure-ds/core 0.7.16 → 0.7.18
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/.cursorrules +22 -11
- package/.github/copilot-instructions.md +22 -11
- package/custom-elements.json +38 -0
- package/dist/types/pds.d.ts +0 -1
- package/dist/types/public/assets/js/pds-manager.d.ts +44 -44
- package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts +9 -0
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-runtime.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/bin/pds-mcp-health.js +2 -1
- package/packages/pds-cli/lib/pds-mcp-core.js +95 -3
- package/packages/pds-cli/lib/pds-mcp-eval-cases.json +13 -0
- package/public/assets/js/app.js +4 -4
- package/public/assets/js/pds-manager.js +144 -162
- package/public/assets/js/pds.js +2 -2
- package/public/assets/pds/components/pds-calendar.js +19 -11
- package/public/assets/pds/components/pds-form.js +85 -2
- package/public/assets/pds/components/pds-omnibox.js +9 -6
- package/public/assets/pds/components/pds-treeview.js +321 -24
- package/public/assets/pds/core/pds-manager.js +144 -162
- package/public/assets/pds/core.js +2 -2
- package/public/assets/pds/vscode-custom-data.json +4 -0
- package/readme.md +12 -59
- package/src/js/pds-core/pds-generator.js +7 -7
- package/src/js/pds-core/pds-live.js +1 -13
- package/src/js/pds-core/pds-ontology.js +2 -2
- package/src/js/pds-core/pds-runtime.js +18 -2
- package/src/js/pds.d.ts +0 -1
|
@@ -12,10 +12,12 @@ import { PDS } from "#pds";
|
|
|
12
12
|
* @attr {boolean} required - Requires a selected node for form validity.
|
|
13
13
|
* @attr {boolean} display-only - Renders as browse-only tree without selection.
|
|
14
14
|
* @attr {boolean} expanded-all - Expands all branch nodes on load.
|
|
15
|
+
* @attr {"off"|"checkboxes"|"auto"} multiselect - Selection mode. "off" keeps single-select behavior.
|
|
15
16
|
* @attr {string} src - Optional URL source for tree JSON.
|
|
16
17
|
*
|
|
17
18
|
* @property {object} settings - Data and behavior settings object.
|
|
18
19
|
* @property {object} options - Alias for settings.
|
|
20
|
+
* @property {string[]} values - Selected values when multiselect is enabled.
|
|
19
21
|
* @property {Function} settings.getChildren - Optional async child loader invoked on first expand per node.
|
|
20
22
|
*
|
|
21
23
|
* @fires treeview-load Fired after root data has been loaded and indexed.
|
|
@@ -33,7 +35,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
33
35
|
static formAssociated = true;
|
|
34
36
|
|
|
35
37
|
static get observedAttributes() {
|
|
36
|
-
return ["name", "value", "disabled", "required", "display-only", "expanded-all", "src"];
|
|
38
|
+
return ["name", "value", "disabled", "required", "display-only", "expanded-all", "multiselect", "src"];
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
#root;
|
|
@@ -44,31 +46,58 @@ export class PdsTreeview extends HTMLElement {
|
|
|
44
46
|
#nodeById = new Map();
|
|
45
47
|
#parentById = new Map();
|
|
46
48
|
#selectedId = null;
|
|
49
|
+
#selectedIds = new Set();
|
|
47
50
|
#focusedId = null;
|
|
48
51
|
#defaultValue = "";
|
|
49
52
|
#loadToken = 0;
|
|
50
53
|
#childrenLoadPromises = new Map();
|
|
54
|
+
#multiselectAutoQuery =
|
|
55
|
+
typeof window !== "undefined" && typeof window.matchMedia === "function"
|
|
56
|
+
? window.matchMedia("(hover: none), (pointer: coarse)")
|
|
57
|
+
: null;
|
|
58
|
+
#onMultiselectAutoQueryChange = () => {
|
|
59
|
+
if (this.multiselect === "auto") {
|
|
60
|
+
this.#renderTree();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
51
63
|
|
|
52
64
|
constructor() {
|
|
53
65
|
super();
|
|
54
66
|
this.#root = this.attachShadow({ mode: "open" });
|
|
55
67
|
this.#internals = this.attachInternals();
|
|
68
|
+
if (!this.hasAttribute("tabindex")) {
|
|
69
|
+
this.tabIndex = -1;
|
|
70
|
+
}
|
|
56
71
|
this.#renderShell();
|
|
57
72
|
void this.#adoptStyles();
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
connectedCallback() {
|
|
61
76
|
this.#defaultValue = this.getAttribute("value") || "";
|
|
77
|
+
if (this.#multiselectAutoQuery?.addEventListener) {
|
|
78
|
+
this.#multiselectAutoQuery.addEventListener("change", this.#onMultiselectAutoQueryChange);
|
|
79
|
+
}
|
|
62
80
|
this.#syncAttributes();
|
|
63
81
|
void this.refresh();
|
|
64
82
|
}
|
|
65
83
|
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
if (this.#multiselectAutoQuery?.removeEventListener) {
|
|
86
|
+
this.#multiselectAutoQuery.removeEventListener("change", this.#onMultiselectAutoQueryChange);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
67
91
|
if (oldValue === newValue) return;
|
|
68
92
|
if (name === "src") {
|
|
69
93
|
void this.refresh();
|
|
70
94
|
return;
|
|
71
95
|
}
|
|
96
|
+
if (name === "multiselect") {
|
|
97
|
+
this.#syncAttributes();
|
|
98
|
+
this.#renderTree();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
72
101
|
this.#syncAttributes();
|
|
73
102
|
if (name === "value" && this.#nodes.length) {
|
|
74
103
|
this.selectByValue(newValue ?? "");
|
|
@@ -83,11 +112,26 @@ export class PdsTreeview extends HTMLElement {
|
|
|
83
112
|
}
|
|
84
113
|
|
|
85
114
|
formResetCallback() {
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
if (this.#isMultiSelectEnabled()) {
|
|
116
|
+
this.values = [];
|
|
117
|
+
this.value = "";
|
|
118
|
+
} else {
|
|
119
|
+
this.value = this.#defaultValue;
|
|
120
|
+
this.selectByValue(this.#defaultValue);
|
|
121
|
+
}
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
formStateRestoreCallback(state) {
|
|
125
|
+
if (this.#isMultiSelectEnabled()) {
|
|
126
|
+
if (Array.isArray(state)) {
|
|
127
|
+
this.values = state;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (state instanceof FormData) {
|
|
131
|
+
this.values = this.name ? state.getAll(this.name) : [];
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
91
135
|
this.value = state ?? "";
|
|
92
136
|
this.selectByValue(this.value);
|
|
93
137
|
}
|
|
@@ -128,6 +172,21 @@ export class PdsTreeview extends HTMLElement {
|
|
|
128
172
|
else this.setAttribute("value", next);
|
|
129
173
|
}
|
|
130
174
|
|
|
175
|
+
get values() {
|
|
176
|
+
return this.selectedNodes.map((node) => String(node.value));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
set values(values) {
|
|
180
|
+
if (!Array.isArray(values)) {
|
|
181
|
+
this.#selectedIds.clear();
|
|
182
|
+
this.#selectedId = null;
|
|
183
|
+
this.#syncValue();
|
|
184
|
+
this.#renderTree();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.selectByValues(values);
|
|
188
|
+
}
|
|
189
|
+
|
|
131
190
|
get disabled() {
|
|
132
191
|
return this.hasAttribute("disabled");
|
|
133
192
|
}
|
|
@@ -164,14 +223,40 @@ export class PdsTreeview extends HTMLElement {
|
|
|
164
223
|
else this.removeAttribute("expanded-all");
|
|
165
224
|
}
|
|
166
225
|
|
|
226
|
+
get multiselect() {
|
|
227
|
+
return this.#normalizeMultiselect(this.getAttribute("multiselect"));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
set multiselect(value) {
|
|
231
|
+
const next = this.#normalizeMultiselect(value);
|
|
232
|
+
if (next === "off") {
|
|
233
|
+
this.removeAttribute("multiselect");
|
|
234
|
+
} else {
|
|
235
|
+
this.setAttribute("multiselect", next);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
167
239
|
get selectedNode() {
|
|
168
|
-
|
|
240
|
+
const selectedId = this.#selectedId && this.#selectedIds.has(this.#selectedId)
|
|
241
|
+
? this.#selectedId
|
|
242
|
+
: this.#selectedIds.values().next().value || null;
|
|
243
|
+
return selectedId ? this.#nodeById.get(selectedId) || null : null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
get selectedNodes() {
|
|
247
|
+
return Array.from(this.#selectedIds)
|
|
248
|
+
.map((id) => this.#nodeById.get(id))
|
|
249
|
+
.filter(Boolean);
|
|
169
250
|
}
|
|
170
251
|
|
|
171
252
|
getSelectedNode() {
|
|
172
253
|
return this.selectedNode;
|
|
173
254
|
}
|
|
174
255
|
|
|
256
|
+
getSelectedNodes() {
|
|
257
|
+
return this.selectedNodes;
|
|
258
|
+
}
|
|
259
|
+
|
|
175
260
|
async refresh() {
|
|
176
261
|
const loadToken = ++this.#loadToken;
|
|
177
262
|
const host = this.#root.querySelector(".tv-host");
|
|
@@ -199,6 +284,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
199
284
|
this.#parentById.clear();
|
|
200
285
|
this.#expandedIds.clear();
|
|
201
286
|
this.#selectedId = null;
|
|
287
|
+
this.#selectedIds.clear();
|
|
202
288
|
this.#focusedId = null;
|
|
203
289
|
this.#renderTree();
|
|
204
290
|
if (host) host.dataset.state = "error";
|
|
@@ -226,7 +312,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
226
312
|
|
|
227
313
|
selectById(id) {
|
|
228
314
|
if (!id || !this.#nodeById.has(id)) return false;
|
|
229
|
-
this.#selectNode(id, { user: false, focus: true });
|
|
315
|
+
this.#selectNode(id, { user: false, focus: true, mode: "exclusive" });
|
|
230
316
|
return true;
|
|
231
317
|
}
|
|
232
318
|
|
|
@@ -234,6 +320,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
234
320
|
const normalized = value == null ? "" : String(value);
|
|
235
321
|
if (!normalized) {
|
|
236
322
|
this.#selectedId = null;
|
|
323
|
+
this.#selectedIds.clear();
|
|
237
324
|
this.#syncValue();
|
|
238
325
|
this.#renderTree();
|
|
239
326
|
return true;
|
|
@@ -241,13 +328,53 @@ export class PdsTreeview extends HTMLElement {
|
|
|
241
328
|
for (const [id, node] of this.#nodeById.entries()) {
|
|
242
329
|
if (String(node.value) === normalized) {
|
|
243
330
|
this.#expandAncestors(id);
|
|
244
|
-
this.#selectNode(id, { user: false, focus: false });
|
|
331
|
+
this.#selectNode(id, { user: false, focus: false, mode: "exclusive" });
|
|
245
332
|
return true;
|
|
246
333
|
}
|
|
247
334
|
}
|
|
248
335
|
return false;
|
|
249
336
|
}
|
|
250
337
|
|
|
338
|
+
selectByValues(values) {
|
|
339
|
+
if (!Array.isArray(values)) return false;
|
|
340
|
+
const normalized = values
|
|
341
|
+
.map((value) => String(value))
|
|
342
|
+
.filter((value) => value.length > 0);
|
|
343
|
+
if (!normalized.length) {
|
|
344
|
+
this.#selectedIds.clear();
|
|
345
|
+
this.#selectedId = null;
|
|
346
|
+
this.#syncValue();
|
|
347
|
+
this.#renderTree();
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const selectedIds = [];
|
|
352
|
+
for (const expected of normalized) {
|
|
353
|
+
for (const [id, node] of this.#nodeById.entries()) {
|
|
354
|
+
if (String(node.value) !== expected) continue;
|
|
355
|
+
if (!selectedIds.includes(id)) {
|
|
356
|
+
selectedIds.push(id);
|
|
357
|
+
this.#expandAncestors(id);
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!selectedIds.length) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (this.#isMultiSelectEnabled()) {
|
|
368
|
+
this.#selectedIds = new Set(selectedIds);
|
|
369
|
+
this.#selectedId = selectedIds[0] || null;
|
|
370
|
+
this.#renderTree();
|
|
371
|
+
this.#syncValue();
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return this.selectById(selectedIds[0]);
|
|
376
|
+
}
|
|
377
|
+
|
|
251
378
|
checkValidity() {
|
|
252
379
|
this.#syncValidity();
|
|
253
380
|
return this.#internals.checkValidity();
|
|
@@ -258,6 +385,16 @@ export class PdsTreeview extends HTMLElement {
|
|
|
258
385
|
return this.#internals.reportValidity();
|
|
259
386
|
}
|
|
260
387
|
|
|
388
|
+
focus(options) {
|
|
389
|
+
const targetId =
|
|
390
|
+
this.#focusedId || this.#selectedId || this.#selectedIds.values().next().value || this.#firstVisibleId();
|
|
391
|
+
if (targetId) {
|
|
392
|
+
this.#focusRow(targetId);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
super.focus(options);
|
|
396
|
+
}
|
|
397
|
+
|
|
261
398
|
#renderShell() {
|
|
262
399
|
this.#root.innerHTML = `
|
|
263
400
|
<div class="tv-host" data-state="ready">
|
|
@@ -281,10 +418,19 @@ export class PdsTreeview extends HTMLElement {
|
|
|
281
418
|
const id = row.getAttribute("data-node-id");
|
|
282
419
|
if (!id) return;
|
|
283
420
|
|
|
421
|
+
const checkbox = target.closest(".tv-checkbox-input");
|
|
422
|
+
if (checkbox) {
|
|
423
|
+
if (!this.displayOnly) {
|
|
424
|
+
this.#selectNode(id, { user: true, focus: true, mode: "toggle" });
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
284
429
|
if (event instanceof MouseEvent && event.detail === 2) {
|
|
285
430
|
void this.#toggleNode(id, true);
|
|
286
431
|
if (!this.displayOnly) {
|
|
287
|
-
this.#
|
|
432
|
+
const mode = this.#isMultiSelectEnabled() ? "exclusive" : "exclusive";
|
|
433
|
+
this.#selectNode(id, { user: true, focus: true, mode });
|
|
288
434
|
} else {
|
|
289
435
|
this.#focusRow(id);
|
|
290
436
|
}
|
|
@@ -292,7 +438,16 @@ export class PdsTreeview extends HTMLElement {
|
|
|
292
438
|
}
|
|
293
439
|
|
|
294
440
|
if (!this.displayOnly) {
|
|
295
|
-
|
|
441
|
+
const mouseEvent = event instanceof MouseEvent ? event : null;
|
|
442
|
+
const useToggleMode =
|
|
443
|
+
this.#isCheckboxSelectionMode() ||
|
|
444
|
+
Boolean(mouseEvent?.ctrlKey || mouseEvent?.metaKey);
|
|
445
|
+
const mode = !this.#isMultiSelectEnabled()
|
|
446
|
+
? "exclusive"
|
|
447
|
+
: useToggleMode
|
|
448
|
+
? "toggle"
|
|
449
|
+
: "exclusive";
|
|
450
|
+
this.#selectNode(id, { user: true, focus: true, mode });
|
|
296
451
|
} else {
|
|
297
452
|
this.#focusRow(id);
|
|
298
453
|
}
|
|
@@ -317,6 +472,8 @@ export class PdsTreeview extends HTMLElement {
|
|
|
317
472
|
event.stopPropagation();
|
|
318
473
|
}
|
|
319
474
|
});
|
|
475
|
+
|
|
476
|
+
this.#syncAriaMultiselect();
|
|
320
477
|
}
|
|
321
478
|
|
|
322
479
|
async #adoptStyles() {
|
|
@@ -352,7 +509,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
352
509
|
.tv-row {
|
|
353
510
|
min-height: var(--tv-row-height);
|
|
354
511
|
display: grid;
|
|
355
|
-
grid-template-columns: var(--tv-toggle-size)
|
|
512
|
+
grid-template-columns: var(--tv-toggle-size) 1fr;
|
|
356
513
|
align-items: center;
|
|
357
514
|
gap: var(--spacing-1);
|
|
358
515
|
border-radius: var(--radius-sm);
|
|
@@ -360,6 +517,18 @@ export class PdsTreeview extends HTMLElement {
|
|
|
360
517
|
outline: none;
|
|
361
518
|
}
|
|
362
519
|
|
|
520
|
+
.tv-row.tv-row-has-prefix {
|
|
521
|
+
grid-template-columns: var(--tv-toggle-size) auto 1fr;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.tv-row.tv-row-has-checkbox {
|
|
525
|
+
grid-template-columns: var(--tv-toggle-size) auto 1fr;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.tv-row.tv-row-has-prefix.tv-row-has-checkbox {
|
|
529
|
+
grid-template-columns: var(--tv-toggle-size) auto auto 1fr;
|
|
530
|
+
}
|
|
531
|
+
|
|
363
532
|
.tv-row[aria-selected="true"] {
|
|
364
533
|
background: var(--color-surface-hover);
|
|
365
534
|
color: var(--color-text-primary);
|
|
@@ -397,6 +566,18 @@ export class PdsTreeview extends HTMLElement {
|
|
|
397
566
|
color: var(--color-text-primary);
|
|
398
567
|
}
|
|
399
568
|
|
|
569
|
+
.tv-check {
|
|
570
|
+
display: inline-flex;
|
|
571
|
+
align-items: center;
|
|
572
|
+
justify-content: center;
|
|
573
|
+
width: var(--tv-toggle-size);
|
|
574
|
+
height: var(--tv-toggle-size);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.tv-checkbox-input {
|
|
578
|
+
margin: var(--spacing-0);
|
|
579
|
+
}
|
|
580
|
+
|
|
400
581
|
:host([disabled]) .tv-row,
|
|
401
582
|
:host([disabled]) .tv-toggle,
|
|
402
583
|
.tv-host[data-state="loading"] .tv-row {
|
|
@@ -440,10 +621,34 @@ export class PdsTreeview extends HTMLElement {
|
|
|
440
621
|
}
|
|
441
622
|
`);
|
|
442
623
|
|
|
624
|
+
const existingSheets = Array.isArray(this.#root.adoptedStyleSheets)
|
|
625
|
+
? this.#root.adoptedStyleSheets
|
|
626
|
+
: [];
|
|
627
|
+
this.#root.adoptedStyleSheets = [
|
|
628
|
+
...existingSheets.filter((sheet) => sheet !== componentStyles),
|
|
629
|
+
componentStyles,
|
|
630
|
+
];
|
|
631
|
+
|
|
443
632
|
await PDS.adoptLayers(this.#root, LAYERS, [componentStyles]);
|
|
444
633
|
}
|
|
445
634
|
|
|
446
635
|
#syncAttributes() {
|
|
636
|
+
if (!this.#isMultiSelectEnabled() && this.#selectedIds.size > 1) {
|
|
637
|
+
const keepId =
|
|
638
|
+
(this.#selectedId && this.#selectedIds.has(this.#selectedId) && this.#selectedId) ||
|
|
639
|
+
this.#selectedIds.values().next().value ||
|
|
640
|
+
null;
|
|
641
|
+
this.#selectedIds = keepId ? new Set([keepId]) : new Set();
|
|
642
|
+
this.#selectedId = keepId;
|
|
643
|
+
}
|
|
644
|
+
if (this.#selectedId && !this.#selectedIds.has(this.#selectedId)) {
|
|
645
|
+
this.#selectedIds.add(this.#selectedId);
|
|
646
|
+
}
|
|
647
|
+
if (!this.#selectedId && this.#selectedIds.size > 0) {
|
|
648
|
+
this.#selectedId = this.#selectedIds.values().next().value || null;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
this.#syncAriaMultiselect();
|
|
447
652
|
this.#syncValue();
|
|
448
653
|
this.#syncTabStops();
|
|
449
654
|
}
|
|
@@ -578,9 +783,10 @@ export class PdsTreeview extends HTMLElement {
|
|
|
578
783
|
const tree = this.#root.querySelector(ROOT_SELECTOR);
|
|
579
784
|
if (!tree) return;
|
|
580
785
|
tree.innerHTML = this.#renderNodes(this.#nodes, 1, this.#canRenderLinks());
|
|
786
|
+
this.#syncAriaMultiselect();
|
|
581
787
|
|
|
582
788
|
if (!this.#focusedId) {
|
|
583
|
-
this.#focusedId = this.#selectedId || this.#firstVisibleId() || null;
|
|
789
|
+
this.#focusedId = this.#selectedId || this.#selectedIds.values().next().value || this.#firstVisibleId() || null;
|
|
584
790
|
}
|
|
585
791
|
this.#syncTabStops();
|
|
586
792
|
this.#syncValue();
|
|
@@ -588,17 +794,25 @@ export class PdsTreeview extends HTMLElement {
|
|
|
588
794
|
|
|
589
795
|
#renderNodes(nodes, level, linksEnabled) {
|
|
590
796
|
if (!nodes.length) return "";
|
|
797
|
+
const useCheckboxes = this.#isCheckboxSelectionMode();
|
|
591
798
|
|
|
592
799
|
return nodes
|
|
593
800
|
.map((node) => {
|
|
594
801
|
const expanded = this.#expandedIds.has(node.id);
|
|
595
802
|
const hasChildren = Boolean(node.hasChildren);
|
|
596
|
-
const
|
|
803
|
+
const hasPrefix = Boolean(node.image || node.icon);
|
|
804
|
+
const selected = this.#selectedIds.has(node.id);
|
|
597
805
|
const toggleGlyph = node.loadingChildren ? "…" : expanded ? "−" : "+";
|
|
598
806
|
const toggle = hasChildren
|
|
599
807
|
? `<button type="button" class="tv-toggle icon-only" data-node-id="${this.#escapeAttribute(node.id)}" aria-label="${expanded ? "Collapse" : "Expand"} ${this.#escapeAttribute(node.text)}" ${node.loadingChildren ? "disabled" : ""}>${toggleGlyph}</button>`
|
|
600
808
|
: `<span class="tv-toggle-gap" aria-hidden="true"></span>`;
|
|
809
|
+
const checkbox = useCheckboxes
|
|
810
|
+
? `<span class="tv-check"><input class="tv-checkbox-input" type="checkbox" data-node-id="${this.#escapeAttribute(node.id)}" aria-label="Select ${this.#escapeAttribute(node.text)}" ${selected ? "checked" : ""} ${this.disabled ? "disabled" : ""}></span>`
|
|
811
|
+
: "";
|
|
601
812
|
const prefix = this.#renderPrefix(node);
|
|
813
|
+
const rowClassParts = ["tv-row", hasPrefix ? "tv-row-has-prefix" : "tv-row-no-prefix"];
|
|
814
|
+
if (useCheckboxes) rowClassParts.push("tv-row-has-checkbox");
|
|
815
|
+
const rowClass = rowClassParts.join(" ");
|
|
602
816
|
const label = linksEnabled && node.link
|
|
603
817
|
? `<a class="tv-label tv-label-link" href="${this.#escapeAttribute(node.link)}">${this.#escapeHtml(node.text)}</a>`
|
|
604
818
|
: `<span class="tv-label">${this.#escapeHtml(node.text)}</span>`;
|
|
@@ -610,7 +824,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
610
824
|
return `
|
|
611
825
|
<li class="tv-item" role="none">
|
|
612
826
|
<div
|
|
613
|
-
class="
|
|
827
|
+
class="${rowClass}"
|
|
614
828
|
role="treeitem"
|
|
615
829
|
aria-level="${level}"
|
|
616
830
|
${hasChildren ? `aria-expanded="${expanded ? "true" : "false"}"` : ""}
|
|
@@ -620,6 +834,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
620
834
|
tabindex="-1"
|
|
621
835
|
>
|
|
622
836
|
${toggle}
|
|
837
|
+
${checkbox}
|
|
623
838
|
${prefix}
|
|
624
839
|
${label}
|
|
625
840
|
</div>
|
|
@@ -641,7 +856,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
641
856
|
if (node.icon) {
|
|
642
857
|
return `<span class="tv-prefix"><pds-icon icon="${this.#escapeAttribute(node.icon)}"></pds-icon></span>`;
|
|
643
858
|
}
|
|
644
|
-
return
|
|
859
|
+
return "";
|
|
645
860
|
}
|
|
646
861
|
|
|
647
862
|
#handleKeydown(event) {
|
|
@@ -706,7 +921,15 @@ export class PdsTreeview extends HTMLElement {
|
|
|
706
921
|
|
|
707
922
|
if (key === "Enter" || key === " ") {
|
|
708
923
|
if (!this.displayOnly) {
|
|
709
|
-
this.#
|
|
924
|
+
if (!this.#isMultiSelectEnabled()) {
|
|
925
|
+
this.#selectNode(activeId, { user: true, focus: true, mode: "exclusive" });
|
|
926
|
+
} else if (this.#isCheckboxSelectionMode()) {
|
|
927
|
+
this.#selectNode(activeId, { user: true, focus: true, mode: "toggle" });
|
|
928
|
+
} else if (key === " " && (event.ctrlKey || event.metaKey)) {
|
|
929
|
+
this.#selectNode(activeId, { user: true, focus: true, mode: "toggle" });
|
|
930
|
+
} else {
|
|
931
|
+
this.#selectNode(activeId, { user: true, focus: true, mode: "exclusive" });
|
|
932
|
+
}
|
|
710
933
|
} else if (currentNode.hasChildren) {
|
|
711
934
|
void this.#toggleNode(activeId, true);
|
|
712
935
|
}
|
|
@@ -836,19 +1059,44 @@ export class PdsTreeview extends HTMLElement {
|
|
|
836
1059
|
return source;
|
|
837
1060
|
}
|
|
838
1061
|
|
|
839
|
-
#selectNode(id, { user, focus }) {
|
|
1062
|
+
#selectNode(id, { user, focus, mode = "exclusive" }) {
|
|
840
1063
|
const node = this.#nodeById.get(id);
|
|
841
1064
|
if (!node) return;
|
|
842
1065
|
|
|
843
|
-
this.#
|
|
1066
|
+
if (!this.#isMultiSelectEnabled()) {
|
|
1067
|
+
this.#selectedId = id;
|
|
1068
|
+
this.#selectedIds = new Set([id]);
|
|
1069
|
+
} else if (mode === "toggle") {
|
|
1070
|
+
if (this.#selectedIds.has(id)) {
|
|
1071
|
+
this.#selectedIds.delete(id);
|
|
1072
|
+
if (this.#selectedId === id) {
|
|
1073
|
+
this.#selectedId = this.#selectedIds.values().next().value || null;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
this.#selectedIds.add(id);
|
|
1077
|
+
this.#selectedId = id;
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
this.#selectedId = id;
|
|
1081
|
+
this.#selectedIds = new Set([id]);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
844
1084
|
this.#expandAncestors(id);
|
|
845
1085
|
this.#renderTree();
|
|
846
1086
|
if (focus) this.#focusRow(id);
|
|
847
1087
|
this.#syncValue();
|
|
1088
|
+
const selectedNodes = this.selectedNodes;
|
|
1089
|
+
const selectedValues = selectedNodes.map((selectedNode) => String(selectedNode.value));
|
|
848
1090
|
|
|
849
1091
|
this.dispatchEvent(
|
|
850
1092
|
new CustomEvent("node-select", {
|
|
851
|
-
detail: {
|
|
1093
|
+
detail: {
|
|
1094
|
+
node,
|
|
1095
|
+
value: node.value,
|
|
1096
|
+
values: selectedValues,
|
|
1097
|
+
selectedNodes,
|
|
1098
|
+
user: Boolean(user),
|
|
1099
|
+
},
|
|
852
1100
|
bubbles: true,
|
|
853
1101
|
composed: true,
|
|
854
1102
|
}),
|
|
@@ -866,12 +1114,19 @@ export class PdsTreeview extends HTMLElement {
|
|
|
866
1114
|
if (preferred) {
|
|
867
1115
|
if (!this.selectByValue(preferred)) {
|
|
868
1116
|
this.#selectedId = null;
|
|
1117
|
+
this.#selectedIds.clear();
|
|
869
1118
|
}
|
|
870
1119
|
} else if (this.#selectedId && this.#nodeById.has(this.#selectedId)) {
|
|
1120
|
+
if (!this.#selectedIds.size) this.#selectedIds.add(this.#selectedId);
|
|
871
1121
|
this.#expandAncestors(this.#selectedId);
|
|
872
1122
|
this.#renderTree();
|
|
1123
|
+
} else if (this.#selectedIds.size) {
|
|
1124
|
+
this.#selectedIds = new Set(Array.from(this.#selectedIds).filter((id) => this.#nodeById.has(id)));
|
|
1125
|
+
this.#selectedId = this.#selectedIds.values().next().value || null;
|
|
1126
|
+
this.#renderTree();
|
|
873
1127
|
} else {
|
|
874
1128
|
this.#selectedId = null;
|
|
1129
|
+
this.#selectedIds.clear();
|
|
875
1130
|
this.#syncValue();
|
|
876
1131
|
}
|
|
877
1132
|
}
|
|
@@ -885,28 +1140,48 @@ export class PdsTreeview extends HTMLElement {
|
|
|
885
1140
|
}
|
|
886
1141
|
|
|
887
1142
|
#syncValue() {
|
|
888
|
-
|
|
889
|
-
|
|
1143
|
+
if (this.displayOnly) {
|
|
1144
|
+
if (this.hasAttribute("value")) this.removeAttribute("value");
|
|
1145
|
+
this.#internals.setFormValue("");
|
|
1146
|
+
this.#syncValidity();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const selectedNodes = this.selectedNodes;
|
|
1151
|
+
const selectedValues = selectedNodes.map((node) => String(node.value));
|
|
1152
|
+
const nextValue = selectedValues[0] || "";
|
|
890
1153
|
|
|
891
1154
|
if (nextValue) {
|
|
892
1155
|
if (this.getAttribute("value") !== nextValue) {
|
|
893
1156
|
this.setAttribute("value", nextValue);
|
|
894
1157
|
}
|
|
895
|
-
this.#internals.setFormValue(nextValue);
|
|
896
1158
|
} else {
|
|
897
1159
|
if (this.hasAttribute("value")) this.removeAttribute("value");
|
|
898
|
-
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (this.#isMultiSelectEnabled() && selectedValues.length > 0 && this.name) {
|
|
1163
|
+
const formValue = new FormData();
|
|
1164
|
+
for (const value of selectedValues) {
|
|
1165
|
+
formValue.append(this.name, value);
|
|
1166
|
+
}
|
|
1167
|
+
this.#internals.setFormValue(formValue);
|
|
1168
|
+
} else {
|
|
1169
|
+
this.#internals.setFormValue(nextValue || "");
|
|
899
1170
|
}
|
|
900
1171
|
|
|
901
1172
|
this.#syncValidity();
|
|
902
1173
|
}
|
|
903
1174
|
|
|
904
1175
|
#syncValidity() {
|
|
905
|
-
if (this.required && !this.displayOnly &&
|
|
1176
|
+
if (this.required && !this.displayOnly && this.#selectedIds.size === 0) {
|
|
1177
|
+
const focusTarget =
|
|
1178
|
+
this.#root.querySelector('.tv-row[tabindex="0"]') ||
|
|
1179
|
+
this.#root.querySelector(".tv-row") ||
|
|
1180
|
+
this;
|
|
906
1181
|
this.#internals.setValidity(
|
|
907
1182
|
{ valueMissing: true },
|
|
908
1183
|
"Please select a node.",
|
|
909
|
-
|
|
1184
|
+
focusTarget,
|
|
910
1185
|
);
|
|
911
1186
|
return;
|
|
912
1187
|
}
|
|
@@ -925,7 +1200,7 @@ export class PdsTreeview extends HTMLElement {
|
|
|
925
1200
|
const rows = this.#root.querySelectorAll(".tv-row");
|
|
926
1201
|
if (!rows.length) return;
|
|
927
1202
|
|
|
928
|
-
const fallbackId = this.#selectedId || this.#firstVisibleId();
|
|
1203
|
+
const fallbackId = this.#selectedId || this.#selectedIds.values().next().value || this.#firstVisibleId();
|
|
929
1204
|
if (!this.#focusedId || !this.#root.querySelector(`.tv-row[data-node-id="${CSS.escape(this.#focusedId)}"]`)) {
|
|
930
1205
|
this.#focusedId = fallbackId || null;
|
|
931
1206
|
}
|
|
@@ -967,6 +1242,28 @@ export class PdsTreeview extends HTMLElement {
|
|
|
967
1242
|
#escapeAttribute(value) {
|
|
968
1243
|
return this.#escapeHtml(value);
|
|
969
1244
|
}
|
|
1245
|
+
|
|
1246
|
+
#normalizeMultiselect(value) {
|
|
1247
|
+
const normalized = String(value || "off").toLowerCase();
|
|
1248
|
+
if (normalized === "checkboxes" || normalized === "auto") return normalized;
|
|
1249
|
+
return "off";
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
#isMultiSelectEnabled() {
|
|
1253
|
+
return this.multiselect !== "off";
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
#isCheckboxSelectionMode() {
|
|
1257
|
+
if (this.multiselect === "checkboxes") return true;
|
|
1258
|
+
if (this.multiselect !== "auto") return false;
|
|
1259
|
+
return Boolean(this.#multiselectAutoQuery?.matches);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
#syncAriaMultiselect() {
|
|
1263
|
+
const tree = this.#root.querySelector(ROOT_SELECTOR);
|
|
1264
|
+
if (!tree) return;
|
|
1265
|
+
tree.setAttribute("aria-multiselectable", this.#isMultiSelectEnabled() ? "true" : "false");
|
|
1266
|
+
}
|
|
970
1267
|
}
|
|
971
1268
|
|
|
972
1269
|
if (!customElements.get("pds-treeview")) {
|