UNPKG

16.6 kBTypeScriptView Raw
1import * as React from 'react';
2
3import { Option } from './filters';
4import { InstructionsContext, ValueEventContext } from './accessibility/index';
5
6import { classNames, noop, scrollIntoView } from './utils';
7
8import { formatGroupLabel, getOptionLabel, getOptionValue } from './builtins';
9
10import { PlaceholderOrValue, SelectComponents, SelectComponentsConfig } from './components/index';
11import { StylesConfig } from './styles';
12import { ThemeConfig } from './theme';
13import {
14 ActionMeta,
15 FocusDirection,
16 FocusEventHandler,
17 GroupTypeBase,
18 InputActionMeta,
19 KeyboardEventHandler,
20 MenuPlacement,
21 MenuPosition,
22 OptionsType,
23 ValueType,
24 OptionTypeBase,
25 SetValueAction,
26} from './types';
27
28export type MouseOrTouchEvent = React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>;
29export type FormatOptionLabelContext = 'menu' | 'value';
30export interface FormatOptionLabelMeta<OptionType extends OptionTypeBase, IsMulti extends boolean> {
31 context: FormatOptionLabelContext;
32 inputValue: string;
33 selectValue: ValueType<OptionType, IsMulti>;
34}
35
36export type SelectComponentsProps = { [key in string]: any };
37
38export interface NamedProps<
39 OptionType extends OptionTypeBase = { label: string; value: string },
40 IsMulti extends boolean = false,
41 GroupType extends GroupTypeBase<OptionType> = GroupTypeBase<OptionType>
42> {
43 /** Aria label (for assistive tech) */
44 'aria-label'?: string;
45 /** HTML ID of an element that should be used as the label (for assistive tech) */
46 'aria-labelledby'?: string;
47 /** Focus the control when it is mounted */
48 autoFocus?: boolean;
49 /** Remove the currently focused option when the user presses backspace */
50 backspaceRemovesValue?: boolean;
51 /** Remove focus from the input when the user selects an option (handy for dismissing the keyboard on touch devices) */
52 blurInputOnSelect?: boolean;
53 /** When the user reaches the top/bottom of the menu, prevent scroll on the scroll-parent */
54 captureMenuScroll?: boolean;
55 /** className attribute applied to the outer component */
56 className?: string;
57 /** classNamePrefix attribute used as a base for inner component classNames */
58 classNamePrefix?: string | null;
59 /** Close the select menu when the user selects an option */
60 closeMenuOnSelect?: boolean;
61 /**
62 * If `true`, close the select menu when the user scrolls the document/body.
63 *
64 * If a function, takes a standard javascript `ScrollEvent` you return a boolean:
65 *
66 * `true` => The menu closes
67 *
68 * `false` => The menu stays open
69 *
70 * This is useful when you have a scrollable modal and want to portal the menu out,
71 * but want to avoid graphical issues.
72 */
73 closeMenuOnScroll?: boolean | EventListener;
74 /**
75 * This complex object includes all the compositional components that are used
76 * in `react-select`. If you wish to overwrite a component, pass in an object
77 * with the appropriate namespace.
78 *
79 * If you only wish to restyle a component, we recommend using the `styles` prop
80 * instead. For a list of the components that can be passed in, and the shape
81 * that will be passed to them, see [the components docs](/api#components)
82 */
83 components?: SelectComponentsConfig<OptionType, IsMulti, GroupType>;
84 /** Whether the value of the select, e.g. SingleValue, should be displayed in the control. */
85 controlShouldRenderValue?: boolean;
86 /** Delimiter used to join multiple values into a single HTML Input value */
87 delimiter?: string;
88 /** Clear all values when the user presses escape AND the menu is closed */
89 escapeClearsValue?: boolean;
90 /** Custom method to filter whether an option should be displayed in the menu */
91 filterOption?: ((option: Option, rawInput: string) => boolean) | null;
92 /** Formats group labels in the menu as React components */
93 formatGroupLabel?: formatGroupLabel<OptionType, GroupType>;
94 /** Formats option labels in the menu and control as React components */
95 formatOptionLabel?: (option: OptionType, labelMeta: FormatOptionLabelMeta<OptionType, IsMulti>) => React.ReactNode;
96 /** Resolves option data to a string to be displayed as the label by components */
97 getOptionLabel?: getOptionLabel<OptionType>;
98 /** Resolves option data to a string to compare options and specify value attributes */
99 getOptionValue?: getOptionValue<OptionType>;
100 /** Hide the selected option from the menu */
101 hideSelectedOptions?: boolean;
102 /** The id to set on the SelectContainer component. */
103 id?: string;
104 /** The value of the search input */
105 inputValue?: string;
106 /** The id of the search input */
107 inputId?: string;
108 /** Define an id prefix for the select components e.g. {your-id}-value */
109 instanceId?: number | string;
110 /** Is the select value clearable */
111 isClearable?: boolean;
112 /** Is the select disabled */
113 isDisabled?: boolean;
114 /** Is the select in a state of loading (async) */
115 isLoading?: boolean;
116 /** Override the built-in logic to detect whether an option is disabled */
117 isOptionDisabled?: (option: OptionType, options: OptionsType<OptionType>) => boolean | false;
118 /** Override the built-in logic to detect whether an option is selected */
119 isOptionSelected?: (option: OptionType, options: OptionsType<OptionType>) => boolean;
120 /** Support multiple selected options */
121 isMulti?: IsMulti;
122 /** Is the select direction right-to-left */
123 isRtl?: boolean;
124 /** Whether to enable search functionality */
125 isSearchable?: boolean;
126 /** Async: Text to display when loading options */
127 loadingMessage?: (obj: { inputValue: string }) => string | null;
128 /** Minimum height of the menu before flipping */
129 minMenuHeight?: number;
130 /** Maximum height of the menu before scrolling */
131 maxMenuHeight?: number;
132 /** Whether the menu is open */
133 menuIsOpen?: boolean;
134 /**
135 * Default placement of the menu in relation to the control. 'auto' will flip
136 * when there isn't enough space below the control.
137 */
138 menuPlacement?: MenuPlacement;
139 /** The CSS position value of the menu, when "fixed" extra layout management is required */
140 menuPosition?: MenuPosition;
141 /** Whether the menu should use a portal, and where it should attach */
142 menuPortalTarget?: HTMLElement | null;
143 /** Whether to block scroll events when the menu is open */
144 menuShouldBlockScroll?: boolean;
145 /** Whether the menu should be scrolled into view when it opens */
146 menuShouldScrollIntoView?: boolean;
147 /** Name of the HTML Input (optional - without this, no input will be rendered) */
148 name?: string;
149 /** Text to display when there are no options */
150 noOptionsMessage?: (obj: { inputValue: string }) => string | null;
151 /** Handle blur events on the control */
152 onBlur?: FocusEventHandler;
153 /** Handle change events on the select */
154 onChange?: (value: ValueType<OptionType, IsMulti>, action: ActionMeta<OptionType>) => void;
155 /** Handle focus events on the control */
156 onFocus?: FocusEventHandler;
157 /** Handle change events on the input */
158 onInputChange?: (newValue: string, actionMeta: InputActionMeta) => void;
159 /** Handle key down events on the select */
160 onKeyDown?: KeyboardEventHandler;
161 /** Handle the menu opening */
162 onMenuOpen?: () => void;
163 /** Handle the menu closing */
164 onMenuClose?: () => void;
165 /** Fired when the user scrolls to the top of the menu */
166 onMenuScrollToTop?: (event: React.SyntheticEvent<HTMLElement>) => void;
167 /** Fired when the user scrolls to the bottom of the menu */
168 onMenuScrollToBottom?: (event: React.SyntheticEvent<HTMLElement>) => void;
169 /** Allows control of whether the menu is opened when the Select is focused */
170 openMenuOnFocus?: boolean;
171 /** Allows control of whether the menu is opened when the Select is clicked */
172 openMenuOnClick?: boolean;
173 /** Array of options that populate the select menu */
174 options?: ReadonlyArray<OptionType | GroupType>;
175 /** Number of options to jump in menu when page{up|down} keys are used */
176 pageSize?: number;
177 /** Placeholder text for the select value */
178 placeholder?: React.ReactNode;
179 /** Status to relay to screen readers */
180 screenReaderStatus?: (obj: { count: number }) => string;
181 /** Style modifier methods */
182 styles?: StylesConfig<OptionType, IsMulti, GroupType>;
183 /** Theme modifier method */
184 theme?: ThemeConfig;
185 /** Sets the tabIndex attribute on the input */
186 tabIndex?: string | null;
187 /** Select the currently focused option when the user presses tab */
188 tabSelectsValue?: boolean;
189 /** The value of the select; reflected by the selected option */
190 value?: readonly OptionType[] | OptionType | null;
191
192 defaultInputValue?: string;
193 defaultMenuIsOpen?: boolean;
194 defaultValue?: readonly OptionType[] | OptionType | null;
195}
196
197export interface Props<
198 OptionType extends OptionTypeBase = { label: string; value: string },
199 IsMulti extends boolean = false,
200 GroupType extends GroupTypeBase<OptionType> = GroupTypeBase<OptionType>
201> extends NamedProps<OptionType, IsMulti, GroupType>,
202 SelectComponentsProps {}
203
204export const defaultProps: Props<any>;
205
206export interface MenuOptions<OptionType extends OptionTypeBase> {
207 render: OptionType[];
208 focusable: OptionType[];
209}
210
211export interface State<OptionType extends OptionTypeBase> {
212 ariaLiveSelection: string;
213 ariaLiveContext: string;
214 inputIsHidden: boolean;
215 isFocused: boolean;
216 isComposing: boolean;
217 focusedOption: OptionType | null;
218 focusedValue: OptionType | null;
219 menuOptions: MenuOptions<OptionType>;
220 selectValue: OptionsType<OptionType>;
221}
222
223export type ElRef = React.Ref<any>;
224
225export default class Select<
226 OptionType extends OptionTypeBase,
227 IsMulti extends boolean = false,
228 GroupType extends GroupTypeBase<OptionType> = GroupTypeBase<OptionType>
229> extends React.Component<Props<OptionType, IsMulti, GroupType>, State<OptionType>> {
230 static defaultProps: Props<any>;
231
232 // Misc. Instance Properties
233 // ------------------------------
234
235 blockOptionHover: boolean;
236 clearFocusValueOnUpdate: boolean;
237 commonProps: any; // TODO
238 components: SelectComponents<OptionType, IsMulti, GroupType>;
239 hasGroups: boolean;
240 initialTouchX: number;
241 initialTouchY: number;
242 inputIsHiddenAfterUpdate: boolean | null;
243 instancePrefix: string;
244 openAfterFocus: boolean;
245 scrollToFocusedOptionOnUpdate: boolean;
246 userIsDragging: boolean | null;
247
248 // Refs
249 // ------------------------------
250
251 controlRef: ElRef;
252 getControlRef: (ref: HTMLElement) => void;
253 focusedOptionRef: ElRef;
254 getFocusedOptionRef: (ref: HTMLElement) => void;
255 menuListRef: ElRef;
256 getMenuListRef: (ref: HTMLElement) => void;
257 inputRef: ElRef;
258 getInputRef: (ref: HTMLElement) => void;
259
260 // Lifecycle
261 // ------------------------------
262
263 cacheComponents: (components: SelectComponents<OptionType, IsMulti, GroupType>) => void;
264
265 // ==============================
266 // Consumer Handlers
267 // ==============================
268
269 onMenuOpen(): void;
270 onMenuClose(): void;
271 onInputChange(newValue: string, actionMeta: InputActionMeta): void;
272
273 // ==============================
274 // Methods
275 // ==============================
276
277 focusInput(): void;
278 blurInput(): void;
279
280 // aliased for consumers
281 focus(): void;
282 blur(): void;
283
284 openMenu(focusOption: 'first' | 'last'): void;
285 focusValue(direction: 'previous' | 'next'): void;
286
287 focusOption(direction: FocusDirection): void;
288 setValue: (newValue: ValueType<OptionType, IsMulti>, action: SetValueAction, option?: OptionType) => void;
289 selectOption: (newValue: OptionType) => void;
290 removeValue: (removedValue: OptionType) => void;
291 clearValue: () => void;
292 popValue: () => void;
293
294 // ==============================
295 // Getters
296 // ==============================
297
298 getCommonProps(): {
299 cx: any;
300 clearValue: () => void;
301 getStyles: (key: string, props: {}) => {};
302 getValue: () => OptionType[];
303 hasValue: boolean;
304 isMulti: boolean;
305 isRtl: boolean;
306 options: OptionsType<any>;
307 selectOption: (newValue: OptionType) => void;
308 setValue: (newValue: ValueType<OptionType, IsMulti>, action: SetValueAction, option?: OptionType) => void;
309 selectProps: Readonly<{
310 children?: React.ReactNode;
311 }> &
312 Readonly<Props<OptionType, IsMulti, GroupType>>;
313 };
314
315 getNextFocusedValue(nextSelectValue: OptionsType<OptionType>): OptionType;
316
317 getNextFocusedOption(options: OptionsType<OptionType>): OptionType;
318 getOptionLabel: getOptionLabel<OptionType>;
319 getOptionValue: getOptionValue<OptionType>;
320 getStyles: (key: string, props: {}) => {};
321 getElementId: (element: 'group' | 'input' | 'listbox' | 'option') => string;
322 getActiveDescendentId: () => any;
323
324 // ==============================
325 // Helpers
326 // ==============================
327 announceAriaLiveSelection: (props: { event: string; context: ValueEventContext }) => void;
328 announceAriaLiveContext: (props: { event: string; context?: InstructionsContext }) => void;
329
330 hasValue(): boolean;
331 hasOptions(): boolean;
332 countOptions(): number;
333 isClearable(): boolean;
334 isOptionDisabled(option: OptionType, selectValue: OptionsType<OptionType>): boolean;
335 isOptionSelected(option: OptionType, selectValue: OptionsType<OptionType>): boolean;
336 filterOption(option: {}, inputValue: string): boolean;
337 formatOptionLabel(data: OptionType, context: FormatOptionLabelContext): React.ReactNode;
338 formatGroupLabel: formatGroupLabel<OptionType, GroupType>;
339
340 // ==============================
341 // Mouse Handlers
342 // ==============================
343
344 onMenuMouseDown: (event: React.MouseEvent<HTMLElement>) => void;
345 onMenuMouseMove: (event: React.MouseEvent<HTMLElement>) => void;
346 onControlMouseDown: (event: MouseOrTouchEvent) => void;
347 onDropdownIndicatorMouseDown: (event: MouseOrTouchEvent) => void;
348 onClearIndicatorMouseDown: (event: MouseOrTouchEvent) => void;
349 onScroll: (event: Event) => void;
350
351 // ==============================
352 // Composition Handlers
353 // ==============================
354
355 startListeningComposition(): void;
356 stopListeningComposition(): void;
357 onCompositionStart: () => void;
358 onCompositionEnd: () => void;
359
360 // ==============================
361 // Touch Handlers
362 // ==============================
363
364 startListeningToTouch(): void;
365 stopListeningToTouch(): void;
366 onTouchStart: (event: TouchEvent) => void;
367 onTouchMove: (event: TouchEvent) => void;
368 onTouchEnd: (event: TouchEvent) => void;
369 onControlTouchEnd: (event: React.TouchEvent<HTMLElement>) => void;
370 onClearIndicatorTouchEnd: (event: React.TouchEvent<HTMLElement>) => void;
371 onDropdownIndicatorTouchEnd: (event: React.TouchEvent<HTMLElement>) => void;
372
373 // ==============================
374 // Focus Handlers
375 // ==============================
376
377 handleInputChange: (event: React.KeyboardEvent<HTMLInputElement>) => void;
378 onInputFocus: (event: React.FocusEvent<HTMLInputElement>) => void;
379 onInputBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
380 onOptionHover: (focusedOption: OptionType) => void;
381 shouldHideSelectedOptions: () => boolean;
382
383 // ==============================
384 // Keyboard Handlers
385 // ==============================
386
387 onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
388
389 // ==============================
390 // Menu Options
391 // ==============================
392
393 buildMenuOptions(
394 props: Props<OptionType, IsMulti, GroupType>,
395 selectValue: OptionsType<OptionType>,
396 ): MenuOptions<OptionType>;
397
398 // ==============================
399 // Renderers
400 // ==============================
401 constructAriaLiveMessage(): string;
402
403 renderInput(): React.ReactNode;
404 renderPlaceholderOrValue(): PlaceholderOrValue<OptionType, IsMulti, GroupType> | null;
405 renderClearIndicator(): React.ReactNode;
406 renderLoadingIndicator(): React.ReactNode;
407 renderIndicatorSeparator(): React.ReactNode;
408 renderDropdownIndicator(): React.ReactNode;
409 renderMenu(): React.ReactNode;
410 renderFormField(): React.ReactNode;
411
412 renderLiveRegion(): React.ReactNode;
413}