Skip to content

[TreeView] Fix selection propagation to exclude disabled items#23012

Merged
michelengelen merged 7 commits into
mui:masterfrom
michelengelen:bugfix/treeview/22942
Jul 23, 2026
Merged

[TreeView] Fix selection propagation to exclude disabled items#23012
michelengelen merged 7 commits into
mui:masterfrom
michelengelen:bugfix/treeview/22942

Conversation

@michelengelen

Copy link
Copy Markdown
Member

When selectionPropagation is enabled on RichTreeView, disabled items (via isItemDisabled or isItemSelectionDisabled) 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

@michelengelen michelengelen self-assigned this Jun 30, 2026
@michelengelen michelengelen added type: bug It doesn't behave as expected. scope: tree view Changes related to the tree view. This includes TreeView, TreeItem. labels Jun 30, 2026
@code-infra-dashboard

code-infra-dashboard Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploy preview

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)
@mui/x-charts-premium 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 🔺+239B(+0.36%) 🔺+36B(+0.18%)
@mui/x-tree-view-pro 🔺+239B(+0.19%) 🔺+41B(+0.11%)
@mui/x-scheduler 🔺+239B(+0.07%) 🔺+75B(+0.08%)
@mui/x-scheduler-premium 🔺+239B(+0.05%) 🔺+67B(+0.05%)
@mui/x-chat 0B(0.00%) 0B(0.00%)
@mui/x-license 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@flaviendelangle flaviendelangle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 LukasTy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work. 👍
One test nitpick.

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@LukasTy

LukasTy commented Jul 1, 2026

Copy link
Copy Markdown
Member

GPT 5.4 findings

  1. packages/x-tree-view/src/internals/plugins/selection/TreeViewSelectionPlugin.test.tsx
    The new test named “should select the parent when all selectable children are selected (non-selectable sibling ignored)” does not test that behavior.
    Its isItemSelectionDisabled predicate disables every node with children, which makes both 1 and 1.2 non-selectable. The assertion then expects ['1.1', '1.2.1'], so no parent is selected at all.
    That means:

    • the test title contradicts the assertion, and
    • the PR still lacks a test for the actual scenario the title describes: a selectable parent auto-selecting while a non-selectable sibling/subtree is ignored.
  2. docs/data/tree-view/rich-tree-view/selection/selection.md
    The new “Propagation with disabled items” section understates the scope of the fix. It says only items disabled via isItemDisabled or item.disabled are excluded, but the implementation uses selectionSelectors.canItemBeSelected(...), so isItemSelectionDisabled is also part of this behavior.
    The PR description already mentions that case, so the docs and code are out of sync.

…2942

# Conflicts:
#	packages/x-tree-view/src/internals/plugins/expansion/TreeViewExpansionPlugin.test.tsx
#	packages/x-tree-view/src/internals/plugins/selection/TreeViewSelectionPlugin.test.tsx
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jul 2, 2026
@michelengelen
michelengelen merged commit 340c74b into mui:master Jul 23, 2026
22 checks passed
@michelengelen
michelengelen deleted the bugfix/treeview/22942 branch July 23, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: tree view Changes related to the tree view. This includes TreeView, TreeItem. type: bug It doesn't behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[tree view] Unselectable children prevent parent from being selected (stays indeterminate)

3 participants