fix: hide filter boxes and picker until wired

A filter box read "Filter Options41 rows" in the page text and the
file picker an empty "Setup Choose a setup": both do nothing without
the script. They are written hidden, keep their room so nothing moves,
and site.ts or the picker's script shows them. The count describes the
input and is spaced from the label. One box markup for the build and
the generator, and the Markdown copy skips hidden chrome.
This commit is contained in:
Abdessamad Derraz committed 2026-11-10 14:12:00 +01:00
1 parent fda6845289
commit c916503958
10 files changed
+65 -25

No files matched your search

+2 -8
View File
@@ -3818,14 +3818,8 @@ def _platform_file_list(rows: list[str], has_path: bool, anchor: str) -> list[st
"""
lines: list[str] = []
if len(rows) >= FILTER_ROWS:
field = f"rb-files-filter-{anchor}"
lines += [
'<div class="rb-table-filter">'
f'<label for="{field}">Filter files</label>'
f'<input id="{field}" type="search" autocomplete="off" placeholder="File, path, status, hash...">'
f'<span class="rb-table-filter-count" aria-live="polite">{len(rows):,} rows</span></div>',
"",
]
lines += [siteentries.filter_box(f"rb-files-filter-{anchor}", "files",
["File", "path", "status", "hash"], len(rows)), ""]
kind = "rb-files rb-files-path" if has_path else "rb-files"
return [*lines, f'<div class="{kind}">', *rows, "</div>"]
+4 -1
View File
@@ -123,7 +123,10 @@ class _Converter(HTMLParser):
self._emit(f"**{' '.join(name.split())}:**")
self._block()
return
if self.skip or tag in SKIPPED or attr.get("aria-hidden") == "true" \
# A `hidden` element is chrome the page's script shows (a filter
# box, the file picker, a setup's install chooser): a tab panel,
# hidden too, was read above.
if self.skip or tag in SKIPPED or attr.get("aria-hidden") == "true" or "hidden" in attr \
or classes & {"sr-only", "sl-anchor-link", "copy", "rb-anchor"}:
if tag not in ("br", "img", "hr", "input", "wbr"):
self.skip += 1
+13 -4
View File
@@ -47,12 +47,21 @@ def filter_placeholder(labels: list[str]) -> str:
def filter_box(ident: str, heading: str, labels: list[str], rows: int, noun: str = "rows") -> str:
"""The filter box rehype-stack-tables.mjs writes above a long table."""
"""The filter box rehype-stack-tables.mjs writes above a long table.
It is `hidden` until site.ts wires it: without the script it filters
nothing, so a reader with no JavaScript, a text dump and the search
skip it while the rows stay. retrobios.css keeps its room meanwhile,
so the table does not move when it shows. The count is the input's
description, and spaces keep label, field and count apart in any text
read from the HTML."""
return (
f'<div class="rb-table-filter"><label for="{ident}">Filter {html.escape(heading)}</label>'
f'<div class="rb-table-filter" hidden {markup.search_exclude()}>'
f'<label for="{ident}">Filter {html.escape(heading)}</label> '
f'<input id="{ident}" type="search" autocomplete="off" '
f'placeholder="{html.escape(filter_placeholder(labels))}">'
f'<span class="rb-table-filter-count" aria-live="polite">{rows:,} {noun}</span></div>'
f'placeholder="{html.escape(filter_placeholder(labels))}" aria-describedby="{ident}-count"> '
f'<span id="{ident}-count" class="rb-table-filter-count" aria-live="polite">'
f"{rows:,} {noun}</span></div>"
)
+1 -1
View File
@@ -409,7 +409,7 @@ class WideTablesStackAndFiltersAreBuilt(unittest.TestCase):
def test_the_filter_box_is_in_the_html(self):
html = _built("gaps-files-to-source")
boxes = re.findall(r'<div class="rb-table-filter">(.*?)</div>\s*<table', html, re.S)
boxes = re.findall(r'<div class="rb-table-filter"[^>]*>(.*?)</div>\s*<table', html, re.S)
self.assertTrue(boxes)
for box in boxes:
self.assertRegex(box, r'<label for="rb-table-filter-\d+">Filter [^<]+</label>')
+4 -3
View File
@@ -49,8 +49,9 @@ class TheEntryTable(unittest.TestCase):
def test_a_long_table_has_its_filter_box_and_stacks(self):
lines = siteentries.entry_table(self._rows(25), gs.ENTRY_HEADS, "rb-entry-filter-x", "Required")
text = "\n".join(lines)
self.assertRegex(text, r'<div class="rb-table-filter"><label for="rb-entry-filter-x">'
r"Filter Required</label><input id=\"rb-entry-filter-x\" type=\"search\"")
self.assertRegex(text, r'<div class="rb-table-filter" hidden data-pagefind-ignore>'
r'<label for="rb-entry-filter-x">Filter Required</label> '
r"<input id=\"rb-entry-filter-x\" type=\"search\"")
self.assertIn('aria-live="polite">25 rows</span></div>\n<table class="rb-entries" '
'data-rb-stack="true">', text)
# The Status column is empty on every row: not written.
@@ -210,7 +211,7 @@ class TheWantedListIsOneFilteredTable(unittest.TestCase):
self.assertIn("| Nintendo - GameCube - BIOS Images | 16 |", self.page)
def test_every_dump_is_one_row_under_a_filter_box(self):
box = re.search(r'<div class="rb-table-filter">.*?</div>\n<details[^>]*>\n'
box = re.search(r'<div class="rb-table-filter"[^>]*>.*?</div>\n<details[^>]*>\n'
r"<summary>All 31 wanted dumps</summary>\n<table class=\"rb-wanted\"",
self.page, re.S)
self.assertIsNotNone(box)
+2 -1
View File
@@ -153,7 +153,8 @@ class ARowPerFile(unittest.TestCase):
def test_a_long_list_gets_a_filter(self):
rows = ["<details></details>"] * generate_site.FILTER_ROWS
block = generate_site._platform_file_list(rows, False, "arcade")
self.assertTrue(block[0].startswith('<div class="rb-table-filter"><label for="rb-files-filter-arcade">'))
self.assertTrue(block[0].startswith('<div class="rb-table-filter" hidden data-pagefind-ignore>'
'<label for="rb-files-filter-arcade">Filter files</label>'))
self.assertNotIn("rb-table-filter", "".join(
generate_site._platform_file_list(rows[1:], False, "arcade")))
+8 -3
View File
@@ -20,7 +20,10 @@ const packs = Object.fromEntries(
);
---
<div class="rb-picker" data-base={base} data-release={JSON.stringify({ tag: release.tag ?? '', packs })}>
{/* hidden until the script fills the setup list: without it the box is an
empty "Choose a setup", which a text dump and a reader without the
script would read. It keeps its room meanwhile, so nothing moves. */}
<div class="rb-picker" hidden data-base={base} data-release={JSON.stringify({ tag: release.tag ?? '', packs })}>
<fieldset>
<label for="rb-pick-platform">Setup</label>
<select id="rb-pick-platform" name="platform">
@@ -29,8 +32,8 @@ const packs = Object.fromEntries(
</fieldset>
<div class="rb-pick-systems" hidden>
<p class="rb-pick-systems-head">
<strong>Systems</strong>
<button type="button" class="rb-text-button" data-all>All</button>
<strong>Systems</strong>{' '}
<button type="button" class="rb-text-button" data-all>All</button>{' '}
<button type="button" class="rb-text-button" data-none>None</button>
</p>
<input type="search" class="rb-pick-filter" placeholder="Type a console" aria-label="Type a console" />
@@ -114,6 +117,7 @@ const packs = Object.fromEntries(
const params = new URLSearchParams(location.search);
const index: { platform: string; name: string }[] = await fetch(`${base}data/picker/index.json`).then((r) => r.json());
for (const entry of index) select.add(new Option(entry.name, entry.platform));
root.hidden = false;
const chosen = () => [...list.querySelectorAll<HTMLInputElement>('input:checked')].map((box) => box.value);
@@ -227,6 +231,7 @@ const packs = Object.fromEntries(
<!-- Global: the checkboxes are created by the script, outside Astro's scoped styles. -->
<style is:global>
.rb-picker[hidden] { display: block; visibility: hidden; }
.rb-picker .rb-pick-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+18 -4
View File
@@ -108,10 +108,16 @@ const headingBefore = (ctx, table, title) => {
// The label a stacked cell shows: the header's title, else its text.
export const stackedLabel = (title, text) => String(title ?? '').trim() || text;
const filterBox = ({ id, heading, labels, rows }) => ({
// The same box as siteentries.filter_box: `hidden` until site.ts wires it,
// so a reader without the script, a text dump and the search skip a field
// that would filter nothing. The count describes the input, and the spaces
// keep label, field and count apart in any text read from the HTML.
const space = () => ({ type: 'text', value: ' ' });
export const filterBox = ({ id, heading, labels, rows }) => ({
type: 'element',
tagName: 'div',
properties: { className: ['rb-table-filter'] },
properties: { className: ['rb-table-filter'], hidden: true, dataPagefindIgnore: true },
children: [
{
type: 'element',
@@ -119,16 +125,24 @@ const filterBox = ({ id, heading, labels, rows }) => ({
properties: { htmlFor: id },
children: [{ type: 'text', value: `Filter ${heading}` }],
},
space(),
{
type: 'element',
tagName: 'input',
properties: { id, type: 'search', autoComplete: 'off', placeholder: filterPlaceholder(labels) },
properties: {
id,
type: 'search',
autoComplete: 'off',
placeholder: filterPlaceholder(labels),
ariaDescribedBy: `${id}-count`,
},
children: [],
},
space(),
{
type: 'element',
tagName: 'span',
properties: { className: ['rb-table-filter-count'], ariaLive: 'polite' },
properties: { id: `${id}-count`, className: ['rb-table-filter-count'], ariaLive: 'polite' },
children: [{ type: 'text', value: `${rows.toLocaleString('en')} rows` }],
},
],
+9
View File
@@ -158,6 +158,14 @@ const labelCells = (table: HTMLTableElement) => {
}
};
// Every filter box is written `hidden` (siteentries.filter_box,
// rehype-stack-tables.mjs): it filters only once this script runs, so a
// reader without it never meets a field that does nothing. The box keeps
// its room while hidden (retrobios.css), so showing it moves nothing.
const showFilters = () => {
for (const box of document.querySelectorAll<HTMLElement>('.rb-table-filter[hidden]')) box.hidden = false;
};
const enhanceTables = () => {
const tables = [...document.querySelectorAll<HTMLTableElement>('.sl-markdown-content table')];
const enhance = (table: HTMLTableElement) => {
@@ -1450,6 +1458,7 @@ resolveFileAddress();
followMovedCard();
openTargetBlock();
addEventListener('hashchange', openTargetBlock);
showFilters();
enhanceTables();
breakPaths();
hashSearch();
+4
View File
@@ -865,6 +865,10 @@ abbr.rb-term[data-open]::after {
align-items: end;
margin: 1rem 0 0.45rem;
}
/* Written `hidden` (siteentries.filter_box, rehype-stack-tables.mjs) and
shown by site.ts: until then it keeps its room, so the table below never
moves, and nothing reads it. */
.rb-table-filter[hidden] { visibility: hidden; }
.sl-markdown-content .rb-table-filter > * { margin: 0; }
.rb-table-filter label { grid-column: 1 / -1; color: var(--rb-text-secondary); font-size: 0.78rem; font-weight: 700; }
.rb-table-filter input {