[TreeView] Fix selection propagation to exclude disabled items#23012
Conversation
…n propagation Signed-off-by: michel <[email protected]>
Signed-off-by: michel <[email protected]>
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
There was a problem hiding this comment.
I doubled checked, the Base UI Tree had the right behavior already.
The fix here aligns correctly the behaviors.
PR review by Claude Code
The selection-propagation fix is logically sound and matches the correct behavior independently implemented in base-ui-plus#4 (skip non-selectable descendants, ignore them in the parent auto-select check, refuse to auto-select a non-selectable parent, omit aria-checked on non-selectable nodes). Nothing is merge-blocking. The remaining issues are a test gap around the new aria-checked behavior and a minor a11y inconsistency on disabled-but-selected items.
Bugs (0)
No findings.
Tests (2)
1. 🟡 The new aria-checked behavior for disabled / non-selectable items has no test
Location: packages/x-tree-view/src/internals/plugins/selection/itemPlugin.ts:80
const isItemInherentlyNotSelectable = isItemDisabled || !isItemSelectable;
...
if (isItemInherentlyNotSelectable) {
// - if the tree contains nodes that are not selectable, aria-checked is not present on those nodes.
ariaChecked = undefined;
} else if (selectionStatus === 'checked') {This reorders the aria-checked resolution so that a disabled or isItemSelectionDisabled item now always omits aria-checked, even when its prior status would have been 'checked' or 'mixed'. That is a deliberate change to a public a11y contract, but no test asserts it. The added test (should show the parent checkbox as checked…) only checks dataset.indeterminate/checked on the checkbox <input>, not the aria-checked attribute on the treeitem root.
Failure scenario: A future refactor reverts the branch ordering (e.g. moves isItemInherentlyNotSelectable below checked), silently restoring aria-checked="true"/"mixed" on disabled nodes, and CI stays green.
Fix: Add an assertion that a disabled / isItemSelectionDisabled item's root element has no aria-checked (and that an indeterminate-by-descendants non-selectable parent also omits it).
2. ℹ️ Descendants skip is only exercised via item disabled, never via isItemSelectionDisabled
Location: packages/x-tree-view/src/internals/plugins/selection/TreeViewSelectionPlugin.test.tsx:869
The descendants-direction fix uses canItemBeSelected, which covers both disabled and isItemSelectionDisabled. But the only descendants test (should not select disabled children when selecting a parent) uses disabled: true. The isItemSelectionDisabled path through selectDescendants is untested — it's only covered for the parents direction.
Failure scenario: A regression that makes selectDescendants check isItemDisabled directly instead of canItemBeSelected would still pass all tests while incorrectly selecting isItemSelectionDisabled descendants.
Fix: Add a descendants-propagation test using isItemSelectionDisabled on a descendant.
Simplifications (0)
No findings.
Docs (1)
1. ℹ️ Disabled-but-selected item shows a checked checkbox with no aria-checked
Location: packages/x-tree-view/src/internals/plugins/selection/itemPlugin.ts:80 / :116
For an item that is disabled yet present in selectedItems (e.g. via defaultSelectedItems), the checkbox still renders checked: selectionStatus === 'checked' (true), while the root now sets aria-checked = undefined. Sighted users see a checked (disabled) checkbox; screen-reader users get no checked state. This is an edge case and arguably acceptable per the APG ("non-selectable nodes omit aria-checked"), but it's an intentional inconsistency worth a code comment so it isn't "fixed" later by accident.
Failure scenario: A maintainer notices the visual-vs-ARIA mismatch on a disabled selected item and assumes it's a bug, re-adding aria-checked and reintroducing the behavior this PR intentionally removed.
Fix: Add a one-line comment at the isItemInherentlyNotSelectable branch noting the checkbox checked prop can diverge from aria-checked for disabled-yet-selected items, by design.
Verdict
Approve after nits — logic is correct and confirmed against the reference implementation; the only gap is missing test coverage for the new aria-checked behavior.
🤖 Review generated with Claude Code
LukasTy
left a comment
There was a problem hiding this comment.
Nice work. 👍
One test nitpick.
…Plugin tests Signed-off-by: michel <[email protected]>
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
GPT 5.4 findings
|
…pagation Signed-off-by: michel <[email protected]>
…2942 # Conflicts: # packages/x-tree-view/src/internals/plugins/expansion/TreeViewExpansionPlugin.test.tsx # packages/x-tree-view/src/internals/plugins/selection/TreeViewSelectionPlugin.test.tsx
… improve API reference handling Signed-off-by: michel <[email protected]>
When
selectionPropagationis enabled onRichTreeView, disabled items (viaisItemDisabledorisItemSelectionDisabled) were incorrectly included in selection counts. Selecting a parent would select disabled descendants, and all children had to be selected — including disabled ones — for the parent to auto-select. This fixes both directions: selecting a parent now skips non-selectable descendants, and the parent auto-selects when all selectable children are checked. The checkbox indeterminate/checked state now also ignores disabled items. Includes tests and a new documentation demo.Fixes #22942