Name the table's local after the class it applies, and let a broken chain show itself
This commit is contained in:
@@ -282,7 +282,7 @@ Revisit only if the stated reason stops holding.
|
||||
`min-height: 100dvh`, not a `100dvh` box with `overflow: hidden`: the sidebar is `position: sticky`
|
||||
at full height and the topbar sticks with it, so the nav stays reachable — on a narrow screen the
|
||||
hamburger is the only way into it, and with no script a long page would otherwise strand the reader.
|
||||
Two things only the document scroller gets: **keyboard paging** unconditionally (Space, PgDn and
|
||||
Two things only the document scroller gets: **keyboard paging** unconditionally (Space, PgDn and
|
||||
End reach a bounded region only once focus is inside it, which without script needs a focusable
|
||||
descendant) and **scroll restoration** on back/forward. The inverse default clipped a page silently
|
||||
in every engine — nothing in a test or a console says content is unreachable below the fold.
|
||||
@@ -293,8 +293,8 @@ Two things only the document scroller gets: **keyboard paging** unconditionally
|
||||
takes that height with **`.scroll-region`** and carries the flex chain down to it, because only the
|
||||
page knows its own tree. The shell must never go looking through a page for a component it
|
||||
recognises — a rule keyed on `.table-wrap` works for a table the content slot holds directly and
|
||||
silently clips one nested any deeper. `data-table` takes `fills: true` for exactly this, since its
|
||||
wrapper is host markup a page cannot put a class on. The `visual.spec.ts` scroll test presses **End** rather than sending a
|
||||
silently clips one nested any deeper. `data-table` takes `scrollRegion: true` for exactly this, since
|
||||
its wrapper is host markup a page cannot put the class on itself. The `visual.spec.ts` scroll test presses **End** rather than sending a
|
||||
wheel event (Firefox's synthetic wheel does not reach the document) and rather than
|
||||
`scrollIntoView`, which a script can apply to an overflow-hidden box that no reader can scroll.
|
||||
- **`ICON_NAMES` (`src/ui/icons.ts`) is a host-owned registry, not a frozen plugin contract**, so it
|
||||
|
||||
+4
-4
@@ -16,8 +16,8 @@ own wrapper was a flex child with `overflow-y: auto`. `.table-wrap` and `.shell-
|
||||
else was, and a long page in `.form-page` clipped everything past the window in every engine.
|
||||
|
||||
Now the shell is `min-height: 100dvh` and the document scrolls. The sidebar is `position: sticky` at
|
||||
full height and the topbar sticks with it, so both stay put as the page flows. Keyboard paging, back/
|
||||
forward scroll restoration and find-in-page work without a page doing anything.
|
||||
full height and the topbar sticks with it, so both stay put as the page flows. Keyboard paging and
|
||||
back/forward scroll restoration work without a page doing anything.
|
||||
|
||||
### A bounded frame is `fill: true`, and the page says what fills it
|
||||
|
||||
@@ -29,7 +29,7 @@ The two halves are split on purpose. The shell bounds the content column, becaus
|
||||
shell's to give and a page computing it would have to know the topbar's own. The page marks the
|
||||
element that takes that height with `.scroll-region`, because only the page knows its own tree — a
|
||||
host rule keyed on a component would work for a table held directly by the content slot and silently
|
||||
clip one nested any deeper. `data-table` takes `fills: true`, which puts the class on its own wrapper.
|
||||
clip one nested any deeper. `data-table` takes `scrollRegion: true`, which puts the class on its own wrapper.
|
||||
|
||||
### Upgrading a plugin
|
||||
|
||||
@@ -39,7 +39,7 @@ clip one nested any deeper. `data-table` takes `fills: true`, which puts the cla
|
||||
`.scroll-region` on that region, and makes every wrapper between it and the content slot a flex
|
||||
column (`display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0`). Miss a wrapper and
|
||||
the region grows instead of scrolling, and `fill`'s bounded box clips it.
|
||||
4. A table whose header must stay put passes `fills: true` to `data-table` on such a page.
|
||||
4. A table whose header must stay put passes `scrollRegion: true` to `data-table` on such a page.
|
||||
`examples/plugins/scheduling` shows the whole chain on its shifts list.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
@@ -940,7 +940,7 @@ A page that is a bounded frame instead — a board of full-height columns, a tab
|
||||
stay put — passes **`fill: true`** to the shell, which bounds the content column. The page then says
|
||||
what fills it: put **`.scroll-region`** on that element, and make every wrapper between it and the
|
||||
content slot a flex column (`display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0`).
|
||||
A `data-table` takes `fills: true`, which puts the class on its own wrapper — its sticky header needs
|
||||
A `data-table` takes `scrollRegion: true`, which puts the class on its own wrapper — its sticky header needs
|
||||
it, since a header only sticks to a scrollport that moves. `examples/plugins/scheduling` shows the
|
||||
whole chain on its shifts list.
|
||||
|
||||
|
||||
@@ -210,16 +210,17 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
|
||||
const bounded = await page.evaluate(() => {
|
||||
const wrap = document.querySelector(".table-wrap");
|
||||
if (!(wrap instanceof HTMLElement)) return null;
|
||||
const frame = document.querySelector(".app-fill");
|
||||
if (!(wrap instanceof HTMLElement) || !(frame instanceof HTMLElement)) return null;
|
||||
return {
|
||||
documentScrolls: document.documentElement.scrollHeight > window.innerHeight,
|
||||
frameScrolls: frame.scrollHeight > frame.clientHeight,
|
||||
regionScrolls: wrap.scrollHeight > wrap.clientHeight,
|
||||
};
|
||||
});
|
||||
expect(bounded, ".table-wrap must render").not.toBeNull();
|
||||
// Both halves: the shell bounds the column, and the page's chain hands that height to the table.
|
||||
// Miss a wrapper and the region grows instead, which is what the bounded box would then clip.
|
||||
expect(bounded?.documentScrolls, "the document must not scroll on a filled page").toBe(false);
|
||||
expect(bounded, ".table-wrap and .app-fill must render").not.toBeNull();
|
||||
// Both halves, and both discriminating: miss a wrapper in the page's chain and the table grows
|
||||
// instead of scrolling, which pushes the frame past its own height.
|
||||
expect(bounded?.frameScrolls, "a filled page must fit its frame").toBe(false);
|
||||
expect(bounded?.regionScrolls, "the table must be the thing that scrolls").toBe(true);
|
||||
|
||||
const headTop = async () => (await page.locator("thead th").first().boundingBox())?.y ?? -1;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%><%
|
||||
const navHtml = include("partials/nav-tree", { nodes: chrome.nav });
|
||||
const filtersHtml = include("partials/filter-bar", filterBar);
|
||||
const tableHtml = include("partials/data-table", { ...table, fills: true });
|
||||
const tableHtml = include("partials/data-table", { ...table, scrollRegion: true });
|
||||
const alertHtml = locals.error ? include("partials/alert", { text: locals.error, tone: "neg" }) : "";
|
||||
const actions = canWrite
|
||||
? '<a class="btn btn-primary" href="' + localeHref(newHref) + '"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-plus"/></svg>' + t("scheduling.shifts.new") + '</a>'
|
||||
|
||||
@@ -734,7 +734,7 @@ th[aria-sort="descending"] .sort-ico { transform: rotate(180deg); }
|
||||
document. For a page whose whole point is a bounded frame — a board of full-height columns, a table
|
||||
whose header must stay put. The height is the shell's to give: a page computing it would have to
|
||||
know the topbar's own. What fills that height is the page's to say — `.scroll-region` below. */
|
||||
.app-fill { height: 100dvh; overflow: hidden; }
|
||||
.app-fill { height: 100dvh; overflow: auto; }
|
||||
.app-fill .content { min-height: 0; }
|
||||
|
||||
/* Marks the one element that takes the leftover height and scrolls. Every ancestor between it and
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
caption?, selectable?, actions? sr-only caption; toggle the check / kebab columns
|
||||
actionsId? id stem for the row-action menus (default `row-actions`);
|
||||
name it when two tables share a page
|
||||
fills? take the leftover height and scroll, so the header stays
|
||||
scrollRegion? take the leftover height and scroll, so the header stays
|
||||
put — needs the shell's `fill: true` and a flex chain
|
||||
above (README → the app shell)
|
||||
columns: { label, sortable?, sort?: "asc"|"desc", href?, className? }[]
|
||||
@@ -23,9 +23,9 @@
|
||||
const rows = locals.rows || [];
|
||||
const emptyText = locals.emptyText || t("table.empty"); // shown when a table that has columns has no rows
|
||||
const actionsId = locals.actionsId || "row-actions";
|
||||
const fills = locals.fills === true;
|
||||
const scrollRegion = locals.scrollRegion === true;
|
||||
-%>
|
||||
<div class="table-wrap<%= fills ? " scroll-region" : "" %>">
|
||||
<div class="table-wrap<%= scrollRegion ? " scroll-region" : "" %>">
|
||||
<table class="table">
|
||||
<% if (caption) { -%>
|
||||
<caption class="sr-only"><%= caption %></caption>
|
||||
|
||||
Reference in New Issue
Block a user