╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │13 var snapshot: ui.Snapshot = undefined; │ │14 var session: ui.Session = undefined; │ │15 var last_pushed_page_maybe: ?ui.RoutablePage = null; // last current_page we told JS about │ │16 +var last_scrolled_focus_id: ?usize = null; // last focused widget we asked JS to scroll into view│ │17 │ │18 fn init(json: []const u8, min_height: u32, max_width: u32) !void { │ │19 _ = page_arena.reset(.free_all); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │87 defer allocator.free(overlay); │ │88 setOverlay(overlay); │ │89 │ │89 - // browser-focus the focused overlay control (text input or submit button) │ │90 - // so the browser handles typing and Enter-to-submit natively. without this│ │91 - // the submit button never gets focus and Enter falls through to the wasm. │ │90 const root_focus = root_ptr.getFocus(); │ │91 if (root_focus.grandchild_id) |gid| { │ │92 if (root_focus.children.get(gid)) |child| { │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │93 + // browser-focus the focused overlay control (text input or submit button) │ │94 + // so the browser handles typing and Enter-to-submit natively. without this │ │95 + // the submit button never gets focus and Enter falls through to the wasm. │ │96 switch (child.focus.kind) { │ │97 .text_input, .text_input_password => _focusInput(@intCast(gid)), │ │98 .custom => |custom| if (std.mem.eql(u8, custom, "submit")) _focusInput(@intCast(gid)),│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 99 else => {}, │ │100 } │ │101 + │ │102 + // keep the focused widget within the browser viewport. only fire on │ │103 + // an actual focus change so we don't fight the user's manual │ │104 + // scrolling on unrelated ticks. │ │105 + if (gid != last_scrolled_focus_id) { │ │106 + last_scrolled_focus_id = gid; │ │107 + const r = child.rect; │ │108 + _scrollToRect(@intCast(r.x), @intCast(r.y), @intCast(r.size.width), @intCast(r.size.height));│ │109 + } │ │110 } │ │111 } │ │112 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │173 extern fn _pushState(arg: [*]const u8, len: u32) void; │ │174 extern fn _focusInput(focus_id: u32) void; │ │175 extern fn _navigate(arg: [*]const u8, len: u32) void; │ │176 +extern fn _scrollToRect(x: u32, y: u32, width: u32, height: u32) void; │ │177 │ │178 /// js calls this first to get a wasm pointer it can write the page json into.│ │179 export fn _alloc(len: u32) ?[*]u8 { │ ╰───────────────────────────────────────────────────────────────────────────────────╯