primer_view_components 0.43.5 → 0.44.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/tree_view/tree_view.d.ts +39 -0
- data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view_icon_pair_element.d.ts +15 -0
- data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view_include_fragment_element.d.ts +9 -0
- data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view_roving_tab_index.d.ts +3 -0
- data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.d.ts +42 -0
- data/app/assets/javascripts/components/primer/primer.d.ts +4 -0
- data/app/assets/javascripts/components/primer/shared_events.d.ts +15 -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/file_tree_view/directory_node.html.erb +5 -0
- data/app/components/primer/alpha/file_tree_view/directory_node.rb +24 -0
- data/app/components/primer/alpha/file_tree_view/file_node.html.erb +2 -0
- data/app/components/primer/alpha/file_tree_view/file_node.rb +14 -0
- data/app/components/primer/alpha/file_tree_view.rb +15 -0
- data/app/components/primer/alpha/skeleton_box.css +1 -0
- data/app/components/primer/alpha/skeleton_box.css.json +6 -0
- data/app/components/primer/alpha/skeleton_box.css.map +1 -0
- data/app/components/primer/alpha/skeleton_box.html.erb +1 -0
- data/app/components/primer/alpha/skeleton_box.pcss +30 -0
- data/app/components/primer/alpha/skeleton_box.rb +29 -0
- data/app/components/primer/alpha/tree_view/icon.html.erb +1 -0
- data/app/components/primer/alpha/tree_view/icon.rb +22 -0
- data/app/components/primer/alpha/tree_view/icon_pair.html.erb +13 -0
- data/app/components/primer/alpha/tree_view/icon_pair.rb +42 -0
- data/app/components/primer/alpha/tree_view/leading_action.html.erb +3 -0
- data/app/components/primer/alpha/tree_view/leading_action.rb +18 -0
- data/app/components/primer/alpha/tree_view/leaf_node.html.erb +18 -0
- data/app/components/primer/alpha/tree_view/leaf_node.rb +96 -0
- data/app/components/primer/alpha/tree_view/loading_failure_message.html.erb +13 -0
- data/app/components/primer/alpha/tree_view/loading_failure_message.rb +31 -0
- data/app/components/primer/alpha/tree_view/node.html.erb +32 -0
- data/app/components/primer/alpha/tree_view/node.rb +194 -0
- data/app/components/primer/alpha/tree_view/skeleton_loader.html.erb +23 -0
- data/app/components/primer/alpha/tree_view/skeleton_loader.rb +36 -0
- data/app/components/primer/alpha/tree_view/spinner_loader.html.erb +20 -0
- data/app/components/primer/alpha/tree_view/spinner_loader.rb +33 -0
- data/app/components/primer/alpha/tree_view/sub_tree.html.erb +21 -0
- data/app/components/primer/alpha/tree_view/sub_tree.rb +113 -0
- data/app/components/primer/alpha/tree_view/sub_tree_container.html.erb +3 -0
- data/app/components/primer/alpha/tree_view/sub_tree_container.rb +39 -0
- data/app/components/primer/alpha/tree_view/sub_tree_node.html.erb +49 -0
- data/app/components/primer/alpha/tree_view/sub_tree_node.rb +188 -0
- data/app/components/primer/alpha/tree_view/tree_view.d.ts +39 -0
- data/app/components/primer/alpha/tree_view/tree_view.js +363 -0
- data/app/components/primer/alpha/tree_view/tree_view.ts +396 -0
- data/app/components/primer/alpha/tree_view/tree_view_icon_pair_element.d.ts +15 -0
- data/app/components/primer/alpha/tree_view/tree_view_icon_pair_element.js +62 -0
- data/app/components/primer/alpha/tree_view/tree_view_icon_pair_element.ts +56 -0
- data/app/components/primer/alpha/tree_view/tree_view_include_fragment_element.d.ts +9 -0
- data/app/components/primer/alpha/tree_view/tree_view_include_fragment_element.js +28 -0
- data/app/components/primer/alpha/tree_view/tree_view_include_fragment_element.ts +28 -0
- data/app/components/primer/alpha/tree_view/tree_view_roving_tab_index.d.ts +3 -0
- data/app/components/primer/alpha/tree_view/tree_view_roving_tab_index.js +130 -0
- data/app/components/primer/alpha/tree_view/tree_view_roving_tab_index.ts +161 -0
- data/app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.d.ts +42 -0
- data/app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.js +418 -0
- data/app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts +470 -0
- data/app/components/primer/alpha/tree_view/visual.html.erb +14 -0
- data/app/components/primer/alpha/tree_view/visual.rb +27 -0
- data/app/components/primer/alpha/tree_view.css +1 -0
- data/app/components/primer/alpha/tree_view.css.json +52 -0
- 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/alpha/tree_view.pcss +373 -0
- data/app/components/primer/alpha/tree_view.rb +439 -0
- 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/beta/spinner.html.erb +1 -1
- data/app/components/primer/beta/spinner.rb +2 -0
- data/app/components/primer/primer.d.ts +4 -0
- data/app/components/primer/primer.js +4 -0
- data/app/components/primer/primer.pcss +2 -0
- data/app/components/primer/primer.ts +4 -0
- data/app/components/primer/shared_events.d.ts +15 -0
- data/app/components/primer/shared_events.ts +19 -0
- data/app/controllers/primer/view_components/tree_view_items.json +293 -0
- data/app/controllers/primer/view_components/tree_view_items_controller.rb +55 -0
- data/app/forms/check_box_with_nested_form.rb +10 -10
- data/app/forms/radio_button_with_nested_form.rb +16 -16
- data/app/views/primer/view_components/tree_view_items/async_alpha.html_fragment.erb +23 -0
- data/app/views/primer/view_components/tree_view_items/index.html_fragment.erb +24 -0
- data/config/routes.rb +2 -0
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/alpha/file_tree_view_preview/default.html.erb +16 -0
- data/previews/primer/alpha/file_tree_view_preview/playground.html.erb +4 -0
- data/previews/primer/alpha/file_tree_view_preview.rb +69 -0
- data/previews/primer/alpha/skeleton_box_preview.rb +20 -0
- data/previews/primer/alpha/tree_view_preview/async_alpha.html.erb +12 -0
- data/previews/primer/alpha/tree_view_preview/buttons.html.erb +10 -0
- data/previews/primer/alpha/tree_view_preview/default.html.erb +24 -0
- data/previews/primer/alpha/tree_view_preview/empty.html.erb +10 -0
- data/previews/primer/alpha/tree_view_preview/form_input.html.erb +14 -0
- data/previews/primer/alpha/tree_view_preview/leaf_node_playground.html.erb +15 -0
- data/previews/primer/alpha/tree_view_preview/links.html.erb +17 -0
- data/previews/primer/alpha/tree_view_preview/loading_failure.html.erb +36 -0
- data/previews/primer/alpha/tree_view_preview/loading_skeleton.html.erb +12 -0
- data/previews/primer/alpha/tree_view_preview/loading_spinner.html.erb +12 -0
- data/previews/primer/alpha/tree_view_preview/playground.html.erb +4 -0
- data/previews/primer/alpha/tree_view_preview.rb +208 -0
- data/static/arguments.json +456 -0
- data/static/audited_at.json +17 -0
- data/static/classes.json +15 -0
- data/static/constants.json +101 -0
- data/static/info_arch.json +1410 -56
- data/static/previews.json +232 -0
- data/static/statuses.json +17 -0
- metadata +89 -8
@@ -0,0 +1,470 @@
|
|
1
|
+
import {controller, target} from '@github/catalyst'
|
2
|
+
import {TreeViewIconPairElement} from './tree_view_icon_pair_element'
|
3
|
+
import {observeMutationsUntilConditionMet} from '../../utils'
|
4
|
+
import {TreeViewIncludeFragmentElement} from './tree_view_include_fragment_element'
|
5
|
+
import {TreeViewElement} from './tree_view'
|
6
|
+
import type {TreeViewNodeInfo} from '../../shared_events'
|
7
|
+
|
8
|
+
type LoadingState = 'loading' | 'error' | 'success'
|
9
|
+
|
10
|
+
export type SelectStrategy = 'self' | 'descendants' | 'mixed_descendants'
|
11
|
+
|
12
|
+
@controller
|
13
|
+
export class TreeViewSubTreeNodeElement extends HTMLElement {
|
14
|
+
@target node: HTMLElement
|
15
|
+
@target subTree: HTMLElement
|
16
|
+
@target iconPair: TreeViewIconPairElement
|
17
|
+
@target toggleButton: HTMLElement
|
18
|
+
@target expandedToggleIcon: HTMLElement
|
19
|
+
@target collapsedToggleIcon: HTMLElement
|
20
|
+
@target includeFragment: TreeViewIncludeFragmentElement
|
21
|
+
@target loadingIndicator: HTMLElement
|
22
|
+
@target loadingFailureMessage: HTMLElement
|
23
|
+
@target retryButton: HTMLButtonElement
|
24
|
+
|
25
|
+
#expanded: boolean | null = null
|
26
|
+
#loadingState: LoadingState = 'success'
|
27
|
+
#abortController: AbortController
|
28
|
+
#activeElementIsLoader: boolean = false
|
29
|
+
|
30
|
+
connectedCallback() {
|
31
|
+
observeMutationsUntilConditionMet(
|
32
|
+
this,
|
33
|
+
() => Boolean(this.node) && Boolean(this.subTree),
|
34
|
+
() => {
|
35
|
+
this.#update()
|
36
|
+
},
|
37
|
+
)
|
38
|
+
|
39
|
+
const {signal} = (this.#abortController = new AbortController())
|
40
|
+
this.addEventListener('click', this, {signal})
|
41
|
+
this.addEventListener('keydown', this, {signal})
|
42
|
+
|
43
|
+
observeMutationsUntilConditionMet(
|
44
|
+
this,
|
45
|
+
() => Boolean(this.includeFragment),
|
46
|
+
() => {
|
47
|
+
this.includeFragment.addEventListener('loadstart', this, {signal})
|
48
|
+
this.includeFragment.addEventListener('error', this, {signal})
|
49
|
+
this.includeFragment.addEventListener('include-fragment-replace', this, {signal})
|
50
|
+
this.includeFragment.addEventListener(
|
51
|
+
'include-fragment-replaced',
|
52
|
+
(e: Event) => {
|
53
|
+
this.#handleIncludeFragmentEvent(e)
|
54
|
+
},
|
55
|
+
{signal},
|
56
|
+
)
|
57
|
+
},
|
58
|
+
)
|
59
|
+
|
60
|
+
observeMutationsUntilConditionMet(
|
61
|
+
this,
|
62
|
+
() => Boolean(this.retryButton),
|
63
|
+
() => {
|
64
|
+
this.retryButton.addEventListener(
|
65
|
+
'click',
|
66
|
+
event => {
|
67
|
+
this.#handleRetryButtonEvent(event)
|
68
|
+
},
|
69
|
+
{signal},
|
70
|
+
)
|
71
|
+
},
|
72
|
+
)
|
73
|
+
|
74
|
+
const checkedMutationObserver = new MutationObserver(() => {
|
75
|
+
if (this.selectStrategy !== 'mixed_descendants') return
|
76
|
+
|
77
|
+
let checkType = 'unknown'
|
78
|
+
|
79
|
+
for (const node of this.eachDirectDescendantNode()) {
|
80
|
+
switch (`${checkType} ${node.getAttribute('aria-checked') || 'false'}`) {
|
81
|
+
case 'unknown mixed':
|
82
|
+
case 'false mixed':
|
83
|
+
case 'true mixed':
|
84
|
+
case 'false true':
|
85
|
+
case 'true false':
|
86
|
+
checkType = 'mixed'
|
87
|
+
break
|
88
|
+
|
89
|
+
case 'unknown false':
|
90
|
+
checkType = 'false'
|
91
|
+
break
|
92
|
+
|
93
|
+
case 'unknown true':
|
94
|
+
checkType = 'true'
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
if (checkType !== 'unknown' && this.node?.getAttribute('aria-checked') !== checkType) {
|
99
|
+
this.node?.setAttribute('aria-checked', checkType)
|
100
|
+
}
|
101
|
+
})
|
102
|
+
|
103
|
+
checkedMutationObserver.observe(this, {
|
104
|
+
childList: true,
|
105
|
+
subtree: true,
|
106
|
+
attributeFilter: ['aria-checked'],
|
107
|
+
})
|
108
|
+
}
|
109
|
+
|
110
|
+
get expanded(): boolean {
|
111
|
+
if (this.#expanded === null) {
|
112
|
+
this.#expanded = this.node.getAttribute('aria-expanded') === 'true'
|
113
|
+
}
|
114
|
+
|
115
|
+
return this.#expanded
|
116
|
+
}
|
117
|
+
|
118
|
+
set expanded(newValue: boolean) {
|
119
|
+
this.#expanded = newValue
|
120
|
+
this.#update()
|
121
|
+
}
|
122
|
+
|
123
|
+
get loadingState(): LoadingState {
|
124
|
+
return this.#loadingState
|
125
|
+
}
|
126
|
+
|
127
|
+
set loadingState(newState: LoadingState) {
|
128
|
+
this.#loadingState = newState
|
129
|
+
this.#update()
|
130
|
+
}
|
131
|
+
|
132
|
+
get selectStrategy(): SelectStrategy {
|
133
|
+
return (this.node.getAttribute('data-select-strategy') || 'descendants') as SelectStrategy
|
134
|
+
}
|
135
|
+
|
136
|
+
disconnectedCallback() {
|
137
|
+
this.#abortController.abort()
|
138
|
+
}
|
139
|
+
|
140
|
+
handleEvent(event: Event) {
|
141
|
+
if (event.target === this.toggleButton) {
|
142
|
+
this.#handleToggleEvent(event)
|
143
|
+
} else if (event.target === this.includeFragment) {
|
144
|
+
this.#handleIncludeFragmentEvent(event)
|
145
|
+
} else if (event instanceof KeyboardEvent) {
|
146
|
+
this.#handleKeyboardEvent(event)
|
147
|
+
} else if (
|
148
|
+
(event.target as Element).closest('[role=treeitem]') === this.node &&
|
149
|
+
event.type === 'click' &&
|
150
|
+
this.#checkboxElement
|
151
|
+
) {
|
152
|
+
this.#handleCheckboxEvent(event)
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
expand() {
|
157
|
+
const alreadyExpanded = this.expanded
|
158
|
+
|
159
|
+
this.expanded = true
|
160
|
+
|
161
|
+
if (!alreadyExpanded && this.treeView) {
|
162
|
+
this.treeView.dispatchEvent(
|
163
|
+
new CustomEvent('treeViewNodeExpanded', {
|
164
|
+
bubbles: true,
|
165
|
+
detail: this.treeView?.infoFromNode(this.node),
|
166
|
+
}),
|
167
|
+
)
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
collapse() {
|
172
|
+
const alreadyCollapsed = !this.expanded
|
173
|
+
|
174
|
+
this.expanded = false
|
175
|
+
|
176
|
+
if (!alreadyCollapsed && this.treeView) {
|
177
|
+
// Prevent issue where currently focusable node is stuck inside a collapsed
|
178
|
+
// sub-tree and no node in the entire tree can be focused
|
179
|
+
const previousNode = this.subTree.querySelector("[tabindex='0']")
|
180
|
+
previousNode?.setAttribute('tabindex', '-1')
|
181
|
+
this.node.setAttribute('tabindex', '0')
|
182
|
+
|
183
|
+
this.treeView.dispatchEvent(
|
184
|
+
new CustomEvent('treeViewNodeCollapsed', {
|
185
|
+
bubbles: true,
|
186
|
+
detail: this.treeView?.infoFromNode(this.node),
|
187
|
+
}),
|
188
|
+
)
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
toggle() {
|
193
|
+
if (this.expanded) {
|
194
|
+
this.collapse()
|
195
|
+
} else {
|
196
|
+
this.expand()
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
get nodes(): NodeListOf<Element> {
|
201
|
+
return this.querySelectorAll(':scope > [role=treeitem]')
|
202
|
+
}
|
203
|
+
|
204
|
+
*eachDirectDescendantNode(): Generator<Element> {
|
205
|
+
for (const leaf of this.subTree.querySelectorAll(':scope > li > .TreeViewItemContainer > [role=treeitem]')) {
|
206
|
+
yield leaf
|
207
|
+
}
|
208
|
+
|
209
|
+
for (const subTree of this.subTree.querySelectorAll(
|
210
|
+
':scope > tree-view-sub-tree-node > li > .TreeViewItemContainer > [role=treeitem]',
|
211
|
+
)) {
|
212
|
+
yield subTree
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
*eachDescendantNode(): Generator<Element> {
|
217
|
+
for (const node of this.subTree.querySelectorAll('[role=treeitem]')) {
|
218
|
+
yield node
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
*eachAncestorSubTreeNode(): Generator<TreeViewSubTreeNodeElement> {
|
223
|
+
if (!this.treeView) return
|
224
|
+
|
225
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
226
|
+
let current: TreeViewSubTreeNodeElement | null = this
|
227
|
+
|
228
|
+
while (current && this.treeView.contains(current)) {
|
229
|
+
yield current
|
230
|
+
|
231
|
+
current = current.parentElement?.closest('tree-view-sub-tree-node') as TreeViewSubTreeNodeElement | null
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
get isEmpty(): boolean {
|
236
|
+
return this.nodes.length === 0
|
237
|
+
}
|
238
|
+
|
239
|
+
get treeView(): TreeViewElement | null {
|
240
|
+
return this.closest('tree-view')
|
241
|
+
}
|
242
|
+
|
243
|
+
#handleToggleEvent(event: Event) {
|
244
|
+
if (event.type === 'click') {
|
245
|
+
this.toggle()
|
246
|
+
// eslint-disable-next-line no-restricted-syntax
|
247
|
+
event.stopPropagation()
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
#handleIncludeFragmentEvent(event: Event) {
|
252
|
+
switch (event.type) {
|
253
|
+
// the request has started
|
254
|
+
case 'loadstart':
|
255
|
+
this.loadingState = 'loading'
|
256
|
+
break
|
257
|
+
|
258
|
+
// the request failed
|
259
|
+
case 'error':
|
260
|
+
this.loadingState = 'error'
|
261
|
+
break
|
262
|
+
|
263
|
+
// request succeeded but element has not yet been replaced
|
264
|
+
case 'include-fragment-replace':
|
265
|
+
this.#activeElementIsLoader = document.activeElement === this.loadingIndicator.closest('[role=treeitem]')
|
266
|
+
this.loadingState = 'success'
|
267
|
+
break
|
268
|
+
|
269
|
+
case 'include-fragment-replaced':
|
270
|
+
// Make sure to expand the new sub-tree, otherwise it looks like nothing happened. This prevents
|
271
|
+
// having to remember to pass `SubTree.new(expanded: true)` in the controller.
|
272
|
+
this.expanded = true
|
273
|
+
|
274
|
+
if (this.#activeElementIsLoader) {
|
275
|
+
const firstItem = this.querySelector('[role=group] > :first-child') as HTMLElement | null
|
276
|
+
if (!firstItem) return
|
277
|
+
|
278
|
+
const content = firstItem.querySelector('[role=treeitem]') as HTMLElement | null
|
279
|
+
if (!content) return
|
280
|
+
|
281
|
+
content.focus()
|
282
|
+
}
|
283
|
+
|
284
|
+
this.#activeElementIsLoader = false
|
285
|
+
break
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
#handleRetryButtonEvent(event: Event) {
|
290
|
+
if (event.type === 'click') {
|
291
|
+
this.loadingState = 'loading'
|
292
|
+
this.includeFragment.refetch()
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
#handleKeyboardEvent(event: KeyboardEvent) {
|
297
|
+
const node = (event.target as HTMLElement).closest('[role=treeitem]')
|
298
|
+
if (!node || this.treeView?.getNodeType(node) !== 'sub-tree') {
|
299
|
+
return
|
300
|
+
}
|
301
|
+
|
302
|
+
switch (event.key) {
|
303
|
+
case 'Enter':
|
304
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
305
|
+
event.preventDefault()
|
306
|
+
break
|
307
|
+
}
|
308
|
+
|
309
|
+
// eslint-disable-next-line no-restricted-syntax
|
310
|
+
event.stopPropagation()
|
311
|
+
|
312
|
+
if (this.#checkboxElement) {
|
313
|
+
this.toggleChecked()
|
314
|
+
} else if (!this.treeView?.nodeHasNativeAction(node)) {
|
315
|
+
// toggle only if this node isn't eg. an anchor or button
|
316
|
+
this.toggle()
|
317
|
+
}
|
318
|
+
|
319
|
+
break
|
320
|
+
|
321
|
+
case 'ArrowRight':
|
322
|
+
// eslint-disable-next-line no-restricted-syntax
|
323
|
+
event.stopPropagation()
|
324
|
+
this.expand()
|
325
|
+
break
|
326
|
+
|
327
|
+
case 'ArrowLeft':
|
328
|
+
// eslint-disable-next-line no-restricted-syntax
|
329
|
+
event.stopPropagation()
|
330
|
+
this.collapse()
|
331
|
+
break
|
332
|
+
|
333
|
+
case ' ':
|
334
|
+
if (this.treeView?.getNodeDisabledValue(node)) {
|
335
|
+
event.preventDefault()
|
336
|
+
break
|
337
|
+
}
|
338
|
+
|
339
|
+
if (this.#checkboxElement) {
|
340
|
+
// eslint-disable-next-line no-restricted-syntax
|
341
|
+
event.stopPropagation()
|
342
|
+
event.preventDefault()
|
343
|
+
|
344
|
+
this.toggleChecked()
|
345
|
+
} else {
|
346
|
+
if (node instanceof HTMLAnchorElement) {
|
347
|
+
// simulate click on space for anchors (buttons already handle this natively)
|
348
|
+
node.click()
|
349
|
+
} else if (!this.treeView?.nodeHasNativeAction(node)) {
|
350
|
+
this.toggle()
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
break
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
#handleCheckboxEvent(event: Event) {
|
359
|
+
if (this.treeView?.getNodeDisabledValue(this.node)) {
|
360
|
+
event.preventDefault()
|
361
|
+
return
|
362
|
+
}
|
363
|
+
|
364
|
+
if (event.type !== 'click') return
|
365
|
+
|
366
|
+
this.toggleChecked()
|
367
|
+
|
368
|
+
// prevent receiving this event twice
|
369
|
+
// eslint-disable-next-line no-restricted-syntax
|
370
|
+
event.stopPropagation()
|
371
|
+
}
|
372
|
+
|
373
|
+
toggleChecked() {
|
374
|
+
const checkValue = this.treeView?.getNodeCheckedValue(this.node) || 'false'
|
375
|
+
const newCheckValue = checkValue === 'false' ? 'true' : 'false'
|
376
|
+
const nodeInfos: TreeViewNodeInfo[] = []
|
377
|
+
const rootInfo = this.treeView?.infoFromNode(this.node, newCheckValue)
|
378
|
+
if (rootInfo) nodeInfos.push(rootInfo)
|
379
|
+
|
380
|
+
if (this.selectStrategy === 'descendants' || this.selectStrategy === 'mixed_descendants') {
|
381
|
+
for (const node of this.eachDescendantNode()) {
|
382
|
+
const info = this.treeView?.infoFromNode(node, newCheckValue)
|
383
|
+
if (info) nodeInfos.push(info)
|
384
|
+
}
|
385
|
+
}
|
386
|
+
|
387
|
+
const checkSuccess = this.dispatchEvent(
|
388
|
+
new CustomEvent('treeViewBeforeNodeChecked', {
|
389
|
+
bubbles: true,
|
390
|
+
cancelable: true,
|
391
|
+
detail: nodeInfos,
|
392
|
+
}),
|
393
|
+
)
|
394
|
+
|
395
|
+
if (!checkSuccess) return
|
396
|
+
|
397
|
+
for (const nodeInfo of nodeInfos) {
|
398
|
+
nodeInfo.node.setAttribute('aria-checked', newCheckValue)
|
399
|
+
}
|
400
|
+
|
401
|
+
this.dispatchEvent(
|
402
|
+
new CustomEvent('treeViewNodeChecked', {
|
403
|
+
bubbles: true,
|
404
|
+
cancelable: true,
|
405
|
+
detail: nodeInfos,
|
406
|
+
}),
|
407
|
+
)
|
408
|
+
}
|
409
|
+
|
410
|
+
#update() {
|
411
|
+
if (this.expanded) {
|
412
|
+
if (this.subTree) this.subTree.hidden = false
|
413
|
+
this.node.setAttribute('aria-expanded', 'true')
|
414
|
+
this.treeView?.expandAncestorsForNode(this)
|
415
|
+
|
416
|
+
if (this.iconPair) {
|
417
|
+
this.iconPair.showExpanded()
|
418
|
+
}
|
419
|
+
|
420
|
+
if (this.expandedToggleIcon && this.collapsedToggleIcon) {
|
421
|
+
this.expandedToggleIcon.removeAttribute('hidden')
|
422
|
+
this.collapsedToggleIcon.setAttribute('hidden', 'hidden')
|
423
|
+
}
|
424
|
+
} else {
|
425
|
+
if (this.subTree) this.subTree.hidden = true
|
426
|
+
this.node.setAttribute('aria-expanded', 'false')
|
427
|
+
|
428
|
+
if (this.iconPair) {
|
429
|
+
this.iconPair.showCollapsed()
|
430
|
+
}
|
431
|
+
|
432
|
+
if (this.expandedToggleIcon && this.collapsedToggleIcon) {
|
433
|
+
this.expandedToggleIcon.setAttribute('hidden', 'hidden')
|
434
|
+
this.collapsedToggleIcon.removeAttribute('hidden')
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
switch (this.loadingState) {
|
439
|
+
case 'loading':
|
440
|
+
if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = true
|
441
|
+
if (this.loadingIndicator) this.loadingIndicator.hidden = false
|
442
|
+
break
|
443
|
+
|
444
|
+
case 'error':
|
445
|
+
if (this.loadingIndicator) this.loadingIndicator.hidden = true
|
446
|
+
if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = false
|
447
|
+
break
|
448
|
+
|
449
|
+
// success/init case
|
450
|
+
default:
|
451
|
+
if (this.loadingIndicator) this.loadingIndicator.hidden = true
|
452
|
+
if (this.loadingFailureMessage) this.loadingFailureMessage.hidden = true
|
453
|
+
}
|
454
|
+
}
|
455
|
+
|
456
|
+
get #checkboxElement(): HTMLElement | null {
|
457
|
+
return this.querySelector('.TreeViewItemCheckbox')
|
458
|
+
}
|
459
|
+
}
|
460
|
+
|
461
|
+
if (!window.customElements.get('tree-view-sub-tree-node')) {
|
462
|
+
window.TreeViewSubTreeNodeElement = TreeViewSubTreeNodeElement
|
463
|
+
window.customElements.define('tree-view-sub-tree-node', TreeViewSubTreeNodeElement)
|
464
|
+
}
|
465
|
+
|
466
|
+
declare global {
|
467
|
+
interface Window {
|
468
|
+
TreeViewSubTreeNodeElement: typeof TreeViewSubTreeNodeElement
|
469
|
+
}
|
470
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= render(
|
2
|
+
Primer::BaseComponent.new(
|
3
|
+
tag: :div,
|
4
|
+
classes: "sr-only TreeViewVisuallyHidden",
|
5
|
+
aria: { hidden: true },
|
6
|
+
id: @id,
|
7
|
+
test_selector: "tree-view-visual-label"
|
8
|
+
)
|
9
|
+
) do %>
|
10
|
+
<%= @label %>
|
11
|
+
<% end %>
|
12
|
+
<div class="TreeViewItemVisual" aria-hidden="true">
|
13
|
+
<%= render(@visual) %>
|
14
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Alpha
|
5
|
+
class TreeView
|
6
|
+
# A `TreeView` visual, either leading or trailing.
|
7
|
+
#
|
8
|
+
# This component is part of the <%= link_to_component(Primer::Alpha::TreeView) %> component and should
|
9
|
+
# not be used directly.
|
10
|
+
class Visual < Primer::Component
|
11
|
+
# @param id [String] This visual's HTML ID.
|
12
|
+
# @param visual [ViewComponent::Base] A renderable component like an instance of <%= link_to_component(Primer::Beta::Octicon) %> to render as the visual.
|
13
|
+
# @param label [String] Text describing this visual that will be visible only to screen readers.
|
14
|
+
def initialize(id:, visual:, label: nil)
|
15
|
+
@id = id
|
16
|
+
@visual = visual
|
17
|
+
@label = label
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_in(_view_context, &block)
|
21
|
+
block&.call(@visual)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -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%}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"name": "alpha/tree_view",
|
3
|
+
"selectors": [
|
4
|
+
".TreeViewRootUlStyles",
|
5
|
+
".TreeViewRootUlStyles .TreeViewItem",
|
6
|
+
":is(.TreeViewRootUlStyles .TreeViewItem):focus-visible>div",
|
7
|
+
"[data-has-leading-action]:is(.TreeViewRootUlStyles .TreeViewItem)",
|
8
|
+
".TreeViewRootUlStyles .TreeViewItemContainer",
|
9
|
+
":is(.TreeViewRootUlStyles .TreeViewItemContainer):hover",
|
10
|
+
":is(.TreeViewRootUlStyles .TreeViewItemContainer):has(.TreeViewFailureMessage):hover",
|
11
|
+
":is(.TreeViewRootUlStyles .TreeViewItemContainer):has([role=treeitem]:focus-visible)",
|
12
|
+
".TreeViewRootUlStyles:where([data-omit-spacer=true]) .TreeViewItemContainer",
|
13
|
+
".TreeViewRootUlStyles .TreeViewItem>.TreeViewItemContainer:has(.TreeViewItemContent[aria-current=true])",
|
14
|
+
":is(.TreeViewRootUlStyles .TreeViewItem>.TreeViewItemContainer:has(.TreeViewItemContent[aria-current=true])):after",
|
15
|
+
".TreeViewRootUlStyles .TreeViewItemToggle",
|
16
|
+
".TreeViewRootUlStyles .TreeViewItemToggleHover:hover",
|
17
|
+
".TreeViewRootUlStyles .TreeViewItemToggleEnd",
|
18
|
+
".TreeViewRootUlStyles a.TreeViewItemContent:hover",
|
19
|
+
".TreeViewRootUlStyles button.TreeViewItemContent:hover",
|
20
|
+
".TreeViewRootUlStyles :has(.TreeViewItemContent[aria-disabled=true])",
|
21
|
+
".TreeViewRootUlStyles .TreeViewItemContent",
|
22
|
+
":is(.TreeViewRootUlStyles .TreeViewItemContent) .TreeViewItemCheckbox",
|
23
|
+
"[aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox",
|
24
|
+
":is([aria-checked=true]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox):before",
|
25
|
+
"[aria-checked=mixed]:is(.TreeViewRootUlStyles .TreeViewItemContent) .FormControl-checkbox",
|
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",
|
33
|
+
".TreeViewRootUlStyles .TreeViewItemContentText",
|
34
|
+
".TreeViewRootUlStyles:where([data-truncate-text=true]) .TreeViewItemContentText",
|
35
|
+
".TreeViewRootUlStyles:where([data-truncate-text=false]) .TreeViewItemContentText",
|
36
|
+
".TreeViewRootUlStyles .TreeViewItemVisual",
|
37
|
+
".TreeViewRootUlStyles .TreeViewItemLeadingAction",
|
38
|
+
":is(.TreeViewRootUlStyles .TreeViewItemLeadingAction)>button",
|
39
|
+
".TreeViewRootUlStyles .TreeViewItemLevelLine",
|
40
|
+
".TreeViewRootUlStyles:focus-within .TreeViewItemLevelLine",
|
41
|
+
".TreeViewRootUlStyles:hover .TreeViewItemLevelLine",
|
42
|
+
".TreeViewRootUlStyles .TreeViewVisuallyHidden",
|
43
|
+
".TreeViewSkeletonItemContainerStyle",
|
44
|
+
".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+1)",
|
45
|
+
".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+2)",
|
46
|
+
".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+3)",
|
47
|
+
".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+4)",
|
48
|
+
".TreeViewSkeletonItemContainerStyle:nth-of-type(5n+5)",
|
49
|
+
".TreeItemSkeletonTextStyles",
|
50
|
+
".TreeViewFailureMessage"
|
51
|
+
]
|
52
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["tree_view.pcss"],"names":[],"mappings":"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","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>
|