Conversation
Resolve accessible-delegate-focus against accessible-item-index when descendants expose logical indices. This keeps focus aligned with materialized ListView rows after scrolling while preserving positional delegation for existing unindexed components. Add a regression test for a virtualized list, an unmaterialized target, and the positional compatibility path. Fixes slint-ui#12645 ChangeLog: Fixed accessibility focus delegation for virtualized lists after scrolling past the first items
ogoffart
left a comment
There was a problem hiding this comment.
These comments are from claude.
The fix does not reach the Qt style. ListItem in
internal/compiler/widgets/qt/std-widgets-impl.slint:11 sets only
accessible-role and accessible-label, with no accessible-item-index. The
other four styles all set it (fluent/components.slint:53,
material:111, cupertino:65, cosmic:109), and even qt/tabwidget.slint:12
sets it for tabs.
So with the native style — the style you get with the Qt backend, which is the
backend whose delegateFocus this PR rewrites — a StandardListView still has
no indexed descendants, keeps the positional path, and focus after scrolling
stays wrong.
The missing property predates this PR, so it is not a regression here. But it
would be good to either add accessible-item-index: root.index; in the same
change, or say in the commit message that Qt-style lists are a follow-up.
Expose logical indices on Qt list items so native-style virtualized lists use the indexed focus delegate lookup. Refresh cached accessibility children before mapping the live delegate position. Remember the last focus target by QObject identity to avoid announcing the same row again when scrolling only changes materialization. Keep the strict fallback for indexed trees: a positional match for a missing logical row would focus a different row. Document that contract and assert the resolved item's index in the regression test.
The focus delegate resolver belongs to the runtime, so exercise it with a .slint fixture compiled by the test drivers instead of an interpreter unit test. Keep the Rust assertions for the scrolled list, missing logical index, and positional compatibility path.
The shared test calls accessible_descendents, whose existing name is flagged by cspell. Match the local spelling exception in the defining runtime module.
Keep the Qt focus delegation tracker dependent only on the delegate-focus property. Resolve the logical index when delegation runs, outside the tracker, to avoid rebuilding the accessibility subtree during scrolling.
Fixes #12645.
accessible-delegate-focuswas resolved as an ordinal among the currently materialized accessible children. For a virtualizedListView, those children start at the visible model index instead of zero, so focus was lost or delegated to the wrong item after scrolling.This change resolves the delegate against
accessible-item-indexwhen descendants expose logical indices. If the requested indexed item is not materialized, focus stays on the parent instead of moving to an unrelated child. Components without indexed descendants retain the existing positional behavior.The shared resolver is used by both AccessKit/Winit and Qt. The regression test covers a scrolled virtualized list, a target outside the materialized window, and the compatibility path for unindexed children.
Tested with:
cargo fmt --checkcargo test -p slint-interpreter --features internal,internal-highlightcargo test -p i-slint-backend-winit --libcargo check -p i-slint-backend-qtcargo clippy -p i-slint-core --lib -- -D warnings