dequelabs / dequelabs/cauldron

TreeView: cascadeSelect/cascadeDeselect don't compose with controlled selection; disabled nodes aren't expandable

Open
#2,453 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
127
Forks
31
Avg merge
2d 12h
Merged PRs (30d)
8

Description

## Summary

`TreeView` (in `7.1.0-canary.6e748e46`, used by axe-reports for the permission-aware dimension filter, dequelabs/axe-reports#3067) has two gaps that force consumer-side workarounds when you need a **controlled, cascading** checkbox tree that also contains **disabled** nodes with accessible descendants.

## 1. `cascadeSelect`/`cascadeDeselect` don't compose with controlled selection

`cascadeSelect`/`cascadeDeselect` are implemented on top of TreeView's **private, uncontrolled** selection state:

```js
const [selectedKeys, setSelectedKeys] = useState(new Set()); // internal only
const isCascade = selectionMode === 'multiple' && (cascadeSelect || cascadeDeselect);
const handleSelectionChange = (selection) =>
setSelectedKeys(prev => isCascade ? applyCascade(items, prev, selection, ...) : selection);

const selectionProps = (isCascade || onAction)
? { selectedKeys, onSelectionChange: handleSelectionChange }
: {};
return ;
```

- There is no public prop to **seed** that internal set or **read** it back — `selectedKeys` / `defaultSelectedKeys` / `onSelectionChange` aren't part of `TreeViewProps`.
- Because `{...other}` is spread **after** `{...selectionProps}`, the only way to control the tree today (passing `selectedKeys`/`onSelectionChange` through to the underlying react-aria `Tree` via the passthrough) **overrides** Cauldron's `handleSelectionChange` — so `applyCascade` never runs. Net result: with controlled selection, `cascadeSelect`/`cascadeDeselect` are silently inert.

We need controlled selection to (a) pre-check applied filters when the modal re-opens, (b) read the checked set on Apply, and (c) Clear all — so we currently can't use the native cascade and have reimplemented `applyCascade` on the consumer side.

**Request:** support controlled `selectedKeys` + `onSelectionChange` as first-class `TreeViewProps` that **compose with** `cascadeSelect`/`cascadeDeselect` (i.e. run the cascade, then call the consumer's `onSelectionChange` with the cascaded result).

## 2. Disabled nodes aren't expandable by default, hiding accessible descendants

With the default `disabledBehavior` (`"all"`), a `disabled` node is fully inert — it can't be focused or expanded — so **accessible children nested under a disabled parent become unreachable**. In our tree a "No access" group can legitimately contain subdimensions the user *can* access (e.g. `Colombia (No access) → Medellín`), and those must still be selectable.

We worked around this with `disabledBehavior="selection"` (keeps disabled rows focusable/expandable, blocks only selection), but under that mode TreeView drops the disabled **styling and ARIA** it applies in `"all"` mode, so we have to reinstate `aria-disabled` (via a `MutationObserver`) and the dimmed control ourselves.

**Request:** allow disabled nodes to remain **expandable** while non-selectable (or document/support `disabledBehavior="selection"` retaining the disabled styling + `aria-disabled` so consumers don't have to re-add them).

## Environment
- `@deque/cauldron-react@7.1.0-canary.6e748e46`
- Consumer: dequelabs/axe-reports (permission-aware chart filters, #3067)

Contributor guide

Open the contributing guide

Research direction

Start at the TreeView implementation described in the issue and trace its selection props, internal state, cascade handlers, and passthrough to the underlying react-aria Tree. Verify controlled selection composes with cascading callbacks, then check disabledBehavior="selection" and the default disabled behavior for expandable descendants, styling, and aria-disabled state.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
accessibility, frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.