╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │265 errdefer detail_scroll.deinit(allocator); │ │266 var frame = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .direction = .vert }); │ │267 errdefer frame.deinit(allocator); │ │268 + // the frame's selected child is its scroll, so the focus chain │ │269 + // reaches the content box (populateDetail points the scroll's │ │270 + // inner box at it). this lets focus recovery descend into the │ │271 + // detail pane after it's laid out beside a too-narrow list. │ │272 + frame.getFocus().child_id = detail_scroll.getFocus().id; │ │273 try frame.children.put(allocator, detail_scroll.getFocus().id, .{ .widget = .{ .scroll = detail_scroll }, .rect = null, .min_size = null });│ │274 break :blk frame; │ │275 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │391 const both_panes_fit = if (constraint.max_size.width) |w| w >= list_max_width + detail_min_width else true; │ │392 self.contentBox().children.values()[list_index].max_size = if (both_panes_fit) .{ .width = list_max_width, .height = null } else null;│ │393 │ │394 + // crossing panes only re-selects the content box's child (focusList / │ │395 + // focusDetail); when the window is too narrow to show both, the pane that │ │396 + // was holding focus is dropped here. the framework recovers focus after │ │397 + // the top-level build by re-deriving it down the selected-child chain, so │ │398 + // there's nothing to fix up afterward. │ │399 try self.box.build(allocator, constraint, root_focus); │ │390 - │ │391 - // if the detail pane is selected but focus is still elsewhere in this │ │392 - // view, the pane was too narrow to lay out when focus crossed over. it's │ │393 - // laid out now, so focus its content box. │ │394 - if (self.detailActive() and inner.children.count() > 0) { │ │395 - if (root_focus.grandchild_id) |g| { │ │396 - const in_view = self.box.getFocus().children.contains(g); │ │397 - const in_detail = inner.children.contains(g); │ │398 - if (in_view and !in_detail) │ │399 - try root_focus.setFocus(inner.getFocus().child_id orelse inner.children.keys()[0]); │ │400 - } │ │401 - } │ │400 } │ │401 │ │402 fn refreshDetail(self: *View, allocator: std.mem.Allocator) !void { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │425 } │ │426 } │ │427 } │ │428 + // point the pane at its content box so focus recovery can land here. │ │429 + if (inner.children.count() > 0) inner.getFocus().child_id = inner.children.keys()[0];│ │430 │ │431 // reset the scroll to the top for the newly-shown file: directly on the │ │432 // terminal (the wasm offset), and via a version bump on the web (so the │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │534 sc.x = std.math.clamp(sc.x, 0, max_x); │ │535 } │ │536 │ │537 - // enter the detail pane by focusing its content box. the host arrives here on │ │538 - // right-arrow or Enter from the list. an empty pane (a directory or ".." row │ │539 - // is selected) can't be entered. │ │537 + // enter the detail pane. a directory or ".." row has no content, so there's │ │538 + // nothing to enter. setFocus handles the too-narrow case where the pane isn't │ │539 + // laid out yet (it gets selected, then focused after the next build). │ │540 fn focusDetail(self: *View, root_focus: *Focus) !void { │ │541 - const inner = self.detailInner(); │ │542 - if (inner.children.count() == 0) return; │ │543 - const target = inner.getFocus().child_id orelse inner.children.keys()[0]; │ │544 - try root_focus.setFocus(target); │ │545 - if (root_focus.grandchild_id == target) return; │ │546 - // the detail pane wasn't laid out last build (too narrow to show beside │ │547 - // the list), so its content isn't in the focus tree yet. select the pane │ │548 - // at the box level, and the content will be focused after the next build. │ │549 - self.contentBox().getFocus().child_id = self.detailOuter().getFocus().id; │ │541 + if (self.detailInner().children.count() == 0) return; │ │542 + root_focus.setFocus(self.detailOuter().getFocus().id); │ │543 } │ │544 │ │545 + // return to the list. │ │546 fn focusList(self: *View, root_focus: *Focus) !void { │ │553 - const lb = self.listBox(); │ │554 - const id = lb.getFocus().child_id orelse (if (lb.children.count() > 0) lb.children.keys()[0] else return);│ │555 - try root_focus.setFocus(id); │ │547 + root_focus.setFocus(self.listScroll().getFocus().id); │ │548 } │ │549 │ │550 fn moveSelection(self: *View, root_focus: *Focus, delta: isize) !void { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │556 const last: isize = @intCast(keys.len - 1); │ │557 const next: usize = @intCast(std.math.clamp(cur + delta, 0, last)); │ │558 if (next == @as(usize, @intCast(cur))) return; │ │567 - try root_focus.setFocus(keys[next]); │ │559 + root_focus.setFocus(keys[next]); │ │560 if (lb.children.values()[next].rect) |rect| self.listScroll().scrollToRect(rect);│ │561 } │ │562 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯