Skip to content

fix: Stack takes part in focus traversal; ListBox clips wide rows - #482

Merged
tannevaled merged 2 commits into
mainfrom
fix/stack-focus-listbox-clip
Oct 5, 2026
Merged

tannevaled merged 2 commits into
mainfrom
fix/stack-focus-listbox-clip

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Two small fixes found while building the claimward Linux and Windows apps, one commit each.

Stack (#480)

Root cause. Stack keeps its pages in a map[string]Widget and exposed neither Children() nor focusableChildren(), so every tree walk stopped at it:

  • Keys: a click focused an Entry inside the page (the page's own container ran focusClick), but the enclosing container's focus list never contained it, and it is that container which routes every key, so typed keys went to the wrong list and were dropped.
  • Popover: PopoverHost finds open popovers through Children(), so a DropDown on a Stack page opened a popover nobody drew on top or routed clicks to.

Fix. Stack yields its visible page to both walks and its OnEvent now runs routeFocusKey and click-to-focus before forwarding, as Container/AlignBox do. Hidden pages are deliberately not yielded: unlike an inactive CardLayout card (collapsed to an empty rect, which gatherFocusables skips), a hidden Stack page keeps the bounds it last had, which are the Stack's own, so a bounds check cannot exclude it. Yielding it would make Tab reach invisible widgets, put them in the accessibility tree where the visible page is, and let an open popover on a hidden page take clicks.

Guard. TestEveryContainerExposesItsChildren only recognised Widget and []Widget fields, which is how Stack got through. It now also treats map[K]Widget as a holder (no other widget in the package holds one; ViewController.refs is not a widget).

Tests (stack_focus_test.go): typed keys reach an Entry on the visible page under a root VBox; Tab walks outside field → visible page and never into a hidden page with stale bounds, then into the new page after SetVisible; PopoverOwners reports the open DropDown on the visible page and nothing once it is hidden; a Stack used as the root routes focus itself; Children/focusableChildren yield only the visible page. Plus a stack case in the existing Children tests.

ListBox (#481)

Root cause. ListBox.Draw (flat and sectioned) pushed its clip only if overflow, a clip meant to stop a partial trailing row bleeding past the bottom. A list that fits vertically was not clipped at all, so a row wider than the list drew past its right edge (a host ItemRenderer too).

Fix. The clip to the content rect is pushed whenever the painter is a painter.Clipper, as Entry and SearchEntry already do. It is the same rect as before, so the overflowing case renders the same.

Test (list_clip_test.go): pixel render on a PixelPainter wider than the list; no pixel right of the list's edge may be touched, for flat-fits, flat-overflows (control, passed before), sectioned-fits, and an ItemRenderer that paints 200 px past its rect.

Verification

  • Before the fix (original stack.go / list.go, new tests): TestStackEntryReceivesTypedKeys, TestStackTakesPartInTabTraversal, TestStackExposesItsVisiblePagePopover, TestStackAsRootRoutesFocus, TestEveryContainerExposesItsChildren ("Stack holds Pages") and the three non-overflow cases of TestListBoxClipsRowsWiderThanTheList fail; the overflow case passes, as expected.
  • Sabotage, each passing go vet: Children yielding every page fails the hidden-page assertions (Tab, popover, Children); dropping routeFocusKey/focusClick from Stack.OnEvent fails TestStackAsRootRoutesFocus.
  • Locally: gofmt clean, go vet ./..., go test ./... with every function at 100.0% coverage, -race on the touched tests.

README does not describe either behaviour, so it is unchanged; the doc comments of Stack, ListBox.Draw and focusEnumerator are updated.

Semver

Minor. (*Stack).Children() is new exported API (it makes *Stack satisfy the child-walk convention). The rest is a behaviour fix.

Known, not changed here: focus held by a widget on a page that SetVisible hides is not cleared, the same as an inactive CardLayout card today.

Fixes #480
Fixes #481

🤖 Generated with Claude Code

tannevaled and others added 2 commits October 5, 2026 15:34
Stack held its pages in a map and exposed neither Children nor
focusableChildren, so every walk stopped at it: an Entry on a Stack page
was clicked into focus inside the page while the window's focus list,
which never reached it, received the typed keys and dropped them; and a
DropDown on a Stack page opened a popover PopoverHost could not see.

Stack now yields its VISIBLE page to both walks, and routes keyboard
events and click-to-focus through the focus system as Container does.
Hidden pages are deliberately not yielded: they keep the Stack's bounds,
so a bounds check cannot exclude them the way it excludes an inactive
CardLayout card.

TestEveryContainerExposesItsChildren now also treats a map of widgets
as a holder; it is the gap that let Stack through.

Fixes #480

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ListBox pushed its clip only while the list overflowed vertically, so a
short list drew a row wider than itself past its right edge, a host
ItemRenderer included. The clip is now pushed whenever the painter can
clip; it is the same content rect, so the overflowing case is unchanged.

Fixes #481

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@tannevaled
tannevaled merged commit e74c343 into main Oct 5, 2026
1 check passed
@tannevaled
tannevaled deleted the fix/stack-focus-listbox-clip branch October 5, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ListBox does not clip rows wider than the list Stack is not part of focus traversal: an Entry in a Stack page gets no keys

1 participant