From 418ab4dbe189aa9e54705c0263a58102d5db2a96 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Fri, 13 May 2022 13:33:29 -0700 Subject: [PATCH 1/3] addressing review follow up from pull 3086 exports some more things from aria/grid package and adds a new hook for selection announcements --- packages/@react-aria/grid/src/index.ts | 2 + packages/@react-aria/grid/src/useGrid.ts | 87 ++----------- .../grid/src/useGridSelectionAnnouncement.ts | 115 ++++++++++++++++++ packages/@react-aria/list/intl/ar-AE.json | 8 -- packages/@react-aria/list/intl/bg-BG.json | 8 -- packages/@react-aria/list/intl/cs-CZ.json | 8 -- packages/@react-aria/list/intl/da-DK.json | 8 -- packages/@react-aria/list/intl/de-DE.json | 8 -- packages/@react-aria/list/intl/el-GR.json | 8 -- packages/@react-aria/list/intl/en-US.json | 8 -- packages/@react-aria/list/intl/es-ES.json | 8 -- packages/@react-aria/list/intl/et-EE.json | 8 -- packages/@react-aria/list/intl/fi-FI.json | 8 -- packages/@react-aria/list/intl/fr-FR.json | 8 -- packages/@react-aria/list/intl/he-IL.json | 8 -- packages/@react-aria/list/intl/hr-HR.json | 8 -- packages/@react-aria/list/intl/hu-HU.json | 8 -- packages/@react-aria/list/intl/it-IT.json | 8 -- packages/@react-aria/list/intl/ja-JP.json | 8 -- packages/@react-aria/list/intl/ko-KR.json | 8 -- packages/@react-aria/list/intl/lt-LT.json | 8 -- packages/@react-aria/list/intl/lv-LV.json | 8 -- packages/@react-aria/list/intl/nb-NO.json | 8 -- packages/@react-aria/list/intl/nl-NL.json | 8 -- packages/@react-aria/list/intl/pl-PL.json | 8 -- packages/@react-aria/list/intl/pt-BR.json | 8 -- packages/@react-aria/list/intl/pt-PT.json | 8 -- packages/@react-aria/list/intl/ro-RO.json | 8 -- packages/@react-aria/list/intl/ru-RU.json | 8 -- packages/@react-aria/list/intl/sk-SK.json | 8 -- packages/@react-aria/list/intl/sl-SI.json | 8 -- packages/@react-aria/list/intl/sr-SP.json | 8 -- packages/@react-aria/list/intl/sv-SE.json | 8 -- packages/@react-aria/list/intl/tr-TR.json | 8 -- packages/@react-aria/list/intl/uk-UA.json | 8 -- packages/@react-aria/list/intl/zh-CN.json | 8 -- packages/@react-aria/list/intl/zh-TW.json | 8 -- packages/@react-aria/list/src/useList.ts | 111 ++--------------- packages/@react-aria/list/src/useListItem.ts | 8 +- .../@react-spectrum/list/src/ListViewItem.tsx | 3 +- 40 files changed, 137 insertions(+), 461 deletions(-) create mode 100644 packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts delete mode 100644 packages/@react-aria/list/intl/ar-AE.json delete mode 100644 packages/@react-aria/list/intl/bg-BG.json delete mode 100644 packages/@react-aria/list/intl/cs-CZ.json delete mode 100644 packages/@react-aria/list/intl/da-DK.json delete mode 100644 packages/@react-aria/list/intl/de-DE.json delete mode 100644 packages/@react-aria/list/intl/el-GR.json delete mode 100644 packages/@react-aria/list/intl/en-US.json delete mode 100644 packages/@react-aria/list/intl/es-ES.json delete mode 100644 packages/@react-aria/list/intl/et-EE.json delete mode 100644 packages/@react-aria/list/intl/fi-FI.json delete mode 100644 packages/@react-aria/list/intl/fr-FR.json delete mode 100644 packages/@react-aria/list/intl/he-IL.json delete mode 100644 packages/@react-aria/list/intl/hr-HR.json delete mode 100644 packages/@react-aria/list/intl/hu-HU.json delete mode 100644 packages/@react-aria/list/intl/it-IT.json delete mode 100644 packages/@react-aria/list/intl/ja-JP.json delete mode 100644 packages/@react-aria/list/intl/ko-KR.json delete mode 100644 packages/@react-aria/list/intl/lt-LT.json delete mode 100644 packages/@react-aria/list/intl/lv-LV.json delete mode 100644 packages/@react-aria/list/intl/nb-NO.json delete mode 100644 packages/@react-aria/list/intl/nl-NL.json delete mode 100644 packages/@react-aria/list/intl/pl-PL.json delete mode 100644 packages/@react-aria/list/intl/pt-BR.json delete mode 100644 packages/@react-aria/list/intl/pt-PT.json delete mode 100644 packages/@react-aria/list/intl/ro-RO.json delete mode 100644 packages/@react-aria/list/intl/ru-RU.json delete mode 100644 packages/@react-aria/list/intl/sk-SK.json delete mode 100644 packages/@react-aria/list/intl/sl-SI.json delete mode 100644 packages/@react-aria/list/intl/sr-SP.json delete mode 100644 packages/@react-aria/list/intl/sv-SE.json delete mode 100644 packages/@react-aria/list/intl/tr-TR.json delete mode 100644 packages/@react-aria/list/intl/uk-UA.json delete mode 100644 packages/@react-aria/list/intl/zh-CN.json delete mode 100644 packages/@react-aria/list/intl/zh-TW.json diff --git a/packages/@react-aria/grid/src/index.ts b/packages/@react-aria/grid/src/index.ts index 2958b83147e..89f7bb87de0 100644 --- a/packages/@react-aria/grid/src/index.ts +++ b/packages/@react-aria/grid/src/index.ts @@ -16,3 +16,5 @@ export * from './useGridRowGroup'; export * from './useGridRow'; export * from './useGridCell'; export * from './useGridSelectionCheckbox'; +export * from './useHighlightSelectionDescription'; +export * from './useGridSelectionAnnouncement'; diff --git a/packages/@react-aria/grid/src/useGrid.ts b/packages/@react-aria/grid/src/useGrid.ts index 3006f57cb19..d5ca5f90dc6 100644 --- a/packages/@react-aria/grid/src/useGrid.ts +++ b/packages/@react-aria/grid/src/useGrid.ts @@ -10,17 +10,15 @@ * governing permissions and limitations under the License. */ -import {announce} from '@react-aria/live-announcer'; -import {AriaLabelingProps, DOMProps, KeyboardDelegate, Selection} from '@react-types/shared'; -import {filterDOMProps, mergeProps, useId, useUpdateEffect} from '@react-aria/utils'; +import {AriaLabelingProps, DOMProps, KeyboardDelegate} from '@react-types/shared'; +import {filterDOMProps, mergeProps, useId} from '@react-aria/utils'; import {GridCollection} from '@react-types/grid'; import {GridKeyboardDelegate} from './GridKeyboardDelegate'; import {gridMap} from './utils'; import {GridState} from '@react-stately/grid'; -import {HTMLAttributes, Key, RefObject, useMemo, useRef} from 'react'; -// @ts-ignore -import intlMessages from '../intl/*.json'; -import {useCollator, useLocale, useMessageFormatter} from '@react-aria/i18n'; +import {HTMLAttributes, Key, RefObject, useMemo} from 'react'; +import {useCollator, useLocale} from '@react-aria/i18n'; +import {useGridSelectionAnnouncement} from './useGridSelectionAnnouncement'; import {useHighlightSelectionDescription} from './useHighlightSelectionDescription'; import {useSelectableCollection} from '@react-aria/selection'; @@ -69,12 +67,11 @@ export function useGrid(props: GridProps, state: GridState state.collection.getItem(key)?.textValue, scrollRef, + getRowText, onRowAction, onCellAction } = props; - let formatMessage = useMessageFormatter(intlMessages); if (!props['aria-label'] && !props['aria-labelledby']) { console.warn('An aria-label or aria-labelledby prop is required for accessibility.'); @@ -126,78 +123,8 @@ export function useGrid(props: GridProps, state: GridState { - if (!state.selectionManager.isFocused) { - lastSelection.current = selection; - - return; - } - - let addedKeys = diffSelection(selection, lastSelection.current); - let removedKeys = diffSelection(lastSelection.current, selection); - - // If adding or removing a single row from the selection, announce the name of that item. - let isReplace = state.selectionManager.selectionBehavior === 'replace'; - let messages = []; - - if ((state.selectionManager.selectedKeys.size === 1 && isReplace)) { - if (state.collection.getItem(state.selectionManager.selectedKeys.keys().next().value)) { - let currentSelectionText = getRowText(state.selectionManager.selectedKeys.keys().next().value); - if (currentSelectionText) { - messages.push(formatMessage('selectedItem', {item: currentSelectionText})); - } - } - } else if (addedKeys.size === 1 && removedKeys.size === 0) { - let addedText = getRowText(addedKeys.keys().next().value); - if (addedText) { - messages.push(formatMessage('selectedItem', {item: addedText})); - } - } else if (removedKeys.size === 1 && addedKeys.size === 0) { - if (state.collection.getItem(removedKeys.keys().next().value)) { - let removedText = getRowText(removedKeys.keys().next().value); - if (removedText) { - messages.push(formatMessage('deselectedItem', {item: removedText})); - } - } - } - - // Announce how many items are selected, except when selecting the first item. - if (state.selectionManager.selectionMode === 'multiple') { - if (messages.length === 0 || selection === 'all' || selection.size > 1 || lastSelection.current === 'all' || lastSelection.current?.size > 1) { - messages.push(selection === 'all' - ? formatMessage('selectedAll') - : formatMessage('selectedCount', {count: selection.size}) - ); - } - } - - if (messages.length > 0) { - announce(messages.join(' ')); - } - - lastSelection.current = selection; - }, [selection]); - + useGridSelectionAnnouncement({getRowText}, state); return { gridProps }; } - -function diffSelection(a: Selection, b: Selection): Set { - let res = new Set(); - if (a === 'all' || b === 'all') { - return res; - } - - for (let key of a.keys()) { - if (!b.has(key)) { - res.add(key); - } - } - - return res; -} diff --git a/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts b/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts new file mode 100644 index 00000000000..6f86fa7ed3e --- /dev/null +++ b/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2022 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {announce} from '@react-aria/live-announcer'; +import {Collection, Node, Selection} from '@react-types/shared'; +// @ts-ignore +import intlMessages from '../intl/*.json'; +import {Key, useRef} from 'react'; +import {SelectionManager} from '@react-stately/selection'; +import {useMessageFormatter} from '@react-aria/i18n'; +import {useUpdateEffect} from '@react-aria/utils'; + +interface useGridSelectionAnnouncementProps { + /** + * A function that returns the text that should be announced by assistive technology when a row is added or removed from selection. + * @default (key) => state.collection.getItem(key)?.textValue + */ + getRowText?: (key: Key) => string +} + +interface GridSelectionState { + /** A collection of items in the grid. */ + collection: Collection>, + /** A set of items that are disabled. */ + disabledKeys: Set, + /** A selection manager to read and update multiple selection state. */ + selectionManager: SelectionManager +} + +export function useGridSelectionAnnouncement(props: useGridSelectionAnnouncementProps, state: GridSelectionState) { + let { + getRowText = (key) => state.collection.getItem(key)?.textValue + } = props; + let formatMessage = useMessageFormatter(intlMessages); + + // Many screen readers do not announce when items in a grid are selected/deselected. + // We do this using an ARIA live region. + let selection = state.selectionManager.rawSelection; + let lastSelection = useRef(selection); + useUpdateEffect(() => { + if (!state.selectionManager.isFocused) { + lastSelection.current = selection; + + return; + } + + let addedKeys = diffSelection(selection, lastSelection.current); + let removedKeys = diffSelection(lastSelection.current, selection); + + // If adding or removing a single row from the selection, announce the name of that item. + let isReplace = state.selectionManager.selectionBehavior === 'replace'; + let messages = []; + + if ((state.selectionManager.selectedKeys.size === 1 && isReplace)) { + if (state.collection.getItem(state.selectionManager.selectedKeys.keys().next().value)) { + let currentSelectionText = getRowText(state.selectionManager.selectedKeys.keys().next().value); + if (currentSelectionText) { + messages.push(formatMessage('selectedItem', {item: currentSelectionText})); + } + } + } else if (addedKeys.size === 1 && removedKeys.size === 0) { + let addedText = getRowText(addedKeys.keys().next().value); + if (addedText) { + messages.push(formatMessage('selectedItem', {item: addedText})); + } + } else if (removedKeys.size === 1 && addedKeys.size === 0) { + if (state.collection.getItem(removedKeys.keys().next().value)) { + let removedText = getRowText(removedKeys.keys().next().value); + if (removedText) { + messages.push(formatMessage('deselectedItem', {item: removedText})); + } + } + } + + // Announce how many items are selected, except when selecting the first item. + if (state.selectionManager.selectionMode === 'multiple') { + if (messages.length === 0 || selection === 'all' || selection.size > 1 || lastSelection.current === 'all' || lastSelection.current?.size > 1) { + messages.push(selection === 'all' + ? formatMessage('selectedAll') + : formatMessage('selectedCount', {count: selection.size}) + ); + } + } + + if (messages.length > 0) { + announce(messages.join(' ')); + } + + lastSelection.current = selection; + }, [selection]); +} + +function diffSelection(a: Selection, b: Selection): Set { + let res = new Set(); + if (a === 'all' || b === 'all') { + return res; + } + + for (let key of a.keys()) { + if (!b.has(key)) { + res.add(key); + } + } + + return res; +} diff --git a/packages/@react-aria/list/intl/ar-AE.json b/packages/@react-aria/list/intl/ar-AE.json deleted file mode 100644 index eb78c078009..00000000000 --- a/packages/@react-aria/list/intl/ar-AE.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} غير المحدد", - "longPressToSelect": "اضغط مطولًا للدخول إلى وضع التحديد.", - "select": "تحديد", - "selectedAll": "جميع العناصر المحددة.", - "selectedCount": "{count, plural, =0 {لم يتم تحديد عناصر} one {# عنصر محدد} other {# عنصر محدد}}.", - "selectedItem": "{item} المحدد" -} diff --git a/packages/@react-aria/list/intl/bg-BG.json b/packages/@react-aria/list/intl/bg-BG.json deleted file mode 100644 index 1af7894ca44..00000000000 --- a/packages/@react-aria/list/intl/bg-BG.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} не е избран.", - "longPressToSelect": "Натиснете и задръжте за да влезете в избирателен режим.", - "select": "Изберете", - "selectedAll": "Всички елементи са избрани.", - "selectedCount": "{count, plural, =0 {Няма избрани елементи} one {# избран елемент} other {# избрани елементи}}.", - "selectedItem": "{item} избран." -} diff --git a/packages/@react-aria/list/intl/cs-CZ.json b/packages/@react-aria/list/intl/cs-CZ.json deleted file mode 100644 index 4e6d2cf58c2..00000000000 --- a/packages/@react-aria/list/intl/cs-CZ.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Položka {item} není vybrána.", - "longPressToSelect": "Dlouhým stisknutím přejdete do režimu výběru.", - "select": "Vybrat", - "selectedAll": "Vybrány všechny položky.", - "selectedCount": "{count, plural, =0 {Nevybrány žádné položky} one {Vybrána # položka} other {Vybráno # položek}}.", - "selectedItem": "Vybrána položka {item}." -} diff --git a/packages/@react-aria/list/intl/da-DK.json b/packages/@react-aria/list/intl/da-DK.json deleted file mode 100644 index 6f9c9aaaf86..00000000000 --- a/packages/@react-aria/list/intl/da-DK.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} ikke valgt.", - "longPressToSelect": "Lav et langt tryk for at aktivere valgtilstand.", - "select": "Vælg", - "selectedAll": "Alle elementer valgt.", - "selectedCount": "{count, plural, =0 {Ingen elementer valgt} one {# element valgt} other {# elementer valgt}}.", - "selectedItem": "{item} valgt." -} diff --git a/packages/@react-aria/list/intl/de-DE.json b/packages/@react-aria/list/intl/de-DE.json deleted file mode 100644 index dc735f4e174..00000000000 --- a/packages/@react-aria/list/intl/de-DE.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} nicht ausgewählt.", - "longPressToSelect": "Gedrückt halten, um Auswahlmodus zu öffnen.", - "select": "Auswählen", - "selectedAll": "Alle Elemente ausgewählt.", - "selectedCount": "{count, plural, =0 {Keine Elemente ausgewählt} one {# Element ausgewählt} other {# Elemente ausgewählt}}.", - "selectedItem": "{item} ausgewählt." -} diff --git a/packages/@react-aria/list/intl/el-GR.json b/packages/@react-aria/list/intl/el-GR.json deleted file mode 100644 index 74f9211c5a6..00000000000 --- a/packages/@react-aria/list/intl/el-GR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Δεν επιλέχθηκε το στοιχείο {item}.", - "longPressToSelect": "Πατήστε παρατεταμένα για να μπείτε σε λειτουργία επιλογής.", - "select": "Επιλογή", - "selectedAll": "Επιλέχθηκαν όλα τα στοιχεία.", - "selectedCount": "{count, plural, =0 {Δεν επιλέχθηκαν στοιχεία} one {Επιλέχθηκε # στοιχείο} other {Επιλέχθηκαν # στοιχεία}}.", - "selectedItem": "Επιλέχθηκε το στοιχείο {item}." -} diff --git a/packages/@react-aria/list/intl/en-US.json b/packages/@react-aria/list/intl/en-US.json deleted file mode 100644 index b4fcc207f28..00000000000 --- a/packages/@react-aria/list/intl/en-US.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} not selected.", - "select": "Select", - "selectedCount": "{count, plural, =0 {No items selected} one {# item selected} other {# items selected}}.", - "selectedAll": "All items selected.", - "selectedItem": "{item} selected.", - "longPressToSelect": "Long press to enter selection mode." -} diff --git a/packages/@react-aria/list/intl/es-ES.json b/packages/@react-aria/list/intl/es-ES.json deleted file mode 100644 index 9ac6b8a9730..00000000000 --- a/packages/@react-aria/list/intl/es-ES.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} no seleccionado.", - "longPressToSelect": "Mantenga pulsado para abrir el modo de selección.", - "select": "Seleccionar", - "selectedAll": "Todos los elementos seleccionados.", - "selectedCount": "{count, plural, =0 {Ningún elemento seleccionado} one {# elemento seleccionado} other {# elementos seleccionados}}.", - "selectedItem": "{item} seleccionado." -} diff --git a/packages/@react-aria/list/intl/et-EE.json b/packages/@react-aria/list/intl/et-EE.json deleted file mode 100644 index 57a2141b983..00000000000 --- a/packages/@react-aria/list/intl/et-EE.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} pole valitud.", - "longPressToSelect": "Valikurežiimi sisenemiseks vajutage pikalt.", - "select": "Vali", - "selectedAll": "Kõik üksused valitud.", - "selectedCount": "{count, plural, =0 {Üksusi pole valitud} one {# üksus valitud} other {# üksust valitud}}.", - "selectedItem": "{item} valitud." -} diff --git a/packages/@react-aria/list/intl/fi-FI.json b/packages/@react-aria/list/intl/fi-FI.json deleted file mode 100644 index 1b7375371ed..00000000000 --- a/packages/@react-aria/list/intl/fi-FI.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Kohdetta {item} ei valittu.", - "longPressToSelect": "Siirry valintatilaan painamalla pitkään.", - "select": "Valitse", - "selectedAll": "Kaikki kohteet valittu.", - "selectedCount": "{count, plural, =0 {Ei yhtään kohdetta valittu} one {# kohde valittu} other {# kohdetta valittu}}.", - "selectedItem": "{item} valittu." -} diff --git a/packages/@react-aria/list/intl/fr-FR.json b/packages/@react-aria/list/intl/fr-FR.json deleted file mode 100644 index c5db90e5f4c..00000000000 --- a/packages/@react-aria/list/intl/fr-FR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} non sélectionné.", - "longPressToSelect": "Appuyez de manière prolongée pour passer en mode de sélection.", - "select": "Sélectionner", - "selectedAll": "Tous les éléments sélectionnés.", - "selectedCount": "{count, plural, =0 {Aucun élément sélectionné} one {# élément sélectionné} other {# éléments sélectionnés}}.", - "selectedItem": "{item} sélectionné." -} diff --git a/packages/@react-aria/list/intl/he-IL.json b/packages/@react-aria/list/intl/he-IL.json deleted file mode 100644 index 7bee1b9eed9..00000000000 --- a/packages/@react-aria/list/intl/he-IL.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} לא נבחר.", - "longPressToSelect": "הקשה ארוכה לכניסה למצב בחירה.", - "select": "בחר", - "selectedAll": "כל הפריטים נבחרו.", - "selectedCount": "{count, plural, =0 {לא נבחרו פריטים} one {פריט # נבחר} other {# פריטים נבחרו}}.", - "selectedItem": "{item} נבחר." -} diff --git a/packages/@react-aria/list/intl/hr-HR.json b/packages/@react-aria/list/intl/hr-HR.json deleted file mode 100644 index b8f21698f6b..00000000000 --- a/packages/@react-aria/list/intl/hr-HR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Stavka {item} nije odabrana.", - "longPressToSelect": "Dugo pritisnite za ulazak u način odabira.", - "select": "Odaberite", - "selectedAll": "Odabrane su sve stavke.", - "selectedCount": "{count, plural, =0 {Nije odabrana nijedna stavka} one {Odabrana je # stavka} other {Odabrano je # stavki}}.", - "selectedItem": "Stavka {item} je odabrana." -} diff --git a/packages/@react-aria/list/intl/hu-HU.json b/packages/@react-aria/list/intl/hu-HU.json deleted file mode 100644 index 2b51e1b6da8..00000000000 --- a/packages/@react-aria/list/intl/hu-HU.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} nincs kijelölve.", - "longPressToSelect": "Nyomja hosszan a kijelöléshez.", - "select": "Kijelölés", - "selectedAll": "Az összes elem kijelölve.", - "selectedCount": "{count, plural, =0 {Egy elem sincs kijelölve} one {# elem kijelölve} other {# elem kijelölve}}.", - "selectedItem": "{item} kijelölve." -} diff --git a/packages/@react-aria/list/intl/it-IT.json b/packages/@react-aria/list/intl/it-IT.json deleted file mode 100644 index 1d402d0c675..00000000000 --- a/packages/@react-aria/list/intl/it-IT.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} non selezionato.", - "longPressToSelect": "Premi a lungo per passare alla modalità di selezione.", - "select": "Seleziona", - "selectedAll": "Tutti gli elementi selezionati.", - "selectedCount": "{count, plural, =0 {Nessun elemento selezionato} one {# elemento selezionato} other {# elementi selezionati}}.", - "selectedItem": "{item} selezionato." -} diff --git a/packages/@react-aria/list/intl/ja-JP.json b/packages/@react-aria/list/intl/ja-JP.json deleted file mode 100644 index 1e5f8653c32..00000000000 --- a/packages/@react-aria/list/intl/ja-JP.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} が選択されていません。", - "longPressToSelect": "長押しして選択モードを開きます。", - "select": "選択", - "selectedAll": "すべての項目を選択しました。", - "selectedCount": "{count, plural, =0 {項目が選択されていません} one {# 項目を選択しました} other {# 項目を選択しました}}。", - "selectedItem": "{item} を選択しました。" -} diff --git a/packages/@react-aria/list/intl/ko-KR.json b/packages/@react-aria/list/intl/ko-KR.json deleted file mode 100644 index 96143803671..00000000000 --- a/packages/@react-aria/list/intl/ko-KR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item}이(가) 선택되지 않았습니다.", - "longPressToSelect": "선택 모드로 들어가려면 길게 누르십시오.", - "select": "선택", - "selectedAll": "모든 항목이 선택되었습니다.", - "selectedCount": "{count, plural, =0 {선택된 항목이 없습니다} one {#개 항목이 선택되었습니다} other {#개 항목이 선택되었습니다}}.", - "selectedItem": "{item}이(가) 선택되었습니다." -} diff --git a/packages/@react-aria/list/intl/lt-LT.json b/packages/@react-aria/list/intl/lt-LT.json deleted file mode 100644 index 59bde223e72..00000000000 --- a/packages/@react-aria/list/intl/lt-LT.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} nepasirinkta.", - "longPressToSelect": "Norėdami įjungti pasirinkimo režimą, paspauskite ir palaikykite.", - "select": "Pasirinkti", - "selectedAll": "Pasirinkti visi elementai.", - "selectedCount": "{count, plural, =0 {Nepasirinktas nė vienas elementas} one {Pasirinktas # elementas} other {Pasirinkta elementų: #}}.", - "selectedItem": "Pasirinkta: {item}." -} diff --git a/packages/@react-aria/list/intl/lv-LV.json b/packages/@react-aria/list/intl/lv-LV.json deleted file mode 100644 index f1282ade995..00000000000 --- a/packages/@react-aria/list/intl/lv-LV.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Vienums {item} nav atlasīts.", - "longPressToSelect": "Ilgi turiet nospiestu. lai ieslēgtu atlases režīmu.", - "select": "Atlasīt", - "selectedAll": "Atlasīti visi vienumi.", - "selectedCount": "{count, plural, =0 {Nav atlasīts neviens vienums} one {Atlasīto vienumu skaits: #} other {Atlasīto vienumu skaits: #}}.", - "selectedItem": "Atlasīts vienums {item}." -} diff --git a/packages/@react-aria/list/intl/nb-NO.json b/packages/@react-aria/list/intl/nb-NO.json deleted file mode 100644 index 52d2c4fe603..00000000000 --- a/packages/@react-aria/list/intl/nb-NO.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} er ikke valgt.", - "longPressToSelect": "Bruk et langt trykk for å gå inn i valgmodus.", - "select": "Velg", - "selectedAll": "Alle elementer er valgt.", - "selectedCount": "{count, plural, =0 {Ingen elementer er valgt} one {# element er valgt} other {# elementer er valgt}}.", - "selectedItem": "{item} er valgt." -} diff --git a/packages/@react-aria/list/intl/nl-NL.json b/packages/@react-aria/list/intl/nl-NL.json deleted file mode 100644 index 61d80eb0725..00000000000 --- a/packages/@react-aria/list/intl/nl-NL.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} niet geselecteerd.", - "longPressToSelect": "Druk lang om de selectiemodus te openen.", - "select": "Selecteren", - "selectedAll": "Alle items geselecteerd.", - "selectedCount": "{count, plural, =0 {Geen items geselecteerd} one {# item geselecteerd} other {# items geselecteerd}}.", - "selectedItem": "{item} geselecteerd." -} diff --git a/packages/@react-aria/list/intl/pl-PL.json b/packages/@react-aria/list/intl/pl-PL.json deleted file mode 100644 index cef60105f09..00000000000 --- a/packages/@react-aria/list/intl/pl-PL.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Nie zaznaczono {item}.", - "longPressToSelect": "Naciśnij i przytrzymaj, aby wejść do trybu wyboru.", - "select": "Zaznacz", - "selectedAll": "Wszystkie zaznaczone elementy.", - "selectedCount": "{count, plural, =0 {Nie zaznaczono żadnych elementów} one {# zaznaczony element} other {# zaznaczonych elementów}}.", - "selectedItem": "Zaznaczono {item}." -} diff --git a/packages/@react-aria/list/intl/pt-BR.json b/packages/@react-aria/list/intl/pt-BR.json deleted file mode 100644 index 87a2a14508a..00000000000 --- a/packages/@react-aria/list/intl/pt-BR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} não selecionado.", - "longPressToSelect": "Mantenha pressionado para entrar no modo de seleção.", - "select": "Selecionar", - "selectedAll": "Todos os itens selecionados.", - "selectedCount": "{count, plural, =0 {Nenhum item selecionado} one {# item selecionado} other {# itens selecionados}}.", - "selectedItem": "{item} selecionado." -} diff --git a/packages/@react-aria/list/intl/pt-PT.json b/packages/@react-aria/list/intl/pt-PT.json deleted file mode 100644 index e0af18f69f5..00000000000 --- a/packages/@react-aria/list/intl/pt-PT.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} não selecionado.", - "longPressToSelect": "Prima continuamente para entrar no modo de seleção.", - "select": "Selecionar", - "selectedAll": "Todos os itens selecionados.", - "selectedCount": "{count, plural, =0 {Nenhum item selecionado} one {# item selecionado} other {# itens selecionados}}.", - "selectedItem": "{item} selecionado." -} diff --git a/packages/@react-aria/list/intl/ro-RO.json b/packages/@react-aria/list/intl/ro-RO.json deleted file mode 100644 index c2c749e47f1..00000000000 --- a/packages/@react-aria/list/intl/ro-RO.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} neselectat.", - "longPressToSelect": "Apăsați lung pentru a intra în modul de selectare.", - "select": "Selectare", - "selectedAll": "Toate elementele selectate.", - "selectedCount": "{count, plural, =0 {Niciun element selectat} one {# element selectat} other {# elemente selectate}}.", - "selectedItem": "{item} selectat." -} diff --git a/packages/@react-aria/list/intl/ru-RU.json b/packages/@react-aria/list/intl/ru-RU.json deleted file mode 100644 index 9e805fe6346..00000000000 --- a/packages/@react-aria/list/intl/ru-RU.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} не выбрано.", - "longPressToSelect": "Нажмите и удерживайте для входа в режим выбора.", - "select": "Выбрать", - "selectedAll": "Выбраны все элементы.", - "selectedCount": "{count, plural, =0 {Нет выбранных элементов} one {# элемент выбран} other {# элементов выбрано}}.", - "selectedItem": "{item} выбрано." -} diff --git a/packages/@react-aria/list/intl/sk-SK.json b/packages/@react-aria/list/intl/sk-SK.json deleted file mode 100644 index 91e6c7b6fd6..00000000000 --- a/packages/@react-aria/list/intl/sk-SK.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Nevybraté položky: {item}.", - "longPressToSelect": "Dlhším stlačením prejdite do režimu výberu.", - "select": "Vybrať", - "selectedAll": "Všetky vybraté položky.", - "selectedCount": "{count, plural, =0 {Žiadne vybraté položky} one {# vybratá položka} other {Počet vybratých položiek:#}}.", - "selectedItem": "Vybraté položky: {item}." -} diff --git a/packages/@react-aria/list/intl/sl-SI.json b/packages/@react-aria/list/intl/sl-SI.json deleted file mode 100644 index dde6830c824..00000000000 --- a/packages/@react-aria/list/intl/sl-SI.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "Element {item} ni izbran.", - "longPressToSelect": "Za izbirni način pritisnite in dlje časa držite.", - "select": "Izberite", - "selectedAll": "Vsi elementi so izbrani.", - "selectedCount": "{count, plural, =0 {Noben element ni izbran} one {# element je izbran} other {# elementov je izbranih}}.", - "selectedItem": "Element {item} je izbran." -} diff --git a/packages/@react-aria/list/intl/sr-SP.json b/packages/@react-aria/list/intl/sr-SP.json deleted file mode 100644 index b2300ab5fb6..00000000000 --- a/packages/@react-aria/list/intl/sr-SP.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} nije izabrano.", - "longPressToSelect": "Dugo pritisnite za ulazak u režim biranja.", - "select": "Izaberite", - "selectedAll": "Izabrane su sve stavke.", - "selectedCount": "{count, plural, =0 {Nije izabrana nijedna stavka} one {Izabrana je # stavka} other {Izabrano je # stavki}}.", - "selectedItem": "{item} je izabrano." -} diff --git a/packages/@react-aria/list/intl/sv-SE.json b/packages/@react-aria/list/intl/sv-SE.json deleted file mode 100644 index b4b58379beb..00000000000 --- a/packages/@react-aria/list/intl/sv-SE.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} ej markerat.", - "longPressToSelect": "Tryck länge när du vill öppna väljarläge.", - "select": "Markera", - "selectedAll": "Alla markerade objekt.", - "selectedCount": "{count, plural, =0 {Inga markerade objekt} one {# markerat objekt} other {# markerade objekt}}.", - "selectedItem": "{item} markerat." -} diff --git a/packages/@react-aria/list/intl/tr-TR.json b/packages/@react-aria/list/intl/tr-TR.json deleted file mode 100644 index 0c441b800c8..00000000000 --- a/packages/@react-aria/list/intl/tr-TR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} seçilmedi.", - "longPressToSelect": "Seçim moduna girmek için uzun basın.", - "select": "Seç", - "selectedAll": "Tüm ögeler seçildi.", - "selectedCount": "{count, plural, =0 {Hiçbir öge seçilmedi} one {# öge seçildi} other {# öge seçildi}}.", - "selectedItem": "{item} seçildi." -} diff --git a/packages/@react-aria/list/intl/uk-UA.json b/packages/@react-aria/list/intl/uk-UA.json deleted file mode 100644 index f113d5407bf..00000000000 --- a/packages/@react-aria/list/intl/uk-UA.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "{item} не вибрано.", - "longPressToSelect": "Виконайте довге натиснення, щоб перейти в режим вибору.", - "select": "Вибрати", - "selectedAll": "Усі елементи вибрано.", - "selectedCount": "{count, plural, =0 {Жодних елементів не вибрано} one {# елемент вибрано} other {Вибрано елементів: #}}.", - "selectedItem": "{item} вибрано." -} diff --git a/packages/@react-aria/list/intl/zh-CN.json b/packages/@react-aria/list/intl/zh-CN.json deleted file mode 100644 index 1b6644bd680..00000000000 --- a/packages/@react-aria/list/intl/zh-CN.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "未选择 {item}。", - "longPressToSelect": "长按以进入选择模式。", - "select": "选择", - "selectedAll": "已选择所有项目。", - "selectedCount": "{count, plural, =0 {未选择项目} one {已选择 # 个项目} other {已选择 # 个项目}}。", - "selectedItem": "已选择 {item}。" -} diff --git a/packages/@react-aria/list/intl/zh-TW.json b/packages/@react-aria/list/intl/zh-TW.json deleted file mode 100644 index bfb55511765..00000000000 --- a/packages/@react-aria/list/intl/zh-TW.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "deselectedItem": "未選取「{item}」。", - "longPressToSelect": "長按以進入選擇模式。", - "select": "選取", - "selectedAll": "已選取所有項目。", - "selectedCount": "{count, plural, =0 {未選取任何項目} one {已選取 # 個項目} other {已選取 # 個項目}}。", - "selectedItem": "已選取「{item}」。" -} diff --git a/packages/@react-aria/list/src/useList.ts b/packages/@react-aria/list/src/useList.ts index 41ad41aa3f9..7a9618a2676 100644 --- a/packages/@react-aria/list/src/useList.ts +++ b/packages/@react-aria/list/src/useList.ts @@ -10,18 +10,13 @@ * governing permissions and limitations under the License. */ -import {announce} from '@react-aria/live-announcer'; import {AriaListProps} from '@react-types/list'; -import {filterDOMProps, mergeProps, useId, useUpdateEffect} from '@react-aria/utils'; -import {HTMLAttributes, Key, RefObject, useMemo, useRef} from 'react'; -// @ts-ignore -import intlMessages from '../intl/*.json'; -import {KeyboardDelegate, Selection} from '@react-types/shared'; +import {filterDOMProps, mergeProps, useId} from '@react-aria/utils'; +import {HTMLAttributes, Key, RefObject} from 'react'; +import {KeyboardDelegate} from '@react-types/shared'; import {listMap} from './utils'; import {ListState} from '@react-stately/list'; -import {useDescription} from '@react-aria/utils'; -import {useInteractionModality} from '@react-aria/interactions'; -import {useMessageFormatter} from '@react-aria/i18n'; +import {useGridSelectionAnnouncement, useHighlightSelectionDescription} from '@react-aria/grid'; import {useSelectableList} from '@react-aria/selection'; export interface AriaListOptions extends Omit, 'children'> { @@ -52,14 +47,12 @@ export interface ListViewAria { * @param ref - The ref attached to the list element. */ export function useList(props: AriaListOptions, state: ListState, ref: RefObject): ListViewAria { - // Rough copy of useGrid, but modifications + things removed for ListView specific case let { isVirtualized, keyboardDelegate, - getRowText = (key) => state.collection.getItem(key)?.textValue, + getRowText, onAction } = props; - let formatMessage = useMessageFormatter(intlMessages); if (!props['aria-label'] && !props['aria-labelledby']) { console.warn('An aria-label or aria-labelledby prop is required for accessibility.'); @@ -78,25 +71,10 @@ export function useList(props: AriaListOptions, state: ListState, ref: let id = useId(); listMap.set(state, {id, onAction}); - // This is useHighlightSelectionDescription copy pasted, it isn't exposed by react-aria/grid. - let modality = useInteractionModality(); - // null is the default if the user hasn't interacted with the list at all yet or the rest of the page - let shouldLongPress = (modality === 'pointer' || modality === 'virtual' || modality == null) - && typeof window !== 'undefined' && 'ontouchstart' in window; - - let interactionDescription = useMemo(() => { - let selectionMode = state.selectionManager.selectionMode; - let selectionBehavior = state.selectionManager.selectionBehavior; - - let message = undefined; - if (shouldLongPress) { - message = formatMessage('longPressToSelect'); - } - - return selectionBehavior === 'replace' && selectionMode !== 'none' && onAction ? message : undefined; - }, [state.selectionManager.selectionMode, state.selectionManager.selectionBehavior, onAction, formatMessage, shouldLongPress]); - - let descriptionProps = useDescription(interactionDescription); + let descriptionProps = useHighlightSelectionDescription({ + selectionManager: state.selectionManager, + hasItemActions: !!onAction + }); let domProps = filterDOMProps(props, {labelable: true}); let gridProps: HTMLAttributes = mergeProps( @@ -115,78 +93,9 @@ export function useList(props: AriaListOptions, state: ListState, ref: gridProps['aria-colcount'] = 1; } - // Many screen readers do not announce when items in a grid are selected/deselected. - // We do this using an ARIA live region. - let selection = state.selectionManager.rawSelection; - let lastSelection = useRef(selection); - useUpdateEffect(() => { - if (!state.selectionManager.isFocused) { - lastSelection.current = selection; - - return; - } - - let addedKeys = diffSelection(selection, lastSelection.current); - let removedKeys = diffSelection(lastSelection.current, selection); - - // If adding or removing a single row from the selection, announce the name of that item. - let isReplace = state.selectionManager.selectionBehavior === 'replace'; - let messages = []; - - if ((state.selectionManager.selectedKeys.size === 1 && isReplace)) { - if (state.collection.getItem(state.selectionManager.selectedKeys.keys().next().value)) { - let currentSelectionText = getRowText(state.selectionManager.selectedKeys.keys().next().value); - if (currentSelectionText) { - messages.push(formatMessage('selectedItem', {item: currentSelectionText})); - } - } - } else if (addedKeys.size === 1 && removedKeys.size === 0) { - let addedText = getRowText(addedKeys.keys().next().value); - if (addedText) { - messages.push(formatMessage('selectedItem', {item: addedText})); - } - } else if (removedKeys.size === 1 && addedKeys.size === 0) { - if (state.collection.getItem(removedKeys.keys().next().value)) { - let removedText = getRowText(removedKeys.keys().next().value); - if (removedText) { - messages.push(formatMessage('deselectedItem', {item: removedText})); - } - } - } - - // Announce how many items are selected, except when selecting the first item. - if (state.selectionManager.selectionMode === 'multiple') { - if (messages.length === 0 || selection === 'all' || selection.size > 1 || lastSelection.current === 'all' || lastSelection.current?.size > 1) { - messages.push(selection === 'all' - ? formatMessage('selectedAll') - : formatMessage('selectedCount', {count: selection.size}) - ); - } - } - - if (messages.length > 0) { - announce(messages.join(' ')); - } - - lastSelection.current = selection; - }, [selection]); + useGridSelectionAnnouncement({getRowText}, state); return { gridProps }; } - -function diffSelection(a: Selection, b: Selection): Set { - let res = new Set(); - if (a === 'all' || b === 'all') { - return res; - } - - for (let key of a.keys()) { - if (!b.has(key)) { - res.add(key); - } - } - - return res; -} diff --git a/packages/@react-aria/list/src/useListItem.ts b/packages/@react-aria/list/src/useListItem.ts index 3230b8f70f1..4ca73905f14 100644 --- a/packages/@react-aria/list/src/useListItem.ts +++ b/packages/@react-aria/list/src/useListItem.ts @@ -26,9 +26,7 @@ export interface AriaListItemOptions { /** Whether the list row is contained in a virtual scroller. */ isVirtualized?: boolean, /** Whether selection should occur on press up instead of press down. */ - shouldSelectOnPressUp?: boolean, - /** Whether the list item is disabled. */ - isDisabled?: boolean + shouldSelectOnPressUp?: boolean } export interface ListItemAria { @@ -51,8 +49,7 @@ export function useListItem(props: AriaListItemOptions, state: ListState, let { node, isVirtualized, - shouldSelectOnPressUp, - isDisabled + shouldSelectOnPressUp } = props; let {direction} = useLocale(); @@ -72,7 +69,6 @@ export function useListItem(props: AriaListItemOptions, state: ListState, isVirtualized, shouldSelectOnPressUp, onAction: onAction ? () => onAction(node.key) : undefined, - isDisabled, focus }); diff --git a/packages/@react-spectrum/list/src/ListViewItem.tsx b/packages/@react-spectrum/list/src/ListViewItem.tsx index 5e567a5d590..b2e8d988b39 100644 --- a/packages/@react-spectrum/list/src/ListViewItem.tsx +++ b/packages/@react-spectrum/list/src/ListViewItem.tsx @@ -58,8 +58,7 @@ export function ListViewItem(props: ListViewItemProps) { let {rowProps, gridCellProps, isPressed} = useListItem({ node: item, isVirtualized: true, - shouldSelectOnPressUp: isListDraggable, - isDisabled + shouldSelectOnPressUp: isListDraggable }, state, rowRef); let {checkboxProps} = useListSelectionCheckbox({key: item.key}, state); From e6f7248735c18a8914bf1bd5ed983d32fe423a2f Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Fri, 13 May 2022 13:37:53 -0700 Subject: [PATCH 2/3] cleanup --- packages/@react-aria/list/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@react-aria/list/package.json b/packages/@react-aria/list/package.json index c64deab732a..921e102c98b 100644 --- a/packages/@react-aria/list/package.json +++ b/packages/@react-aria/list/package.json @@ -22,7 +22,6 @@ "@react-aria/grid": "^3.2.5", "@react-aria/i18n": "^3.3.8", "@react-aria/interactions": "^3.8.3", - "@react-aria/live-announcer": "^3.0.5", "@react-aria/selection": "^3.8.1", "@react-aria/utils": "^3.11.3", "@react-stately/list": "^3.4.4", From 03fda44f9e98bdee87fcc26ecae5bb2ae8fd61c1 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Fri, 13 May 2022 14:05:32 -0700 Subject: [PATCH 3/3] proper interface capitalization --- packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts b/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts index 6f86fa7ed3e..fc28c2dc9c8 100644 --- a/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts +++ b/packages/@react-aria/grid/src/useGridSelectionAnnouncement.ts @@ -19,7 +19,7 @@ import {SelectionManager} from '@react-stately/selection'; import {useMessageFormatter} from '@react-aria/i18n'; import {useUpdateEffect} from '@react-aria/utils'; -interface useGridSelectionAnnouncementProps { +interface UseGridSelectionAnnouncementProps { /** * A function that returns the text that should be announced by assistive technology when a row is added or removed from selection. * @default (key) => state.collection.getItem(key)?.textValue @@ -36,7 +36,7 @@ interface GridSelectionState { selectionManager: SelectionManager } -export function useGridSelectionAnnouncement(props: useGridSelectionAnnouncementProps, state: GridSelectionState) { +export function useGridSelectionAnnouncement(props: UseGridSelectionAnnouncementProps, state: GridSelectionState) { let { getRowText = (key) => state.collection.getItem(key)?.textValue } = props;