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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/app/assets/javascripts/components/primer/alpha/segmented_control.d.ts +2 -2
  4. data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view.d.ts +11 -1
  5. data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
  6. data/app/assets/javascripts/components/primer/open_project/filterable_tree_view.d.ts +29 -0
  7. data/app/assets/javascripts/components/primer/primer.d.ts +5 -4
  8. data/app/assets/javascripts/primer_view_components.js +1 -1
  9. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  10. data/app/assets/styles/primer_view_components.css +1 -1
  11. data/app/assets/styles/primer_view_components.css.map +1 -1
  12. data/app/components/primer/{open_project → alpha}/file_tree_view/directory_node.rb +1 -1
  13. data/app/components/primer/{open_project → alpha}/file_tree_view/file_node.rb +1 -1
  14. data/app/components/primer/{open_project → alpha}/file_tree_view.rb +1 -1
  15. data/app/components/primer/alpha/segmented_control.d.ts +2 -2
  16. data/app/components/primer/alpha/segmented_control.js +12 -0
  17. data/app/components/primer/alpha/segmented_control.ts +16 -1
  18. data/app/components/primer/{open_project → alpha}/skeleton_box.css.json +1 -1
  19. data/app/components/primer/{open_project → alpha}/skeleton_box.css.map +1 -1
  20. data/app/components/primer/{open_project → alpha}/skeleton_box.pcss +1 -1
  21. data/app/components/primer/{open_project → alpha}/skeleton_box.rb +3 -1
  22. data/app/components/primer/alpha/stack.css +1 -1
  23. data/app/components/primer/alpha/stack.css.json +5 -1
  24. data/app/components/primer/alpha/stack.css.map +1 -1
  25. data/app/components/primer/alpha/stack.pcss +13 -0
  26. data/app/components/primer/alpha/stack.rb +2 -1
  27. data/app/components/primer/{open_project → alpha}/tree_view/icon.rb +2 -2
  28. data/app/components/primer/{open_project → alpha}/tree_view/icon_pair.rb +4 -4
  29. data/app/components/primer/{open_project → alpha}/tree_view/leading_action.rb +2 -2
  30. data/app/components/primer/{open_project → alpha}/tree_view/leaf_node.rb +6 -6
  31. data/app/components/primer/{open_project → alpha}/tree_view/loading_failure_message.rb +2 -2
  32. data/app/components/primer/{open_project → alpha}/tree_view/node.rb +30 -14
  33. data/app/components/primer/{open_project → alpha}/tree_view/skeleton_loader.html.erb +3 -3
  34. data/app/components/primer/{open_project → alpha}/tree_view/skeleton_loader.rb +4 -4
  35. data/app/components/primer/{open_project → alpha}/tree_view/spinner_loader.html.erb +2 -2
  36. data/app/components/primer/{open_project → alpha}/tree_view/spinner_loader.rb +4 -4
  37. data/app/components/primer/{open_project → alpha}/tree_view/sub_tree.html.erb +1 -1
  38. data/app/components/primer/{open_project → alpha}/tree_view/sub_tree.rb +10 -10
  39. data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_container.rb +2 -2
  40. data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_node.rb +28 -18
  41. data/app/components/primer/{open_project → alpha}/tree_view/tree_view.d.ts +11 -1
  42. data/app/components/primer/{open_project → alpha}/tree_view/tree_view.js +120 -20
  43. data/app/components/primer/{open_project → alpha}/tree_view/tree_view.ts +137 -18
  44. data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.js +0 -1
  45. data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.ts +0 -1
  46. data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.d.ts +5 -1
  47. data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.js +27 -4
  48. data/app/components/primer/{open_project → alpha}/tree_view/tree_view_sub_tree_node_element.ts +36 -5
  49. data/app/components/primer/{open_project → alpha}/tree_view/visual.rb +2 -2
  50. data/app/components/primer/alpha/tree_view.css +1 -0
  51. data/app/components/primer/{open_project → alpha}/tree_view.css.json +8 -1
  52. data/app/components/primer/alpha/tree_view.css.map +1 -0
  53. data/app/components/primer/alpha/tree_view.html.erb +12 -0
  54. data/app/components/primer/{open_project → alpha}/tree_view.pcss +39 -0
  55. data/app/components/primer/{open_project → alpha}/tree_view.rb +20 -12
  56. data/app/components/primer/beta/breadcrumbs.css +1 -1
  57. data/app/components/primer/beta/breadcrumbs.css.json +0 -1
  58. data/app/components/primer/beta/breadcrumbs.css.map +1 -1
  59. data/app/components/primer/beta/breadcrumbs.pcss +2 -8
  60. data/app/components/primer/beta/progress_bar.css +1 -1
  61. data/app/components/primer/beta/progress_bar.css.map +1 -1
  62. data/app/components/primer/beta/progress_bar.pcss +3 -2
  63. data/app/components/primer/beta/relative_time.rb +3 -0
  64. data/app/components/primer/open_project/filterable_tree_view/sub_tree.rb +39 -0
  65. data/app/components/primer/open_project/filterable_tree_view.d.ts +29 -0
  66. data/app/components/primer/open_project/filterable_tree_view.html.erb +28 -0
  67. data/app/components/primer/open_project/filterable_tree_view.js +409 -0
  68. data/app/components/primer/open_project/filterable_tree_view.rb +254 -0
  69. data/app/components/primer/open_project/filterable_tree_view.ts +492 -0
  70. data/app/components/primer/primer.d.ts +5 -4
  71. data/app/components/primer/primer.js +5 -4
  72. data/app/components/primer/primer.pcss +2 -2
  73. data/app/components/primer/primer.ts +5 -4
  74. data/app/controllers/primer/view_components/tree_view_items_controller.rb +1 -1
  75. data/app/forms/check_box_with_nested_form.rb +10 -10
  76. data/app/forms/radio_button_with_nested_form.rb +16 -16
  77. data/app/lib/primer/experimental_slot_helpers.rb +2 -2
  78. data/app/lib/primer/forms/base_component.rb +1 -1
  79. data/app/lib/primer/forms/dsl/text_field_input.rb +2 -0
  80. data/app/views/primer/view_components/tree_view_items/async_alpha.html_fragment.erb +1 -1
  81. data/app/views/primer/view_components/tree_view_items/index.html_fragment.erb +1 -1
  82. data/config/locales/en.yml +20 -0
  83. data/lib/primer/view_components/version.rb +2 -2
  84. data/previews/primer/{open_project → alpha}/file_tree_view_preview/default.html.erb +1 -1
  85. data/previews/primer/{open_project → alpha}/file_tree_view_preview/playground.html.erb +1 -1
  86. data/previews/primer/{open_project → alpha}/file_tree_view_preview.rb +1 -1
  87. data/previews/primer/{open_project → alpha}/skeleton_box_preview.rb +3 -3
  88. data/previews/primer/{open_project → alpha}/tree_view_preview/async_alpha.html.erb +1 -1
  89. data/previews/primer/{open_project → alpha}/tree_view_preview/buttons.html.erb +5 -5
  90. data/previews/primer/{open_project → alpha}/tree_view_preview/default.html.erb +5 -5
  91. data/previews/primer/{open_project → alpha}/tree_view_preview/empty.html.erb +1 -1
  92. data/previews/primer/alpha/tree_view_preview/form_input.html.erb +14 -0
  93. data/previews/primer/{open_project → alpha}/tree_view_preview/leaf_node_playground.html.erb +2 -2
  94. data/previews/primer/{open_project → alpha}/tree_view_preview/links.html.erb +5 -5
  95. data/previews/primer/{open_project → alpha}/tree_view_preview/loading_failure.html.erb +1 -1
  96. data/previews/primer/{open_project → alpha}/tree_view_preview/loading_skeleton.html.erb +1 -1
  97. data/previews/primer/{open_project → alpha}/tree_view_preview/loading_spinner.html.erb +1 -1
  98. data/previews/primer/{open_project → alpha}/tree_view_preview/playground.html.erb +1 -1
  99. data/previews/primer/{open_project → alpha}/tree_view_preview.rb +34 -15
  100. data/previews/primer/open_project/filterable_tree_view_preview/_custom_select_js.html.erb +62 -0
  101. data/previews/primer/open_project/filterable_tree_view_preview/custom_checkbox_text.html.erb +26 -0
  102. data/previews/primer/open_project/filterable_tree_view_preview/custom_no_results_text.html.erb +28 -0
  103. data/previews/primer/open_project/filterable_tree_view_preview/custom_segmented_control.html.erb +31 -0
  104. data/previews/primer/open_project/filterable_tree_view_preview/default.html.erb +26 -0
  105. data/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb +32 -0
  106. data/previews/primer/open_project/filterable_tree_view_preview/playground.html.erb +26 -0
  107. data/previews/primer/open_project/filterable_tree_view_preview/stress_test.html.erb +28 -0
  108. data/previews/primer/open_project/filterable_tree_view_preview.rb +125 -0
  109. data/static/arguments.json +1685 -1581
  110. data/static/audited_at.json +19 -17
  111. data/static/classes.json +5 -5
  112. data/static/constants.json +137 -98
  113. data/static/info_arch.json +6396 -6146
  114. data/static/previews.json +120 -21
  115. data/static/statuses.json +19 -17
  116. metadata +102 -84
  117. data/app/components/primer/open_project/tree_view.css +0 -1
  118. data/app/components/primer/open_project/tree_view.css.map +0 -1
  119. data/app/components/primer/open_project/tree_view.html.erb +0 -7
  120. /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.d.ts +0 -0
  121. /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.d.ts +0 -0
  122. /data/app/assets/javascripts/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.d.ts +0 -0
  123. /data/app/components/primer/{open_project → alpha}/file_tree_view/directory_node.html.erb +0 -0
  124. /data/app/components/primer/{open_project → alpha}/file_tree_view/file_node.html.erb +0 -0
  125. /data/app/components/primer/{open_project → alpha}/skeleton_box.css +0 -0
  126. /data/app/components/primer/{open_project → alpha}/skeleton_box.html.erb +0 -0
  127. /data/app/components/primer/{open_project → alpha}/tree_view/icon.html.erb +0 -0
  128. /data/app/components/primer/{open_project → alpha}/tree_view/icon_pair.html.erb +0 -0
  129. /data/app/components/primer/{open_project → alpha}/tree_view/leading_action.html.erb +0 -0
  130. /data/app/components/primer/{open_project → alpha}/tree_view/leaf_node.html.erb +0 -0
  131. /data/app/components/primer/{open_project → alpha}/tree_view/loading_failure_message.html.erb +0 -0
  132. /data/app/components/primer/{open_project → alpha}/tree_view/node.html.erb +0 -0
  133. /data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_container.html.erb +0 -0
  134. /data/app/components/primer/{open_project → alpha}/tree_view/sub_tree_node.html.erb +0 -0
  135. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.d.ts +0 -0
  136. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.js +0 -0
  137. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_icon_pair_element.ts +0 -0
  138. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_include_fragment_element.d.ts +0 -0
  139. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.d.ts +0 -0
  140. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.js +0 -0
  141. /data/app/components/primer/{open_project → alpha}/tree_view/tree_view_roving_tab_index.ts +0 -0
  142. /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 target = event.target as Element
63
- const node = target.closest('[role=treeitem]')
114
+ const eventTarget = event.target as Element
115
+ const node = eventTarget.closest('[role=treeitem]')
64
116
  if (!node) return null
65
117
 
66
- if (target.closest('.TreeViewItemToggle')) return null
67
- if (target.closest('.TreeViewItemLeadingAction')) return null
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
- // only handle checking of leaf nodes
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.#setNodeCheckedValue(node, 'false')
166
+ this.setNodeCheckedValue(node, 'false')
95
167
  } else {
96
- this.#setNodeCheckedValue(node, 'true')
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.#setNodeCheckedValue(node, 'false')
238
+ this.setNodeCheckedValue(node, 'false')
149
239
  } else {
150
- this.#setNodeCheckedValue(node, 'true')
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.#setNodeCheckedValue(node, 'true')
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.#setNodeCheckedValue(node, 'false')
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
- #setNodeCheckedValue(node: Element, value: TreeViewCheckedValue) {
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
@@ -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
  }
@@ -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
 
@@ -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(): string;
23
+ get selectStrategy(): SelectStrategy;
24
+ get level(): number;
23
25
  disconnectedCallback(): void;
24
26
  handleEvent(event: Event): void;
25
27
  expand(): void;
@@ -27,11 +29,13 @@ export declare class TreeViewSubTreeNodeElement extends HTMLElement {
27
29
  toggle(): void;
28
30
  get nodes(): NodeListOf<Element>;
29
31
  eachDirectDescendantNode(): Generator<Element>;
32
+ eachDirectDescendantSubTreeNode(): Generator<TreeViewSubTreeNodeElement>;
30
33
  eachDescendantNode(): Generator<Element>;
31
34
  eachAncestorSubTreeNode(): Generator<TreeViewSubTreeNodeElement>;
32
35
  get isEmpty(): boolean;
33
36
  get treeView(): TreeViewElement | null;
34
37
  toggleChecked(): void;
38
+ changeSelectStrategy(newStrategy: SelectStrategy): void;
35
39
  }
36
40
  declare global {
37
41
  interface Window {
@@ -48,7 +48,7 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
48
48
  }, { signal });
49
49
  });
50
50
  const checkedMutationObserver = new MutationObserver(() => {
51
- if (this.selectStrategy !== 'descendants')
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.getAttribute('aria-checked') || 'false';
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();
@@ -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 !== 'descendants') return
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(): string {
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.getAttribute('aria-checked') || 'false'
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 OpenProject
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::OpenProject::TreeView) %> component and should
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": "open_project/tree_view",
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>