Skip to content

perf(TreeView): make rows paint-safe and reduce hover/focus invalidation#7899

Open
mattcosta7 wants to merge 3 commits into
mainfrom
treeview-paint-safe-perf
Open

perf(TreeView): make rows paint-safe and reduce hover/focus invalidation#7899
mattcosta7 wants to merge 3 commits into
mainfrom
treeview-paint-safe-perf

Conversation

@mattcosta7
Copy link
Copy Markdown
Contributor

@mattcosta7 mattcosta7 commented May 29, 2026

Closes #

Five targeted improvements to `TreeView` style + structure aimed at making rows cheaper to render at scale and removing a latent bug in the documented `containIntrinsicSize` API. No visual, layout, or public-API changes at the default rendering — all changes are either invisible at the default state or behind a CSS containment property the consumer opts into.

Changelog

Changed

  • Current-item indicator is now paint-safe via `overflow-clip-margin`. The indicator pseudo-element is positioned at `left: -8px` of `.TreeViewItemContainer`. That meant any consumer applying `contain: paint` to the row, or the documented `containIntrinsicSize` prop on `TreeView.Item` (which sets `content-visibility: auto` — implying `contain: paint` — on the same container), would clip the indicator on `current` items. Both `.TreeViewItem` and `.TreeViewItemContainer` now declare `overflow-clip-margin: var(--base-size-8)`. The property is a no-op when no paint containment is active, so default rendering is byte-identical.
  • Skeleton-row hover suppression no longer uses `:has()`. `:has(.TreeViewItemSkeleton):hover` matched on every row container in every tree, forcing subtree invalidation tracking on every row. `LoadingItem` now communicates with the placeholder `Item` via a module-private `LoadingPlaceholderContext` that emits a positive `data-loading` attribute on the `
  • `, and the CSS selector targets that directly. No new public prop.
  • Nesting indicator lines no longer rely on a root-scope `:hover`/`:focus-within` descendant selector. Color is driven by an inherited `--tree-line-color` custom property toggled on the root `
      `. Same UX (lines fade in while the tree is interacted with on hover-capable devices, always visible on coarse pointers); the engine updates one property on one element instead of re-matching the descendant selector against every `.TreeViewItemLevelLine` in the tree.
    • Fixed unitless `outline-offset: -2` in the forced-colors focus-ring fallback. Browsers were silently dropping the declaration, so forced-colors users were getting no focus indicator on tree items.
    • Made `grid-template-columns` declare the `trailingAction` track explicitly (`auto`) so it matches the 5-area `grid-template-areas` declaration on the same element. Clarity, no behavior change.

    New

    • Storybook story `TreeView/Features/CurrentItemWithContainIntrinsicSize` exercising the previously-broken `current` + `containIntrinsicSize` combination so VRT locks in the contract going forward.

    Removed

    Nothing public.

    Rollout strategy

    • Patch release
    • Minor release
    • Major release; if selected, include a written rollout or migration plan
    • None; if selected, include a brief description as to why

    Testing & Reviewing

    • All 94 `packages/react/src/TreeView/**` unit tests pass.
    • `tsc --noEmit` on `packages/react` is clean.

    VRT expectations: No change in default rendering. The one place a change could surface is forced-colors focus rings on tree items, which go from "nothing" (the silent `outline-offset: -2` drop) to a 2px inset `HighlightText` outline. That's an accessibility fix.

    Internal-only note: this lets the github-ui escape hatch in `packages/diff-file-tree/DiffFileTree.module.css` (`content-visibility: visible; contain: none;` for `[aria-current='true']` and `:focus-visible`) be removed once this lands.

    Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 21ac203

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@mattcosta7 mattcosta7 force-pushed the treeview-paint-safe-perf branch from a12402c to ddb1910 Compare May 29, 2026 11:52
@github-actions github-actions Bot requested a deployment to storybook-preview-7899 May 29, 2026 11:55 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-7899 May 29, 2026 12:06 Inactive
…ver/focus invalidation

- Add 'overflow-clip-margin: var(--base-size-8)' to '.TreeViewItem' and
  '.TreeViewItemContainer' so the current-item indicator (positioned at
  'left: -8px' of the row container) remains visible when a consumer applies
  'contain: paint' to the row, or when the documented 'containIntrinsicSize'
  prop triggers 'content-visibility: auto' (which implies paint containment)
  on the container. The property is a no-op when no paint containment is
  active, so default rendering is unchanged. Safari 17 silently ignores the
  property and keeps the pre-existing clipped-indicator behavior under
  consumer-side 'contain: paint' (no regression from prior versions); Safari
  18+ supports it, as does 'content-visibility: auto'.
- Replace ':has(.TreeViewItemSkeleton):hover' with a positive '[data-loading]'
  marker emitted on the placeholder row's <li> by 'LoadingItem' via a
  module-private 'LoadingPlaceholderContext'. No public-API change; avoids the
  broad ':has()' invalidation cost across every row in large trees.
- Replace the root-scope ':hover'/':focus-within' descendant selectors on
  level lines with an inherited '--tree-line-color' custom property toggled on
  the root <ul>. Same UX; one property toggle on one element instead of
  selector re-matching against every '.TreeViewItemLevelLine' descendant.
- Fix unitless 'outline-offset: -2' in the forced-colors focus-ring fallback
  that browsers were silently dropping (so forced-colors users now actually
  get a focus indicator on tree items).
- Make 'grid-template-columns' declare the 'trailingAction' track explicitly
  ('auto') so it matches the 5-area 'grid-template-areas' declaration.
@mattcosta7 mattcosta7 force-pushed the treeview-paint-safe-perf branch from ddb1910 to 59c4714 Compare May 29, 2026 12:21
@primer
Copy link
Copy Markdown
Contributor

primer Bot commented May 29, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

Removed comments regarding Safari 17 behavior and stylelint rules.
@mattcosta7 mattcosta7 self-assigned this May 29, 2026
@mattcosta7 mattcosta7 marked this pull request as ready for review May 29, 2026 13:22
@mattcosta7 mattcosta7 requested a review from a team as a code owner May 29, 2026 13:22
@mattcosta7 mattcosta7 requested review from Copilot and liuliu-dev May 29, 2026 13:22
@github-actions github-actions Bot requested a deployment to storybook-preview-7899 May 29, 2026 13:26 Abandoned
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Targeted performance and correctness fixes to TreeView styling. Rows are made paint-safe so the current-item indicator survives contain: paint / content-visibility: auto, the skeleton hover-suppression :has() selector is replaced with a positively-marked data-loading attribute, and nesting-line hover toggling is moved from a descendant selector to a single inherited custom property. Also fixes a unitless outline-offset: -2 in the forced-colors focus fallback and makes the trailing-action grid column explicit.

Changes:

  • Add overflow-clip-margin: var(--base-size-8) on .TreeViewItem and .TreeViewItemContainer; fix unitless outline-offset; make grid-template-columns declare the trailingAction track explicitly.
  • Replace :has(.TreeViewItemSkeleton):hover with a module-private LoadingPlaceholderContext that sets data-loading on the <li>, and key the CSS off that attribute.
  • Drive nesting indicator color through an inherited --tree-line-color custom property toggled on the root <ul> instead of via :hover/:focus-within descendant selectors; add a VRT story for current + containIntrinsicSize.
Show a summary per file
File Description
packages/react/src/TreeView/TreeView.tsx Adds LoadingPlaceholderContext, consumes it in Item to emit data-loading, and wraps both LoadingItem return paths with the provider.
packages/react/src/TreeView/TreeView.module.css Adds overflow-clip-margin, explicit auto trailing column, fixes outline-offset: -2px, replaces :has() skeleton selector with [data-loading], refactors level-line color to inherited --tree-line-color.
packages/react/src/TreeView/TreeView.features.stories.tsx Adds CurrentItemWithContainIntrinsicSize story for VRT coverage of the previously-clipped indicator.
.changeset/treeview-indicator-paint-safe.md Patch changeset summarizing the five changes.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0


&:where([data-omit-spacer='true']) .TreeViewItemContainer {
grid-template-columns: 0 0 0 1fr;
grid-template-columns: 0 0 0 1fr auto;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto is the default, but it reads nicer imp when it's 5 columns defined for 5 columns

@liuliu-dev liuliu-dev added the Canary Release Apply this label when you want CI to create a canary release of the current PR label May 29, 2026
@primer-integration
Copy link
Copy Markdown

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/21960

@primer-integration
Copy link
Copy Markdown

Integration test results from github/github-ui:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary Release Apply this label when you want CI to create a canary release of the current PR integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants