sdr_view_components 0.21.0 → 0.22.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/README.md +31 -0
- data/app/assets/sdr_view_components.css +20 -0
- data/app/components/sdr_view_components/tables/treegrid_expand_collapse_row_component.html.erb +7 -0
- data/app/components/sdr_view_components/tables/treegrid_expand_collapse_row_component.rb +15 -0
- data/app/components/sdr_view_components/tables/treegrid_file_row_component.html.erb +9 -0
- data/app/components/sdr_view_components/tables/treegrid_file_row_component.rb +26 -0
- data/app/components/sdr_view_components/tables/treegrid_folder_row_component.html.erb +11 -0
- data/app/components/sdr_view_components/tables/treegrid_folder_row_component.rb +35 -0
- data/app/components/sdr_view_components/tables/treegrid_row_component.rb +25 -0
- data/app/components/sdr_view_components/tables/treegrid_table_component.rb +78 -0
- data/app/javascript/sdr_view_components/treegrid_controller.js +193 -0
- data/lib/sdr_view_components/version.rb +1 -1
- data/spec/components/previews/sdr_view_components/tables/treegrid_table_component_preview/default.html.erb +10 -0
- data/spec/components/previews/sdr_view_components/tables/treegrid_table_component_preview.rb +24 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1368eefb0da795ca19eb50623c50573654bc681bd419cd4f250a759aa1c969d9
|
|
4
|
+
data.tar.gz: 61d17dbefaeca0a568b845c2fc9872df5a484496cd1b9b07b18961e9d870afb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d504ffc348734b15f10d607a2396f02e74e57922d3360ffd4b7c411b49156e1e5b1279997c21d1721bb1338c8a5ea18cfce4da7ee6995e27472cd5c3f466be6a
|
|
7
|
+
data.tar.gz: 59983cc56c2abfb208d2bfe3a05d5ff6656c93cdbb4e2c1ce9c9e8b54b7393ce2637b428703b4305aebbcacc249edadf3a14387849c9718db19a060668e59dde
|
data/README.md
CHANGED
|
@@ -96,6 +96,19 @@ import TabOverflowController from "sdr_view_components/tab_overflow_controller"
|
|
|
96
96
|
application.register("sdr-tab-overflow", TabOverflowController)
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
`SdrViewComponents::Tables::TreegridTableComponent` (see [Treegrid table component](#treegrid-table-component) below) ships `sdr_view_components/treegrid_controller`, registered as `sdr-treegrid`:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
pin "sdr_view_components/treegrid_controller", to: "sdr_view_components/treegrid_controller.js"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import { application } from "controllers/application"
|
|
107
|
+
import TreegridController from "sdr_view_components/treegrid_controller"
|
|
108
|
+
|
|
109
|
+
application.register("sdr-treegrid", TreegridController)
|
|
110
|
+
```
|
|
111
|
+
|
|
99
112
|
## Usage
|
|
100
113
|
|
|
101
114
|
### Form components
|
|
@@ -250,6 +263,24 @@ Instead of `heading:`, the heading can be provided with the `heading_content` sl
|
|
|
250
263
|
<% end %>
|
|
251
264
|
```
|
|
252
265
|
|
|
266
|
+
### Treegrid table component
|
|
267
|
+
|
|
268
|
+
`SdrViewComponents::Tables::TreegridTableComponent` renders files as an [ARIA treegrid](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/): a row for each file, preceded by a collapsible row for each folder in its path, with Expand all / Collapse all controls when there are folders. Provide files in path order (each folder's files before its subfolders), with the `path_parts` of each file's folder. It requires the `sdr-treegrid` controller (see [JavaScript](#javascript) above).
|
|
269
|
+
|
|
270
|
+
Folders are expanded unless `collapsed: true` is given. With `remember_expanded: true` (which requires an `id:`), folders the user expands or collapses stay so when the table is rendered again on the same page, e.g., when its turbo frame reloads. Enter on a file row activates the element in the row marked `data-sdr-treegrid-target="fileAction"`, if any.
|
|
271
|
+
|
|
272
|
+
```erb
|
|
273
|
+
<%= render SdrViewComponents::Tables::TreegridTableComponent.new(label: 'Files', id: 'files', collapsed: true) do |component| %>
|
|
274
|
+
<% component.with_header(label: 'File/folder name') %>
|
|
275
|
+
<% component.with_header(label: 'Size') %>
|
|
276
|
+
<% files.each do |file| %>
|
|
277
|
+
<% component.with_file(path_parts: file.path_parts, label: file.filename) do |row| %>
|
|
278
|
+
<% row.with_cell { number_to_human_size(file.size) } %>
|
|
279
|
+
<% end %>
|
|
280
|
+
<% end %>
|
|
281
|
+
<% end %>
|
|
282
|
+
```
|
|
283
|
+
|
|
253
284
|
### Navigation components
|
|
254
285
|
|
|
255
286
|
SdrViewComponents provides components for building navigation bar items and dropdown menus:
|
|
@@ -216,6 +216,26 @@ table.table-data th {
|
|
|
216
216
|
white-space: nowrap;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/* Treegrid tables */
|
|
220
|
+
table.table-treegrid tr[data-sdr-treegrid-role="folder"] {
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
table.table-treegrid .treegrid-name {
|
|
225
|
+
overflow-wrap: anywhere;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.treegrid-folder-icon {
|
|
229
|
+
display: inline-block;
|
|
230
|
+
margin-right: 0.25rem;
|
|
231
|
+
color: var(--bs-link-color);
|
|
232
|
+
transition: transform 0.2s ease-in-out;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
table.table-treegrid tr[aria-expanded="false"] .treegrid-folder-icon {
|
|
236
|
+
transform: rotate(180deg);
|
|
237
|
+
}
|
|
238
|
+
|
|
219
239
|
/* Radio buttons */
|
|
220
240
|
|
|
221
241
|
/* Inputs / labels are rendered in the opposite order than BS styles expect. */
|
data/app/components/sdr_view_components/tables/treegrid_expand_collapse_row_component.html.erb
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<tr class="treegrid-controls">
|
|
2
|
+
<%= tag.td colspan: do %>
|
|
3
|
+
<button type="button" class="btn btn-link p-0" data-action="sdr-treegrid#expandAll">Expand all</button>
|
|
4
|
+
<span aria-hidden="true">|</span>
|
|
5
|
+
<button type="button" class="btn btn-link p-0" data-action="sdr-treegrid#collapseAll">Collapse all</button>
|
|
6
|
+
<% end %>
|
|
7
|
+
</tr>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
# Component for rendering the Expand all / Collapse all row of a treegrid table.
|
|
6
|
+
class TreegridExpandCollapseRowComponent < BaseComponent
|
|
7
|
+
def initialize(colspan:)
|
|
8
|
+
@colspan = colspan
|
|
9
|
+
super()
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :colspan
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%= tag.tr role: 'row', tabindex: -1, hidden: hidden?, aria: { level: }, data: { sdr_treegrid_role: 'file' },
|
|
2
|
+
**row_options do %>
|
|
3
|
+
<%= tag.th scope: 'row', role: 'rowheader', class: 'treegrid-name fw-normal', style: indent_style do %>
|
|
4
|
+
<%= label || label_content %>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% cells.each do |cell| %>
|
|
7
|
+
<%= cell %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
# Component for rendering a file row of a treegrid table.
|
|
6
|
+
# Within the row, the element with data-sdr-treegrid-target="fileAction" (if any) is activated by Enter.
|
|
7
|
+
class TreegridFileRowComponent < TreegridRowComponent
|
|
8
|
+
renders_one :label_content
|
|
9
|
+
renders_many :cells, SdrViewComponents::Tables::CellComponent
|
|
10
|
+
|
|
11
|
+
# @param path_parts [Array<String>] the path of the file's folder (e.g., ['folder1', 'subfolder'])
|
|
12
|
+
# @param label [String] the file name; alternatively, provide label_content
|
|
13
|
+
def initialize(path_parts: [], label: nil, hidden: false, **row_options)
|
|
14
|
+
@label = label
|
|
15
|
+
@row_options = row_options
|
|
16
|
+
super(path_parts:, hidden:)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :label, :row_options
|
|
20
|
+
|
|
21
|
+
def level
|
|
22
|
+
path_parts.size + 1
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= tag.tr role: 'row', tabindex: -1, hidden: hidden?,
|
|
2
|
+
aria: { level:, expanded: expanded?.to_s },
|
|
3
|
+
data: { sdr_treegrid_role: 'folder', sdr_treegrid_key: key, action: 'click->sdr-treegrid#toggle' } do %>
|
|
4
|
+
<%= tag.th scope: 'row', role: 'rowheader', class: 'treegrid-name', style: indent_style do %>
|
|
5
|
+
<%= helpers.icon(icon_classes: 'bi bi-chevron-up', classes: 'treegrid-folder-icon', aria: { hidden: true }) %>
|
|
6
|
+
<span class="fw-bold"><%= label %></span>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% empty_cells.times do %>
|
|
9
|
+
<td></td>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
# Component for rendering a folder row of a treegrid table.
|
|
6
|
+
class TreegridFolderRowComponent < TreegridRowComponent
|
|
7
|
+
# @param path_parts [Array<String>] the folder's path (e.g., ['folder1', 'subfolder'])
|
|
8
|
+
# @param empty_cells [Integer] the number of empty cells following the folder name
|
|
9
|
+
def initialize(path_parts:, expanded:, empty_cells:, hidden: false)
|
|
10
|
+
@expanded = expanded
|
|
11
|
+
@empty_cells = empty_cells
|
|
12
|
+
super(path_parts:, hidden:)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :empty_cells
|
|
16
|
+
|
|
17
|
+
def expanded?
|
|
18
|
+
@expanded
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def level
|
|
22
|
+
path_parts.size
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def label
|
|
26
|
+
path_parts.last
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Identifies the folder when the table is rendered again.
|
|
30
|
+
def key
|
|
31
|
+
path_parts.join('/')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
# Base component for the folder and file rows of a treegrid table.
|
|
6
|
+
class TreegridRowComponent < BaseComponent
|
|
7
|
+
def initialize(path_parts:, hidden: false)
|
|
8
|
+
@path_parts = path_parts
|
|
9
|
+
@hidden = hidden
|
|
10
|
+
super()
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :path_parts
|
|
14
|
+
|
|
15
|
+
def hidden?
|
|
16
|
+
@hidden
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Subclasses must provide level (the row's depth in the tree, starting at 1).
|
|
20
|
+
def indent_style
|
|
21
|
+
"padding-left: #{((level - 1) * 1.25) + 0.5}rem;"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
# Component for rendering files as a treegrid table, with a collapsible row for each folder in their paths.
|
|
6
|
+
# Requires the sdr-treegrid Stimulus controller.
|
|
7
|
+
class TreegridTableComponent < BaseTableComponent
|
|
8
|
+
# Files must be provided in path order, so that each folder's files and subfolders follow its row.
|
|
9
|
+
renders_many :files, lambda { |path_parts: [], **args|
|
|
10
|
+
TreegridFileRowComponent.new(path_parts:, hidden: collapsed? && path_parts.any?, **args)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# @param collapsed [Boolean] whether folders are initially collapsed
|
|
14
|
+
# @param remember_expanded [Boolean] whether folders the user expands or collapses stay so when the table
|
|
15
|
+
# is rendered again (e.g., when its turbo frame reloads); requires an id
|
|
16
|
+
def initialize(collapsed: false, remember_expanded: false, classes: [], data: {}, **args)
|
|
17
|
+
raise ArgumentError, 'remember_expanded requires an id' if remember_expanded && args[:id].blank?
|
|
18
|
+
|
|
19
|
+
@collapsed = collapsed
|
|
20
|
+
super(classes: merge_classes('table-treegrid', classes),
|
|
21
|
+
role: 'treegrid',
|
|
22
|
+
data: data.merge(controller: merge_classes('sdr-treegrid', data[:controller]),
|
|
23
|
+
action: merge_actions('keydown->sdr-treegrid#navigate', data[:action]),
|
|
24
|
+
sdr_treegrid_remember_expanded_value: remember_expanded),
|
|
25
|
+
**args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def collapsed?
|
|
29
|
+
@collapsed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def rows?
|
|
33
|
+
files?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Folder rows are inserted before the first file in each folder.
|
|
37
|
+
def rows
|
|
38
|
+
@rows ||= [expand_collapse_row, *file_and_folder_rows].compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def file_and_folder_rows
|
|
44
|
+
last_path_parts = []
|
|
45
|
+
files.flat_map do |file|
|
|
46
|
+
folder_rows = new_folder_path_parts(last_path_parts:, path_parts: file.path_parts).map do |folder_path_parts|
|
|
47
|
+
folder_row(folder_path_parts)
|
|
48
|
+
end
|
|
49
|
+
last_path_parts = file.path_parts
|
|
50
|
+
[*folder_rows, file]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<Array<String>>] the path parts of each folder in path_parts that is not in last_path_parts
|
|
55
|
+
def new_folder_path_parts(last_path_parts:, path_parts:)
|
|
56
|
+
matching_count = path_parts.zip(last_path_parts).take_while { |part, last_part| part == last_part }.size
|
|
57
|
+
(matching_count...path_parts.size).map { |index| path_parts[0..index] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def folder_row(folder_path_parts)
|
|
61
|
+
render TreegridFolderRowComponent.new(path_parts: folder_path_parts,
|
|
62
|
+
expanded: !collapsed?,
|
|
63
|
+
hidden: collapsed? && folder_path_parts.size > 1,
|
|
64
|
+
empty_cells: column_count - 1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def expand_collapse_row
|
|
68
|
+
return if files.none? { |file| file.path_parts.any? }
|
|
69
|
+
|
|
70
|
+
render TreegridExpandCollapseRowComponent.new(colspan: column_count)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def column_count
|
|
74
|
+
[headers.size, 1].max
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Expanded state of the folders that users have toggled, by table id then folder key, kept across renders.
|
|
4
|
+
const expandedByTable = new Map()
|
|
5
|
+
|
|
6
|
+
// Expands and collapses the folder rows of a treegrid table (TreegridTableComponent),
|
|
7
|
+
// with keyboard navigation per https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/
|
|
8
|
+
// Only one row (the last focused) is in the tab order.
|
|
9
|
+
export default class extends Controller {
|
|
10
|
+
static targets = ["fileAction"]
|
|
11
|
+
static values = { rememberExpanded: Boolean }
|
|
12
|
+
|
|
13
|
+
connect() {
|
|
14
|
+
if (this.rememberExpandedValue) this.restoreExpanded()
|
|
15
|
+
this.element.addEventListener("focusin", this.focusRow)
|
|
16
|
+
this.setTabbableRow(this.visibleRows()[0])
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnect() {
|
|
20
|
+
this.element.removeEventListener("focusin", this.focusRow)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
focusRow = (event) => {
|
|
24
|
+
if (this.isTreeRow(event.target)) this.setTabbableRow(event.target)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setTabbableRow(row) {
|
|
28
|
+
if (!row) return
|
|
29
|
+
|
|
30
|
+
this.element.querySelectorAll(':scope > tbody > tr[tabindex="0"]').forEach((other) => { other.tabIndex = -1 })
|
|
31
|
+
row.tabIndex = 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
toggle(event) {
|
|
35
|
+
this.toggleFolder(event.target.closest("tr"))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
expandAll(event) {
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
this.folderRows().forEach((row) => {
|
|
41
|
+
if (!this.isOpenFolder(row)) this.toggleFolder(row)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
collapseAll(event) {
|
|
46
|
+
event.preventDefault()
|
|
47
|
+
this.folderRows().forEach((row) => {
|
|
48
|
+
if (this.isOpenFolder(row)) this.toggleFolder(row)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
navigate(event) {
|
|
53
|
+
const row = event.target
|
|
54
|
+
if (!this.isTreeRow(row)) return
|
|
55
|
+
|
|
56
|
+
const target = this.navigationTarget(event.key, row)
|
|
57
|
+
if (target === undefined) return
|
|
58
|
+
|
|
59
|
+
event.preventDefault()
|
|
60
|
+
if (typeof target === "function") {
|
|
61
|
+
target()
|
|
62
|
+
} else {
|
|
63
|
+
target?.focus()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// @return [Function, Element, null, undefined] an action or the row to focus; undefined if the key isn't handled
|
|
68
|
+
navigationTarget(key, row) {
|
|
69
|
+
switch (key) {
|
|
70
|
+
case "Enter":
|
|
71
|
+
if (this.isFolder(row)) return () => this.toggleFolder(row)
|
|
72
|
+
return this.fileAction(row) ? () => this.fileAction(row).click() : undefined
|
|
73
|
+
case "ArrowRight":
|
|
74
|
+
if (!this.isFolder(row)) return undefined
|
|
75
|
+
return this.isOpenFolder(row) ? this.nextVisibleRow(row) : () => this.toggleFolder(row)
|
|
76
|
+
case "ArrowLeft":
|
|
77
|
+
if (this.isOpenFolder(row)) return () => this.toggleFolder(row)
|
|
78
|
+
return this.level(row) > 1 ? this.parentRow(row) : undefined
|
|
79
|
+
case "ArrowUp":
|
|
80
|
+
return this.previousVisibleRow(row)
|
|
81
|
+
case "ArrowDown":
|
|
82
|
+
return this.nextVisibleRow(row)
|
|
83
|
+
case "Home":
|
|
84
|
+
return this.visibleRows()[0]
|
|
85
|
+
case "End":
|
|
86
|
+
return this.visibleRows().at(-1)
|
|
87
|
+
default:
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toggleFolder(row) {
|
|
93
|
+
const expand = !this.isOpenFolder(row)
|
|
94
|
+
row.setAttribute("aria-expanded", expand)
|
|
95
|
+
if (this.rememberExpandedValue) this.expandedFolders().set(row.dataset.sdrTreegridKey, expand)
|
|
96
|
+
|
|
97
|
+
if (expand) {
|
|
98
|
+
this.showDescendantRows(row)
|
|
99
|
+
} else {
|
|
100
|
+
const descendants = this.descendantRows(row)
|
|
101
|
+
descendants.forEach((descendant) => { descendant.hidden = true })
|
|
102
|
+
// Keeps a row in the tab order when the tabbable row is hidden.
|
|
103
|
+
if (descendants.some((descendant) => descendant.tabIndex === 0)) this.setTabbableRow(row)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Shows the descendants of an expanded folder, except those within collapsed subfolders.
|
|
108
|
+
showDescendantRows(row) {
|
|
109
|
+
let collapsedLevel = null
|
|
110
|
+
this.descendantRows(row).forEach((descendant) => {
|
|
111
|
+
if (collapsedLevel !== null && this.level(descendant) > collapsedLevel) return
|
|
112
|
+
|
|
113
|
+
descendant.hidden = false
|
|
114
|
+
collapsedLevel = this.isFolder(descendant) && !this.isOpenFolder(descendant) ? this.level(descendant) : null
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Rows are in tree order, so a folder within a collapsed folder is only marked expanded.
|
|
119
|
+
restoreExpanded() {
|
|
120
|
+
this.folderRows().forEach((row) => {
|
|
121
|
+
const expanded = this.expandedFolders().get(row.dataset.sdrTreegridKey)
|
|
122
|
+
if (expanded === undefined || expanded === this.isOpenFolder(row)) return
|
|
123
|
+
|
|
124
|
+
if (expanded && row.hidden) {
|
|
125
|
+
row.setAttribute("aria-expanded", true)
|
|
126
|
+
} else {
|
|
127
|
+
this.toggleFolder(row)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
expandedFolders() {
|
|
133
|
+
if (!expandedByTable.has(this.element.id)) expandedByTable.set(this.element.id, new Map())
|
|
134
|
+
return expandedByTable.get(this.element.id)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
descendantRows(row) {
|
|
138
|
+
const descendants = []
|
|
139
|
+
let sibling = row.nextElementSibling
|
|
140
|
+
while (sibling && this.isTreeRow(sibling) && this.level(sibling) > this.level(row)) {
|
|
141
|
+
descendants.push(sibling)
|
|
142
|
+
sibling = sibling.nextElementSibling
|
|
143
|
+
}
|
|
144
|
+
return descendants
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
parentRow(row) {
|
|
148
|
+
let sibling = row.previousElementSibling
|
|
149
|
+
while (sibling && this.isTreeRow(sibling)) {
|
|
150
|
+
if (this.level(sibling) === this.level(row) - 1) return sibling
|
|
151
|
+
sibling = sibling.previousElementSibling
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
nextVisibleRow(row) {
|
|
157
|
+
const rows = this.visibleRows()
|
|
158
|
+
return rows[rows.indexOf(row) + 1] || null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
previousVisibleRow(row) {
|
|
162
|
+
const rows = this.visibleRows()
|
|
163
|
+
return rows[rows.indexOf(row) - 1] || null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
visibleRows() {
|
|
167
|
+
return Array.from(this.element.querySelectorAll(":scope > tbody > tr[aria-level]:not([hidden])"))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
folderRows() {
|
|
171
|
+
return this.element.querySelectorAll(':scope > tbody > tr[data-sdr-treegrid-role="folder"]')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fileAction(row) {
|
|
175
|
+
return this.fileActionTargets.find((target) => row.contains(target))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
isTreeRow(element) {
|
|
179
|
+
return element.matches("tr[aria-level]")
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
isFolder(row) {
|
|
183
|
+
return row.dataset.sdrTreegridRole === "folder"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isOpenFolder(row) {
|
|
187
|
+
return this.isFolder(row) && row.getAttribute("aria-expanded") === "true"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
level(row) {
|
|
191
|
+
return Number(row.getAttribute("aria-level"))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= render(SdrViewComponents::Tables::TreegridTableComponent.new(label: 'Files', id: 'preview-files', collapsed:,
|
|
2
|
+
remember_expanded: true)) do |component| %>
|
|
3
|
+
<% component.with_header(label: 'File/folder name') %>
|
|
4
|
+
<% component.with_header(label: 'Size') %>
|
|
5
|
+
<% files.each do |file| %>
|
|
6
|
+
<% component.with_file(path_parts: file[:path_parts], label: file[:label]) do |row| %>
|
|
7
|
+
<% row.with_cell { file[:size] } %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SdrViewComponents
|
|
4
|
+
module Tables
|
|
5
|
+
class TreegridTableComponentPreview < ViewComponent::Preview
|
|
6
|
+
FILES = [
|
|
7
|
+
{ path_parts: [], label: 'README.txt', size: '1 KB' },
|
|
8
|
+
{ path_parts: %w[images], label: 'image1.tif', size: '10 MB' },
|
|
9
|
+
{ path_parts: %w[images], label: 'image2.tif', size: '12 MB' },
|
|
10
|
+
{ path_parts: %w[images thumbnails], label: 'image1.jpg', size: '20 KB' },
|
|
11
|
+
{ path_parts: %w[data], label: 'data.csv', size: '3 MB' }
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def default
|
|
15
|
+
render_with_template(locals: { files: FILES, collapsed: false })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def collapsed
|
|
19
|
+
render_with_template(template: 'sdr_view_components/tables/treegrid_table_component_preview/default',
|
|
20
|
+
locals: { files: FILES, collapsed: true })
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sdr_view_components
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aaron Collier
|
|
@@ -180,6 +180,14 @@ files:
|
|
|
180
180
|
- app/components/sdr_view_components/tables/row_component.html.erb
|
|
181
181
|
- app/components/sdr_view_components/tables/row_component.rb
|
|
182
182
|
- app/components/sdr_view_components/tables/table_component.rb
|
|
183
|
+
- app/components/sdr_view_components/tables/treegrid_expand_collapse_row_component.html.erb
|
|
184
|
+
- app/components/sdr_view_components/tables/treegrid_expand_collapse_row_component.rb
|
|
185
|
+
- app/components/sdr_view_components/tables/treegrid_file_row_component.html.erb
|
|
186
|
+
- app/components/sdr_view_components/tables/treegrid_file_row_component.rb
|
|
187
|
+
- app/components/sdr_view_components/tables/treegrid_folder_row_component.html.erb
|
|
188
|
+
- app/components/sdr_view_components/tables/treegrid_folder_row_component.rb
|
|
189
|
+
- app/components/sdr_view_components/tables/treegrid_row_component.rb
|
|
190
|
+
- app/components/sdr_view_components/tables/treegrid_table_component.rb
|
|
183
191
|
- app/javascript/sdr_view_components/lookbook.js
|
|
184
192
|
- app/javascript/sdr_view_components/tab_error_controller.js
|
|
185
193
|
- app/javascript/sdr_view_components/tab_link_controller.js
|
|
@@ -187,6 +195,7 @@ files:
|
|
|
187
195
|
- app/javascript/sdr_view_components/tab_overflow_controller.js
|
|
188
196
|
- app/javascript/sdr_view_components/tab_select_controller.js
|
|
189
197
|
- app/javascript/sdr_view_components/toast_controller.js
|
|
198
|
+
- app/javascript/sdr_view_components/treegrid_controller.js
|
|
190
199
|
- app/views/layouts/lookbook.html.erb
|
|
191
200
|
- config/routes.rb
|
|
192
201
|
- lib/sdr_view_components.rb
|
|
@@ -368,6 +377,8 @@ files:
|
|
|
368
377
|
- spec/components/previews/sdr_view_components/tables/row_component_preview/with_value.html.erb
|
|
369
378
|
- spec/components/previews/sdr_view_components/tables/row_component_preview/with_values.html.erb
|
|
370
379
|
- spec/components/previews/sdr_view_components/tables/table_component_preview.rb
|
|
380
|
+
- spec/components/previews/sdr_view_components/tables/treegrid_table_component_preview.rb
|
|
381
|
+
- spec/components/previews/sdr_view_components/tables/treegrid_table_component_preview/default.html.erb
|
|
371
382
|
homepage: https://github.com/sul-dlss/sdr_view_components
|
|
372
383
|
licenses: []
|
|
373
384
|
metadata:
|