diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx index 42c4cc56d45a2..031a0b7e85fd7 100644 --- a/src/components/Picker/BasePicker.tsx +++ b/src/components/Picker/BasePicker.tsx @@ -16,6 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {isMobile} from '@libs/Browser'; import getOperatingSystem from '@libs/getOperatingSystem'; import CONST from '@src/CONST'; +import getAccessibilityLabelConfig from './getAccessibilityLabelConfig'; import type {BasePickerProps} from './types'; type IconToRender = () => ReactElement; @@ -46,7 +47,7 @@ function BasePicker({ const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); - + const {shouldAnnounceSelectedLabel, shouldUseCustomAccessibilityLabel} = getAccessibilityLabelConfig(); const [isHighlighted, setIsHighlighted] = useState(false); // reference to the root View @@ -166,6 +167,19 @@ function BasePicker({ // Disable Tab focus on mobile to prevent soft keyboard navigation jumping to picker (#25759) const pickerTabIndex = isMobile() ? -1 : 0; + const selectedItem = items.find((item) => item.value === value); + const selectedLabel = selectedItem?.label ?? ''; + const defaultAccessibilityLabel = accessibilityLabel ?? label ?? selectedLabel; + const enhancedAccessibilityLabel = useMemo(() => { + if (!defaultAccessibilityLabel) { + return selectedLabel || ''; + } + if (selectedLabel) { + return `${defaultAccessibilityLabel}${shouldAnnounceSelectedLabel ? `, ${selectedLabel}` : ''}, ${translate(isHighlighted ? 'common.expanded' : 'common.collapsed')}`; + } + return defaultAccessibilityLabel; + }, [defaultAccessibilityLabel, selectedLabel, shouldAnnounceSelectedLabel, translate, isHighlighted]); + if (isDisabled && shouldShowOnlyTextWhenDisabled) { return ( @@ -183,6 +197,8 @@ function BasePicker({ ); } + const actualAccessibilityLabel = shouldUseCustomAccessibilityLabel ? enhancedAccessibilityLabel : defaultAccessibilityLabel; + return ( <> ({ onClose={disableHighlight} textInputProps={{ allowFontScaling: false, + accessibilityRole: CONST.ROLE.COMBOBOX, + accessibilityLabel: actualAccessibilityLabel, importantForAccessibility: 'no-hide-descendants', }} touchableDoneProps={{ @@ -220,8 +238,7 @@ function BasePicker({ touchableWrapperProps={{ accessible: true, accessibilityRole: CONST.ROLE.COMBOBOX, - accessibilityLabel, - accessibilityState: {disabled: isDisabled, expanded: isHighlighted}, + accessibilityLabel: actualAccessibilityLabel, }} pickerProps={{ ref: picker, @@ -231,7 +248,8 @@ function BasePicker({ disableHighlight(); onBlur(); }, - accessibilityLabel, + accessibilityLabel: actualAccessibilityLabel, + accessibilityRole: CONST.ROLE.COMBOBOX, ...additionalPickerEvents(enableHighlight, (inputValue, index) => { onValueChange(inputValue, index); disableHighlight(); diff --git a/src/components/Picker/getAccessibilityLabelConfig/index.native.ts b/src/components/Picker/getAccessibilityLabelConfig/index.native.ts new file mode 100644 index 0000000000000..3ec9209934557 --- /dev/null +++ b/src/components/Picker/getAccessibilityLabelConfig/index.native.ts @@ -0,0 +1,8 @@ +const getAccessibilityLabelConfig = () => { + return { + shouldAnnounceSelectedLabel: true, + shouldUseCustomAccessibilityLabel: true, + }; +}; + +export default getAccessibilityLabelConfig; diff --git a/src/components/Picker/getAccessibilityLabelConfig/index.ts b/src/components/Picker/getAccessibilityLabelConfig/index.ts new file mode 100644 index 0000000000000..b76e9300425fa --- /dev/null +++ b/src/components/Picker/getAccessibilityLabelConfig/index.ts @@ -0,0 +1,12 @@ +import {isMobile} from '@libs/Browser'; + +const getAccessibilityLabelConfig = () => { + // Mobile Web: the picker uses the `value` attribute, which VoiceOver already announces. + // Avoid duplicating the selected label in the accessibility label. + return { + shouldAnnounceSelectedLabel: false, + shouldUseCustomAccessibilityLabel: isMobile(), + }; +}; + +export default getAccessibilityLabelConfig; diff --git a/src/languages/de.ts b/src/languages/de.ts index 56eef0b683adf..758eea4ce9632 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -452,6 +452,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Als CSV herunterladen', print: 'Drucken', help: 'Hilfe', + collapsed: 'Eingeklappt', + expanded: 'Ausgeklappt', expenseReport: 'Spesenabrechnung', expenseReports: 'Spesenabrechnungen', rateOutOfPolicy: 'Satz außerhalb der Richtlinie', diff --git a/src/languages/en.ts b/src/languages/en.ts index 9e1b3c985c71b..65a0a5d54d858 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -471,6 +471,8 @@ const translations = { downloadAsCSV: 'Download as CSV', print: 'Print', help: 'Help', + collapsed: 'Collapsed', + expanded: 'Expanded', expenseReport: 'Expense Report', expenseReports: 'Expense Reports', // @context Rate as a noun, not a verb diff --git a/src/languages/es.ts b/src/languages/es.ts index 28c768351a01a..c3c6216df528d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -357,6 +357,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Descargar como CSV', print: 'Imprimir', help: 'Ayuda', + collapsed: 'Contraído', + expanded: 'Expandido', expenseReport: 'Informe de Gastos', expenseReports: 'Informes de Gastos', rateOutOfPolicy: 'Tasa fuera de póliza', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index e3141cb9aa261..d54af35a56583 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -452,6 +452,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Télécharger au format CSV', print: 'Imprimer', help: 'Aide', + collapsed: 'Réduit', + expanded: 'Développé', expenseReport: 'Note de frais', expenseReports: 'Notes de frais', rateOutOfPolicy: 'Taux hors politique', diff --git a/src/languages/it.ts b/src/languages/it.ts index 2814a3f55957f..a4bb66dacc91b 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -452,6 +452,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Scarica come CSV', print: 'Stampa', help: 'Aiuto', + collapsed: 'Comprresso', + expanded: 'Espanso', expenseReport: 'Nota spese', expenseReports: 'Note spese', rateOutOfPolicy: 'Tariffa fuori policy', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 1fb0fb5313e16..28c64cdb68e69 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -451,6 +451,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'CSVとしてダウンロード', print: '印刷', help: 'ヘルプ', + collapsed: '折りたたみ', + expanded: '展開', expenseReport: '経費精算書', expenseReports: '経費レポート', rateOutOfPolicy: 'ポリシー対象外のレート', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 5bee400ffe6f5..b94131ce520d4 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -451,6 +451,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Downloaden als CSV', print: 'Afdrukken', help: 'Help', + collapsed: 'Ingeklapt', + expanded: 'Uitgeklapt', expenseReport: 'Declaratie', expenseReports: 'Declaraties', rateOutOfPolicy: 'Tarief buiten beleid', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 3bea0ab6b5ffd..5f611415045fb 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -451,6 +451,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Pobierz jako CSV', print: 'Drukuj', help: 'Pomoc', + collapsed: 'Zwinięte', + expanded: 'Rozwinięte', expenseReport: 'Raport wydatków', expenseReports: 'Raporty wydatków', rateOutOfPolicy: 'Stawka poza zasadami', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index e2c3fb53a3f5f..472f8c007e24f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -451,6 +451,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: 'Baixar como CSV', print: 'Imprimir', help: 'Ajuda', + collapsed: 'Recolhido', + expanded: 'Expandido', expenseReport: 'Relatório de despesas', expenseReports: 'Relatórios de despesas', rateOutOfPolicy: 'Taxa fora da política', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 96415f4fa273d..516b75ea3758f 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -451,6 +451,8 @@ const translations: TranslationDeepObject = { downloadAsCSV: '下载为 CSV', print: '打印', help: '帮助', + collapsed: '已折叠', + expanded: '已展开', expenseReport: '报销报告', expenseReports: '报销报告', rateOutOfPolicy: '超出政策的费率',