fix(ui): split separator ARIA range reflects the real clamp, not 10–90

_buildHandle hard-coded aria-valuemin/max at 10/90 (inherited from the
old ui/static implementation) while the actual drag/keyboard clamp is
_ratioBounds — the cell minimums against the split node's OWN px region
(a 1200px host really clamps at ~17/83; nested splits sit tighter), so
assistive tech was told a wider range than the separator allows.

aria-valuenow/min/max are now all written in _applyLayout's handle loop
from _ratioBounds(h.node) — one writer, refreshed on every drag,
keyboard nudge, and structural change. A bare window resize can stale
the advertised range until the next interaction (no resize listener by
design — % insets make resizes free), still strictly truer than a
constant. The max>=min guard covers a host shrunk below two cell
minimums, where the bounds legitimately cross.
This commit is contained in:
Patrick Buckley
2026-06-12 00:10:45 -07:00
parent d8619ce3c8
commit ce105c4ed1
2 changed files with 22 additions and 3 deletions
+5
View File
@@ -736,6 +736,11 @@ def test_pane_manager_split_engine() -> None:
assert 'setAttribute("role", "separator")' in pane
assert "setPointerCapture" in pane and "_ratioBounds(node)" in pane
assert '"aria-valuenow"' in pane and "ArrowRight" in pane
# the ARIA range mirrors the REAL clamp (_ratioBounds per handle in the
# _applyLayout loop) — never a hard-coded constant
assert "this._ratioBounds(h.node)" in pane
assert '"aria-valuemin"' in pane and '"aria-valuemax"' in pane
assert 'setAttribute("aria-valuemin", "10")' not in pane
# limits: cell minimums + cap (the old ui/static ceiling, kept)
assert "SPLIT_MAX_CELLS = 6" in pane
assert "SPLIT_MIN_W = 200" in pane and "SPLIT_MIN_H = 150" in pane
+17 -3
View File
@@ -806,6 +806,21 @@ export class PaneManager {
el.style.top = h.y * 100 + "%";
if (h.node.dir === "row") el.style.height = h.span * 100 + "%";
else el.style.width = h.span * 100 + "%";
// The ARIA range is the REAL clamp (_ratioBounds: the cell minimums
// against this split's OWN px region — nested splits sit tighter than
// any constant; the old hard-coded 1090 misreported it to AT). It
// refreshes with every drag/keyboard/structure pass through here; a
// bare window resize can stale it until the next interaction (no
// resize listener by design — % insets make resizes free), which is
// still strictly truer than a constant. The max>=min guard covers a
// host shrunk below two minimums, where the bounds legitimately cross.
const b = this._ratioBounds(h.node);
const lo = Math.round(b.min * 100);
el.setAttribute("aria-valuemin", String(lo));
el.setAttribute(
"aria-valuemax",
String(Math.max(lo, Math.round(b.max * 100))),
);
el.setAttribute("aria-valuenow", String(Math.round(h.node.ratio * 100)));
}
}
@@ -916,9 +931,8 @@ export class PaneManager {
"aria-orientation",
node.dir === "row" ? "vertical" : "horizontal",
);
el.setAttribute("aria-valuemin", "10");
el.setAttribute("aria-valuemax", "90");
el.setAttribute("aria-valuenow", String(Math.round(node.ratio * 100)));
// aria-valuenow/min/max are written by _applyLayout's handle loop (the
// single writer) — the range comes from _ratioBounds, not a constant.
el.setAttribute(
"aria-label",
node.dir === "row"