dequelabs / dequelabs/cauldron
Multiselect Combobox: no way to intercept or gate chip deselection
- Dominant language
- TypeScript
- Stars
- 127
- Forks
- 31
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 8
Description
## Problem
When a user clicks the × button on a chip in a multiselect Combobox, Cauldron applies the deselection to its internal state **before** firing `onSelectionChange`. This means consumers cannot:
- Show a confirmation dialog before the removal happens
- Conditionally prevent a deselection
- Gate removal behind any async operation
By the time `onSelectionChange` fires, the chip is already gone from the Combobox's internal state. The `value` prop is not re-read unless the component remounts.
## Use case
In axe-reports (dequelabs/axe-reports#3141), removing a dimension also removes access to all its subdimensions — a destructive action. We need to show a confirmation modal before allowing the removal.
## Current workaround
We force-remount the Combobox by changing its React `key` prop, which causes it to re-read the `value` prop (still containing the original selection) and restore the chip. Then we show the modal. If the user confirms, we update state and remount again. If they cancel, the chip is already restored.
```tsx
// In useRemovalConfirmation hook
const [comboboxResetKey, setComboboxResetKey] = useState(0);
const checkRemovalConfirmation = (dimension) => {
if (hasShownRef.current) return true; // skip modal
hasShownRef.current = true;
setPendingRemoval(dimension);
setComboboxResetKey(prev => prev + 1); // force remount to undo deselection
return false;
};
// In JSX
```
### Problems with this workaround
- **Loses Combobox internal state** — scroll position, typed filter text, open/close state are all reset on remount
- **Fragile** — depends on Cauldron re-reading `value` on mount and not caching `previousValue` from the old instance
- **Focus management complexity** — the remount destroys the DOM element that had focus, requiring deferred focus restoration via effects
## Suggested solutions (any of these would work)
1. **`onBeforeSelectionChange` callback** — fires before internal state changes, return `false` to prevent the deselection
2. **Fully controlled selection mode** — Combobox doesn't manage selection internally when `value` is provided as a controlled prop (similar to controlled `` in React)
3. **`confirmDeselection` prop** — accepts a callback `(removedValue: string) => Promise` that Cauldron awaits before applying the change
Option 2 (fully controlled mode) would be the most flexible and consistent with React conventions.
## Affected version
`@deque/cauldron-react` 6.18.5 (also verified on 6.27.0 — same behavior)
Contributor guide
Assessment
This issue has not been assessed yet.