Nothing
# Row groups: separator rows, rowspan labels and fillers, column-width
# alignment past group columns, and sorting by groups.
assert("separator row groups render correctly", {
html = build(list(
data = list(g = c("A", "A", "B"), v = 1:3),
row_group = "g"
))
(has_match(html, 'class="lt-row-group".*>A</th>.*>B</th>'))
})
assert("rowspan row groups render label + filler cells", {
# Group of n > 1 rows: non-spanning label cell (aligns with body cells) plus
# empty filler spanning the rest. "A" spans 3 rows: label on row 1, filler
# rowspan="2" after. Single-row "B": plain label, no filler.
html = build(list(
data = list(g = c("A", "A", "A", "B"), v = 1:4),
row_group = list("g")
))
(has_match(html, '<th scope="row" class="lt-row-group lt-row-open">A</th>'))
(has_match(html, '<th class="lt-row-group" rowspan="2"></th>'))
(has_match(html, '<th scope="row" class="lt-row-group">B</th>'))
# the label cell must not span rows itself
(grepl('rowspan="[0-9]+">A</th>', html) %==% FALSE)
})
assert("two-row group filler omits the rowspan attribute", {
# 2-row group: 1 filler row, no rowspan attribute needed
html = build(list(
data = list(g = c("A", "A"), v = 1:2),
row_group = list("g")
))
(has_match(html, '<th class="lt-row-group"></th>'))
(grepl('rowspan', html) %==% FALSE)
})
assert("column widths align past rowspan group columns", {
# Body-column width must not shift onto the leading rowspan group column:
# colgroup gets an empty <col> per group column.
html = build(list(
data = list(g = c("A", "A"), v = 1:2),
row_group = list("g"),
ops = list(list(type = "width", widths = list(v = "9em")))
))
(has_match(html, '<colgroup><col><col style="width:9em"></colgroup>'))
})
assert("full-width control rows span the rowspan group columns too", {
# rowspan group adds a leading column not in `_cols`; table-wide control rows
# (head bar, pager, no-match row, detail) must span it too
d = data.frame(g = c("A", "A", "B"), x = c(1, 2, 3), y = c(4, 5, 6))
x = lt(d) |> lt_group(~ g) |>
lt_interactive(pager = 2, search = TRUE, download = TRUE)
# 3 = 1 group column + 2 data columns
(lti_eval(x, 't.querySelector(".lti-head td").colSpan') %==% '3')
(lti_eval(x, 't.querySelector(".lti-pager-row td").colSpan') %==% '3')
miss = 'var s = t.querySelector(".lti-bar input[type=search]");
s.value = "zzz"; s.dispatchEvent(new Event("change"))'
(lti_eval(x, 't.querySelector(".lti-empty td").colSpan', miss) %==% '3')
xd = lt(d) |> lt_group(~ g) |> lt_interactive(detail = ~ y)
open = 't.querySelector(".lti-expand").click()'
(lti_eval(xd, 't.querySelector(".lti-detail td").colSpan', open) %==% '3')
})
assert("an under-header filter lines up past the rowspan group columns", {
# filter row leads with empty group-column cells so funnels stay under their
# own headers
d = data.frame(g = c("A", "A", "B"), x = c(1, 2, 3), y = c(4, 5, 6))
x = lt(d) |> lt_group(~ g) |> lt_interactive(filter = list(y = "range"))
# 3 = 1 group cell + x + y; the funnel's cell is the last one (column y)
(lti_eval(x, 't.querySelectorAll(".lti-filters > td").length') %==% '3')
(lti_eval(x, 't.querySelector(".lti-filters .lti-funnel").closest("td").cellIndex')
%==% lti_eval(x, '[...t.querySelectorAll("thead th")].find(th => th.textContent.trim() === "y").cellIndex'))
})
assert("sort by groups reorders rows", {
html = build(list(
data = list(g = c("B", "A", "B", "A"), v = c(1, 2, 3, 4)),
row_group = list("g")
))
# After sorting by g, A rows (2,4) come before B rows (1,3)
(has_match(html, ">2</td>.*>1</td>"))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.