playbook_ui 16.4.0.pre.alpha.displaybreakpoints15091 → 16.4.0.pre.alpha.play2838formcustomvalidationsconsistency15140
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/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +46 -1
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +2 -2
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +2 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.html.erb +57 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.md +7 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/index.js +82 -0
- data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +21 -4
- data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +115 -9
- data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +12 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +4 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +9 -1
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +67 -28
- data/app/pb_kits/playbook/pb_icon/icon.rb +7 -1
- data/app/pb_kits/playbook/pb_list/_list_mixin.scss +4 -4
- data/app/pb_kits/playbook/pb_selectable_list/_selectable_list.scss +19 -1
- data/app/pb_kits/playbook/utilities/_display.scss +0 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eca9c4e210533f089680afa7791fdf3679c6c9548918571ab7ecd7b57bfa92da
|
|
4
|
+
data.tar.gz: 5c4d72efd74fe5d8a81c3354d1aeb3b092733a4b9f8ad860c5479ca0f29bc381
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7cef2caa94eef888ad35f53c2d8a4a34b2999908fdfddff010a115f8afcbc37df16cf146391b3f8053a5d0f23f2703908f33319c6838e3b1ca8a7ea70a5444d
|
|
7
|
+
data.tar.gz: 03f7261435bad5cc915908e9bfbe694082728e3613e5add4c2b36083fbc863668e2a9078ea303d0f2bcd545d35cc1aedc291d307edf78384b222c764d9fc0699
|
|
@@ -63,6 +63,51 @@
|
|
|
63
63
|
width: 100%;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Override pb_table endcap: first and last column use same padding as rest of row
|
|
67
|
+
// First column
|
|
68
|
+
.pb_table.table-sm tbody tr td:first-child,
|
|
69
|
+
.pb_table.table-sm tbody tr .pb_table_td:first-child,
|
|
70
|
+
.pb_table.table-sm tbody .pb_table_tr td:first-child,
|
|
71
|
+
.pb_table.table-sm tbody .pb_table_tr .pb_table_td:first-child,
|
|
72
|
+
.pb_table.table-sm .pb_table_tbody tr td:first-child,
|
|
73
|
+
.pb_table.table-sm .pb_table_tbody tr .pb_table_td:first-child,
|
|
74
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr td:first-child,
|
|
75
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
|
|
76
|
+
padding-left: $space-xs;
|
|
77
|
+
}
|
|
78
|
+
.pb_table.table-md tbody tr td:first-child,
|
|
79
|
+
.pb_table.table-md tbody tr .pb_table_td:first-child,
|
|
80
|
+
.pb_table.table-md tbody .pb_table_tr td:first-child,
|
|
81
|
+
.pb_table.table-md tbody .pb_table_tr .pb_table_td:first-child,
|
|
82
|
+
.pb_table.table-md .pb_table_tbody tr td:first-child,
|
|
83
|
+
.pb_table.table-md .pb_table_tbody tr .pb_table_td:first-child,
|
|
84
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr td:first-child,
|
|
85
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
|
|
86
|
+
padding-left: $space-sm;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Last column
|
|
90
|
+
.pb_table.table-sm tbody tr td:last-child,
|
|
91
|
+
.pb_table.table-sm tbody tr .pb_table_td:last-child,
|
|
92
|
+
.pb_table.table-sm tbody .pb_table_tr td:last-child,
|
|
93
|
+
.pb_table.table-sm tbody .pb_table_tr .pb_table_td:last-child,
|
|
94
|
+
.pb_table.table-sm .pb_table_tbody tr td:last-child,
|
|
95
|
+
.pb_table.table-sm .pb_table_tbody tr .pb_table_td:last-child,
|
|
96
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr td:last-child,
|
|
97
|
+
.pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
|
|
98
|
+
padding-right: $space-xs;
|
|
99
|
+
}
|
|
100
|
+
.pb_table.table-md tbody tr td:last-child,
|
|
101
|
+
.pb_table.table-md tbody tr .pb_table_td:last-child,
|
|
102
|
+
.pb_table.table-md tbody .pb_table_tr td:last-child,
|
|
103
|
+
.pb_table.table-md tbody .pb_table_tr .pb_table_td:last-child,
|
|
104
|
+
.pb_table.table-md .pb_table_tbody tr td:last-child,
|
|
105
|
+
.pb_table.table-md .pb_table_tbody tr .pb_table_td:last-child,
|
|
106
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr td:last-child,
|
|
107
|
+
.pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
|
|
108
|
+
padding-right: $space-sm;
|
|
109
|
+
}
|
|
110
|
+
|
|
66
111
|
// Virtualized Table and Rows for Infinite Scroll
|
|
67
112
|
scrollbar-gutter: stable right-edges;
|
|
68
113
|
.virtualized-header-row-header {
|
|
@@ -720,7 +765,7 @@
|
|
|
720
765
|
}
|
|
721
766
|
}
|
|
722
767
|
|
|
723
|
-
// Row Pinning -
|
|
768
|
+
// Row Pinning - React uses inline style; Rails passes same style via html_options from table_body
|
|
724
769
|
.pinned-row {
|
|
725
770
|
box-shadow: 0 4px 10px 0 rgba($shadow, 0.16) !important;
|
|
726
771
|
}
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
}) %>
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
10
|
-
<%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props)) do %>
|
|
10
|
+
<%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props || {})) do %>
|
|
11
11
|
<% if content.present? %>
|
|
12
12
|
<% content.presence %>
|
|
13
13
|
<% else %>
|
|
14
14
|
<%= pb_rails("advanced_table/table_header", props: { table_id: object.id, column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, show_actions_bar: object.show_actions_bar, inline_row_loading: object.inline_row_loading, persist_toggle_expansion_button: object.persist_toggle_expansion_button, table_data: object.table_data }) %>
|
|
15
|
-
<%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading }) %>
|
|
15
|
+
<%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading, pinned_rows: object.pinned_rows }) %>
|
|
16
16
|
<% end %>
|
|
17
17
|
<% end %>
|
|
18
18
|
<% end %>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<%# Example sort method for demonstration purposes %>
|
|
2
|
+
<% if params["sort"] %>
|
|
3
|
+
<% sort_param = params["sort"].gsub(/_(asc|desc)\z/, "") %>
|
|
4
|
+
<% sort_direction = params["sort"].end_with?("_asc") ? 1 : -1 %>
|
|
5
|
+
<% @table_data_with_id.sort! do |a, b|
|
|
6
|
+
value_a = a[sort_param] || a[sort_param.to_sym]
|
|
7
|
+
value_b = b[sort_param] || b[sort_param.to_sym]
|
|
8
|
+
|
|
9
|
+
value_a = value_a.to_i if value_a.is_a?(String) && value_a.match?(/^\d+$/)
|
|
10
|
+
value_b = value_b.to_i if value_b.is_a?(String) && value_b.match?(/^\d+$/)
|
|
11
|
+
|
|
12
|
+
sort_direction * (value_a <=> value_b)
|
|
13
|
+
end %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<% column_definitions = [
|
|
17
|
+
{
|
|
18
|
+
accessor: "year",
|
|
19
|
+
label: "Year",
|
|
20
|
+
cellAccessors: ["quarter", "month", "day"],
|
|
21
|
+
sort_menu: [
|
|
22
|
+
{ item: "Year", link: "?sort=year_asc#pinned_rows_table", active: params["sort"] == "year_asc", direction: "asc" },
|
|
23
|
+
{ item: "Year", link: "?sort=year_desc#pinned_rows_table", active: params["sort"] == "year_desc", direction: "desc" }
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
accessor: "newEnrollments",
|
|
28
|
+
label: "New Enrollments",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
accessor: "scheduledMeetings",
|
|
32
|
+
label: "Scheduled Meetings",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
accessor: "attendanceRate",
|
|
36
|
+
label: "Attendance Rate",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessor: "completedClasses",
|
|
40
|
+
label: "Completed Classes",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessor: "classCompletionRate",
|
|
44
|
+
label: "Class Completion Rate",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
accessor: "graduatedStudents",
|
|
48
|
+
label: "Graduated Students",
|
|
49
|
+
}
|
|
50
|
+
] %>
|
|
51
|
+
|
|
52
|
+
<% pinned_rows = { top: ["8"] } %>
|
|
53
|
+
|
|
54
|
+
<%= pb_rails("advanced_table", props: { id: "pinned_rows_table", table_data: @table_data_with_id, column_definitions: column_definitions, max_height: "xs", pinned_rows: pinned_rows, responsive: "none", table_props: { sticky: true }}) do %>
|
|
55
|
+
<%= pb_rails("advanced_table/table_header", props: { table_id: "pinned_rows_table", column_definitions: column_definitions }) %>
|
|
56
|
+
<%= pb_rails("advanced_table/table_body", props: { table_id: "pinned_rows_table", table_data: @table_data_with_id, column_definitions: column_definitions, pinned_rows: pinned_rows }) %>
|
|
57
|
+
<% end %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Use the `pinned_rows` prop to pin specific rows to the top of an Advanced Table. Pinned rows will remain at the top when scrolling through table data and will not change position if sorting is used.
|
|
2
|
+
|
|
3
|
+
**NOTE:**
|
|
4
|
+
- Sticky header required: Pinned rows must be used with `sticky: true` via `table_props` (works with both responsive and non-responsive tables)
|
|
5
|
+
- Row ids required: Each object within the `table_data` array must contain a unique `id` in order to attach an id to all Rows for this to function.
|
|
6
|
+
- `pinned_rows` takes a hash with a `top` key containing an array of row ids, as shown in the code snippet below.
|
|
7
|
+
- For expandable rows, use the parent id in `pinned_rows[:top]`; all its children will automatically be pinned with it. If a child id is passed without the parent being pinned, nothing will be pinned.
|
|
@@ -7,6 +7,7 @@ examples:
|
|
|
7
7
|
- advanced_table_table_props: Table Props
|
|
8
8
|
- advanced_table_sticky_header_rails: Sticky Header
|
|
9
9
|
- advanced_table_table_props_sticky_header: Sticky Header for Responsive Table
|
|
10
|
+
- advanced_table_pinned_rows_rails: Pinned Rows
|
|
10
11
|
- advanced_table_beta_sort: Enable Sorting
|
|
11
12
|
- advanced_table_responsive: Responsive Tables
|
|
12
13
|
- advanced_table_custom_cell_rails: Custom Components for Cells
|
|
@@ -196,6 +196,17 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
|
196
196
|
if (table.dataset.pbAdvancedTableInitialized) return;
|
|
197
197
|
table.dataset.pbAdvancedTableInitialized = "true";
|
|
198
198
|
|
|
199
|
+
// Measure header height so pinned rows don't overlap when header wraps (e.g. mobile)
|
|
200
|
+
if (mainTable) {
|
|
201
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(mainTable);
|
|
202
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
203
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(mainTable);
|
|
204
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(mainTable);
|
|
205
|
+
});
|
|
206
|
+
resizeObserver.observe(table);
|
|
207
|
+
mainTable._advancedTableHeaderResizeObserver = resizeObserver;
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
// Delegate checkbox changes
|
|
200
211
|
table.addEventListener("change", (event) => {
|
|
201
212
|
const checkbox = event.target.closest('input[type="checkbox"]');
|
|
@@ -315,9 +326,64 @@ export default class PbAdvancedTable extends PbEnhancedElement {
|
|
|
315
326
|
lastVisibleRow.classList.add("last-visible-row");
|
|
316
327
|
lastVisibleRow.classList.add("last-row-cell");
|
|
317
328
|
}
|
|
329
|
+
|
|
330
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(parentElement);
|
|
331
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(table);
|
|
318
332
|
}
|
|
319
333
|
}
|
|
320
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Measure thead height and set --advanced-table-header-height so pinned rows and
|
|
337
|
+
* multi-row sticky headers use the correct offset. Re-run when header wraps (e.g. mobile).
|
|
338
|
+
*/
|
|
339
|
+
static updateStickyHeaderRowHeights(advancedTableWrapper) {
|
|
340
|
+
if (!advancedTableWrapper) return;
|
|
341
|
+
const table = advancedTableWrapper.querySelector("table.pb_table");
|
|
342
|
+
const thead = table?.querySelector("thead");
|
|
343
|
+
if (!thead) return;
|
|
344
|
+
|
|
345
|
+
const rows = Array.from(thead.querySelectorAll("tr"));
|
|
346
|
+
let totalHeight = 0;
|
|
347
|
+
rows.forEach((tr, index) => {
|
|
348
|
+
const h = tr.offsetHeight;
|
|
349
|
+
if (index === 0) {
|
|
350
|
+
advancedTableWrapper.style.setProperty(
|
|
351
|
+
"--advanced-table-header-row-0-height",
|
|
352
|
+
`${h}px`
|
|
353
|
+
);
|
|
354
|
+
} else if (index === 1) {
|
|
355
|
+
advancedTableWrapper.style.setProperty(
|
|
356
|
+
"--advanced-table-header-row-1-height",
|
|
357
|
+
`${h}px`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
totalHeight += h;
|
|
361
|
+
});
|
|
362
|
+
advancedTableWrapper.style.setProperty(
|
|
363
|
+
"--advanced-table-header-height",
|
|
364
|
+
`${totalHeight}px`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Recompute sticky top for visible pinned rows so collapsed rows don't leave a gap.
|
|
370
|
+
* Call after expand/collapse and on load.
|
|
371
|
+
*/
|
|
372
|
+
static updatePinnedRowsStickyTops(advancedTableWrapper) {
|
|
373
|
+
const pinnedTbody = advancedTableWrapper?.querySelector("tbody.pinned-rows-tbody");
|
|
374
|
+
if (!pinnedTbody) return;
|
|
375
|
+
|
|
376
|
+
const pinnedRows = Array.from(pinnedTbody.querySelectorAll("tr.pinned-row"));
|
|
377
|
+
const visibleRows = pinnedRows.filter(
|
|
378
|
+
(tr) => tr.style.display !== "none" && tr.offsetParent !== null
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const headerOffset = "var(--advanced-table-header-height, 44px)";
|
|
382
|
+
visibleRows.forEach((tr, index) => {
|
|
383
|
+
tr.style.top = `calc(${headerOffset} + 2.5em * ${index})`;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
321
387
|
hideCloseIcon() {
|
|
322
388
|
const closeIcon = this.element.querySelector(UP_ARROW_SELECTOR);
|
|
323
389
|
closeIcon.style.display = "none";
|
|
@@ -519,3 +585,19 @@ window.expandAllRows = (element) => {
|
|
|
519
585
|
window.expandAllSubRows = (element, rowDepth) => {
|
|
520
586
|
PbAdvancedTable.handleToggleAllSubRows(element, rowDepth);
|
|
521
587
|
};
|
|
588
|
+
|
|
589
|
+
// Fix header height and pinned row sticky tops on load (header wrap + collapsed rows)
|
|
590
|
+
function updateAllAdvancedTableStickyHeights() {
|
|
591
|
+
document.querySelectorAll(".pb_advanced_table").forEach((wrapper) => {
|
|
592
|
+
PbAdvancedTable.updateStickyHeaderRowHeights(wrapper);
|
|
593
|
+
PbAdvancedTable.updatePinnedRowsStickyTops(wrapper);
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (typeof document !== "undefined") {
|
|
598
|
+
if (document.readyState === "loading") {
|
|
599
|
+
document.addEventListener("DOMContentLoaded", updateAllAdvancedTableStickyHeights);
|
|
600
|
+
} else {
|
|
601
|
+
updateAllAdvancedTableStickyHeights();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
<% table_data = object.table_data || [] %>
|
|
2
|
+
<% if object.has_pinned_rows? %>
|
|
3
|
+
<%= pb_content_tag(:tbody, class: "pinned-rows-tbody") do %>
|
|
4
|
+
<% next_index = 0 %>
|
|
5
|
+
<% object.pinned_root_rows.each do |root_info| %>
|
|
6
|
+
<% row_output, next_index = object.render_row_and_children(root_info[:row], object.column_definitions, root_info[:depth], root_info[:depth] > 0, root_info[:ancestor_ids] || [], root_info[:ancestor_ids]&.first, immediate_parent_row_id: object.row_id_for(root_info[:parent_row]), is_pinned_row: true, pinned_index: next_index, initial_table_data_attributes: root_info[:depth].to_i > 0 ? object.pinned_root_initial_data_attributes(root_info) : nil) %>
|
|
7
|
+
<%= row_output %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<%= pb_content_tag(:tbody) do %>
|
|
11
|
+
<% table_data.each do |row| %>
|
|
12
|
+
<% result = object.render_row_and_children(row, object.column_definitions, 0, false, skip_pinned_ids: object.pinned_ids_set) %>
|
|
13
|
+
<%= result.is_a?(Array) ? result[0] : result %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% else %>
|
|
17
|
+
<%= pb_content_tag(:tbody) do %>
|
|
18
|
+
<% table_data.each do |row| %>
|
|
19
|
+
<%= object.render_row_and_children(row, object.column_definitions, 0, false) %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
5
22
|
<% end %>
|
|
@@ -27,6 +27,8 @@ module Playbook
|
|
|
27
27
|
default: []
|
|
28
28
|
prop :inline_row_loading, type: Playbook::Props::Boolean,
|
|
29
29
|
default: false
|
|
30
|
+
prop :pinned_rows, type: Playbook::Props::HashProp,
|
|
31
|
+
default: {}
|
|
30
32
|
|
|
31
33
|
def flatten_columns(columns)
|
|
32
34
|
columns.flat_map do |col|
|
|
@@ -42,14 +44,21 @@ module Playbook
|
|
|
42
44
|
end.compact
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil, additional_classes: "", table_data_attributes: {}, immediate_parent_row_id: nil)
|
|
47
|
+
def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil, additional_classes: "", table_data_attributes: {}, immediate_parent_row_id: nil, is_pinned_row: false, pinned_index: nil, skip_pinned_ids: nil, initial_table_data_attributes: nil)
|
|
48
|
+
if skip_pinned_ids && row_id_for(row) && skip_pinned_ids.include?(row_id_for(row).to_s)
|
|
49
|
+
return is_pinned_row ? [ActiveSupport::SafeBuffer.new, pinned_index] : ActiveSupport::SafeBuffer.new
|
|
50
|
+
end
|
|
51
|
+
|
|
46
52
|
top_parent_id ||= row.object_id
|
|
47
53
|
new_ancestor_ids = ancestor_ids + [row.object_id]
|
|
48
|
-
leaf_columns = flatten_columns(column_definitions)
|
|
54
|
+
leaf_columns = flatten_columns(column_definitions || [])
|
|
49
55
|
|
|
50
56
|
output = ActiveSupport::SafeBuffer.new
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
subrow_headers_arr = subrow_headers || []
|
|
58
|
+
is_first_child_of_subrow = current_depth.positive? && first_parent_child && subrow_headers_arr[current_depth - 1].present?
|
|
59
|
+
last_row = subrow_headers_arr.length == current_depth
|
|
60
|
+
|
|
61
|
+
next_pinned_index = pinned_index
|
|
53
62
|
|
|
54
63
|
subrow_ancestor_ids = ancestor_ids + ["#{row.object_id}sr"]
|
|
55
64
|
subrow_data_attributes = {
|
|
@@ -58,7 +67,16 @@ module Playbook
|
|
|
58
67
|
row_parent: "#{table_id}_#{ancestor_ids.last}",
|
|
59
68
|
}
|
|
60
69
|
# Subrow header if applicable
|
|
61
|
-
|
|
70
|
+
if is_first_child_of_subrow && enable_toggle_expansion == "all"
|
|
71
|
+
subrow_props = { row: row, column_definitions: leaf_columns, depth: current_depth, subrow_header: subrow_headers_arr[current_depth - 1], collapsible_trail: collapsible_trail, classname: "toggle-content", responsive: responsive, subrow_data_attributes: subrow_data_attributes, last_row: last_row, immediate_parent_row_id: immediate_parent_row_id }
|
|
72
|
+
if is_pinned_row && next_pinned_index
|
|
73
|
+
subrow_props[:is_pinned_row] = true
|
|
74
|
+
subrow_props[:pinned_index] = next_pinned_index
|
|
75
|
+
subrow_props[:html_options] = { style: build_pinned_row_style(next_pinned_index, background: "var(--pb_table_sticky_bg, #f5f5f5)") }
|
|
76
|
+
next_pinned_index += 1
|
|
77
|
+
end
|
|
78
|
+
output << pb_rails("advanced_table/table_subrow_header", props: subrow_props)
|
|
79
|
+
end
|
|
62
80
|
|
|
63
81
|
current_data_attributes = if current_depth.zero?
|
|
64
82
|
{
|
|
@@ -67,11 +85,19 @@ module Playbook
|
|
|
67
85
|
row_parent: nil,
|
|
68
86
|
}
|
|
69
87
|
else
|
|
70
|
-
table_data_attributes
|
|
88
|
+
initial_table_data_attributes || table_data_attributes
|
|
71
89
|
end
|
|
72
90
|
|
|
73
91
|
# Additional class and data attributes needed for toggle logic
|
|
74
|
-
|
|
92
|
+
row_props = { table_id: table_id, row: row, column_definitions: leaf_columns, depth: current_depth, collapsible_trail: collapsible_trail, classname: additional_classes, table_data_attributes: current_data_attributes, responsive: responsive, loading: loading, selectable_rows: selectable_rows, row_id: row[:id], enable_toggle_expansion: enable_toggle_expansion, row_styling: row_styling, last_row: last_row, immediate_parent_row_id: immediate_parent_row_id, inline_row_loading: inline_row_loading }
|
|
93
|
+
if is_pinned_row && next_pinned_index
|
|
94
|
+
row_props[:is_pinned_row] = true
|
|
95
|
+
row_props[:pinned_index] = next_pinned_index
|
|
96
|
+
row_bg = (row_styling || []).find { |s| s[:row_id].to_s == row_id_for(row).to_s }&.[](:background_color) || "white"
|
|
97
|
+
row_props[:html_options] = { style: build_pinned_row_style(next_pinned_index, background: row_bg) }
|
|
98
|
+
next_pinned_index += 1
|
|
99
|
+
end
|
|
100
|
+
output << pb_rails("advanced_table/table_row", props: row_props)
|
|
75
101
|
|
|
76
102
|
# Render inline loading row when inline_row_loading is enabled and row has empty children
|
|
77
103
|
if inline_row_loading
|
|
@@ -103,11 +129,21 @@ module Playbook
|
|
|
103
129
|
advanced_table_content: data_content,
|
|
104
130
|
}
|
|
105
131
|
|
|
106
|
-
|
|
132
|
+
child_opts = { additional_classes: "toggle-content", table_data_attributes: child_data_attributes, immediate_parent_row_id: row[:id] }
|
|
133
|
+
child_opts[:is_pinned_row] = is_pinned_row
|
|
134
|
+
child_opts[:pinned_index] = next_pinned_index if is_pinned_row
|
|
135
|
+
child_opts[:skip_pinned_ids] = skip_pinned_ids if skip_pinned_ids
|
|
136
|
+
|
|
137
|
+
child_output, next_pinned_index = render_row_and_children(child_row, column_definitions, current_depth + 1, is_first_child, new_ancestor_ids, top_parent_id, **child_opts)
|
|
138
|
+
output << child_output
|
|
107
139
|
end
|
|
108
140
|
end
|
|
109
141
|
|
|
110
|
-
|
|
142
|
+
if is_pinned_row
|
|
143
|
+
[output, next_pinned_index]
|
|
144
|
+
else
|
|
145
|
+
output
|
|
146
|
+
end
|
|
111
147
|
end
|
|
112
148
|
|
|
113
149
|
def classname
|
|
@@ -142,6 +178,76 @@ module Playbook
|
|
|
142
178
|
end
|
|
143
179
|
end
|
|
144
180
|
|
|
181
|
+
def row_id_for(row)
|
|
182
|
+
return nil if row.nil?
|
|
183
|
+
|
|
184
|
+
row[:id] || row["id"]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def pinned_top_ids
|
|
188
|
+
return [] if pinned_rows.nil? || !pinned_rows.respond_to?(:[])
|
|
189
|
+
|
|
190
|
+
top = pinned_rows["top"] || pinned_rows[:top]
|
|
191
|
+
Array(top).map(&:to_s)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def pinned_ids_set
|
|
195
|
+
return Set.new if pinned_top_ids.blank?
|
|
196
|
+
|
|
197
|
+
set = Set.new
|
|
198
|
+
pinned_root_rows.each do |root|
|
|
199
|
+
collect_row_and_descendant_ids(root[:row], set)
|
|
200
|
+
end
|
|
201
|
+
set
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def collect_row_and_descendant_ids(row, set)
|
|
205
|
+
id = row_id_for(row)
|
|
206
|
+
set.add(id.to_s) if id
|
|
207
|
+
row_children_for(row)&.each { |child| collect_row_and_descendant_ids(child, set) }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def find_row_by_id(data, id, depth: 0, ancestor_ids: [], parent_row: nil)
|
|
211
|
+
id_str = id.to_s
|
|
212
|
+
Array(data).each do |row|
|
|
213
|
+
return { row: row, depth: depth, ancestor_ids: ancestor_ids, parent_row: parent_row } if row_id_for(row).to_s == id_str
|
|
214
|
+
|
|
215
|
+
found = find_row_by_id(row_children_for(row), id_str, depth: depth + 1, ancestor_ids: ancestor_ids + [row.object_id], parent_row: row)
|
|
216
|
+
return found if found
|
|
217
|
+
end
|
|
218
|
+
nil
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def pinned_root_rows
|
|
222
|
+
return [] if pinned_top_ids.blank?
|
|
223
|
+
|
|
224
|
+
pinned_top_ids.filter_map { |id| find_row_by_id(table_data, id) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def has_pinned_rows?
|
|
228
|
+
pinned_root_rows.any?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Build inline style for sticky pinned row (matches React). Pass via html_options so the tr gets the attribute.
|
|
232
|
+
def build_pinned_row_style(pinned_index, background: "white")
|
|
233
|
+
header_offset = "var(--advanced-table-header-height, 44px)"
|
|
234
|
+
row_offset = "calc(2.5em * #{pinned_index})"
|
|
235
|
+
"position: sticky; top: calc(#{header_offset} + #{row_offset}); z-index: 3; background: #{background};"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def pinned_root_initial_data_attributes(root_info)
|
|
239
|
+
return {} if root_info[:depth].to_i.zero?
|
|
240
|
+
|
|
241
|
+
anc = root_info[:ancestor_ids] || []
|
|
242
|
+
content = (anc + [root_info[:row].object_id]).join("-")
|
|
243
|
+
{
|
|
244
|
+
top_parent: "#{table_id}_#{anc.first}",
|
|
245
|
+
row_depth: root_info[:depth],
|
|
246
|
+
row_parent: "#{table_id}_#{anc.last}",
|
|
247
|
+
advanced_table_content: content,
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
145
251
|
def cell_accessors_length(col_defs)
|
|
146
252
|
first_col = col_defs.first
|
|
147
253
|
return 0 unless first_col
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
button_color = row_style&.[](:expand_button_color)
|
|
4
4
|
bg_color = row_style&.[](:background_color)
|
|
5
5
|
font_color = row_style&.[](:font_color)
|
|
6
|
+
tr_options = (object.html_options || {}).stringify_keys
|
|
7
|
+
tr_options["class"] = [tr_options["class"], object.classname].reject(&:blank?).join(" ")
|
|
6
8
|
%>
|
|
7
9
|
|
|
8
|
-
<%= pb_content_tag(:tr) do %>
|
|
10
|
+
<%= pb_content_tag(:tr, tr_options) do %>
|
|
9
11
|
<% has_separate_checkbox = object.selectable_rows && object.enable_toggle_expansion == "none" %>
|
|
10
12
|
<% if has_separate_checkbox %>
|
|
11
13
|
<%= object.render_checkbox_cell %>
|
|
@@ -35,13 +35,24 @@ module Playbook
|
|
|
35
35
|
default: ""
|
|
36
36
|
prop :inline_row_loading, type: Playbook::Props::Boolean,
|
|
37
37
|
default: false
|
|
38
|
+
prop :is_pinned_row, type: Playbook::Props::Boolean,
|
|
39
|
+
default: false
|
|
40
|
+
prop :pinned_index, type: Playbook::Props::Numeric,
|
|
41
|
+
default: nil
|
|
42
|
+
prop :html_options, type: Playbook::Props::HashProp,
|
|
43
|
+
default: {}
|
|
44
|
+
prop :classname, type: Playbook::Props::String,
|
|
45
|
+
default: ""
|
|
38
46
|
|
|
39
47
|
def data
|
|
40
48
|
Hash(prop(:data)).merge(table_data_attributes)
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
def classname
|
|
44
|
-
|
|
52
|
+
classes = ["pb_table_tr", "pb-bg-row-white", subrow_depth_classname]
|
|
53
|
+
classes << "pinned-row" if is_pinned_row
|
|
54
|
+
classes.reject!(&:blank?)
|
|
55
|
+
generate_classname(*classes, separator: " ")
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
def td_classname(column, index)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
<% tr_options = (object.html_options || {}).stringify_keys %>
|
|
2
|
+
<% tr_options["class"] = [tr_options["class"], object.classname].reject(&:blank?).join(" ") %>
|
|
3
|
+
|
|
4
|
+
<%= pb_content_tag(:tr, tr_options) do %>
|
|
2
5
|
<% object.column_definitions.each_with_index do |column, index| %>
|
|
3
6
|
<%= pb_rails("table/table_cell", props: { classname: object.td_classname(index) }) do %>
|
|
4
7
|
<%= pb_rails("flex", props:{ align: "center", justify: "start" }) do %>
|
|
@@ -19,13 +19,21 @@ module Playbook
|
|
|
19
19
|
prop :responsive, type: Playbook::Props::Enum,
|
|
20
20
|
values: %w[none scroll],
|
|
21
21
|
default: "scroll"
|
|
22
|
+
prop :is_pinned_row, type: Playbook::Props::Boolean,
|
|
23
|
+
default: false
|
|
24
|
+
prop :pinned_index, type: Playbook::Props::Numeric,
|
|
25
|
+
default: nil
|
|
26
|
+
prop :html_options, type: Playbook::Props::HashProp,
|
|
27
|
+
default: {}
|
|
22
28
|
|
|
23
29
|
def data
|
|
24
30
|
Hash(prop(:data)).merge(subrow_data_attributes)
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
def classname
|
|
28
|
-
|
|
34
|
+
classes = ["pb_table_tr", "bg-silver", "pb_subrow_header", subrow_depth_classname]
|
|
35
|
+
classes << "pinned-row" if is_pinned_row
|
|
36
|
+
generate_classname(*classes, separator: " ")
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
def td_classname(index)
|