From 5aeb9bee8bb477488d818aaffc54f9376b648dea Mon Sep 17 00:00:00 2001 From: naveedsajjdawan <88137903+naveedsajjdawan@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:23:22 +0500 Subject: [PATCH] docs(useMultipleSelection): provide working removal announcements --- .all-contributorsrc | 7 +++ README.md | 3 +- src/hooks/useMultipleSelection/README.md | 74 ++++++++++++++++++------ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3a2475107..d2a133397 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1033,6 +1033,13 @@ "avatar_url": "https://avatars.githubusercontent.com/u/25305144?v=4", "profile": "https://github.com/zmdavis", "contributions": ["code", "bug"] + }, + { + "login": "naveedsajjdawan", + "name": "Naveed Sajjad", + "avatar_url": "https://avatars.githubusercontent.com/u/88137903?v=4", + "profile": "https://github.com/naveedsajjdawan", + "contributions": ["doc"] } ], "repoHost": "https://github.com", diff --git a/README.md b/README.md index b0c647a31..c71cecfce 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ autocomplete, combobox or select dropdown components.

[![downloads][downloads-badge]][npmcharts] [![version][version-badge]][package] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-113-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-145-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Chat][chat-badge]][chat] [![Code of Conduct][coc-badge]][coc] [![Join the community on Spectrum][spectrum-badge]][spectrum] @@ -1440,6 +1440,7 @@ Thanks goes to these people ([emoji key][emojis]): Tom Jenkinson
Tom Jenkinson

🚇 Alice Hendicott
Alice Hendicott

💻 🐛 Zach Davis
Zach Davis

💻 🐛 + Naveed Sajjad
Naveed Sajjad

📖 diff --git a/src/hooks/useMultipleSelection/README.md b/src/hooks/useMultipleSelection/README.md index 8a3ab6834..846aae5c4 100644 --- a/src/hooks/useMultipleSelection/README.md +++ b/src/hooks/useMultipleSelection/README.md @@ -462,41 +462,77 @@ function itemToKey(item) { ### getA11yStatusMessage -> `function({/* see below */})` | default messages provided in English +> `function({activeIndex, selectedItems})` | optional, no default This function is passed as props to a status updating function nested within -that allows you to create your own ARIA statuses. It is called when the state -changes: `selectedItem`, `highlightedIndex`, `inputValue` or `isOpen`. +that allows you to create your own ARIA statuses. It is called after the initial +mount when `activeIndex` or `selectedItems` changes. There is no default function provided anymore since v9, so if there's no prop -passed, no aria live status message is created. An implementation that resembles -the previous default is written below, should you want to keep pre v9 behaviour. +passed, no aria live status message is created. The example below opts into +announcements when selected books are removed. We don't provide this as a default anymore since we consider that screen readers have been significantly improved and they can convey information about items count, possible actions and highlighted items only from the HTML markup, without the need for aria-live regions. -```js -function getA11yStatusMessage(state) { - const {selectedItems} = state +```jsx +import * as React from 'react' +import {useMultipleSelection} from 'downshift' - if (selectedItems.length === previousSelectedItemsRef.current.length) { - return '' - } +const initialSelectedItems = [ + {id: 1, title: 'To Kill a Mockingbird'}, + {id: 2, title: 'War and Peace'}, +] - const removedSelectedItem = previousSelectedItemsRef.current.find( - selectedItem => - selectedItems.findIndex( - item => props.itemToKey(item) === props.itemToKey(selectedItem), - ) < 0, - ) +function SelectedBooks() { + const previousSelectedItemsRef = React.useRef(initialSelectedItems) + const {selectedItems, getSelectedItemProps, getDropdownProps} = + useMultipleSelection({ + initialSelectedItems, + itemToKey: item => item.id, + getA11yStatusMessage({selectedItems: currentSelectedItems}) { + const previousSelectedItems = previousSelectedItemsRef.current + previousSelectedItemsRef.current = currentSelectedItems + + const currentIds = new Set(currentSelectedItems.map(item => item.id)) + return previousSelectedItems + .filter(item => !currentIds.has(item.id)) + .map(item => `${item.title} has been removed.`) + .join(' ') + }, + }) - // where itemToString is a function that returns the string equivalent for an item. - return `${itemToString(removedSelectedItem)} has been removed.` + return ( +
+

+ Focus a selected book and press Delete or Backspace to remove it. +

+ {selectedItems.map((selectedItem, index) => ( + + {selectedItem.title} + + ))} + + +
+ ) } ``` +Keep the previous selection in a ref initialized to the same selection as the +hook. Update it on every callback, including replacements with the same number +of items. Compare stable IDs so new object references do not count as removals. +This example returns an empty message for additions or focus changes and +announces each removed book when multiple books are removed at once. For a +controlled selection, initialize the ref from your initial `selectedItems` prop +instead. + ### onActiveIndexChange > `function(changes: object)` | optional, no useful default