Skip to content

v4.0.0

Choose a tag to compare

@stamat stamat released this 10 Aug 02:27
· 9 commits to main since this release

Arrow keys did nothing. Enter did nothing. The panel had no role, the field never said it had
a popup, and results appearing in it announced nothing at all — a change a sighted reader
watches happen and a screen reader user is told nothing about, which is
WCAG 2.2 4.1.3 unmet. The
only way through a hit was the pointer.

The index behind it was fetched on every docs page whether or not anybody searched, and if
the file was not there the failure was swallowed: from then on every query answered
No results, which is a true sentence about a search that never happened.

The search index poops writes is every page's front matter copied out verbatim, and the
theme dropped a result's title, description and url straight into innerHTML. A <img src=x onerror=…> in one page's title: therefore ran on every page of the site, since the
search box is in the topbar.

The default footer also credited Poops and stopped there, so the layout, styles and script a
visitor is actually looking at had no name anywhere on the page. The 💩 trailed after the
sentence as a full stop of its own, and a screen reader read it out as "pile of poo".

Added

  • The search field is <search-elemental> and <suggest-elemental>, which is the whole
    keyboard the panel never had: and walk the results and wrap,
    Home and End reach the ends once a row is under the cursor and stay
    with the caret until then, Enter follows the row, Escape closes. The
    cursor is aria-activedescendant rather than focus, so typing carries on while you look,
    and the field now reports itself as a combobox with a popup instead of as a plain text box.
    A settled search says 5 results, No results or Search failed in a role="status"
    region — the announcement the panel filling itself never made.

    Two more things the reader gets: Escape empties the field rather than only
    closing the panel, and focus leaving empties it too — this field is in the topbar of
    every page, so a query left in it outlives the results it fetched, and on a phone a field
    with something in it will not fold back into its icon.

    The DOM changes, and a site with its own rules for it needs them: #search-results is gone.
    The panel is suggest-elemental[open] holding ul > li > a[role="option"], the wrapper is
    search-elemental.search carrying data-state, and the row classes .sr-title / .sr-desc
    are unchanged inside it. .sr-empty still exists but has moved out of the panel and is now
    p.sr-note.sr-empty (see below). The panel's look is the element's optional theme with
    --suggest-elemental-surface, --suggest-elemental-active, --suggest-elemental-radius,
    --suggest-elemental-inset and --suggest-elemental-max-height pointed at this theme's
    tokens; anything that styled .search-results directly wants those instead.

    Measured against the same build before the change, docs.min.js grows by 8.4KB and
    docs.min.css by 3.5KB — minified, before compression, and everything in this entry
    included.

  • The search field has a clear button a keyboard can reach. <input type="search"> gets a
    cross of its own in Chromium and Safari, none in Firefox, and the one it gets is drawn by
    the non-standard ::-webkit-search-cancel-button — mouse-only, no tab stop, and absent from
    the accessibility tree. It is switched off with appearance: none and replaced by
    button.search-clear: the octicon x at the magnifier's weight and distance from its end of
    the field, a 24px target around a 16px glyph, aria-label="Clear the search", and a focus
    ring, because a control you can tab to and cannot see you have reached is
    2.4.7 Focus Visible unmet.
    It is visibility: hidden while the field is empty, which takes it out of the tab order with
    the look. Pressing it empties the field and hands the caret back.

    The field is 2rem of padding at both ends now, and the pending spinner moved one control
    inward to --search-elemental-spinner-inset-inline: 2rem so the two never share a corner.

  • The empty and failed searches are drawn as well as spoken. p.sr-note.sr-empty says
    No results and p.sr-note.sr-error says Search failed, in a box under the field,
    shown by data-state alone with no script involved. The words are empty-text and
    error-text on the element, so what a reader sees and what the live region reads out is one
    sentence rather than two that can drift.

    They sit outside the panel, which is the one place this theme departs from the element's own
    advice: a listbox may only own options, so a "No results" row inside the panel is
    aria-required-children — critical, and script/a11y fails the build on it. Measured, not
    assumed: the row was written that way first and the sweep caught it.

  • / and ⌘K / Ctrl+K put the cursor in the search field. The field was reachable by
    pointer or by tabbing the length of the topbar, and every docs site a reader arrives from
    answers to one of these. The slash is ignored while an <input>, <textarea>, <select>
    or a contenteditable holds focus — it is a character somebody may be mid-word in — and the
    modifier pair works from inside a field, where it selects what is already typed. Shift is
    where it stops — Ctrl+Shift+K opens the web console and stays the browser's. What the two
    do cost is Firefox's quick-find and its Ctrl+K search bar, and Chrome's Ctrl+K
    address-bar search, on a docs page. Nothing was added to the markup: no hint sits beside the
    field.

  • A grouped section in the mock site, and poops 2.3 to build it with. Kitchen sink and
    Live samples now carry navGroup: Samples, poops 2.3's front-matter grouping — the pages
    stay where they are, urls and breadcrumbs unchanged, and only the sidebar gains the heading.
    Filler with a job: a group is a section node with no page of its own, so it is the first
    thing in the mock to render the navtree.html branch that omits the Overview link —
    every other section there has an index page. The sweep and the unit suite cover it now
    rather than by claim. poops moved to ^2.3.0 as a devDependency only; the peer range
    stays >=2.0.0, because the theme reads the tree as data and an older poops just leaves the
    field unread.

Changed

  • search-index.json is fetched on the first query, not on page load. The field is in the
    topbar of every docs page and most visits never type in it, so the request went out for
    everybody and paid off for a few. The first search of a visit now waits for the network and
    gets a spinner while it does; every one after it answers from an index already in memory.

  • The search field is 2.2rem tall, set rather than left to fall out of its padding and the
    16px iOS floor, which came to 2.6rem — the tallest thing in a bar standing next to 2.25rem
    icon buttons.

  • book-of-elementals moves to ^0.7.0 from ^0.5.0, which is where the two search
    elements arrive. Nothing this theme already used changed shape: the hover tint the 0.6
    release evened out across the book is re-pointed at --bg-alt here and always was.

  • The default footer names poops-docs-theme beside Poops, and the 💩 moved in front of
    the Poops link with aria-hidden="true" on it, so it reads as decoration rather than as a
    word. Both layouts change; a site setting site.footer renders its own html and is
    untouched.

Fixed

  • A missing search index says so instead of reporting no results. fetch resolves on a
    404, and the load was wrapped in a catch that threw the error away — so a site that never
    generated search-index.json, or one whose file was briefly unreachable, had a search box
    that answered every query with No results for the rest of the visit. The response is
    checked, the failure reaches the element as a rejection, and the field says Search failed
    in the box and in the live region. The failed load is dropped rather than remembered, so the
    next keystroke tries again.

  • The mobile drawer no longer slides itself shut on page load. Closed is the state the
    drawer arrives in — <disclosure-elemental> writes it at upgrade — but the transform
    transition was live from the first frame, so on a load where the script landed after the
    first paint (a cold cache, a slow phone) the browser animated the difference between the
    rail the stylesheet had already drawn and the closed drawer the script asked for. The
    transition rules now key off a sidebar-nav-ready class that docs.js puts on
    #sidebar-nav when the toggle is first tapped, so a drawer nobody has touched cannot
    travel. That first tap still slides — the class goes on before the element writes the state,
    with a reflow between them. Counting animation frames instead was tried and measured: a
    closed panel painted for two frames still slid in from nothing when the rule arrived.
    Crossing the breakpoint before any tap now snaps instead of sliding, which is what the
    element already does with the state itself. A site overriding
    #sidebar-nav { transition: … } needs the class in its selector now.

  • Escape no longer closes the sidebar rail on a wide screen. Escape and the scrim are the
    light dismiss a drawer over a scrim wants and the disclosure pattern does not owe it — but
    the keyboard is on the page at every width, and <disclosure-elemental> writes open from
    its media query only when the query changes. A query that still matches changes nothing,
    so Escape above 60rem closed the rail and nothing put it back: the navigation was gone for
    the rest of the visit, with the toggle that would reopen it display: none at that width.
    The close now returns early while the element reports data-mode="pinned", which covers the
    scrim as well as the key.

Security

  • Search results are built as nodes, so front matter cannot become markup. The row is now
    the one place the index is crossed from data into DOM: title and description go in as
    textContent, and the url is resolved against the page and dropped unless its scheme is
    http: or https:href takes a javascript: url as readily as a path. An entry that
    fails that check is left out of the list rather than rendered as a dead link. .sr-title and
    .sr-desc still name the two pieces of a row, though the box around them moved with the
    panel — see the search entry under Added. Authoring markup in a title: and expecting it
    to render never worked in the sidebar or the <title> either; it now does not work here.

Full Changelog: v3.1.1...v4.0.0