openproject-primer_view_components 0.70.5 → 0.72.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 +20 -0
- data/app/assets/javascripts/components/primer/alpha/segmented_control.d.ts +2 -2
- data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view.d.ts +11 -1
- data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
- data/app/assets/javascripts/components/primer/open_project/filterable_tree_view.d.ts +29 -0
- data/app/assets/javascripts/components/primer/primer.d.ts +5 -4
- 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/{open_project → alpha}/file_tree_view/directory_node.rb +1 -1
- data/app/components/primer/{open_project → alpha}/file_tree_view/file_node.rb +1 -1
- data/app/components/primer/{open_project → alpha}/file_tree_view.rb +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/{open_project → alpha}/skeleton_box.css.json +1 -1
- data/app/components/primer/{open_project → alpha}/skeleton_box.css.map +1 -1
- data/app/components/primer/{open_project → alpha}/skeleton_box.pcss +1 -1
- data/app/components/primer/{open_project → alpha}/skeleton_box.rb +3 -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 → alpha}/tree_view/icon.rb +2 -2
- data/app/components/primer/{open_project → alpha}/tree_view/icon_pair.rb +4 -4
- data/app/components/primer/{open_project → alpha}/tree_view/leading_action.rb +2 -2
- data/app/components/primer/{open_project → alpha}/tree_view/leaf_node.rb +6 -6
- data/app/components/primer/{open_project → alpha}/tree_view/loading_failure_message.rb +2 -2
- data/app/components/primer/{open_project → alpha}/tree_view/node.rb +30 -14
- data/app/components/primer/{open_project → alpha}/tree_view/skeleton_loader.html.erb +3 -3
- data/app/components/primer/{open_project → alpha}/tree_view/skeleton_loader.rb +4 -4
- data/app/components/primer/{open_project → alpha}/tree_view/spinner_loader.html.erb +2 -2
- data/app/components/primer/{open_project → alpha}/tree_view/spinner_loader.rb +4 -4
- data/app/components/primer/{open_project → alpha}/tree_view/sub_tree.html.erb +1 -1
- data/app/components/primer/{open_project → alpha}/tree_view/sub_tree.rb +10 -10
- data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_container.rb +2 -2
- data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_node.rb +28 -18
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view.d.ts +11 -1
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view.js +120 -20
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view.ts +137 -18
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.js +0 -1
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.ts +0 -1
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.js +27 -4
- data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.ts +36 -5
- data/app/components/primer/{open_project → alpha}/tree_view/visual.rb +2 -2
- data/app/components/primer/alpha/tree_view.css +1 -0
- data/app/components/primer/{open_project → alpha}/tree_view.css.json +8 -1
- data/app/components/primer/alpha/tree_view.css.map +1 -0
- data/app/components/primer/alpha/tree_view.html.erb +12 -0
- data/app/components/primer/{open_project → alpha}/tree_view.pcss +39 -0
- data/app/components/primer/{open_project → alpha}/tree_view.rb +20 -12
- data/app/components/primer/beta/breadcrumbs.css +1 -1
- data/app/components/primer/beta/breadcrumbs.css.json +0 -1
- data/app/components/primer/beta/breadcrumbs.css.map +1 -1
- data/app/components/primer/beta/breadcrumbs.pcss +2 -8
- data/app/components/primer/beta/progress_bar.css +1 -1
- data/app/components/primer/beta/progress_bar.css.map +1 -1
- data/app/components/primer/beta/progress_bar.pcss +3 -2
- data/app/components/primer/beta/relative_time.rb +3 -0
- 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/primer.d.ts +5 -4
- data/app/components/primer/primer.js +5 -4
- data/app/components/primer/primer.pcss +2 -2
- data/app/components/primer/primer.ts +5 -4
- data/app/controllers/primer/view_components/tree_view_items_controller.rb +1 -1
- data/app/forms/check_box_with_nested_form.rb +10 -10
- data/app/forms/radio_button_with_nested_form.rb +16 -16
- data/app/lib/primer/experimental_slot_helpers.rb +2 -2
- data/app/lib/primer/forms/base_component.rb +1 -1
- data/app/lib/primer/forms/dsl/text_field_input.rb +2 -0
- data/app/views/primer/view_components/tree_view_items/async_alpha.html_fragment.erb +1 -1
- data/app/views/primer/view_components/tree_view_items/index.html_fragment.erb +1 -1
- data/config/locales/en.yml +20 -0
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/{open_project → alpha}/file_tree_view_preview/default.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/file_tree_view_preview/playground.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/file_tree_view_preview.rb +1 -1
- data/previews/primer/{open_project → alpha}/skeleton_box_preview.rb +3 -3
- data/previews/primer/{open_project → alpha}/tree_view_preview/async_alpha.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/tree_view_preview/buttons.html.erb +5 -5
- data/previews/primer/{open_project → alpha}/tree_view_preview/default.html.erb +5 -5
- data/previews/primer/{open_project → alpha}/tree_view_preview/empty.html.erb +1 -1
- data/previews/primer/alpha/tree_view_preview/form_input.html.erb +14 -0
- data/previews/primer/{open_project → alpha}/tree_view_preview/leaf_node_playground.html.erb +2 -2
- data/previews/primer/{open_project → alpha}/tree_view_preview/links.html.erb +5 -5
- data/previews/primer/{open_project → alpha}/tree_view_preview/loading_failure.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/tree_view_preview/loading_skeleton.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/tree_view_preview/loading_spinner.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/tree_view_preview/playground.html.erb +1 -1
- data/previews/primer/{open_project → alpha}/tree_view_preview.rb +34 -15
- 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/stress_test.html.erb +28 -0
- data/previews/primer/open_project/filterable_tree_view_preview.rb +125 -0
- data/static/arguments.json +1685 -1581
- data/static/audited_at.json +19 -17
- data/static/classes.json +5 -5
- data/static/constants.json +137 -98
- data/static/info_arch.json +6396 -6146
- data/static/previews.json +120 -21
- data/static/statuses.json +19 -17
- metadata +102 -84
- data/app/components/primer/open_project/tree_view.css +0 -1
- data/app/components/primer/open_project/tree_view.css.map +0 -1
- data/app/components/primer/open_project/tree_view.html.erb +0 -7
- /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.d.ts +0 -0
- /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.d.ts +0 -0
- /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.d.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/file_tree_view/directory_node.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/file_tree_view/file_node.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/skeleton_box.css +0 -0
- /data/app/components/primer/{open_project → alpha}/skeleton_box.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/icon.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/icon_pair.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/leading_action.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/leaf_node.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/loading_failure_message.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/node.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_container.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_node.html.erb +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.d.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.js +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.d.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.d.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.js +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.ts +0 -0
- /data/app/components/primer/{open_project → alpha}/tree_view/visual.html.erb +0 -0
@@ -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
|
data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.js
RENAMED
@@ -24,6 +24,5 @@ TreeViewIncludeFragmentElement = __decorate([
|
|
24
24
|
export { TreeViewIncludeFragmentElement };
|
25
25
|
if (!window.customElements.get('tree-view-include-fragment')) {
|
26
26
|
window.TreeViewIncludeFragmentElement = TreeViewIncludeFragmentElement;
|
27
|
-
// eslint-disable-next-line custom-elements/extends-correct-class
|
28
27
|
window.customElements.define('tree-view-include-fragment', TreeViewIncludeFragmentElement);
|
29
28
|
}
|
data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.ts
RENAMED
@@ -18,7 +18,6 @@ export class TreeViewIncludeFragmentElement extends IncludeFragmentElement {
|
|
18
18
|
|
19
19
|
if (!window.customElements.get('tree-view-include-fragment')) {
|
20
20
|
window.TreeViewIncludeFragmentElement = TreeViewIncludeFragmentElement
|
21
|
-
// eslint-disable-next-line custom-elements/extends-correct-class
|
22
21
|
window.customElements.define('tree-view-include-fragment', TreeViewIncludeFragmentElement)
|
23
22
|
}
|
24
23
|
|
data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.d.ts
RENAMED
@@ -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 {
|
data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.js
RENAMED
@@ -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();
|
data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.ts
RENAMED
@@ -7,6 +7,8 @@ import type {TreeViewNodeInfo} from '../../shared_events'
|
|
7
7
|
|
8
8
|
type LoadingState = 'loading' | 'error' | 'success'
|
9
9
|
|
10
|
+
export type SelectStrategy = 'self' | 'descendants' | 'mixed_descendants'
|
11
|
+
|
10
12
|
@controller
|
11
13
|
export class TreeViewSubTreeNodeElement extends HTMLElement {
|
12
14
|
@target node: HTMLElement
|
@@ -70,7 +72,7 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
70
72
|
)
|
71
73
|
|
72
74
|
const checkedMutationObserver = new MutationObserver(() => {
|
73
|
-
if (this.selectStrategy !== '
|
75
|
+
if (this.selectStrategy !== 'mixed_descendants') return
|
74
76
|
|
75
77
|
let checkType = 'unknown'
|
76
78
|
|
@@ -127,8 +129,12 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
127
129
|
this.#update()
|
128
130
|
}
|
129
131
|
|
130
|
-
get selectStrategy():
|
131
|
-
return this.node.getAttribute('data-select-strategy') || 'descendants'
|
132
|
+
get selectStrategy(): SelectStrategy {
|
133
|
+
return (this.node.getAttribute('data-select-strategy') || 'descendants') as SelectStrategy
|
134
|
+
}
|
135
|
+
|
136
|
+
get level(): number {
|
137
|
+
return parseInt(this.node.getAttribute('aria-level') || '0')
|
132
138
|
}
|
133
139
|
|
134
140
|
disconnectedCallback() {
|
@@ -211,6 +217,12 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
211
217
|
}
|
212
218
|
}
|
213
219
|
|
220
|
+
*eachDirectDescendantSubTreeNode(): Generator<TreeViewSubTreeNodeElement> {
|
221
|
+
for (const subTree of this.subTree.querySelectorAll(':scope > tree-view-sub-tree-node')) {
|
222
|
+
yield subTree as TreeViewSubTreeNodeElement
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
214
226
|
*eachDescendantNode(): Generator<Element> {
|
215
227
|
for (const node of this.subTree.querySelectorAll('[role=treeitem]')) {
|
216
228
|
yield node
|
@@ -299,6 +311,11 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
299
311
|
|
300
312
|
switch (event.key) {
|
301
313
|
case 'Enter':
|
314
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
315
|
+
event.preventDefault()
|
316
|
+
break
|
317
|
+
}
|
318
|
+
|
302
319
|
// eslint-disable-next-line no-restricted-syntax
|
303
320
|
event.stopPropagation()
|
304
321
|
|
@@ -324,6 +341,11 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
324
341
|
break
|
325
342
|
|
326
343
|
case ' ':
|
344
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
345
|
+
event.preventDefault()
|
346
|
+
break
|
347
|
+
}
|
348
|
+
|
327
349
|
if (this.#checkboxElement) {
|
328
350
|
// eslint-disable-next-line no-restricted-syntax
|
329
351
|
event.stopPropagation()
|
@@ -344,6 +366,11 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
344
366
|
}
|
345
367
|
|
346
368
|
#handleCheckboxEvent(event: Event) {
|
369
|
+
if (this.treeView?.getNodeDisabledValue(this.node)) {
|
370
|
+
event.preventDefault()
|
371
|
+
return
|
372
|
+
}
|
373
|
+
|
347
374
|
if (event.type !== 'click') return
|
348
375
|
|
349
376
|
this.toggleChecked()
|
@@ -354,13 +381,13 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
354
381
|
}
|
355
382
|
|
356
383
|
toggleChecked() {
|
357
|
-
const checkValue = this.node
|
384
|
+
const checkValue = this.treeView?.getNodeCheckedValue(this.node) || 'false'
|
358
385
|
const newCheckValue = checkValue === 'false' ? 'true' : 'false'
|
359
386
|
const nodeInfos: TreeViewNodeInfo[] = []
|
360
387
|
const rootInfo = this.treeView?.infoFromNode(this.node, newCheckValue)
|
361
388
|
if (rootInfo) nodeInfos.push(rootInfo)
|
362
389
|
|
363
|
-
if (this.selectStrategy === 'descendants') {
|
390
|
+
if (this.selectStrategy === 'descendants' || this.selectStrategy === 'mixed_descendants') {
|
364
391
|
for (const node of this.eachDescendantNode()) {
|
365
392
|
const info = this.treeView?.infoFromNode(node, newCheckValue)
|
366
393
|
if (info) nodeInfos.push(info)
|
@@ -439,6 +466,10 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
439
466
|
get #checkboxElement(): HTMLElement | null {
|
440
467
|
return this.querySelector('.TreeViewItemCheckbox')
|
441
468
|
}
|
469
|
+
|
470
|
+
changeSelectStrategy(newStrategy: SelectStrategy) {
|
471
|
+
this.node.setAttribute('data-select-strategy', newStrategy)
|
472
|
+
}
|
442
473
|
}
|
443
474
|
|
444
475
|
if (!window.customElements.get('tree-view-sub-tree-node')) {
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Primer
|
4
|
-
module
|
4
|
+
module Alpha
|
5
5
|
class TreeView
|
6
6
|
# A `TreeView` visual, either leading or trailing.
|
7
7
|
#
|
8
|
-
# This component is part of the <%= link_to_component(Primer::
|
8
|
+
# This component is part of the <%= link_to_component(Primer::Alpha::TreeView) %> component and should
|
9
9
|
# not be used directly.
|
10
10
|
class Visual < Primer::Component
|
11
11
|
# @param id [String] This visual's HTML ID.
|
@@ -0,0 +1 @@
|
|
1
|
+
.TreeViewRootUlStyles{list-style:none;margin:0;padding:0}.TreeViewRootUlStyles .TreeViewItem{outline:none}:is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div{box-shadow:var(--boxShadow-thick) var(--fgColor-accent)}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div{outline:2px solid HighlightText;outline-offset:-2}}[data-has-leading-action]:is(.TreeViewRootUlStyles .TreeViewItem){--has-leading-action:1}.TreeViewRootUlStyles .TreeViewItemContainer{--level:1;--toggle-width:1rem;--min-item-height:2rem;border-radius:var(--borderRadius-medium);color:var(--fgColor-default);display:grid;font-size:var(--text-body-size-medium);grid-template-areas:"spacer leadingAction toggle content";grid-template-columns:var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;position:relative;width:100%;--leading-action-width:calc(var(--has-leading-action, 0)*1.5rem);--spacer-width:calc((var(--level) - 1)*(var(--toggle-width)/2))}:is(.TreeViewRootUlStyles .TreeViewItemContainer):hover{background-color:var(--control-transparent-bgColor-hover)}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItemContainer):hover{outline:2px solid #0000;outline-offset:-2px}}@media (pointer:coarse){.TreeViewRootUlStyles .TreeViewItemContainer{--toggle-width:1.5rem;--min-item-height:2.75rem}}:is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover{background-color:initial;cursor:default}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover{outline:none}}:is(.TreeViewRootUlStyles .TreeViewItemContainer):has([role=treeitem]:focus-visible){box-shadow:var(--boxShadow-thick) var(--fgColor-accent)}.TreeViewRootUlStyles:where([data-omit-spacer=true]) .TreeViewItemContainer{grid-template-columns:0 0 0 1fr}.TreeViewRootUlStyles .TreeViewItem>.TreeViewItemContainer:has(.TreeViewItemContent[aria-current=true]){background-color:var(--control-transparent-bgColor-selected)}:is(.TreeViewRootUlStyles .TreeViewItem>.TreeViewItemContainer:has(.TreeViewItemContent[aria-current=true])):after{background-color:var(--fgColor-accent);border-radius:var(--borderRadius-medium);content:"";height:1.5rem;left:calc(var(--base-size-8)*-1);position:absolute;top:calc(50% - var(--base-size-12));width:.25rem}@media (forced-colors:active){:is(.TreeViewRootUlStyles .TreeViewItem>.TreeViewItemContainer:has(.TreeViewItemContent[aria-current=true])):after{background-color:HighlightText}}.TreeViewRootUlStyles .TreeViewItemToggle{align-items:flex-start;color:var(--fgColor-muted);cursor:pointer;display:flex;grid-area:toggle;height:100%;justify-content:center;padding-top:calc(var(--min-item-height)/2 - var(--base-size-12)/2)}.TreeViewRootUlStyles .TreeViewItemToggleHover:hover{background-color:var(--control-transparent-bgColor-hover)}.TreeViewRootUlStyles .TreeViewItemToggleEnd{border-bottom-left-radius:var(--borderRadius-medium);border-top-left-radius:var(--borderRadius-medium)}.TreeViewRootUlStyles a.TreeViewItemContent:hover,.TreeViewRootUlStyles button.TreeViewItemContent:hover{-webkit-text-decoration:underline;text-decoration:underline;text-decoration-color:var(--control-fgColor-rest)}.TreeViewRootUlStyles :has(.TreeViewItemContent[aria-disabled=true]){cursor:not-allowed}.TreeViewRootUlStyles .TreeViewItemContent{cursor:pointer;display:flex;gap:var(--stack-gap-condensed);grid-area:content;height:100%;line-height:var(--custom-line-height,var(--text-body-lineHeight-medium,1.4285));outline:none;padding:0 var(--base-size-8);padding-bottom:calc((var(--min-item-height) - var(--custom-line-height, 1.3rem))/2);padding-top:calc((var(--min-item-height) - var(--custom-line-height, 1.3rem))/2)}.TreeViewRootUlStyles .TreeViewItemContent,:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox{background-color:initial;border:none;text-align:left;touch-action:manipulation;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox{border-radius:var(--borderRadius-medium);color:var(--control-fgColor-rest);position:relative;transition:background 33.333ms linear}[aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox{background:var(--control-checked-bgColor-rest);border-color:var(--control-checked-borderColor-rest);transition:background-color,border-color 80ms cubic-bezier(.32,0,.67,0) 0s}:is([aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):before{animation:checkmarkIn 80ms cubic-bezier(.65,0,.35,1) 80ms forwards;transition:visibility 0s linear 0s;visibility:visible}[aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox{background:var(--control-checked-bgColor-rest);border-color:var(--control-checked-borderColor-rest);transition:background-color,border-color 80ms cubic-bezier(.32,0,.67,0) 0s}:is([aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):before{animation:checkmarkIn 80ms cubic-bezier(.65,0,.35,1) 80ms forwards;clip-path:none;mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIyIiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTAgMiI+PHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAxYTEgMSAwIDAgMSAxLTFoOGExIDEgMCAxIDEgMCAySDFhMSAxIDAgMCAxLTEtMSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+");visibility:visible}[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent){pointer-events:none}[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemContentText{color:var(--control-fgColor-disabled)}:is([aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemVisual) svg,[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemVisual{fill:var(--control-fgColor-disabled)}@media (hover:hover){:is([aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):hover{cursor:not-allowed}[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent):hover{background-color:initial;cursor:not-allowed}}.TreeViewRootUlStyles .TreeViewItemContentText{color:var(--control-fgColor-rest);flex:1 1 auto;width:0}.TreeViewRootUlStyles:where([data-truncate-text=true]) .TreeViewItemContentText{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.TreeViewRootUlStyles:where([data-truncate-text=false]) .TreeViewItemContentText{word-break:break-word}.TreeViewRootUlStyles .TreeViewItemVisual{align-items:center;color:var(--fgColor-muted);display:flex;height:var(--custom-line-height,1.3rem)}.TreeViewRootUlStyles .TreeViewItemLeadingAction{color:var(--fgColor-muted);display:flex;grid-area:leadingAction}:is(.TreeViewRootUlStyles .TreeViewItemLeadingAction)>button{flex-shrink:1}.TreeViewRootUlStyles .TreeViewItemLevelLine{border-color:var(--borderColor-muted);border-right:var(--borderWidth-thin) solid;height:100%;width:100%}@media (hover:hover){.TreeViewRootUlStyles .TreeViewItemLevelLine{border-color:#0000}.TreeViewRootUlStyles:focus-within .TreeViewItemLevelLine,.TreeViewRootUlStyles:hover .TreeViewItemLevelLine{border-color:var(--borderColor-muted)}}.TreeViewRootUlStyles .TreeViewVisuallyHidden{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.TreeViewSkeletonItemContainerStyle{align-items:center;column-gap:.5rem;display:flex;height:2rem}@media (pointer:coarse){.TreeViewSkeletonItemContainerStyle{height:2.75rem}}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+1){--tree-item-loading-width:67%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+2){--tree-item-loading-width:47%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+3){--tree-item-loading-width:73%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+4){--tree-item-loading-width:64%}.TreeViewSkeletonItemContainerStyle:nth-of-type(5n+5){--tree-item-loading-width:50%}.TreeItemSkeletonTextStyles{width:var(--tree-item-loading-width,67%)}.TreeViewFailureMessage{align-items:center;display:grid;gap:.5rem;grid-template-columns:auto 1fr;width:100%}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
{
|
2
|
-
"name": "
|
2
|
+
"name": "alpha/tree_view",
|
3
3
|
"selectors": [
|
4
4
|
".TreeViewRootUlStyles",
|
5
5
|
".TreeViewRootUlStyles .TreeViewItem",
|
@@ -17,12 +17,19 @@
|
|
17
17
|
".TreeViewRootUlStyles .TreeViewItemToggleEnd",
|
18
18
|
".TreeViewRootUlStyles a.TreeViewItemContent:hover",
|
19
19
|
".TreeViewRootUlStyles button.TreeViewItemContent:hover",
|
20
|
+
".TreeViewRootUlStyles :has(.TreeViewItemContent[aria-disabled=true])",
|
20
21
|
".TreeViewRootUlStyles .TreeViewItemContent",
|
21
22
|
":is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox",
|
22
23
|
"[aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox",
|
23
24
|
":is([aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):before",
|
24
25
|
"[aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox",
|
25
26
|
":is([aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):before",
|
27
|
+
"[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent)",
|
28
|
+
"[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemContentText",
|
29
|
+
":is([aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemVisual) svg",
|
30
|
+
"[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemVisual",
|
31
|
+
":is([aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):hover",
|
32
|
+
"[aria-disabled=true]:is(.TreeViewRootUlStyles .TreeViewItemContent):hover",
|
26
33
|
".TreeViewRootUlStyles .TreeViewItemContentText",
|
27
34
|
".TreeViewRootUlStyles:where([data-truncate-text=true]) .TreeViewItemContentText",
|
28
35
|
".TreeViewRootUlStyles:where([data-truncate-text=false]) .TreeViewItemContentText",
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["tree_view.pcss"],"names":[],"mappings":"AAEA,sBAGE,eAAgB,CADhB,QAAS,CADT,SAsUF,CArTE,oCACE,YAeF,CAbE,2DACE,uDAOF,CALE,8BAHF,2DAII,+BAAgC,CAEhC,iBAEJ,CADE,CAGF,kEACE,sBACF,CAGF,6CACE,SAAU,CACV,mBAAoB,CACpB,sBAAuB,CAOvB,wCAAyC,CADzC,4BAA6B,CAH7B,YAAa,CAEb,sCAAuC,CAIvC,yDAA0D,CAD1D,6FAA8F,CAN9F,iBAAkB,CAElB,UAAW,CAOX,gEAAmE,CACnE,+DA4BF,CA1BE,wDACE,yDAMF,CAJE,8BAHF,wDAII,uBAA8B,CAC9B,mBAEJ,CADE,CAGF,wBA1BF,6CA2BI,qBAAsB,CACtB,yBAeJ,CAdE,CAEA,qFAEE,wBAA6B,CAD7B,cAMF,CAHE,8BAJF,qFAKI,YAEJ,CADE,CAGF,qFACE,uDACF,CAGF,4EACE,+BACF,CAGA,wGACE,4DAwBF,CApBE,mHAaE,sCAAuC,CACvC,wCAAyC,CARzC,UAAW,CADX,aAAc,CAFd,gCAAmC,CAFnC,iBAAkB,CAClB,mCAAoC,CAEpC,YAeF,CAHE,8BAhBF,mHAiBI,8BAEJ,CADE,CAIJ,0CAWE,sBAAuB,CAHvB,0BAA2B,CAI3B,cAAe,CAXf,YAAa,CAQb,gBAAiB,CAPjB,WAAY,CAQZ,sBAAuB,CAHvB,kEAMF,CAEA,qDACE,yDACF,CAEA,6CAEE,oDAAqD,CADrD,iDAEF,CAGA,yGACE,iCAA0B,CAA1B,yBAA0B,CAC1B,iDACF,CAEA,qEACE,kBACF,CAEA,2CAWE,cAAe,CAVf,YAAa,CAmBb,8BAA+B,CAD/B,iBAAkB,CAjBlB,WAAY,CAgBZ,+EAAkF,CAdlF,YAAa,CADb,4BAA6B,CAc7B,mFAAsF,CAFtF,gFAqFF,CA9EE,iHAfA,wBAA6B,CAC7B,WAAY,CAHZ,eAAgB,CAIhB,yBAA0B,CAH1B,wBAAiB,CAAjB,gBAAiB,CAIjB,uCAuBA,CAXA,sEAOE,wCAAyC,CALzC,iCAAkC,CADlC,iBAAkB,CAOlB,qCAGF,CAGE,yFACE,8CAA+C,CAC/C,oDAAqD,CACrD,0EAQF,CALE,qGAGE,kEAAwE,CADxE,kCAAmC,CADnC,kBAGF,CAKF,0FACE,8CAA+C,CAC/C,oDAAqD,CACrD,0EASF,CANE,sGAGE,kEAAwE,CACxE,cAAe,CAFf,wUAAia,CADja,kBAIF,CAIJ,oEACE,mBAgCF,CA9BE,6FACE,qCACF,CAME,yLACE,oCACF,CAKA,qBAEE,qGACE,kBACF,CAMF,0EAEE,wBAA6B,CAD7B,kBAEF,CARA,CAaN,+CACE,iCAAkC,CAClC,aAAc,CACd,OACF,CAEA,gFACE,eAAgB,CAChB,sBAAuB,CACvB,kBACF,CAEA,iFAEE,qBACF,CAEA,0CAOE,kBAAmB,CADnB,0BAA2B,CAL3B,YAAa,CAIb,uCAGF,CAEA,iDAEE,0BAA2B,CAD3B,YAAa,CAEb,uBAKF,CAHE,6DACE,aACF,CAGF,6CAQE,qCAAsC,CACtC,0CAA2C,CAP3C,WAAY,CADZ,UASF,CAQA,qBACE,6CACE,kBACF,CAEA,6GAEE,qCACF,CACF,CAEA,8CAGE,UAAW,CAGX,WAAY,CACZ,eAAgB,CAHhB,SAAU,CAHV,iBAAkB,CAClB,SAAU,CAMV,kBAAsB,CAEtB,cAAe,CADf,kBAEF,CAGF,oCAEE,kBAAmB,CACnB,gBAAkB,CAFlB,YAAa,CAGb,WAyBF,CAvBE,wBANF,oCAOI,cAsBJ,CArBE,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAEA,sDACE,6BACF,CAGF,4BACE,wCACF,CAEA,wBAKE,kBAAmB,CAJnB,YAAa,CAEb,SAAW,CADX,8BAA+B,CAE/B,UAEF","file":"tree_view.css","sourcesContent":["/* stylelint-disable selector-max-type -- Copied from primer/react */\n\n.TreeViewRootUlStyles {\n padding: 0;\n margin: 0;\n list-style: none;\n\n /*\n * WARNING: This is a performance optimization.\n *\n * We define styles for the tree items at the root level of the tree\n * to avoid recomputing the styles for each item when the tree updates.\n * We're sacrificing maintainability for performance because TreeView\n * needs to be performant enough to handle large trees (thousands of items).\n *\n * This is intended to be a temporary solution until we can improve the\n * performance of our styling patterns.\n *\n * Do NOT copy this pattern without understanding the tradeoffs.\n */\n & .TreeViewItem {\n outline: none;\n\n &:focus-visible > div {\n box-shadow: var(--boxShadow-thick) var(--fgColor-accent);\n\n @media (forced-colors: active) {\n outline: 2px solid HighlightText;\n /* stylelint-disable-next-line declaration-property-value-no-unknown -- Copied from primer/react */\n outline-offset: -2;\n }\n }\n\n &[data-has-leading-action] {\n --has-leading-action: 1;\n }\n }\n\n & .TreeViewItemContainer {\n --level: 1;\n --toggle-width: 1rem;\n --min-item-height: 2rem;\n\n position: relative;\n display: grid;\n width: 100%;\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-default);\n border-radius: var(--borderRadius-medium);\n grid-template-columns: var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;\n grid-template-areas: 'spacer leadingAction toggle content';\n\n --leading-action-width: calc(var(--has-leading-action, 0) * 1.5rem);\n --spacer-width: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2));\n\n &:hover {\n background-color: var(--control-transparent-bgColor-hover);\n\n @media (forced-colors: active) {\n outline: 2px solid transparent;\n outline-offset: -2px;\n }\n }\n\n @media (pointer: coarse) {\n --toggle-width: 1.5rem;\n --min-item-height: 2.75rem;\n }\n\n &:has(.TreeViewFailureMessage):hover {\n cursor: default;\n background-color: transparent;\n\n @media (forced-colors: active) {\n outline: none;\n }\n }\n\n &:has([role='treeitem']:focus-visible) {\n box-shadow: var(--boxShadow-thick) var(--fgColor-accent);\n }\n }\n\n &:where([data-omit-spacer='true']) .TreeViewItemContainer {\n grid-template-columns: 0 0 0 1fr;\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n & .TreeViewItem > .TreeViewItemContainer:has(.TreeViewItemContent[aria-current='true']) {\n background-color: var(--control-transparent-bgColor-selected);\n\n /* Current item indicator */\n /* stylelint-disable-next-line selector-max-specificity -- Copied from primer/react */\n &::after {\n position: absolute;\n top: calc(50% - var(--base-size-12));\n left: calc(-1 * var(--base-size-8));\n width: 0.25rem;\n height: 1.5rem;\n content: '';\n\n /*\n * Use fgColor accent for consistency across all themes. Using the \"correct\" variable,\n * --bgColor-accent-emphasis, causes vrt failures for dark high contrast mode\n */\n /* stylelint-disable-next-line primer/colors */\n background-color: var(--fgColor-accent);\n border-radius: var(--borderRadius-medium);\n\n @media (forced-colors: active) {\n background-color: HighlightText;\n }\n }\n }\n\n & .TreeViewItemToggle {\n display: flex;\n height: 100%;\n\n /* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap\n across more lines. */\n /* stylelint-disable-next-line primer/spacing */\n padding-top: calc(var(--min-item-height) / 2 - var(--base-size-12) / 2);\n color: var(--fgColor-muted);\n grid-area: toggle;\n justify-content: center;\n align-items: flex-start;\n cursor: pointer;\n }\n\n & .TreeViewItemToggleHover:hover {\n background-color: var(--control-transparent-bgColor-hover);\n }\n\n & .TreeViewItemToggleEnd {\n border-top-left-radius: var(--borderRadius-medium);\n border-bottom-left-radius: var(--borderRadius-medium);\n }\n\n /* stylelint-disable-next-line selector-no-qualifying-type */\n & a.TreeViewItemContent:hover, button.TreeViewItemContent:hover {\n text-decoration: underline;\n text-decoration-color: var(--control-fgColor-rest);\n }\n\n & :has(.TreeViewItemContent[aria-disabled=\"true\"]) {\n cursor: not-allowed;\n }\n\n & .TreeViewItemContent {\n display: flex;\n height: 100%;\n padding: 0 var(--base-size-8);\n outline: none;\n text-align: left;\n user-select: none;\n background-color: transparent;\n border: none;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n cursor: pointer;\n\n /* The dynamic top and bottom padding to maintain the minimum item height for single line items */\n /* stylelint-disable-next-line primer/spacing */\n padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);\n /* stylelint-disable-next-line primer/spacing */\n padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);\n line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285));\n grid-area: content;\n gap: var(--stack-gap-condensed);\n\n & .TreeViewItemCheckbox {\n position: relative;\n color: var(--control-fgColor-rest);\n text-align: left;\n user-select: none;\n background-color: transparent;\n border: none;\n border-radius: var(--borderRadius-medium);\n transition: background 33.333ms linear;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n\n &[aria-checked='true'] {\n & .FormControl-checkbox {\n background: var(--control-checked-bgColor-rest);\n border-color: var(--control-checked-borderColor-rest);\n transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; /* unchecked -> checked */\n\n /* stylelint-disable-next-line max-nesting-depth, selector-max-specificity -- Copied from primer/react */\n &::before {\n visibility: visible;\n transition: visibility 0s linear 0s;\n animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;\n }\n }\n }\n\n &[aria-checked='mixed'] {\n & .FormControl-checkbox {\n background: var(--control-checked-bgColor-rest);\n border-color: var(--control-checked-borderColor-rest);\n transition: background-color, border-color 80ms cubic-bezier(0.32, 0, 0.67, 0) 0ms; /* unchecked -> checked */\n\n /* stylelint-disable-next-line max-nesting-depth, selector-max-specificity -- Copied from primer/react */\n &::before {\n visibility: visible;\n mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMiIgdmlld0JveD0iMCAwIDEwIDIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMCAxQzAgMC40NDc3MTUgMC40NDc3MTUgMCAxIDBIOUM5LjU1MjI5IDAgMTAgMC40NDc3MTUgMTAgMUMxMCAxLjU1MjI4IDkuNTUyMjkgMiA5IDJIMUMwLjQ0NzcxNSAyIDAgMS41NTIyOCAwIDFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K');\n animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;\n clip-path: none;\n }\n }\n }\n\n &[aria-disabled='true'] {\n pointer-events: none;\n\n & .TreeViewItemContentText {\n color: var(--control-fgColor-disabled);\n }\n\n & .TreeViewItemVisual {\n fill: var(--control-fgColor-disabled);\n\n /* stylelint-disable-next-line max-nesting-depth, selector-max-compound-selectors, selector-max-specificity */\n & svg {\n fill: var(--control-fgColor-disabled);\n }\n }\n\n & .FormControl-checkbox {\n /* stylelint-disable-next-line max-nesting-depth */\n @media (hover: hover) {\n /* stylelint-disable-next-line max-nesting-depth, selector-max-specificity */\n &:hover {\n cursor: not-allowed;\n }\n }\n }\n\n @media (hover: hover) {\n /* stylelint-disable-next-line max-nesting-depth */\n &:hover {\n cursor: not-allowed;\n background-color: transparent;\n }\n }\n }\n }\n\n & .TreeViewItemContentText {\n color: var(--control-fgColor-rest);\n flex: 1 1 auto;\n width: 0;\n }\n\n &:where([data-truncate-text='true']) .TreeViewItemContentText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &:where([data-truncate-text='false']) .TreeViewItemContentText {\n /* stylelint-disable-next-line declaration-property-value-keyword-no-deprecated -- Copied from primer/react */\n word-break: break-word;\n }\n\n & .TreeViewItemVisual {\n display: flex;\n\n /* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap\n across more lines. */\n height: var(--custom-line-height, 1.3rem);\n color: var(--fgColor-muted);\n align-items: center;\n }\n\n & .TreeViewItemLeadingAction {\n display: flex;\n color: var(--fgColor-muted);\n grid-area: leadingAction;\n\n & > button {\n flex-shrink: 1;\n }\n }\n\n & .TreeViewItemLevelLine {\n width: 100%;\n height: 100%;\n\n /*\n * On devices without hover, the nesting indicator lines\n * appear at all times.\n */\n border-color: var(--borderColor-muted);\n border-right: var(--borderWidth-thin) solid;\n }\n\n /*\n * On devices with :hover support, the nesting indicator lines\n * fade in when the user mouses over the entire component,\n * or when there's focus inside the component. This makes\n * sure the component remains simple when not in use.\n */\n @media (hover: hover) {\n .TreeViewItemLevelLine {\n border-color: transparent;\n }\n\n &:hover .TreeViewItemLevelLine,\n &:focus-within .TreeViewItemLevelLine {\n border-color: var(--borderColor-muted);\n }\n }\n\n & .TreeViewVisuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n}\n\n.TreeViewSkeletonItemContainerStyle {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n height: 2rem;\n\n @media (pointer: coarse) {\n height: 2.75rem;\n }\n\n &:nth-of-type(5n + 1) {\n --tree-item-loading-width: 67%;\n }\n\n &:nth-of-type(5n + 2) {\n --tree-item-loading-width: 47%;\n }\n\n &:nth-of-type(5n + 3) {\n --tree-item-loading-width: 73%;\n }\n\n &:nth-of-type(5n + 4) {\n --tree-item-loading-width: 64%;\n }\n\n &:nth-of-type(5n + 5) {\n --tree-item-loading-width: 50%;\n }\n}\n\n.TreeItemSkeletonTextStyles {\n width: var(--tree-item-loading-width, 67%);\n}\n\n.TreeViewFailureMessage {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 0.5rem;\n width: 100%;\n align-items: center;\n}\n"]}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<tree-view>
|
2
|
+
<% if acts_as_form_input? %>
|
3
|
+
<%= @form_arguments[:builder].hidden_field(@form_arguments[:name], multiple: true, skip_default_ids: true, form: "", data: { target: "tree-view.formInputPrototype" }) %>
|
4
|
+
<div data-target="tree-view.formInputContainer"></div>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
8
|
+
<% nodes.each do |node| %>
|
9
|
+
<%= node %>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
12
|
+
</tree-view>
|