╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │189 document.addEventListener("focusin", (event) => { │ │190 const t = event.target; │ │191 if (!t || !t.dataset || !t.dataset.focusId) return; │ │192 - wasmInstance.exports._onMouseClick(Number(t.dataset.focusId)); │ │193 - wasmInstance.exports._tick(minRows(), maxCols()); │ │192 + // sync the wasm focus to wherever the browser moved focus │ │193 + wasmInstance.exports._setFocus(Number(t.dataset.focusId)); │ │194 + // a click's mousedown fires this focusin before the click event. ticking│ │195 + // here would rebuild #grid and destroy the node before its click lands, │ │196 + // swallowing the navigation and forcing a second click — so don't │ │197 + // re-render for a grid element. overlay controls live outside #grid and │ │198 + // survive the diff, so they still tick. │ │199 + if (!grid.contains(t)) wasmInstance.exports._tick(minRows(), maxCols()); │ │200 }); │ │201 │ │202 grid.addEventListener("click", (event) => { │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │151 try root_ptr.getFocus().setFocus(focus_id); │ │152 } │ │153 │ │154 +fn setFocus(focus_id: usize) !void { │ │155 + const root_ptr = if (root) |*root_value| root_value else return error.NotStarted;│ │156 + try root_ptr.getFocus().setFocus(focus_id); │ │157 +} │ │158 + │ │159 fn consoleLog(arg: []const u8) void { │ │160 _consoleLog(arg.ptr, @intCast(arg.len)); │ │161 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │222 }; │ │223 } │ │224 │ │225 +export fn _setFocus(focus_id: u32) void { │ │226 + setFocus(focus_id) catch |err| { │ │227 + var buf: [256]u8 = undefined; │ │228 + const str = std.fmt.bufPrint(&buf, "setFocus: {}", .{err}) catch unreachable; │ │229 + consoleLog(str); │ │230 + }; │ │231 +} │ │232 + │ │233 // js calls this when the value of an overlay <input> changes; we replace │ │234 // the matching TextInput's content with the supplied utf-8 bytes. │ │235 export fn _setTextInputValue(focus_id: u32, value_ptr: [*]const u8, value_len: u32) void {│ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │471 try out.appendSlice(alloc, ">"); │ │472 }, │ │473 .a => |href| { │ │474 - try out.appendSlice(alloc, "<a class=\"clickable\" data-focus-id=\""); │ │474 + // tabindex=-1 keeps these out of the browser's tab order │ │475 + try out.appendSlice(alloc, "<a class=\"clickable\" tabindex=\"-1\" data-focus-id=\"");│ │476 try out.appendSlice(alloc, try std.fmt.bufPrint(&id_buf, "{d}", .{id})); │ │477 try out.appendSlice(alloc, "\" href=\""); │ │478 try appendEscapedHtml(alloc, out, href); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯