perf(ActionList): replace :has(~ .SubGroup [data-active]) collapsed-parent indicator with attribute selector#7902
Conversation
🦋 Changeset detectedLatest commit: ad88153 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
…oup-active-indicator
There was a problem hiding this comment.
Pull request overview
This PR reduces CSS selector-matching cost in ActionList by removing a high-cost :has(~ .SubGroup …) rule and replacing it with a constant-time attribute-based selector driven by state already computed by NavList.ItemWithSubNav.
Changes:
- Replaced the collapsed-parent “active descendant” indicator selector from
:has(~ .SubGroup [data-active='true'])to.ActionListItem[data-has-subitem='true'][data-active='true'] > .ActionListContent[aria-expanded='false']. - Added a changeset documenting the internal performance change and the behavioral expectation for direct
ActionList.Item+ActionList.SubItemconsumers.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/ActionList/ActionList.module.css | Removes the expensive :has() rule and applies the same styles using parent data-active + data-has-subitem attributes. |
| .changeset/perf-actionlist-subgroup-active-indicator.md | Adds a patch changeset describing the selector change and the updated expectation for parent active state. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Low
| '@primer/react': patch | ||
| --- | ||
|
|
||
| ActionList: replace the `:has(~ .SubGroup [data-active='true'])` selector that styled collapsed parent items containing an active descendant with a plain attribute + child-combinator selector. The collapsed-parent active indicator now depends on the parent item carrying `data-active='true'`, which the supported `NavList.ItemWithSubNav` consumer already sets via its `containsCurrentItem` walk. |
…eset The `& + .ActionListItem .ActionListSubContent::before` half never matched (.ActionListItem is the parent of .ActionListContent, not a sibling) and was pre-existing dead code on main. Removing it preserves rendered output.
|
Closing as no longer necessary. Main already merged #8115 ("perf(css): remove expensive |
Closes #
Replaces the most expensive
:has()selector remaining inpackages/react/src/ActionList/ActionList.module.css—&:has(~ .SubGroup [data-active='true'])— with a plain attribute + child-combinator selector. This was the worst-shape:has()left in the package: it combined a general sibling combinator with a descendant search, and the engine had to re-run that walk against every collapsed parent any time a descendantdata-activeattribute changed.Before:
After:
The selector goes from a sibling walk + descendant subtree search (re-evaluated on every
data-activemutation inside the subtree) to a constant-time attribute lookup on the same<li>that the consumer already controls.Why this is safe:
The new selector depends on
data-activebeing set on the parent<li>. The only in-repo consumer that renders anActionList.ItemwithActionList.SubItemisNavList.ItemWithSubNav, which already does its ownhasCurrentNavItemrecursion and passesactive={!isOpen && containsCurrentItem}to the parent — exactly matching the condition the old:has()was detecting. Net visual output is identical forNavList.SubNavusage.Behavior change for direct
ActionList.Item+ActionList.SubItemconsumersIf a consumer renders
ActionList.SubItemdirectly (withoutNavList.SubNav) and relied on the:has()selector to auto-light-up a collapsed parent when a descendant haddata-active='true', they will now need to passactive={true}to the parentActionList.Itemthemselves. This is the same patternNavList.ItemWithSubNavalready implements; no other in-repo usage exists.Changelog
Changed
ActionList.module.cssno longer uses:has()to detect an active descendant in a collapsed parent's sub-group; the same styles fire when the parentActionList.Itemitself hasactive={true}(whichNavList.ItemWithSubNavalready sets via its existingcontainsCurrentItemwalk).Removed
&:has(~ .SubGroup [data-active='true'])selector fromActionList.module.css.Rollout strategy
Testing & Reviewing
packages/react/src/ActionList/**+packages/react/src/NavList/**unit tests pass.tsc --noEmitonpackages/reactis clean.-webkit-tap-highlight-colorbrowser-compat warning at line 509, unrelated to this change).VRT expectations: No change to
NavList.WithSubItems/NavList.WithNestedSubItems/NavList.WithTrailingActionInSubItemrenderings — the parentActionList.Itemalready carriesdata-active='true'whenever the previous:has()would have matched.Companion to #7901 (PageHeader) and #7894 (other ActionList
:has()selectors).Merge checklist