• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JedWatson / react-select / 14b9164f-7ebc-4af3-a108-73c5186d143a

pending completion
14b9164f-7ebc-4af3-a108-73c5186d143a

Pull #6045

circleci

jarodsim
feat: add changeset
Pull Request #6045: fix: allow loadOptions('') to be called when input is cleared

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.77
/packages/react-select/src/accessibility/index.ts
1
import type { AriaAttributes } from 'react';
2
import {
3
  ActionMeta,
4
  GroupBase,
5
  InitialInputFocusedActionMeta,
6
  OnChangeValue,
7
  Options,
8
  OptionsOrGroups,
9
} from '../types';
10

11
export type OptionContext = 'menu' | 'value';
12

13
export type GuidanceContext = 'menu' | 'input' | 'value';
14

15
export type AriaSelection<Option, IsMulti extends boolean> =
16
  | InitialInputFocusedActionMeta<Option, IsMulti>
17
  | (ActionMeta<Option> & {
18
      value: OnChangeValue<Option, IsMulti>;
19
      option?: Option;
20
      options?: Options<Option>;
21
    });
22

23
export interface AriaGuidanceProps {
24
  /** String value of selectProp aria-label */
25
  'aria-label': AriaAttributes['aria-label'];
26
  /** String indicating user's current context and available keyboard interactivity */
27
  context: GuidanceContext;
28
  /** Boolean value of selectProp isSearchable */
29
  isSearchable: boolean;
30
  /** Boolean value of selectProp isMulti */
31
  isMulti: boolean;
32
  /** Boolean value of selectProp isDisabled */
33
  isDisabled: boolean | null;
34
  /** Boolean value of selectProp tabSelectsValue */
35
  tabSelectsValue: boolean;
36
  /** Boolean value indicating if user focused the input for the first time */
37
  isInitialFocus: boolean;
38
}
39

40
export type AriaOnChangeProps<Option, IsMulti extends boolean> = AriaSelection<
41
  Option,
42
  IsMulti
43
> & {
44
  /** String derived label from selected or removed option/value */
45
  label: string;
46
  /** Array of labels derived from multiple selected or cleared options */
47
  labels: string[];
48
  /** Boolean indicating if the selected menu option is disabled */
49
  isDisabled: boolean | null;
50
};
51

52
export interface AriaOnFilterProps {
53
  /** String indicating current inputValue of the input */
54
  inputValue: string;
55
  /** String derived from selectProp screenReaderStatus */
56
  resultsMessage: string;
57
}
58

59
export interface AriaOnFocusProps<Option, Group extends GroupBase<Option>> {
60
  /** String indicating whether the option was focused in the menu or as (multi-) value */
61
  context: OptionContext;
62
  /** Option that is being focused */
63
  focused: Option;
64
  /** Boolean indicating whether focused menu option has been disabled */
65
  isDisabled: boolean;
66
  /** Boolean indicating whether focused menu option is an already selected option */
67
  isSelected: boolean;
68
  /** String derived label from focused option/value */
69
  label: string;
70
  /** Options provided as props to Select used to determine indexing */
71
  options: OptionsOrGroups<Option, Group>;
72
  /** selected option(s) of the Select */
73
  selectValue: Options<Option>;
74
  /** Boolean indicating whether user uses Apple device */
75
  isAppleDevice: boolean;
76
}
77

78
export type AriaGuidance = (props: AriaGuidanceProps) => string;
79
export type AriaOnChange<Option, IsMulti extends boolean> = (
80
  props: AriaOnChangeProps<Option, IsMulti>
81
) => string;
82
export type AriaOnFilter = (props: AriaOnFilterProps) => string;
83
export type AriaOnFocus<
84
  Option,
85
  Group extends GroupBase<Option> = GroupBase<Option>
86
> = (props: AriaOnFocusProps<Option, Group>) => string;
87

88
export interface AriaLiveMessages<
89
  Option,
90
  IsMulti extends boolean,
91
  Group extends GroupBase<Option>
92
> {
93
  /** Guidance message used to convey component state and specific keyboard interactivity */
94
  guidance?: (props: AriaGuidanceProps) => string;
95
  /** OnChange message used to convey changes to value but also called when user selects disabled option */
96
  onChange?: (props: AriaOnChangeProps<Option, IsMulti>) => string;
97
  /** OnFilter message used to convey information about filtered results displayed in the menu */
98
  onFilter?: (props: AriaOnFilterProps) => string;
99
  /** OnFocus message used to convey information about the currently focused option or value */
100
  onFocus?: (props: AriaOnFocusProps<Option, Group>) => string;
101
}
102

103
export const defaultAriaLiveMessages = {
5✔
104
  guidance: (props: AriaGuidanceProps) => {
105
    const { isSearchable, isMulti, tabSelectsValue, context, isInitialFocus } =
106
      props;
783✔
107
    switch (context) {
783!
108
      case 'menu':
109
        return `Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu${
571✔
110
          tabSelectsValue
571✔
111
            ? ', press Tab to select the option and exit the menu'
112
            : ''
113
        }.`;
114
      case 'input':
115
        return isInitialFocus
210✔
116
          ? `${props['aria-label'] || 'Select'} is focused ${
80✔
117
              isSearchable ? ',type to refine list' : ''
40!
118
            }, press Down to open the menu, ${
119
              isMulti ? ' press left to focus selected values' : ''
40✔
120
            }`
121
          : '';
122
      case 'value':
123
        return 'Use left and right to toggle between focused values, press Backspace to remove the currently focused value';
2✔
124
      default:
125
        return '';
×
126
    }
127
  },
128

129
  onChange: <Option, IsMulti extends boolean>(
130
    props: AriaOnChangeProps<Option, IsMulti>
131
  ) => {
132
    const { action, label = '', labels, isDisabled } = props;
116!
133
    switch (action) {
116!
134
      case 'deselect-option':
135
      case 'pop-value':
136
      case 'remove-value':
137
        return `option ${label}, deselected.`;
8✔
138
      case 'clear':
139
        return 'All selected options have been cleared.';
5✔
140
      case 'initial-input-focus':
141
        return `option${labels.length > 1 ? 's' : ''} ${labels.join(
57✔
142
          ','
143
        )}, selected.`;
144
      case 'select-option':
145
        return isDisabled
46✔
146
          ? `option ${label} is disabled. Select another option.`
147
          : `option ${label}, selected.`;
148
      default:
149
        return '';
×
150
    }
151
  },
152

153
  onFocus: <Option, Group extends GroupBase<Option>>(
154
    props: AriaOnFocusProps<Option, Group>
155
  ) => {
156
    const {
157
      context,
158
      focused,
159
      options,
160
      label = '',
×
161
      selectValue,
162
      isDisabled,
163
      isSelected,
164
      isAppleDevice,
165
    } = props;
465✔
166

167
    const getArrayIndex = (arr: OptionsOrGroups<Option, Group>, item: Option) =>
465✔
168
      arr && arr.length ? `${arr.indexOf(item) + 1} of ${arr.length}` : '';
2!
169

170
    if (context === 'value' && selectValue) {
465✔
171
      return `value ${label} focused, ${getArrayIndex(selectValue, focused)}.`;
2✔
172
    }
173

174
    if (context === 'menu' && isAppleDevice) {
463!
175
      const disabled = isDisabled ? ' disabled' : '';
×
176
      const status = `${isSelected ? ' selected' : ''}${disabled}`;
×
177
      return `${label}${status}, ${getArrayIndex(options, focused)}.`;
×
178
    }
179
    return '';
463✔
180
  },
181

182
  onFilter: (props: AriaOnFilterProps) => {
183
    const { inputValue, resultsMessage } = props;
586✔
184
    return `${resultsMessage}${
586✔
185
      inputValue ? ' for search term ' + inputValue : ''
586✔
186
    }.`;
187
  },
188
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc