Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions skills/leaf/packages/default/widgets/lf-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@
*
* Authored content is never replaced, so there is no failSoft. */
import {
DISCLOSE,
HIDDEN,
PRESS,
actionStands,
conversationBox,
conversationInput,
Expand All @@ -110,6 +110,7 @@ import {
measure,
offer,
once,
paintKeys,
quoted,
reachedForWords,
relabel,
Expand Down Expand Up @@ -526,13 +527,25 @@ customElements.define(
count.textContent = `${options.length} option${options.length === 1 ? "" : "s"}`;
this.#row.append(this.#title, count);
this.#row.setAttribute("aria-controls", options.map((o) => o.id).join(" "));
// Which way it stands, before the scope below is declared: a row wearing
// role="button" and aria-expanded is the disclosure pattern, and DISCLOSE reads
// that pair to answer what works here. Written at birth rather than left to the
// first #open, so the row is never briefly a control the runtime cannot place.
this.#row.setAttribute("aria-expanded", "false");
this.#row.onclick = () => this.#open(!this.#isOpen, true);
// The same press the runtime's disclosure scope owns, re-worded in this group's
// terms. Its bindings come from DISCLOSE rather than from PRESS, which is what
// that primitive is for: a nearer scope keeps only the keys it names, so a row
// naming the pair alone took the arrow off the line and left the reader with
// "open or close" beside a stray "→ open" instead of one chip saying which way
// this section goes. Named through DISCLOSE the two cannot come apart, and the
// word follows the row the way the runtime's own does.
keys(this.#row, "In a settled ask", [
{
id: "option.toggle-settled",
keys: PRESS,
keys: () => DISCLOSE(this.#row),
Comment thread
leaf-agent marked this conversation as resolved.
Resolved
does: "Open or close the settled ask",
line: "open or close",
line: () => (this.#isOpen ? "close" : "open"),
run: () => this.#row.click(),
},
]);
Expand Down Expand Up @@ -580,6 +593,12 @@ customElements.define(
if (open) el.removeAttribute("hidden");
else el.setAttribute("hidden", HIDDEN);
this.#row.setAttribute("aria-expanded", open ? "true" : "false");
// The row's bindings read that attribute, and nothing else repaints them: the
// disclosure watcher repaints the line alone, so aria-keyshortcuts would keep
// whichever way the row was standing when it was declared — naming the arrow that
// no longer moves and withholding the one that does. This is the repaint `keys`
// asks of a control whose keys change with its state.
paintKeys();
Comment thread
leaf-agent marked this conversation as resolved.
Resolved
if (remember) tabStore.set(SETTLED_KEY + this.id, open ? "1" : "0");
}

Expand Down
13 changes: 13 additions & 0 deletions tests/test_render_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2130,11 +2130,24 @@ def test_the_arrows_say_which_way_the_section_under_the_reader_goes(browser, ser
# to retry can tell those apart.
said = key_line(page)
assert re.search(opened + r"\s*close", said), said
# The line is one of two surfaces naming this row's keys, and the other is read by
# somebody who cannot see the first. A row whose bindings answer from its own state
# has to repaint both when the state moves, and only the line had a watch: the
# attribute kept whichever way the row was standing when the scope was declared, so
# it went on promising the arrow that no longer moves this section and withholding
# the one that does.
#
# Read once for the reason the line is, and by the same clock: the heartbeat repaints
# scopes too, so a retrying assertion goes green on the tick that lands inside its
# budget and the fix it is meant to hold has nothing to fail against.
assert row.get_attribute("aria-keyshortcuts") == "Enter Space ArrowLeft"
page.keyboard.press("ArrowRight")
expect(row).to_have_attribute("aria-expanded", "true")
page.keyboard.press("ArrowLeft")
expect(row).to_have_attribute("aria-expanded", "false")
expect(page.locator("#st-keep")).to_be_hidden()
page.evaluate(RENDERED)
assert row.get_attribute("aria-keyshortcuts") == "Enter Space ArrowRight"

# A disclosure in a message, where the disclosure scope does not reach: thread markup
# is a second document beside the version, and the arrows are the page's. A diff,
Expand Down
Loading