authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 22:26:54-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 22:37:36-07:00
log99c4702676053808b8621aec20d27b8f0815e627
treeafc5282ac9ed56fd803e38c941d6128678bc6d83
parentaff2de64c7f950b0e204e4c3e7e094e644847f4d
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: preserve browser accessibility identities and disabled states

Carry 64-bit AccessKit node IDs as decimal strings across the JavaScript bridge so clicks return to the original node. Use explicit true/false values for aria-disabled. Assisted-by: gpt-6.1-sol

2 files changed, 10 insertions(+), 11 deletions(-)

crates/snowbound/src/web.rs+9-10
......@@ -428,7 +428,7 @@ fn mirrored(update: &accesskit::TreeUpdate) -> js_sys::Object {
428428 let children: js_sys::Array = node
429429 .children()
430430 .iter()
431 .map(|child| JsValue::from_f64(child.0 as f64))
431 .map(|child| JsValue::from_str(&child.0.to_string()))
432432 .collect();
433433 let bounds = node.bounds().map_or(JsValue::NULL, |rect| {
434434 js_sys::Array::of4(
......@@ -441,7 +441,7 @@ fn mirrored(update: &accesskit::TreeUpdate) -> js_sys::Object {
441441 });
442442 let text = |text: Option<&str>| text.map_or(JsValue::NULL, JsValue::from_str);
443443 [
444 JsValue::from_f64(id.0 as f64),
444 JsValue::from_str(&id.0.to_string()),
445445 JsValue::from_str(&format!("{:?}", node.role())),
446446 text(node.label()),
447447 text(node.value()),
......@@ -462,13 +462,12 @@ fn mirrored(update: &accesskit::TreeUpdate) -> js_sys::Object {
462462 let _ = js_sys::Reflect::set(&mirrored, &key.into(), value);
463463 };
464464 set("tree", &tree(update.tree_id));
465 set("focus", &JsValue::from_f64(update.focus.0 as f64));
465 set("focus", &JsValue::from_str(&update.focus.0.to_string()));
466466 set(
467467 "root",
468 &update
469 .tree
470 .as_ref()
471 .map_or(JsValue::NULL, |info| JsValue::from_f64(info.root.0 as f64)),
468 &update.tree.as_ref().map_or(JsValue::NULL, |info| {
469 JsValue::from_str(&info.root.0.to_string())
470 }),
472471 );
473472 set("nodes", &nodes);
474473 mirrored
......@@ -486,8 +485,8 @@ pub fn accessibility(on: bool) {
486485
487486/// Assistive technology acted on node `node` of tree `tree`: 0 clicks it, 1 focuses it.
488487#[wasm_bindgen]
489pub fn access(tree: String, node: f64, action: u8) {
490 let Ok(tree) = tree.parse() else {
488pub fn access(tree: String, node: String, action: u8) {
489 let (Ok(tree), Ok(node)) = (tree.parse(), node.parse()) else {
491490 return;
492491 };
493492 let request = accesskit::ActionRequest {
......@@ -496,7 +495,7 @@ pub fn access(tree: String, node: f64, action: u8) {
496495 _ => accesskit::Action::Click,
497496 },
498497 target_tree: accesskit::TreeId(tree),
499 target_node: accesskit::NodeId(node as u64),
498 target_node: accesskit::NodeId(node),
500499 data: None,
501500 };
502501 send(UserEvent::Then(Box::new(move |state| {
crates/snowbound/web/glue.js+1-1
......@@ -564,7 +564,7 @@ export function mirrorTree({ tree, focus, root, nodes }) {
564564 const text = role === "Label" || role === "TextRun" || role === "StaticText";
565565 if (name && !text) node.setAttribute("aria-label", name);
566566 else node.removeAttribute("aria-label");
567 node.toggleAttribute("aria-disabled", disabled);
567 node.setAttribute("aria-disabled", String(disabled));
568568 if (toggled === null) node.removeAttribute("aria-checked");
569569 else node.setAttribute("aria-checked", String(toggled));
570570 const kids = children.map(element);