openproject-primer_view_components 0.70.5 → 0.71.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/app/assets/javascripts/components/primer/alpha/segmented_control.d.ts +2 -2
- data/app/assets/javascripts/components/primer/open_project/filterable_tree_view.d.ts +29 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view.d.ts +11 -1
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
- data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/segmented_control.d.ts +2 -2
- data/app/components/primer/alpha/segmented_control.js +12 -0
- data/app/components/primer/alpha/segmented_control.ts +16 -1
- data/app/components/primer/alpha/stack.css +1 -1
- data/app/components/primer/alpha/stack.css.json +5 -1
- data/app/components/primer/alpha/stack.css.map +1 -1
- data/app/components/primer/alpha/stack.pcss +13 -0
- data/app/components/primer/alpha/stack.rb +2 -1
- data/app/components/primer/open_project/filterable_tree_view/sub_tree.rb +39 -0
- data/app/components/primer/open_project/filterable_tree_view.d.ts +29 -0
- data/app/components/primer/open_project/filterable_tree_view.html.erb +28 -0
- data/app/components/primer/open_project/filterable_tree_view.js +409 -0
- data/app/components/primer/open_project/filterable_tree_view.rb +254 -0
- data/app/components/primer/open_project/filterable_tree_view.ts +492 -0
- data/app/components/primer/open_project/tree_view/node.rb +19 -3
- data/app/components/primer/open_project/tree_view/sub_tree_node.rb +14 -4
- data/app/components/primer/open_project/tree_view/tree_view.d.ts +11 -1
- data/app/components/primer/open_project/tree_view/tree_view.js +120 -20
- data/app/components/primer/open_project/tree_view/tree_view.ts +137 -18
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.js +27 -4
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.ts +36 -5
- data/app/components/primer/open_project/tree_view.css +1 -1
- data/app/components/primer/open_project/tree_view.css.json +9 -0
- data/app/components/primer/open_project/tree_view.css.map +1 -1
- data/app/components/primer/open_project/tree_view.html.erb +4 -0
- data/app/components/primer/open_project/tree_view.pcss +48 -0
- data/app/components/primer/open_project/tree_view.rb +6 -1
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/lib/primer/forms/base_component.rb +1 -1
- data/app/lib/primer/forms/dsl/text_field_input.rb +2 -0
- data/config/locales/en.yml +20 -0
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/open_project/filterable_tree_view_preview/_custom_select_js.html.erb +62 -0
- data/previews/primer/open_project/filterable_tree_view_preview/custom_checkbox_text.html.erb +26 -0
- data/previews/primer/open_project/filterable_tree_view_preview/custom_no_results_text.html.erb +28 -0
- data/previews/primer/open_project/filterable_tree_view_preview/custom_segmented_control.html.erb +31 -0
- data/previews/primer/open_project/filterable_tree_view_preview/default.html.erb +26 -0
- data/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb +32 -0
- data/previews/primer/open_project/filterable_tree_view_preview/playground.html.erb +26 -0
- data/previews/primer/open_project/filterable_tree_view_preview.rb +125 -0
- data/previews/primer/open_project/tree_view_preview/buttons.html.erb +4 -4
- data/previews/primer/open_project/tree_view_preview/default.html.erb +4 -4
- data/previews/primer/open_project/tree_view_preview/leaf_node_playground.html.erb +1 -1
- data/previews/primer/open_project/tree_view_preview/links.html.erb +4 -4
- data/previews/primer/open_project/tree_view_preview.rb +18 -8
- data/static/arguments.json +89 -3
- data/static/audited_at.json +2 -0
- data/static/constants.json +40 -1
- data/static/info_arch.json +220 -3
- data/static/previews.json +86 -0
- data/static/statuses.json +2 -0
- metadata +18 -2
@@ -15,8 +15,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
15
15
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
16
16
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
17
17
|
};
|
18
|
-
var _TreeViewElement_instances, _TreeViewElement_abortController, _TreeViewElement_autoExpandFrom, _TreeViewElement_eventIsActivation, _TreeViewElement_nodeForEvent, _TreeViewElement_handleNodeEvent, _TreeViewElement_eventIsCheckboxToggle, _TreeViewElement_handleCheckboxToggle, _TreeViewElement_handleNodeActivated, _TreeViewElement_handleNodeFocused, _TreeViewElement_handleNodeKeyboardEvent
|
19
|
-
import { controller } from '@github/catalyst';
|
18
|
+
var _TreeViewElement_instances, _TreeViewElement_abortController, _TreeViewElement_autoExpandFrom, _TreeViewElement_eventIsActivation, _TreeViewElement_nodeForEvent, _TreeViewElement_handleNodeEvent, _TreeViewElement_eventIsCheckboxToggle, _TreeViewElement_handleCheckboxToggle, _TreeViewElement_handleNodeActivated, _TreeViewElement_handleNodeFocused, _TreeViewElement_handleNodeKeyboardEvent;
|
19
|
+
import { controller, target } from '@github/catalyst';
|
20
20
|
import { useRovingTabIndex } from './tree_view_roving_tab_index';
|
21
21
|
let TreeViewElement = class TreeViewElement extends HTMLElement {
|
22
22
|
constructor() {
|
@@ -43,12 +43,53 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
43
43
|
}
|
44
44
|
}
|
45
45
|
}).observe(this, { childList: true, subtree: true });
|
46
|
+
const updateInputsObserver = new MutationObserver(mutations => {
|
47
|
+
if (!this.formInputContainer)
|
48
|
+
return;
|
49
|
+
// There is another MutationObserver in TreeViewSubTreeNodeElement that manages checking/unchecking
|
50
|
+
// nodes based on the component's select strategy. These two observers can conflict and cause infinite
|
51
|
+
// looping, so we make sure something actually changed before computing inputs again.
|
52
|
+
const somethingChanged = mutations.some(m => {
|
53
|
+
if (!(m.target instanceof HTMLElement))
|
54
|
+
return false;
|
55
|
+
return m.target.getAttribute('aria-checked') !== m.oldValue;
|
56
|
+
});
|
57
|
+
if (!somethingChanged)
|
58
|
+
return;
|
59
|
+
const newInputs = [];
|
60
|
+
// eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
|
61
|
+
for (const node of this.querySelectorAll('[role=treeitem][aria-checked=true]')) {
|
62
|
+
const newInput = this.formInputPrototype.cloneNode();
|
63
|
+
newInput.removeAttribute('data-target');
|
64
|
+
newInput.removeAttribute('form');
|
65
|
+
const payload = {
|
66
|
+
path: this.getNodePath(node),
|
67
|
+
};
|
68
|
+
const inputValue = this.getFormInputValueForNode(node);
|
69
|
+
if (inputValue)
|
70
|
+
payload.value = inputValue;
|
71
|
+
newInput.value = JSON.stringify(payload);
|
72
|
+
newInputs.push(newInput);
|
73
|
+
}
|
74
|
+
this.formInputContainer.replaceChildren(...newInputs);
|
75
|
+
});
|
76
|
+
updateInputsObserver.observe(this, {
|
77
|
+
childList: true,
|
78
|
+
subtree: true,
|
79
|
+
attributeFilter: ['aria-checked'],
|
80
|
+
});
|
46
81
|
// eslint-disable-next-line github/no-then -- We don't want to wait for this to resolve, just get on with it
|
47
82
|
customElements.whenDefined('tree-view-sub-tree-node').then(() => {
|
48
83
|
// depends on TreeViewSubTreeNodeElement#eachAncestorSubTreeNode, which may not be defined yet
|
49
84
|
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_autoExpandFrom).call(this, this);
|
50
85
|
});
|
51
86
|
}
|
87
|
+
rootLeafNodes() {
|
88
|
+
return this.querySelectorAll(':scope > ul > li > .TreeViewItemContainer [role=treeitem]');
|
89
|
+
}
|
90
|
+
rootSubTreeNodes() {
|
91
|
+
return this.querySelectorAll(':scope > ul > tree-view-sub-tree-node');
|
92
|
+
}
|
52
93
|
disconnectedCallback() {
|
53
94
|
__classPrivateFieldGet(this, _TreeViewElement_abortController, "f").abort();
|
54
95
|
}
|
@@ -58,6 +99,9 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
58
99
|
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleNodeEvent).call(this, node, event);
|
59
100
|
}
|
60
101
|
}
|
102
|
+
getFormInputValueForNode(node) {
|
103
|
+
return node.getAttribute('data-value');
|
104
|
+
}
|
61
105
|
getNodePath(node) {
|
62
106
|
const rawPath = node.getAttribute('data-path');
|
63
107
|
if (rawPath) {
|
@@ -101,13 +145,13 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
101
145
|
const node = this.nodeAtPath(path);
|
102
146
|
if (!node)
|
103
147
|
return;
|
104
|
-
|
148
|
+
this.setNodeCheckedValue(node, 'true');
|
105
149
|
}
|
106
150
|
uncheckAtPath(path) {
|
107
151
|
const node = this.nodeAtPath(path);
|
108
152
|
if (!node)
|
109
153
|
return;
|
110
|
-
|
154
|
+
this.setNodeCheckedValue(node, 'false');
|
111
155
|
}
|
112
156
|
toggleCheckedAtPath(path) {
|
113
157
|
const node = this.nodeAtPath(path);
|
@@ -128,6 +172,12 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
128
172
|
return 'false';
|
129
173
|
return this.getNodeCheckedValue(node);
|
130
174
|
}
|
175
|
+
disabledValueAtPath(path) {
|
176
|
+
const node = this.nodeAtPath(path);
|
177
|
+
if (!node)
|
178
|
+
return false;
|
179
|
+
return this.getNodeDisabledValue(node);
|
180
|
+
}
|
131
181
|
nodeAtPath(path, selector) {
|
132
182
|
const pathStr = JSON.stringify(path);
|
133
183
|
return this.querySelector(`${selector || ''}[data-path="${CSS.escape(pathStr)}"]`);
|
@@ -141,9 +191,23 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
141
191
|
leafAtPath(path) {
|
142
192
|
return this.nodeAtPath(path, '[data-node-type=leaf]');
|
143
193
|
}
|
194
|
+
setNodeCheckedValue(node, value) {
|
195
|
+
node.setAttribute('aria-checked', value.toString());
|
196
|
+
}
|
144
197
|
getNodeCheckedValue(node) {
|
145
198
|
return (node.getAttribute('aria-checked') || 'false');
|
146
199
|
}
|
200
|
+
getNodeDisabledValue(node) {
|
201
|
+
return node.getAttribute('aria-disabled') === 'true';
|
202
|
+
}
|
203
|
+
setNodeDisabledValue(node, disabled) {
|
204
|
+
if (disabled) {
|
205
|
+
node.setAttribute('aria-disabled', 'true');
|
206
|
+
}
|
207
|
+
else {
|
208
|
+
node.removeAttribute('aria-disabled');
|
209
|
+
}
|
210
|
+
}
|
147
211
|
nodeHasCheckBox(node) {
|
148
212
|
return node.querySelector('.TreeViewItemCheckbox') !== null;
|
149
213
|
}
|
@@ -160,6 +224,11 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
160
224
|
}
|
161
225
|
}
|
162
226
|
}
|
227
|
+
changeSelectStrategy(newStrategy) {
|
228
|
+
for (const subTreeNode of this.querySelectorAll('tree-view-sub-tree-node')) {
|
229
|
+
subTreeNode.changeSelectStrategy(newStrategy);
|
230
|
+
}
|
231
|
+
}
|
163
232
|
// PRIVATE API METHOD
|
164
233
|
//
|
165
234
|
// This would normally be marked private, but it's called by TreeViewSubTreeNodes
|
@@ -189,22 +258,22 @@ _TreeViewElement_eventIsActivation = function _TreeViewElement_eventIsActivation
|
|
189
258
|
return event.type === 'click';
|
190
259
|
};
|
191
260
|
_TreeViewElement_nodeForEvent = function _TreeViewElement_nodeForEvent(event) {
|
192
|
-
const
|
193
|
-
const node =
|
261
|
+
const eventTarget = event.target;
|
262
|
+
const node = eventTarget.closest('[role=treeitem]');
|
194
263
|
if (!node)
|
195
264
|
return null;
|
196
|
-
if (
|
265
|
+
if (eventTarget.closest('.TreeViewItemToggle'))
|
197
266
|
return null;
|
198
|
-
if (
|
267
|
+
if (eventTarget.closest('.TreeViewItemLeadingAction'))
|
199
268
|
return null;
|
200
269
|
return node;
|
201
270
|
};
|
202
271
|
_TreeViewElement_handleNodeEvent = function _TreeViewElement_handleNodeEvent(node, event) {
|
203
272
|
if (__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_eventIsCheckboxToggle).call(this, event, node)) {
|
204
|
-
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleCheckboxToggle).call(this, node);
|
273
|
+
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleCheckboxToggle).call(this, event, node);
|
205
274
|
}
|
206
275
|
else if (__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_eventIsActivation).call(this, event)) {
|
207
|
-
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleNodeActivated).call(this, node);
|
276
|
+
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleNodeActivated).call(this, event, node);
|
208
277
|
}
|
209
278
|
else if (event.type === 'focusin') {
|
210
279
|
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_handleNodeFocused).call(this, node);
|
@@ -216,19 +285,43 @@ _TreeViewElement_handleNodeEvent = function _TreeViewElement_handleNodeEvent(nod
|
|
216
285
|
_TreeViewElement_eventIsCheckboxToggle = function _TreeViewElement_eventIsCheckboxToggle(event, node) {
|
217
286
|
return event.type === 'click' && this.nodeHasCheckBox(node);
|
218
287
|
};
|
219
|
-
_TreeViewElement_handleCheckboxToggle = function _TreeViewElement_handleCheckboxToggle(node) {
|
220
|
-
|
288
|
+
_TreeViewElement_handleCheckboxToggle = function _TreeViewElement_handleCheckboxToggle(event, node) {
|
289
|
+
if (this.getNodeDisabledValue(node)) {
|
290
|
+
event.preventDefault();
|
291
|
+
return;
|
292
|
+
}
|
293
|
+
// only handle checking of leaf nodes, see TreeViewSubTreeNodeElement for the code that
|
294
|
+
// handles checking sub tree items.
|
221
295
|
const type = this.getNodeType(node);
|
222
296
|
if (type !== 'leaf')
|
223
297
|
return;
|
298
|
+
const checkValue = this.getNodeCheckedValue(node);
|
299
|
+
const newCheckValue = checkValue === 'false' ? 'true' : 'false';
|
300
|
+
const nodeInfo = this.infoFromNode(node, newCheckValue);
|
301
|
+
const checkSuccess = this.dispatchEvent(new CustomEvent('treeViewBeforeNodeChecked', {
|
302
|
+
bubbles: true,
|
303
|
+
cancelable: true,
|
304
|
+
detail: [nodeInfo],
|
305
|
+
}));
|
306
|
+
if (!checkSuccess)
|
307
|
+
return;
|
224
308
|
if (this.getNodeCheckedValue(node) === 'true') {
|
225
|
-
|
309
|
+
this.setNodeCheckedValue(node, 'false');
|
226
310
|
}
|
227
311
|
else {
|
228
|
-
|
312
|
+
this.setNodeCheckedValue(node, 'true');
|
229
313
|
}
|
314
|
+
this.dispatchEvent(new CustomEvent('treeViewNodeChecked', {
|
315
|
+
bubbles: true,
|
316
|
+
cancelable: true,
|
317
|
+
detail: [nodeInfo],
|
318
|
+
}));
|
230
319
|
};
|
231
|
-
_TreeViewElement_handleNodeActivated = function _TreeViewElement_handleNodeActivated(node) {
|
320
|
+
_TreeViewElement_handleNodeActivated = function _TreeViewElement_handleNodeActivated(event, node) {
|
321
|
+
if (this.getNodeDisabledValue(node)) {
|
322
|
+
event.preventDefault();
|
323
|
+
return;
|
324
|
+
}
|
232
325
|
// do not emit activation events for buttons and anchors, since it is assumed any activation
|
233
326
|
// behavior for these element types is user- or browser-defined
|
234
327
|
if (!(node instanceof HTMLDivElement))
|
@@ -262,13 +355,17 @@ _TreeViewElement_handleNodeKeyboardEvent = function _TreeViewElement_handleNodeK
|
|
262
355
|
switch (event.key) {
|
263
356
|
case ' ':
|
264
357
|
case 'Enter':
|
358
|
+
if (this.getNodeDisabledValue(node)) {
|
359
|
+
event.preventDefault();
|
360
|
+
break;
|
361
|
+
}
|
265
362
|
if (this.nodeHasCheckBox(node)) {
|
266
363
|
event.preventDefault();
|
267
364
|
if (this.getNodeCheckedValue(node) === 'true') {
|
268
|
-
|
365
|
+
this.setNodeCheckedValue(node, 'false');
|
269
366
|
}
|
270
367
|
else {
|
271
|
-
|
368
|
+
this.setNodeCheckedValue(node, 'true');
|
272
369
|
}
|
273
370
|
}
|
274
371
|
else if (node instanceof HTMLAnchorElement) {
|
@@ -278,9 +375,12 @@ _TreeViewElement_handleNodeKeyboardEvent = function _TreeViewElement_handleNodeK
|
|
278
375
|
break;
|
279
376
|
}
|
280
377
|
};
|
281
|
-
|
282
|
-
|
283
|
-
|
378
|
+
__decorate([
|
379
|
+
target
|
380
|
+
], TreeViewElement.prototype, "formInputContainer", void 0);
|
381
|
+
__decorate([
|
382
|
+
target
|
383
|
+
], TreeViewElement.prototype, "formInputPrototype", void 0);
|
284
384
|
TreeViewElement = __decorate([
|
285
385
|
controller
|
286
386
|
], TreeViewElement);
|
@@ -1,10 +1,13 @@
|
|
1
|
-
import {controller} from '@github/catalyst'
|
2
|
-
import {TreeViewSubTreeNodeElement} from './tree_view_sub_tree_node_element'
|
1
|
+
import {controller, target} from '@github/catalyst'
|
2
|
+
import {SelectStrategy, TreeViewSubTreeNodeElement} from './tree_view_sub_tree_node_element'
|
3
3
|
import {useRovingTabIndex} from './tree_view_roving_tab_index'
|
4
4
|
import type {TreeViewNodeType, TreeViewCheckedValue, TreeViewNodeInfo} from '../../shared_events'
|
5
5
|
|
6
6
|
@controller
|
7
7
|
export class TreeViewElement extends HTMLElement {
|
8
|
+
@target formInputContainer: HTMLElement
|
9
|
+
@target formInputPrototype: HTMLInputElement
|
10
|
+
|
8
11
|
#abortController: AbortController
|
9
12
|
|
10
13
|
connectedCallback() {
|
@@ -29,6 +32,47 @@ export class TreeViewElement extends HTMLElement {
|
|
29
32
|
}
|
30
33
|
}).observe(this, {childList: true, subtree: true})
|
31
34
|
|
35
|
+
const updateInputsObserver = new MutationObserver(mutations => {
|
36
|
+
if (!this.formInputContainer) return
|
37
|
+
|
38
|
+
// There is another MutationObserver in TreeViewSubTreeNodeElement that manages checking/unchecking
|
39
|
+
// nodes based on the component's select strategy. These two observers can conflict and cause infinite
|
40
|
+
// looping, so we make sure something actually changed before computing inputs again.
|
41
|
+
const somethingChanged = mutations.some(m => {
|
42
|
+
if (!(m.target instanceof HTMLElement)) return false
|
43
|
+
return m.target.getAttribute('aria-checked') !== m.oldValue
|
44
|
+
})
|
45
|
+
|
46
|
+
if (!somethingChanged) return
|
47
|
+
|
48
|
+
const newInputs = []
|
49
|
+
|
50
|
+
// eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
|
51
|
+
for (const node of this.querySelectorAll('[role=treeitem][aria-checked=true]')) {
|
52
|
+
const newInput = this.formInputPrototype.cloneNode() as HTMLInputElement
|
53
|
+
newInput.removeAttribute('data-target')
|
54
|
+
newInput.removeAttribute('form')
|
55
|
+
|
56
|
+
const payload: {path: string[]; value?: string} = {
|
57
|
+
path: this.getNodePath(node),
|
58
|
+
}
|
59
|
+
|
60
|
+
const inputValue = this.getFormInputValueForNode(node)
|
61
|
+
if (inputValue) payload.value = inputValue
|
62
|
+
|
63
|
+
newInput.value = JSON.stringify(payload)
|
64
|
+
newInputs.push(newInput)
|
65
|
+
}
|
66
|
+
|
67
|
+
this.formInputContainer.replaceChildren(...newInputs)
|
68
|
+
})
|
69
|
+
|
70
|
+
updateInputsObserver.observe(this, {
|
71
|
+
childList: true,
|
72
|
+
subtree: true,
|
73
|
+
attributeFilter: ['aria-checked'],
|
74
|
+
})
|
75
|
+
|
32
76
|
// eslint-disable-next-line github/no-then -- We don't want to wait for this to resolve, just get on with it
|
33
77
|
customElements.whenDefined('tree-view-sub-tree-node').then(() => {
|
34
78
|
// depends on TreeViewSubTreeNodeElement#eachAncestorSubTreeNode, which may not be defined yet
|
@@ -36,6 +80,14 @@ export class TreeViewElement extends HTMLElement {
|
|
36
80
|
})
|
37
81
|
}
|
38
82
|
|
83
|
+
rootLeafNodes(): NodeListOf<HTMLElement> {
|
84
|
+
return this.querySelectorAll(':scope > ul > li > .TreeViewItemContainer [role=treeitem]')
|
85
|
+
}
|
86
|
+
|
87
|
+
rootSubTreeNodes(): NodeListOf<TreeViewSubTreeNodeElement> {
|
88
|
+
return this.querySelectorAll(':scope > ul > tree-view-sub-tree-node')
|
89
|
+
}
|
90
|
+
|
39
91
|
#autoExpandFrom(root: HTMLElement) {
|
40
92
|
for (const element of root.querySelectorAll('[aria-expanded=true]')) {
|
41
93
|
this.expandAncestorsForNode(element as HTMLElement)
|
@@ -59,21 +111,21 @@ export class TreeViewElement extends HTMLElement {
|
|
59
111
|
}
|
60
112
|
|
61
113
|
#nodeForEvent(event: Event): Element | null {
|
62
|
-
const
|
63
|
-
const node =
|
114
|
+
const eventTarget = event.target as Element
|
115
|
+
const node = eventTarget.closest('[role=treeitem]')
|
64
116
|
if (!node) return null
|
65
117
|
|
66
|
-
if (
|
67
|
-
if (
|
118
|
+
if (eventTarget.closest('.TreeViewItemToggle')) return null
|
119
|
+
if (eventTarget.closest('.TreeViewItemLeadingAction')) return null
|
68
120
|
|
69
121
|
return node
|
70
122
|
}
|
71
123
|
|
72
124
|
#handleNodeEvent(node: Element, event: Event) {
|
73
125
|
if (this.#eventIsCheckboxToggle(event, node)) {
|
74
|
-
this.#handleCheckboxToggle(node)
|
126
|
+
this.#handleCheckboxToggle(event, node)
|
75
127
|
} else if (this.#eventIsActivation(event)) {
|
76
|
-
this.#handleNodeActivated(node)
|
128
|
+
this.#handleNodeActivated(event, node)
|
77
129
|
} else if (event.type === 'focusin') {
|
78
130
|
this.#handleNodeFocused(node)
|
79
131
|
} else if (event instanceof KeyboardEvent) {
|
@@ -85,19 +137,52 @@ export class TreeViewElement extends HTMLElement {
|
|
85
137
|
return event.type === 'click' && this.nodeHasCheckBox(node)
|
86
138
|
}
|
87
139
|
|
88
|
-
#handleCheckboxToggle(node: Element) {
|
89
|
-
|
140
|
+
#handleCheckboxToggle(event: Event, node: Element) {
|
141
|
+
if (this.getNodeDisabledValue(node)) {
|
142
|
+
event.preventDefault()
|
143
|
+
return
|
144
|
+
}
|
145
|
+
|
146
|
+
// only handle checking of leaf nodes, see TreeViewSubTreeNodeElement for the code that
|
147
|
+
// handles checking sub tree items.
|
90
148
|
const type = this.getNodeType(node)
|
91
149
|
if (type !== 'leaf') return
|
92
150
|
|
151
|
+
const checkValue = this.getNodeCheckedValue(node)
|
152
|
+
const newCheckValue = checkValue === 'false' ? 'true' : 'false'
|
153
|
+
const nodeInfo = this.infoFromNode(node, newCheckValue)
|
154
|
+
|
155
|
+
const checkSuccess = this.dispatchEvent(
|
156
|
+
new CustomEvent('treeViewBeforeNodeChecked', {
|
157
|
+
bubbles: true,
|
158
|
+
cancelable: true,
|
159
|
+
detail: [nodeInfo],
|
160
|
+
}),
|
161
|
+
)
|
162
|
+
|
163
|
+
if (!checkSuccess) return
|
164
|
+
|
93
165
|
if (this.getNodeCheckedValue(node) === 'true') {
|
94
|
-
this
|
166
|
+
this.setNodeCheckedValue(node, 'false')
|
95
167
|
} else {
|
96
|
-
this
|
168
|
+
this.setNodeCheckedValue(node, 'true')
|
97
169
|
}
|
170
|
+
|
171
|
+
this.dispatchEvent(
|
172
|
+
new CustomEvent('treeViewNodeChecked', {
|
173
|
+
bubbles: true,
|
174
|
+
cancelable: true,
|
175
|
+
detail: [nodeInfo],
|
176
|
+
}),
|
177
|
+
)
|
98
178
|
}
|
99
179
|
|
100
|
-
#handleNodeActivated(node: Element) {
|
180
|
+
#handleNodeActivated(event: Event, node: Element) {
|
181
|
+
if (this.getNodeDisabledValue(node)) {
|
182
|
+
event.preventDefault()
|
183
|
+
return
|
184
|
+
}
|
185
|
+
|
101
186
|
// do not emit activation events for buttons and anchors, since it is assumed any activation
|
102
187
|
// behavior for these element types is user- or browser-defined
|
103
188
|
if (!(node instanceof HTMLDivElement)) return
|
@@ -141,13 +226,18 @@ export class TreeViewElement extends HTMLElement {
|
|
141
226
|
switch (event.key) {
|
142
227
|
case ' ':
|
143
228
|
case 'Enter':
|
229
|
+
if (this.getNodeDisabledValue(node)) {
|
230
|
+
event.preventDefault()
|
231
|
+
break
|
232
|
+
}
|
233
|
+
|
144
234
|
if (this.nodeHasCheckBox(node)) {
|
145
235
|
event.preventDefault()
|
146
236
|
|
147
237
|
if (this.getNodeCheckedValue(node) === 'true') {
|
148
|
-
this
|
238
|
+
this.setNodeCheckedValue(node, 'false')
|
149
239
|
} else {
|
150
|
-
this
|
240
|
+
this.setNodeCheckedValue(node, 'true')
|
151
241
|
}
|
152
242
|
} else if (node instanceof HTMLAnchorElement) {
|
153
243
|
// simulate click on space
|
@@ -158,6 +248,10 @@ export class TreeViewElement extends HTMLElement {
|
|
158
248
|
}
|
159
249
|
}
|
160
250
|
|
251
|
+
getFormInputValueForNode(node: Element): string | null {
|
252
|
+
return node.getAttribute('data-value')
|
253
|
+
}
|
254
|
+
|
161
255
|
getNodePath(node: Element): string[] {
|
162
256
|
const rawPath = node.getAttribute('data-path')
|
163
257
|
|
@@ -210,14 +304,14 @@ export class TreeViewElement extends HTMLElement {
|
|
210
304
|
const node = this.nodeAtPath(path)
|
211
305
|
if (!node) return
|
212
306
|
|
213
|
-
this
|
307
|
+
this.setNodeCheckedValue(node, 'true')
|
214
308
|
}
|
215
309
|
|
216
310
|
uncheckAtPath(path: string[]) {
|
217
311
|
const node = this.nodeAtPath(path)
|
218
312
|
if (!node) return
|
219
313
|
|
220
|
-
this
|
314
|
+
this.setNodeCheckedValue(node, 'false')
|
221
315
|
}
|
222
316
|
|
223
317
|
toggleCheckedAtPath(path: string[]) {
|
@@ -240,6 +334,13 @@ export class TreeViewElement extends HTMLElement {
|
|
240
334
|
return this.getNodeCheckedValue(node)
|
241
335
|
}
|
242
336
|
|
337
|
+
disabledValueAtPath(path: string[]): boolean {
|
338
|
+
const node = this.nodeAtPath(path)
|
339
|
+
if (!node) return false
|
340
|
+
|
341
|
+
return this.getNodeDisabledValue(node)
|
342
|
+
}
|
343
|
+
|
243
344
|
nodeAtPath(path: string[], selector?: string): Element | null {
|
244
345
|
const pathStr = JSON.stringify(path)
|
245
346
|
return this.querySelector(`${selector || ''}[data-path="${CSS.escape(pathStr)}"]`)
|
@@ -256,7 +357,7 @@ export class TreeViewElement extends HTMLElement {
|
|
256
357
|
return this.nodeAtPath(path, '[data-node-type=leaf]') as HTMLLIElement | null
|
257
358
|
}
|
258
359
|
|
259
|
-
|
360
|
+
setNodeCheckedValue(node: Element, value: TreeViewCheckedValue) {
|
260
361
|
node.setAttribute('aria-checked', value.toString())
|
261
362
|
}
|
262
363
|
|
@@ -264,6 +365,18 @@ export class TreeViewElement extends HTMLElement {
|
|
264
365
|
return (node.getAttribute('aria-checked') || 'false') as TreeViewCheckedValue
|
265
366
|
}
|
266
367
|
|
368
|
+
getNodeDisabledValue(node: Element): boolean {
|
369
|
+
return node.getAttribute('aria-disabled') === 'true'
|
370
|
+
}
|
371
|
+
|
372
|
+
setNodeDisabledValue(node: Element, disabled: boolean) {
|
373
|
+
if (disabled) {
|
374
|
+
node.setAttribute('aria-disabled', 'true')
|
375
|
+
} else {
|
376
|
+
node.removeAttribute('aria-disabled')
|
377
|
+
}
|
378
|
+
}
|
379
|
+
|
267
380
|
nodeHasCheckBox(node: Element): boolean {
|
268
381
|
return node.querySelector('.TreeViewItemCheckbox') !== null
|
269
382
|
}
|
@@ -283,6 +396,12 @@ export class TreeViewElement extends HTMLElement {
|
|
283
396
|
}
|
284
397
|
}
|
285
398
|
|
399
|
+
changeSelectStrategy(newStrategy: SelectStrategy) {
|
400
|
+
for (const subTreeNode of this.querySelectorAll<TreeViewSubTreeNodeElement>('tree-view-sub-tree-node')) {
|
401
|
+
subTreeNode.changeSelectStrategy(newStrategy)
|
402
|
+
}
|
403
|
+
}
|
404
|
+
|
286
405
|
// PRIVATE API METHOD
|
287
406
|
//
|
288
407
|
// This would normally be marked private, but it's called by TreeViewSubTreeNodes
|
@@ -2,6 +2,7 @@ import { TreeViewIconPairElement } from './tree_view_icon_pair_element';
|
|
2
2
|
import { TreeViewIncludeFragmentElement } from './tree_view_include_fragment_element';
|
3
3
|
import { TreeViewElement } from './tree_view';
|
4
4
|
type LoadingState = 'loading' | 'error' | 'success';
|
5
|
+
export type SelectStrategy = 'self' | 'descendants' | 'mixed_descendants';
|
5
6
|
export declare class TreeViewSubTreeNodeElement extends HTMLElement {
|
6
7
|
#private;
|
7
8
|
node: HTMLElement;
|
@@ -19,7 +20,8 @@ export declare class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
19
20
|
set expanded(newValue: boolean);
|
20
21
|
get loadingState(): LoadingState;
|
21
22
|
set loadingState(newState: LoadingState);
|
22
|
-
get selectStrategy():
|
23
|
+
get selectStrategy(): SelectStrategy;
|
24
|
+
get level(): number;
|
23
25
|
disconnectedCallback(): void;
|
24
26
|
handleEvent(event: Event): void;
|
25
27
|
expand(): void;
|
@@ -27,11 +29,13 @@ export declare class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
27
29
|
toggle(): void;
|
28
30
|
get nodes(): NodeListOf<Element>;
|
29
31
|
eachDirectDescendantNode(): Generator<Element>;
|
32
|
+
eachDirectDescendantSubTreeNode(): Generator<TreeViewSubTreeNodeElement>;
|
30
33
|
eachDescendantNode(): Generator<Element>;
|
31
34
|
eachAncestorSubTreeNode(): Generator<TreeViewSubTreeNodeElement>;
|
32
35
|
get isEmpty(): boolean;
|
33
36
|
get treeView(): TreeViewElement | null;
|
34
37
|
toggleChecked(): void;
|
38
|
+
changeSelectStrategy(newStrategy: SelectStrategy): void;
|
35
39
|
}
|
36
40
|
declare global {
|
37
41
|
interface Window {
|
@@ -48,7 +48,7 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
48
48
|
}, { signal });
|
49
49
|
});
|
50
50
|
const checkedMutationObserver = new MutationObserver(() => {
|
51
|
-
if (this.selectStrategy !== '
|
51
|
+
if (this.selectStrategy !== 'mixed_descendants')
|
52
52
|
return;
|
53
53
|
let checkType = 'unknown';
|
54
54
|
for (const node of this.eachDirectDescendantNode()) {
|
@@ -95,7 +95,10 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
95
95
|
__classPrivateFieldGet(this, _TreeViewSubTreeNodeElement_instances, "m", _TreeViewSubTreeNodeElement_update).call(this);
|
96
96
|
}
|
97
97
|
get selectStrategy() {
|
98
|
-
return this.node.getAttribute('data-select-strategy') || 'descendants';
|
98
|
+
return (this.node.getAttribute('data-select-strategy') || 'descendants');
|
99
|
+
}
|
100
|
+
get level() {
|
101
|
+
return parseInt(this.node.getAttribute('aria-level') || '0');
|
99
102
|
}
|
100
103
|
disconnectedCallback() {
|
101
104
|
__classPrivateFieldGet(this, _TreeViewSubTreeNodeElement_abortController, "f").abort();
|
@@ -160,6 +163,11 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
160
163
|
yield subTree;
|
161
164
|
}
|
162
165
|
}
|
166
|
+
*eachDirectDescendantSubTreeNode() {
|
167
|
+
for (const subTree of this.subTree.querySelectorAll(':scope > tree-view-sub-tree-node')) {
|
168
|
+
yield subTree;
|
169
|
+
}
|
170
|
+
}
|
163
171
|
*eachDescendantNode() {
|
164
172
|
for (const node of this.subTree.querySelectorAll('[role=treeitem]')) {
|
165
173
|
yield node;
|
@@ -182,13 +190,13 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
182
190
|
return this.closest('tree-view');
|
183
191
|
}
|
184
192
|
toggleChecked() {
|
185
|
-
const checkValue = this.node
|
193
|
+
const checkValue = this.treeView?.getNodeCheckedValue(this.node) || 'false';
|
186
194
|
const newCheckValue = checkValue === 'false' ? 'true' : 'false';
|
187
195
|
const nodeInfos = [];
|
188
196
|
const rootInfo = this.treeView?.infoFromNode(this.node, newCheckValue);
|
189
197
|
if (rootInfo)
|
190
198
|
nodeInfos.push(rootInfo);
|
191
|
-
if (this.selectStrategy === 'descendants') {
|
199
|
+
if (this.selectStrategy === 'descendants' || this.selectStrategy === 'mixed_descendants') {
|
192
200
|
for (const node of this.eachDescendantNode()) {
|
193
201
|
const info = this.treeView?.infoFromNode(node, newCheckValue);
|
194
202
|
if (info)
|
@@ -211,6 +219,9 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
211
219
|
detail: nodeInfos,
|
212
220
|
}));
|
213
221
|
}
|
222
|
+
changeSelectStrategy(newStrategy) {
|
223
|
+
this.node.setAttribute('data-select-strategy', newStrategy);
|
224
|
+
}
|
214
225
|
};
|
215
226
|
_TreeViewSubTreeNodeElement_expanded = new WeakMap();
|
216
227
|
_TreeViewSubTreeNodeElement_loadingState = new WeakMap();
|
@@ -269,6 +280,10 @@ _TreeViewSubTreeNodeElement_handleKeyboardEvent = function _TreeViewSubTreeNodeE
|
|
269
280
|
}
|
270
281
|
switch (event.key) {
|
271
282
|
case 'Enter':
|
283
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
284
|
+
event.preventDefault();
|
285
|
+
break;
|
286
|
+
}
|
272
287
|
// eslint-disable-next-line no-restricted-syntax
|
273
288
|
event.stopPropagation();
|
274
289
|
if (__classPrivateFieldGet(this, _TreeViewSubTreeNodeElement_instances, "a", _TreeViewSubTreeNodeElement_checkboxElement_get)) {
|
@@ -290,6 +305,10 @@ _TreeViewSubTreeNodeElement_handleKeyboardEvent = function _TreeViewSubTreeNodeE
|
|
290
305
|
this.collapse();
|
291
306
|
break;
|
292
307
|
case ' ':
|
308
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
309
|
+
event.preventDefault();
|
310
|
+
break;
|
311
|
+
}
|
293
312
|
if (__classPrivateFieldGet(this, _TreeViewSubTreeNodeElement_instances, "a", _TreeViewSubTreeNodeElement_checkboxElement_get)) {
|
294
313
|
// eslint-disable-next-line no-restricted-syntax
|
295
314
|
event.stopPropagation();
|
@@ -309,6 +328,10 @@ _TreeViewSubTreeNodeElement_handleKeyboardEvent = function _TreeViewSubTreeNodeE
|
|
309
328
|
}
|
310
329
|
};
|
311
330
|
_TreeViewSubTreeNodeElement_handleCheckboxEvent = function _TreeViewSubTreeNodeElement_handleCheckboxEvent(event) {
|
331
|
+
if (this.treeView?.getNodeDisabledValue(this.node)) {
|
332
|
+
event.preventDefault();
|
333
|
+
return;
|
334
|
+
}
|
312
335
|
if (event.type !== 'click')
|
313
336
|
return;
|
314
337
|
this.toggleChecked();
|