Skip to content

feat(ui): add accessible tree suite - #1065

Draft
EhabY wants to merge 5 commits into
mainfrom
feat/ui-tree-suite
Draft

feat(ui): add accessible tree suite#1065
EhabY wants to merge 5 commits into
mainfrom
feat/ui-tree-suite

Conversation

@EhabY

@EhabY EhabY commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add the Tree, TreeItem, and TreeGroup component suite: ARIA tree semantics, roving tabindex, hierarchy navigation, and buffered type-ahead
  • Controlled single selection (Tree.selectedItemId) and branch expansion (TreeItem.expanded)
  • Match VS Code tree geometry and states across the shared, Modern, and stable styles, with an explorer variant for icon-less branch rows
  • Storybook and pixel coverage in all four themes; the nested story forces real :hover via storybook-addon-pseudo-states so screenshots capture hover and selection together

Closes #1037

Validation

  • pnpm test:webview - 353 tests passed
  • pnpm typecheck
  • pnpm exec eslint on the changed files
  • Stories verified in a running Storybook in dark and light themes

@EhabY EhabY self-assigned this Aug 5, 2026
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 510e466 to 309f05d Compare August 5, 2026 14:11
Base automatically changed from feat/ui-package-gaps to main August 7, 2026 10:52
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch 6 times, most recently from ddeb99e to 3117b68 Compare August 11, 2026 15:59
EhabY added 4 commits August 12, 2026 00:21
Add the Tree, TreeItem, and TreeGroup compound components: ARIA tree
semantics with roving tabindex, hierarchy navigation, buffered
type-ahead, and controlled selection and expansion. Styling matches
VS Code tree geometry and states across the shared, Modern, and stable
styles, with an explorer variant for icon-less branch rows.

Cover the suite with unit tests and pixel stories across all four
themes; the nested story forces real :hover via
storybook-addon-pseudo-states so screenshots capture hover and
selection together.
Pin the ancestors of the topmost visible row like VS Code's tree sticky
scroll, opt in with `stickyScroll` and cap the pinned levels with a
number. Branch rows pin themselves with `position: sticky` at an offset
derived from their level, so the browser produces the push-out as a
subtree scrolls past and no scroll listener or duplicate row widget is
needed. Rows past the cap and leaves never pin.

Pinned rows paint over scrolled content with sideBarStickyScroll's
background. Webviews receive no workbench.tree.* settings, so the README
shows the host how to read them and pass them in.
Opt in with `multiSelect`, which swaps the singular selection props for
`selectedItemIds` and `onSelectedItemsChange` and marks the tree
aria-multiselectable, matching VS Code's per-tree multipleSelectionSupport.

Ctrl/Cmd click toggles a row, Shift click and Shift arrows extend from
the anchor, and Ctrl/Cmd+A takes every visible enabled row. Ranges walk
tree order, so disabled and collapsed rows stay out of them.

Indent guide owners collapse into a set: siblings selected together share
one owner, which a list would have counted twice and republished on every
commit.
Render a branch's group only while it is expanded, so cost tracks what
is open rather than the size of the tree. A 100k-node tree browsed a
folder at a time goes from 4.4s to 357ms to mount, 746MB to 110MB of
heap, and 9.7ms to 0.3ms per keystroke, with scrolling back at 60fps.

Fully expanded trees are unchanged, so the remaining limit is rows open
at once: ~10k is comfortable, 50k degrades, and 100k needs a virtualized
tree instead. A group that only exists while open needs neither the
hidden attribute nor the rule that forced it to win.
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 3117b68 to 814fbbf Compare August 11, 2026 21:28
Add a Focused story: focus is its own state, so a row can carry the blue
outline without being selected while the selected row keeps its
background. Give the multi-select story the same active row VS Code
draws inside a multi-selection.

Both force the pseudo state through the addon and take real DOM focus for
the tree's focused class, since React listens for focusin and the
synthetic focus event does not bubble to it in a browser.

Sticky scroll scrolls on mount rather than from its play function, and
into a shorter viewport, so the snapshot actually shows pinned ancestors
with a row clipped beneath them.
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.

ui: Tree suite with native-fidelity look and ARIA keyboard nav

1 participant