╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 // a real browser navigation to another top-level page (the wasm │ │ 95 // can't render it client-side without server data). │ │ 96 window.location.assign(readWasmString(ptr, len)); │ │ 97 + }, │ │ 98 + _scrollToRect: function (x, y, w, h) { │ │ 99 + // the rect arrives in grid cells; map it to document coordinates │ │100 + // via the measured cell size and the grid origin, then scroll the │ │101 + // minimum needed to bring the widget fully into view │ │102 + if (!cellHeight) measureCell(); │ │103 + const gridTop = grid.getBoundingClientRect().top + window.scrollY; │ │104 + const top = gridTop + y * cellHeight; │ │105 + const bottom = top + h * cellHeight; │ │106 + const viewTop = window.scrollY; │ │107 + const viewBottom = viewTop + document.documentElement.clientHeight; │ │108 + if (top < viewTop) { │ │109 + window.scrollTo({ top }); │ │110 + } else if (bottom > viewBottom) { │ │111 + window.scrollTo({ top: bottom - document.documentElement.clientHeight });│ │112 + } │ │113 }, │ │114 }, │ │115 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 { │ ╰───────────────────────────────────────────────────────────────────────────────────╯