dequelabs / dequelabs/cauldron
TreeView: cascadeSelect/cascadeDeselect don't compose with controlled selection; disabled nodes aren't expandable
- 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
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