Software: Apache/2.4.41 (Ubuntu). PHP/8.0.30 uname -a: Linux apirnd 5.4.0-204-generic #224-Ubuntu SMP Thu Dec 5 13:38:28 UTC 2024 x86_64 uid=33(www-data) gid=33(www-data) groups=33(www-data) Safe-mode: OFF (not secure) /var/www/html/wincloud_gateway/node_modules/react-select/src/ drwxr-xr-x | |
| Viewing file: Select action/file-type: // @flow
import React, { Component, type ElementRef, type Node } from 'react';
import { MenuPlacer } from './components/Menu';
import LiveRegion from './components/LiveRegion';
import { createFilter } from './filters';
import { DummyInput, ScrollManager } from './internal/index';
import {
type AriaLiveProp,
type AriaLiveMessagesProps,
type AriaSelectionType,
} from './accessibility/index';
import {
classNames,
cleanValue,
isTouchCapable,
isMobileDevice,
noop,
scrollIntoView,
isDocumentElement,
} from './utils';
import {
formatGroupLabel as formatGroupLabelBuiltin,
getOptionLabel as getOptionLabelBuiltin,
getOptionValue as getOptionValueBuiltin,
isOptionDisabled as isOptionDisabledBuiltin,
} from './builtins';
import {
defaultComponents,
type PlaceholderOrValue,
type SelectComponentsConfig,
} from './components/index';
import { defaultStyles, type StylesConfig } from './styles';
import { defaultTheme, type ThemeConfig } from './theme';
import type {
ActionMeta,
ActionTypes,
FocusDirection,
FocusEventHandler,
GroupType,
InputActionMeta,
KeyboardEventHandler,
MenuPlacement,
MenuPosition,
OptionsType,
OptionType,
ValueType,
} from './types';
type MouseOrTouchEvent =
| SyntheticMouseEvent<HTMLElement>
| SyntheticTouchEvent<HTMLElement>;
type FormatOptionLabelContext = 'menu' | 'value';
type FormatOptionLabelMeta = {
context: FormatOptionLabelContext,
inputValue: string,
selectValue: ValueType,
};
export type Props = {
/* Aria label (for assistive tech) */
'aria-label'?: string,
/* HTML ID of an element that should be used as the label (for assistive tech) */
'aria-labelledby'?: string,
/* Used to set the priority with which screen reader should treat updates to live regions. The possible settings are: off, polite (default) or assertive */
'aria-live'?: AriaLiveProp,
/* Customize the messages used by the aria-live component */
ariaLiveMessages?: AriaLiveMessagesProps,
/* Focus the control when it is mounted */
autoFocus?: boolean,
/* Remove the currently focused option when the user presses backspace when Select isClearable or isMulti */
backspaceRemovesValue: boolean,
/* Remove focus from the input when the user selects an option (handy for dismissing the keyboard on touch devices) */
blurInputOnSelect: boolean,
/* When the user reaches the top/bottom of the menu, prevent scroll on the scroll-parent */
captureMenuScroll: boolean,
/* Sets a className attribute on the outer component */
className?: string,
/*
If provided, all inner components will be given a prefixed className attribute.
This is useful when styling via CSS classes instead of the Styles API approach.
*/
classNamePrefix?: string | null,
/* Close the select menu when the user selects an option */
closeMenuOnSelect: boolean,
/*
If `true`, close the select menu when the user scrolls the document/body.
If a function, takes a standard javascript `ScrollEvent` you return a boolean:
`true` => The menu closes
`false` => The menu stays open
This is useful when you have a scrollable modal and want to portal the menu out,
but want to avoid graphical issues.
*/
closeMenuOnScroll: boolean | EventListener,
/*
This complex object includes all the compositional components that are used
in `react-select`. If you wish to overwrite a component, pass in an object
with the appropriate namespace.
If you only wish to restyle a component, we recommend using the `styles` prop
instead. For a list of the components that can be passed in, and the shape
that will be passed to them, see [the components docs](/components)
*/
components: SelectComponentsConfig,
/* Whether the value of the select, e.g. SingleValue, should be displayed in the control. */
controlShouldRenderValue: boolean,
/* Delimiter used to join multiple values into a single HTML Input value */
delimiter?: string,
/* Clear all values when the user presses escape AND the menu is closed */
escapeClearsValue: boolean,
/* Custom method to filter whether an option should be displayed in the menu */
filterOption:
| (({ label: string, value: string, data: OptionType }, string) => boolean)
| null,
/*
Formats group labels in the menu as React components
An example can be found in the [Replacing builtins](/advanced#replacing-builtins) documentation.
*/
formatGroupLabel: typeof formatGroupLabelBuiltin,
/* Formats option labels in the menu and control as React components */
formatOptionLabel?: (OptionType, FormatOptionLabelMeta) => Node,
/*
Resolves option data to a string to be displayed as the label by components
Note: Failure to resolve to a string type can interfere with filtering and
screen reader support.
*/
getOptionLabel: typeof getOptionLabelBuiltin,
/* Resolves option data to a string to compare options and specify value attributes */
getOptionValue: typeof getOptionValueBuiltin,
/* Hide the selected option from the menu */
hideSelectedOptions?: boolean,
/* The id to set on the SelectContainer component. */
id?: string,
/* The value of the search input */
inputValue: string,
/* The id of the search input */
inputId?: string,
/* Define an id prefix for the select components e.g. {your-id}-value */
instanceId?: number | string,
/* Is the select value clearable */
isClearable?: boolean,
/* Is the select disabled */
isDisabled: boolean,
/* Is the select in a state of loading (async) */
isLoading: boolean,
/*
Override the built-in logic to detect whether an option is disabled
An example can be found in the [Replacing builtins](/advanced#replacing-builtins) documentation.
*/
isOptionDisabled: (OptionType, OptionsType) => boolean | false,
/* Override the built-in logic to detect whether an option is selected */
isOptionSelected?: (OptionType, OptionsType) => boolean,
/* Support multiple selected options */
isMulti: boolean,
/* Is the select direction right-to-left */
isRtl: boolean,
/* Whether to enable search functionality */
isSearchable: boolean,
/* Async: Text to display when loading options */
loadingMessage: ({ inputValue: string }) => string | null,
/* Minimum height of the menu before flipping */
minMenuHeight: number,
/* Maximum height of the menu before scrolling */
maxMenuHeight: number,
/* Whether the menu is open */
menuIsOpen: boolean,
/* Default placement of the menu in relation to the control. 'auto' will flip
when there isn't enough space below the control. */
menuPlacement: MenuPlacement,
/* The CSS position value of the menu, when "fixed" extra layout management is required */
menuPosition: MenuPosition,
/*
Whether the menu should use a portal, and where it should attach
An example can be found in the [Portaling](/advanced#portaling) documentation
*/
menuPortalTarget?: HTMLElement,
/* Whether to block scroll events when the menu is open */
menuShouldBlockScroll: boolean,
/* Whether the menu should be scrolled into view when it opens */
menuShouldScrollIntoView: boolean,
/* Name of the HTML Input (optional - without this, no input will be rendered) */
name?: string,
/* Text to display when there are no options */
noOptionsMessage: ({ inputValue: string }) => Node | null,
/* Handle blur events on the control */
onBlur?: FocusEventHandler,
/* Handle change events on the select */
onChange: (ValueType, ActionMeta) => void,
/* Handle focus events on the control */
onFocus?: FocusEventHandler,
/* Handle change events on the input */
onInputChange: (string, InputActionMeta) => void,
/* Handle key down events on the select */
onKeyDown?: KeyboardEventHandler,
/* Handle the menu opening */
onMenuOpen: () => void,
/* Handle the menu closing */
onMenuClose: () => void,
/* Fired when the user scrolls to the top of the menu */
onMenuScrollToTop?: (SyntheticEvent<HTMLElement>) => void,
/* Fired when the user scrolls to the bottom of the menu */
onMenuScrollToBottom?: (SyntheticEvent<HTMLElement>) => void,
/* Allows control of whether the menu is opened when the Select is focused */
openMenuOnFocus: boolean,
/* Allows control of whether the menu is opened when the Select is clicked */
openMenuOnClick: boolean,
/* Array of options that populate the select menu */
options: OptionsType,
/* Number of options to jump in menu when page{up|down} keys are used */
pageSize: number,
/* Placeholder for the select value */
placeholder: Node,
/* Status to relay to screen readers */
screenReaderStatus: ({ count: number }) => string,
/*
Style modifier methods
A basic example can be found at the bottom of the [Replacing builtins](/advanced#replacing-builtins) documentation.
*/
styles: StylesConfig,
/* Theme modifier method */
theme?: ThemeConfig,
/* Sets the tabIndex attribute on the input */
tabIndex: number | string,
/* Select the currently focused option when the user presses tab */
tabSelectsValue: boolean,
/* The value of the select; reflected by the selected option */
value: ValueType,
/* Sets the form attribute on the input */
form?: string,
};
export const defaultProps = {
'aria-live': 'polite',
backspaceRemovesValue: true,
blurInputOnSelect: isTouchCapable(),
captureMenuScroll: !isTouchCapable(),
closeMenuOnSelect: true,
closeMenuOnScroll: false,
components: {},
controlShouldRenderValue: true,
escapeClearsValue: false,
filterOption: createFilter(),
formatGroupLabel: formatGroupLabelBuiltin,
getOptionLabel: getOptionLabelBuiltin,
getOptionValue: getOptionValueBuiltin,
isDisabled: false,
isLoading: false,
isMulti: false,
isRtl: false,
isSearchable: true,
isOptionDisabled: isOptionDisabledBuiltin,
loadingMessage: () => 'Loading...',
maxMenuHeight: 300,
minMenuHeight: 140,
menuIsOpen: false,
menuPlacement: 'bottom',
menuPosition: 'absolute',
menuShouldBlockScroll: false,
menuShouldScrollIntoView: !isMobileDevice(),
noOptionsMessage: () => 'No options',
openMenuOnFocus: false,
openMenuOnClick: true,
options: [],
pageSize: 5,
placeholder: 'Select...',
screenReaderStatus: ({ count }: { count: number }) =>
`${count} result${count !== 1 ? 's' : ''} available`,
styles: {},
tabIndex: '0',
tabSelectsValue: true,
};
type State = {
ariaSelection: AriaSelectionType | null,
inputIsHidden: boolean,
isFocused: boolean,
focusedOption: OptionType | null,
focusedValue: OptionType | null,
selectValue: OptionsType,
clearFocusValueOnUpdate: boolean,
inputIsHiddenAfterUpdate: ?boolean,
prevProps: Props | void,
};
type ElRef = ElementRef<*>;
type CategorizedOption = {
type: 'option',
data: OptionType,
isDisabled: boolean,
isSelected: boolean,
label: string,
value: string,
index: number,
};
type CategorizedGroup = {
type: 'group',
data: GroupType,
options: OptionsType,
index: number,
};
type CategorizedGroupOrOption = CategorizedGroup | CategorizedOption;
function toCategorizedOption(
props: Props,
option: OptionType,
selectValue: OptionsType,
index: number
) {
const isDisabled = isOptionDisabled(props, option, selectValue);
const isSelected = isOptionSelected(props, option, selectValue);
const label = getOptionLabel(props, option);
const value = getOptionValue(props, option);
return {
type: 'option',
data: option,
isDisabled,
isSelected,
label,
value,
index,
};
}
function buildCategorizedOptions(
props: Props,
selectValue: OptionsType
): CategorizedGroupOrOption[] {
return (props.options
.map((groupOrOption, groupOrOptionIndex) => {
if (groupOrOption.options) {
const categorizedOptions = groupOrOption.options
.map((option, optionIndex) =>
toCategorizedOption(props, option, selectValue, optionIndex)
)
.filter(categorizedOption => isFocusable(props, categorizedOption));
return categorizedOptions.length > 0
? {
type: 'group',
data: groupOrOption,
options: categorizedOptions,
index: groupOrOptionIndex,
}
: undefined;
}
const categorizedOption = toCategorizedOption(
props,
groupOrOption,
selectValue,
groupOrOptionIndex
);
return isFocusable(props, categorizedOption)
? categorizedOption
: undefined;
})
// Flow limitation (see https://github.com/facebook/flow/issues/1414)
.filter(categorizedOption => !!categorizedOption): any[]);
}
function buildFocusableOptionsFromCategorizedOptions(
categorizedOptions: CategorizedGroupOrOption[]
) {
return categorizedOptions.reduce((optionsAccumulator, categorizedOption) => {
if (categorizedOption.type === 'group') {
optionsAccumulator.push(
...categorizedOption.options.map(option => option.data)
);
} else {
optionsAccumulator.push(categorizedOption.data);
}
return optionsAccumulator;
}, []);
}
function buildFocusableOptions(props: Props, selectValue: OptionsType) {
return buildFocusableOptionsFromCategorizedOptions(
buildCategorizedOptions(props, selectValue)
);
}
function isFocusable(props: Props, categorizedOption: CategorizedOption) {
const { inputValue = '' } = props;
const { data, isSelected, label, value } = categorizedOption;
return (
(!shouldHideSelectedOptions(props) || !isSelected) &&
filterOption(props, { label, value, data }, inputValue)
);
}
function getNextFocusedValue(state: State, nextSelectValue: OptionsType) {
const { focusedValue, selectValue: lastSelectValue } = state;
const lastFocusedIndex = lastSelectValue.indexOf(focusedValue);
if (lastFocusedIndex > -1) {
const nextFocusedIndex = nextSelectValue.indexOf(focusedValue);
if (nextFocusedIndex > -1) {
// the focused value is still in the selectValue, return it
return focusedValue;
} else if (lastFocusedIndex < nextSelectValue.length) {
// the focusedValue is not present in the next selectValue array by
// reference, so return the new value at the same index
return nextSelectValue[lastFocusedIndex];
}
}
return null;
}
function getNextFocusedOption(state: State, options: OptionsType) {
const { focusedOption: lastFocusedOption } = state;
return lastFocusedOption && options.indexOf(lastFocusedOption) > -1
? lastFocusedOption
: options[0];
}
const getOptionLabel = (props: Props, data: OptionType): string => {
return props.getOptionLabel(data);
};
const getOptionValue = (props: Props, data: OptionType): string => {
return props.getOptionValue(data);
};
function isOptionDisabled(
props: Props,
option: OptionType,
selectValue: OptionsType
): boolean {
return typeof props.isOptionDisabled === 'function'
? props.isOptionDisabled(option, selectValue)
: false;
}
function isOptionSelected(
props: Props,
option: OptionType,
selectValue: OptionsType
): boolean {
if (selectValue.indexOf(option) > -1) return true;
if (typeof props.isOptionSelected === 'function') {
return props.isOptionSelected(option, selectValue);
}
const candidate = getOptionValue(props, option);
return selectValue.some(i => getOptionValue(props, i) === candidate);
}
function filterOption(
props: Props,
option: { label: string, value: string, data: OptionType },
inputValue: string
) {
return props.filterOption ? props.filterOption(option, inputValue) : true;
}
const shouldHideSelectedOptions = (props: Props) => {
const { hideSelectedOptions, isMulti } = props;
if (hideSelectedOptions === undefined) return isMulti;
return hideSelectedOptions;
};
let instanceId = 1;
export default class Select extends Component<Props, State> {
static defaultProps = defaultProps;
state = {
ariaSelection: null,
focusedOption: null,
focusedValue: null,
inputIsHidden: false,
isFocused: false,
selectValue: [],
clearFocusValueOnUpdate: false,
inputIsHiddenAfterUpdate: undefined,
prevProps: undefined,
};
// Misc. Instance Properties
// ------------------------------
blockOptionHover: boolean = false;
isComposing: boolean = false;
commonProps: any; // TODO
initialTouchX: number = 0;
initialTouchY: number = 0;
instancePrefix: string = '';
openAfterFocus: boolean = false;
scrollToFocusedOptionOnUpdate: boolean = false;
userIsDragging: ?boolean;
// Refs
// ------------------------------
controlRef: ElRef = null;
getControlRef = (ref: ?HTMLElement) => {
this.controlRef = ref;
};
focusedOptionRef: ElRef = null;
getFocusedOptionRef = (ref: ?HTMLElement) => {
this.focusedOptionRef = ref;
};
menuListRef: ElRef = null;
getMenuListRef = (ref: ?HTMLElement) => {
this.menuListRef = ref;
};
inputRef: ElRef = null;
getInputRef = (ref: ?HTMLElement) => {
this.inputRef = ref;
};
// Lifecycle
// ------------------------------
constructor(props: Props) {
super(props);
this.instancePrefix =
'react-select-' + (this.props.instanceId || ++instanceId);
this.state.selectValue = cleanValue(props.value);
}
static getDerivedStateFromProps(props: Props, state: State) {
const {
prevProps,
clearFocusValueOnUpdate,
inputIsHiddenAfterUpdate,
} = state;
const { options, value, menuIsOpen, inputValue } = props;
let newMenuOptionsState = {};
if (
prevProps &&
(value !== prevProps.value ||
options !== prevProps.options ||
menuIsOpen !== prevProps.menuIsOpen ||
inputValue !== prevProps.inputValue)
) {
const selectValue = cleanValue(value);
const focusableOptions = menuIsOpen
? buildFocusableOptions(props, selectValue)
: [];
const focusedValue = clearFocusValueOnUpdate
? getNextFocusedValue(state, selectValue)
: null;
const focusedOption = getNextFocusedOption(state, focusableOptions);
newMenuOptionsState = {
selectValue,
focusedOption,
focusedValue,
clearFocusValueOnUpdate: false,
};
}
// some updates should toggle the state of the input visibility
const newInputIsHiddenState =
inputIsHiddenAfterUpdate != null && props !== prevProps
? {
inputIsHidden: inputIsHiddenAfterUpdate,
inputIsHiddenAfterUpdate: undefined,
}
: {};
return {
...newMenuOptionsState,
...newInputIsHiddenState,
prevProps: props,
};
}
componentDidMount() {
this.startListeningComposition();
this.startListeningToTouch();
if (this.props.closeMenuOnScroll && document && document.addEventListener) {
// Listen to all scroll events, and filter them out inside of 'onScroll'
document.addEventListener('scroll', this.onScroll, true);
}
if (this.props.autoFocus) {
this.focusInput();
}
}
componentDidUpdate(prevProps: Props) {
const { isDisabled, menuIsOpen } = this.props;
const { isFocused } = this.state;
if (
// ensure focus is restored correctly when the control becomes enabled
(isFocused && !isDisabled && prevProps.isDisabled) ||
// ensure focus is on the Input when the menu opens
(isFocused && menuIsOpen && !prevProps.menuIsOpen)
) {
this.focusInput();
}
if (isFocused && isDisabled && !prevProps.isDisabled) {
// ensure select state gets blurred in case Select is programatically disabled while focused
this.setState({ isFocused: false }, this.onMenuClose);
}
// scroll the focused option into view if necessary
if (
this.menuListRef &&
this.focusedOptionRef &&
this.scrollToFocusedOptionOnUpdate
) {
scrollIntoView(this.menuListRef, this.focusedOptionRef);
this.scrollToFocusedOptionOnUpdate = false;
}
}
componentWillUnmount() {
this.stopListeningComposition();
this.stopListeningToTouch();
document.removeEventListener('scroll', this.onScroll, true);
}
// ==============================
// Consumer Handlers
// ==============================
onMenuOpen() {
this.props.onMenuOpen();
}
onMenuClose() {
this.onInputChange('', { action: 'menu-close' });
this.props.onMenuClose();
}
onInputChange(newValue: string, actionMeta: InputActionMeta) {
this.props.onInputChange(newValue, actionMeta);
}
// ==============================
// Methods
// ==============================
focusInput() {
if (!this.inputRef) return;
this.inputRef.focus();
}
blurInput() {
if (!this.inputRef) return;
this.inputRef.blur();
}
// aliased for consumers
focus = this.focusInput;
blur = this.blurInput;
openMenu(focusOption: 'first' | 'last') {
const { selectValue, isFocused } = this.state;
const focusableOptions = this.buildFocusableOptions();
let openAtIndex = focusOption === 'first' ? 0 : focusableOptions.length - 1;
if (!this.props.isMulti) {
const selectedIndex = focusableOptions.indexOf(selectValue[0]);
if (selectedIndex > -1) {
openAtIndex = selectedIndex;
}
}
// only scroll if the menu isn't already open
this.scrollToFocusedOptionOnUpdate = !(isFocused && this.menuListRef);
this.setState(
{
inputIsHiddenAfterUpdate: false,
focusedValue: null,
focusedOption: focusableOptions[openAtIndex],
},
() => this.onMenuOpen()
);
}
focusValue(direction: 'previous' | 'next') {
const { selectValue, focusedValue } = this.state;
// Only multiselects support value focusing
if (!this.props.isMulti) return;
this.setState({
focusedOption: null,
});
let focusedIndex = selectValue.indexOf(focusedValue);
if (!focusedValue) {
focusedIndex = -1;
}
const lastIndex = selectValue.length - 1;
let nextFocus = -1;
if (!selectValue.length) return;
switch (direction) {
case 'previous':
if (focusedIndex === 0) {
// don't cycle from the start to the end
nextFocus = 0;
} else if (focusedIndex === -1) {
// if nothing is focused, focus the last value first
nextFocus = lastIndex;
} else {
nextFocus = focusedIndex - 1;
}
break;
case 'next':
if (focusedIndex > -1 && focusedIndex < lastIndex) {
nextFocus = focusedIndex + 1;
}
break;
}
this.setState({
inputIsHidden: nextFocus !== -1,
focusedValue: selectValue[nextFocus],
});
}
focusOption(direction: FocusDirection = 'first') {
const { pageSize } = this.props;
const { focusedOption } = this.state;
const options = this.getFocusableOptions();
if (!options.length) return;
let nextFocus = 0; // handles 'first'
let focusedIndex = options.indexOf(focusedOption);
if (!focusedOption) {
focusedIndex = -1;
}
if (direction === 'up') {
nextFocus = focusedIndex > 0 ? focusedIndex - 1 : options.length - 1;
} else if (direction === 'down') {
nextFocus = (focusedIndex + 1) % options.length;
} else if (direction === 'pageup') {
nextFocus = focusedIndex - pageSize;
if (nextFocus < 0) nextFocus = 0;
} else if (direction === 'pagedown') {
nextFocus = focusedIndex + pageSize;
if (nextFocus > options.length - 1) nextFocus = options.length - 1;
} else if (direction === 'last') {
nextFocus = options.length - 1;
}
this.scrollToFocusedOptionOnUpdate = true;
this.setState({
focusedOption: options[nextFocus],
focusedValue: null,
});
}
onChange = (newValue: ValueType, actionMeta: ActionMeta) => {
const { onChange, name } = this.props;
actionMeta.name = name;
this.ariaOnChange(newValue, actionMeta);
onChange(newValue, actionMeta);
};
setValue = (
newValue: ValueType,
action: ActionTypes = 'set-value',
option?: OptionType
) => {
const { closeMenuOnSelect, isMulti } = this.props;
this.onInputChange('', { action: 'set-value' });
if (closeMenuOnSelect) {
this.setState({ inputIsHiddenAfterUpdate: !isMulti });
this.onMenuClose();
}
// when the select value should change, we should reset focusedValue
this.setState({ clearFocusValueOnUpdate: true });
this.onChange(newValue, { action, option });
};
selectOption = (newValue: OptionType) => {
const { blurInputOnSelect, isMulti, name } = this.props;
const { selectValue } = this.state;
const deselected = isMulti && this.isOptionSelected(newValue, selectValue);
const isDisabled = this.isOptionDisabled(newValue, selectValue);
if (deselected) {
const candidate = this.getOptionValue(newValue);
this.setValue(
selectValue.filter(i => this.getOptionValue(i) !== candidate),
'deselect-option',
newValue
);
} else if (!isDisabled) {
// Select option if option is not disabled
if (isMulti) {
this.setValue([...selectValue, newValue], 'select-option', newValue);
} else {
this.setValue(newValue, 'select-option');
}
} else {
this.ariaOnChange(newValue, { action: 'select-option', name });
return;
}
if (blurInputOnSelect) {
this.blurInput();
}
};
removeValue = (removedValue: OptionType) => {
const { isMulti } = this.props;
const { selectValue } = this.state;
const candidate = this.getOptionValue(removedValue);
const newValueArray = selectValue.filter(
i => this.getOptionValue(i) !== candidate
);
const newValue = isMulti ? newValueArray : newValueArray[0] || null;
this.onChange(newValue, { action: 'remove-value', removedValue });
this.focusInput();
};
clearValue = () => {
const { selectValue } = this.state;
this.onChange(this.props.isMulti ? [] : null, {
action: 'clear',
removedValues: selectValue,
});
};
popValue = () => {
const { isMulti } = this.props;
const { selectValue } = this.state;
const lastSelectedValue = selectValue[selectValue.length - 1];
const newValueArray = selectValue.slice(0, selectValue.length - 1);
const newValue = isMulti ? newValueArray : newValueArray[0] || null;
this.onChange(newValue, {
action: 'pop-value',
removedValue: lastSelectedValue,
});
};
// ==============================
// Getters
// ==============================
getTheme() {
// Use the default theme if there are no customizations.
if (!this.props.theme) {
return defaultTheme;
}
// If the theme prop is a function, assume the function
// knows how to merge the passed-in default theme with
// its own modifications.
if (typeof this.props.theme === 'function') {
return this.props.theme(defaultTheme);
}
// Otherwise, if a plain theme object was passed in,
// overlay it with the default theme.
return {
...defaultTheme,
...this.props.theme,
};
}
getValue = () => this.state.selectValue;
cx = (...args: any) => classNames(this.props.classNamePrefix, ...args);
getCommonProps() {
const {
clearValue,
cx,
getStyles,
getValue,
selectOption,
setValue,
props,
} = this;
const { isMulti, isRtl, options } = props;
const hasValue = this.hasValue();
return {
clearValue,
cx,
getStyles,
getValue,
hasValue,
isMulti,
isRtl,
options,
selectOption,
selectProps: props,
setValue,
theme: this.getTheme(),
};
}
getOptionLabel = (data: OptionType): string => {
return getOptionLabel(this.props, data);
};
getOptionValue = (data: OptionType): string => {
return getOptionValue(this.props, data);
};
getStyles = (key: string, props: {}): {} => {
const base = defaultStyles[key](props);
base.boxSizing = 'border-box';
const custom = this.props.styles[key];
return custom ? custom(base, props) : base;
};
getElementId = (element: 'group' | 'input' | 'listbox' | 'option') => {
return `${this.instancePrefix}-${element}`;
};
getComponents = () => {
return defaultComponents(this.props);
};
buildCategorizedOptions = () =>
buildCategorizedOptions(this.props, this.state.selectValue);
getCategorizedOptions = () =>
this.props.menuIsOpen ? this.buildCategorizedOptions() : [];
buildFocusableOptions = () =>
buildFocusableOptionsFromCategorizedOptions(this.buildCategorizedOptions());
getFocusableOptions = () =>
this.props.menuIsOpen ? this.buildFocusableOptions() : [];
// ==============================
// Helpers
// ==============================
ariaOnChange = (value: ValueType, actionMeta: ActionMeta) => {
this.setState({ ariaSelection: { value, ...actionMeta } });
};
hasValue() {
const { selectValue } = this.state;
return selectValue.length > 0;
}
hasOptions() {
return !!this.getFocusableOptions().length;
}
isClearable(): boolean {
const { isClearable, isMulti } = this.props;
// single select, by default, IS NOT clearable
// multi select, by default, IS clearable
if (isClearable === undefined) return isMulti;
return isClearable;
}
isOptionDisabled(option: OptionType, selectValue: OptionsType): boolean {
return isOptionDisabled(this.props, option, selectValue);
}
isOptionSelected(option: OptionType, selectValue: OptionsType): boolean {
return isOptionSelected(this.props, option, selectValue);
}
filterOption(
option: { label: string, value: string, data: OptionType },
inputValue: string
) {
return filterOption(this.props, option, inputValue);
}
formatOptionLabel(data: OptionType, context: FormatOptionLabelContext): Node {
if (typeof this.props.formatOptionLabel === 'function') {
const { inputValue } = this.props;
const { selectValue } = this.state;
return this.props.formatOptionLabel(data, {
context,
inputValue,
selectValue,
});
} else {
return this.getOptionLabel(data);
}
}
formatGroupLabel(data: GroupType) {
return this.props.formatGroupLabel(data);
}
// ==============================
// Mouse Handlers
// ==============================
onMenuMouseDown = (event: SyntheticMouseEvent<HTMLElement>) => {
if (event.button !== 0) {
return;
}
event.stopPropagation();
event.preventDefault();
this.focusInput();
};
onMenuMouseMove = (event: SyntheticMouseEvent<HTMLElement>) => {
this.blockOptionHover = false;
};
onControlMouseDown = (event: MouseOrTouchEvent) => {
const { openMenuOnClick } = this.props;
if (!this.state.isFocused) {
if (openMenuOnClick) {
this.openAfterFocus = true;
}
this.focusInput();
} else if (!this.props.menuIsOpen) {
if (openMenuOnClick) {
this.openMenu('first');
}
} else {
if (
// $FlowFixMe
event.target.tagName !== 'INPUT' &&
event.target.tagName !== 'TEXTAREA'
) {
this.onMenuClose();
}
}
if (
// $FlowFixMe
event.target.tagName !== 'INPUT' &&
event.target.tagName !== 'TEXTAREA'
) {
event.preventDefault();
}
};
onDropdownIndicatorMouseDown = (event: MouseOrTouchEvent) => {
// ignore mouse events that weren't triggered by the primary button
if (event && event.type === 'mousedown' && event.button !== 0) {
return;
}
if (this.props.isDisabled) return;
const { isMulti, menuIsOpen } = this.props;
this.focusInput();
if (menuIsOpen) {
this.setState({ inputIsHiddenAfterUpdate: !isMulti });
this.onMenuClose();
} else {
this.openMenu('first');
}
event.preventDefault();
event.stopPropagation();
};
onClearIndicatorMouseDown = (event: MouseOrTouchEvent) => {
// ignore mouse events that weren't triggered by the primary button
if (event && event.type === 'mousedown' && event.button !== 0) {
return;
}
this.clearValue();
event.stopPropagation();
this.openAfterFocus = false;
if (event.type === 'touchend') {
this.focusInput();
} else {
setTimeout(() => this.focusInput());
}
};
onScroll = (event: Event) => {
if (typeof this.props.closeMenuOnScroll === 'boolean') {
if (
event.target instanceof HTMLElement &&
isDocumentElement(event.target)
) {
this.props.onMenuClose();
}
} else if (typeof this.props.closeMenuOnScroll === 'function') {
if (this.props.closeMenuOnScroll(event)) {
this.props.onMenuClose();
}
}
};
// ==============================
// Composition Handlers
// ==============================
startListeningComposition() {
if (document && document.addEventListener) {
document.addEventListener(
'compositionstart',
this.onCompositionStart,
false
);
document.addEventListener('compositionend', this.onCompositionEnd, false);
}
}
stopListeningComposition() {
if (document && document.removeEventListener) {
document.removeEventListener('compositionstart', this.onCompositionStart);
document.removeEventListener('compositionend', this.onCompositionEnd);
}
}
onCompositionStart = () => {
this.isComposing = true;
};
onCompositionEnd = () => {
this.isComposing = false;
};
// ==============================
// Touch Handlers
// ==============================
startListeningToTouch() {
if (document && document.addEventListener) {
document.addEventListener('touchstart', this.onTouchStart, false);
document.addEventListener('touchmove', this.onTouchMove, false);
document.addEventListener('touchend', this.onTouchEnd, false);
}
}
stopListeningToTouch() {
if (document && document.removeEventListener) {
document.removeEventListener('touchstart', this.onTouchStart);
document.removeEventListener('touchmove', this.onTouchMove);
document.removeEventListener('touchend', this.onTouchEnd);
}
}
onTouchStart = ({ touches }: TouchEvent) => {
const touch = touches && touches.item(0);
if (!touch) {
return;
}
this.initialTouchX = touch.clientX;
this.initialTouchY = touch.clientY;
this.userIsDragging = false;
};
onTouchMove = ({ touches }: TouchEvent) => {
const touch = touches && touches.item(0);
if (!touch) {
return;
}
const deltaX = Math.abs(touch.clientX - this.initialTouchX);
const deltaY = Math.abs(touch.clientY - this.initialTouchY);
const moveThreshold = 5;
this.userIsDragging = deltaX > moveThreshold || deltaY > moveThreshold;
};
onTouchEnd = (event: TouchEvent) => {
if (this.userIsDragging) return;
// close the menu if the user taps outside
// we're checking on event.target here instead of event.currentTarget, because we want to assert information
// on events on child elements, not the document (which we've attached this handler to).
if (
this.controlRef &&
!this.controlRef.contains(event.target) &&
this.menuListRef &&
!this.menuListRef.contains(event.target)
) {
this.blurInput();
}
// reset move vars
this.initialTouchX = 0;
this.initialTouchY = 0;
};
onControlTouchEnd = (event: SyntheticTouchEvent<HTMLElement>) => {
if (this.userIsDragging) return;
this.onControlMouseDown(event);
};
onClearIndicatorTouchEnd = (event: SyntheticTouchEvent<HTMLElement>) => {
if (this.userIsDragging) return;
this.onClearIndicatorMouseDown(event);
};
onDropdownIndicatorTouchEnd = (event: SyntheticTouchEvent<HTMLElement>) => {
if (this.userIsDragging) return;
this.onDropdownIndicatorMouseDown(event);
};
// ==============================
// Focus Handlers
// ==============================
handleInputChange = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
const inputValue = event.currentTarget.value;
this.setState({ inputIsHiddenAfterUpdate: false });
this.onInputChange(inputValue, { action: 'input-change' });
if (!this.props.menuIsOpen) {
this.onMenuOpen();
}
};
onInputFocus = (event: SyntheticFocusEvent<HTMLInputElement>) => {
if (this.props.onFocus) {
this.props.onFocus(event);
}
this.setState({
inputIsHiddenAfterUpdate: false,
isFocused: true,
});
if (this.openAfterFocus || this.props.openMenuOnFocus) {
this.openMenu('first');
}
this.openAfterFocus = false;
};
onInputBlur = (event: SyntheticFocusEvent<HTMLInputElement>) => {
if (this.menuListRef && this.menuListRef.contains(document.activeElement)) {
this.inputRef.focus();
return;
}
if (this.props.onBlur) {
this.props.onBlur(event);
}
this.onInputChange('', { action: 'input-blur' });
this.onMenuClose();
this.setState({
focusedValue: null,
isFocused: false,
});
};
onOptionHover = (focusedOption: OptionType) => {
if (this.blockOptionHover || this.state.focusedOption === focusedOption) {
return;
}
this.setState({ focusedOption });
};
shouldHideSelectedOptions = () => {
return shouldHideSelectedOptions(this.props);
};
// ==============================
// Keyboard Handlers
// ==============================
onKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const {
isMulti,
backspaceRemovesValue,
escapeClearsValue,
inputValue,
isClearable,
isDisabled,
menuIsOpen,
onKeyDown,
tabSelectsValue,
openMenuOnFocus,
} = this.props;
const { focusedOption, focusedValue, selectValue } = this.state;
if (isDisabled) return;
if (typeof onKeyDown === 'function') {
onKeyDown(event);
if (event.defaultPrevented) {
return;
}
}
// Block option hover events when the user has just pressed a key
this.blockOptionHover = true;
switch (event.key) {
case 'ArrowLeft':
if (!isMulti || inputValue) return;
this.focusValue('previous');
break;
case 'ArrowRight':
if (!isMulti || inputValue) return;
this.focusValue('next');
break;
case 'Delete':
case 'Backspace':
if (inputValue) return;
if (focusedValue) {
this.removeValue(focusedValue);
} else {
if (!backspaceRemovesValue) return;
if (isMulti) {
this.popValue();
} else if (isClearable) {
this.clearValue();
}
}
break;
case 'Tab':
if (this.isComposing) return;
if (
event.shiftKey ||
!menuIsOpen ||
!tabSelectsValue ||
!focusedOption ||
// don't capture the event if the menu opens on focus and the focused
// option is already selected; it breaks the flow of navigation
(openMenuOnFocus && this.isOptionSelected(focusedOption, selectValue))
) {
return;
}
this.selectOption(focusedOption);
break;
case 'Enter':
if (event.keyCode === 229) {
// ignore the keydown event from an Input Method Editor(IME)
// ref. https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
break;
}
if (menuIsOpen) {
if (!focusedOption) return;
if (this.isComposing) return;
this.selectOption(focusedOption);
break;
}
return;
case 'Escape':
if (menuIsOpen) {
this.setState({ inputIsHiddenAfterUpdate: false });
this.onInputChange('', { action: 'menu-close' });
this.onMenuClose();
} else if (isClearable && escapeClearsValue) {
this.clearValue();
}
break;
case ' ': // space
if (inputValue) {
return;
}
if (!menuIsOpen) {
this.openMenu('first');
break;
}
if (!focusedOption) return;
this.selectOption(focusedOption);
break;
case 'ArrowUp':
if (menuIsOpen) {
this.focusOption('up');
} else {
this.openMenu('last');
}
break;
case 'ArrowDown':
if (menuIsOpen) {
this.focusOption('down');
} else {
this.openMenu('first');
}
break;
case 'PageUp':
if (!menuIsOpen) return;
this.focusOption('pageup');
break;
case 'PageDown':
if (!menuIsOpen) return;
this.focusOption('pagedown');
break;
case 'Home':
if (!menuIsOpen) return;
this.focusOption('first');
break;
case 'End':
if (!menuIsOpen) return;
this.focusOption('last');
break;
default:
return;
}
event.preventDefault();
};
// ==============================
// Renderers
// ==============================
renderInput() {
const {
isDisabled,
isSearchable,
inputId,
inputValue,
tabIndex,
form,
} = this.props;
const { Input } = this.getComponents();
const { inputIsHidden } = this.state;
const { commonProps } = this;
const id = inputId || this.getElementId('input');
// aria attributes makes the JSX "noisy", separated for clarity
const ariaAttributes = {
'aria-autocomplete': 'list',
'aria-label': this.props['aria-label'],
'aria-labelledby': this.props['aria-labelledby'],
};
if (!isSearchable) {
// use a dummy input to maintain focus/blur functionality
return (
<DummyInput
id={id}
innerRef={this.getInputRef}
onBlur={this.onInputBlur}
onChange={noop}
onFocus={this.onInputFocus}
readOnly
disabled={isDisabled}
tabIndex={tabIndex}
form={form}
value=""
{...ariaAttributes}
/>
);
}
return (
<Input
{...commonProps}
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
id={id}
innerRef={this.getInputRef}
isDisabled={isDisabled}
isHidden={inputIsHidden}
onBlur={this.onInputBlur}
onChange={this.handleInputChange}
onFocus={this.onInputFocus}
spellCheck="false"
tabIndex={tabIndex}
form={form}
type="text"
value={inputValue}
{...ariaAttributes}
/>
);
}
renderPlaceholderOrValue(): ?PlaceholderOrValue {
const {
MultiValue,
MultiValueContainer,
MultiValueLabel,
MultiValueRemove,
SingleValue,
Placeholder,
} = this.getComponents();
const { commonProps } = this;
const {
controlShouldRenderValue,
isDisabled,
isMulti,
inputValue,
placeholder,
} = this.props;
const { selectValue, focusedValue, isFocused } = this.state;
if (!this.hasValue() || !controlShouldRenderValue) {
return inputValue ? null : (
<Placeholder
{...commonProps}
key="placeholder"
isDisabled={isDisabled}
isFocused={isFocused}
>
{placeholder}
</Placeholder>
);
}
if (isMulti) {
const selectValues: Array<any> = selectValue.map((opt, index) => {
const isOptionFocused = opt === focusedValue;
return (
<MultiValue
{...commonProps}
components={{
Container: MultiValueContainer,
Label: MultiValueLabel,
Remove: MultiValueRemove,
}}
isFocused={isOptionFocused}
isDisabled={isDisabled}
key={`${this.getOptionValue(opt)}${index}`}
index={index}
removeProps={{
onClick: () => this.removeValue(opt),
onTouchEnd: () => this.removeValue(opt),
onMouseDown: e => {
e.preventDefault();
e.stopPropagation();
},
}}
data={opt}
>
{this.formatOptionLabel(opt, 'value')}
</MultiValue>
);
});
return selectValues;
}
if (inputValue) {
return null;
}
const singleValue = selectValue[0];
return (
<SingleValue {...commonProps} data={singleValue} isDisabled={isDisabled}>
{this.formatOptionLabel(singleValue, 'value')}
</SingleValue>
);
}
renderClearIndicator() {
const { ClearIndicator } = this.getComponents();
const { commonProps } = this;
const { isDisabled, isLoading } = this.props;
const { isFocused } = this.state;
if (
!this.isClearable() ||
!ClearIndicator ||
isDisabled ||
!this.hasValue() ||
isLoading
) {
return null;
}
const innerProps = {
onMouseDown: this.onClearIndicatorMouseDown,
onTouchEnd: this.onClearIndicatorTouchEnd,
'aria-hidden': 'true',
};
return (
<ClearIndicator
{...commonProps}
innerProps={innerProps}
isFocused={isFocused}
/>
);
}
renderLoadingIndicator() {
const { LoadingIndicator } = this.getComponents();
const { commonProps } = this;
const { isDisabled, isLoading } = this.props;
const { isFocused } = this.state;
if (!LoadingIndicator || !isLoading) return null;
const innerProps = { 'aria-hidden': 'true' };
return (
<LoadingIndicator
{...commonProps}
innerProps={innerProps}
isDisabled={isDisabled}
isFocused={isFocused}
/>
);
}
renderIndicatorSeparator() {
const { DropdownIndicator, IndicatorSeparator } = this.getComponents();
// separator doesn't make sense without the dropdown indicator
if (!DropdownIndicator || !IndicatorSeparator) return null;
const { commonProps } = this;
const { isDisabled } = this.props;
const { isFocused } = this.state;
return (
<IndicatorSeparator
{...commonProps}
isDisabled={isDisabled}
isFocused={isFocused}
/>
);
}
renderDropdownIndicator() {
const { DropdownIndicator } = this.getComponents();
if (!DropdownIndicator) return null;
const { commonProps } = this;
const { isDisabled } = this.props;
const { isFocused } = this.state;
const innerProps = {
onMouseDown: this.onDropdownIndicatorMouseDown,
onTouchEnd: this.onDropdownIndicatorTouchEnd,
'aria-hidden': 'true',
};
return (
<DropdownIndicator
{...commonProps}
innerProps={innerProps}
isDisabled={isDisabled}
isFocused={isFocused}
/>
);
}
renderMenu() {
const {
Group,
GroupHeading,
Menu,
MenuList,
MenuPortal,
LoadingMessage,
NoOptionsMessage,
Option,
} = this.getComponents();
const { commonProps } = this;
const { focusedOption } = this.state;
const {
captureMenuScroll,
inputValue,
isLoading,
loadingMessage,
minMenuHeight,
maxMenuHeight,
menuIsOpen,
menuPlacement,
menuPosition,
menuPortalTarget,
menuShouldBlockScroll,
menuShouldScrollIntoView,
noOptionsMessage,
onMenuScrollToTop,
onMenuScrollToBottom,
} = this.props;
if (!menuIsOpen) return null;
// TODO: Internal Option Type here
const render = (props: OptionType, id: string) => {
const { type, data, isDisabled, isSelected, label, value } = props;
const isFocused = focusedOption === data;
const onHover = isDisabled ? undefined : () => this.onOptionHover(data);
const onSelect = isDisabled ? undefined : () => this.selectOption(data);
const optionId = `${this.getElementId('option')}-${id}`;
const innerProps = {
id: optionId,
onClick: onSelect,
onMouseMove: onHover,
onMouseOver: onHover,
tabIndex: -1,
};
return (
<Option
{...commonProps}
innerProps={innerProps}
data={data}
isDisabled={isDisabled}
isSelected={isSelected}
key={optionId}
label={label}
type={type}
value={value}
isFocused={isFocused}
innerRef={isFocused ? this.getFocusedOptionRef : undefined}
>
{this.formatOptionLabel(props.data, 'menu')}
</Option>
);
};
let menuUI;
if (this.hasOptions()) {
menuUI = this.getCategorizedOptions().map(item => {
if (item.type === 'group') {
const { data, options, index: groupIndex } = item;
const groupId = `${this.getElementId('group')}-${groupIndex}`;
const headingId = `${groupId}-heading`;
return (
<Group
{...commonProps}
key={groupId}
data={data}
options={options}
Heading={GroupHeading}
headingProps={{
id: headingId,
data: item.data,
}}
label={this.formatGroupLabel(item.data)}
>
{item.options.map(option =>
render(option, `${groupIndex}-${option.index}`)
)}
</Group>
);
} else if (item.type === 'option') {
return render(item, `${item.index}`);
}
});
} else if (isLoading) {
const message = loadingMessage({ inputValue });
if (message === null) return null;
menuUI = <LoadingMessage {...commonProps}>{message}</LoadingMessage>;
} else {
const message = noOptionsMessage({ inputValue });
if (message === null) return null;
menuUI = <NoOptionsMessage {...commonProps}>{message}</NoOptionsMessage>;
}
const menuPlacementProps = {
minMenuHeight,
maxMenuHeight,
menuPlacement,
menuPosition,
menuShouldScrollIntoView,
};
const menuElement = (
<MenuPlacer {...commonProps} {...menuPlacementProps}>
{({ ref, placerProps: { placement, maxHeight } }) => (
<Menu
{...commonProps}
{...menuPlacementProps}
innerRef={ref}
innerProps={{
onMouseDown: this.onMenuMouseDown,
onMouseMove: this.onMenuMouseMove,
}}
isLoading={isLoading}
placement={placement}
>
<ScrollManager
captureEnabled={captureMenuScroll}
onTopArrive={onMenuScrollToTop}
onBottomArrive={onMenuScrollToBottom}
lockEnabled={menuShouldBlockScroll}
>
{scrollTargetRef => (
<MenuList
{...commonProps}
innerRef={(instance: HTMLElement | null): void => {
this.getMenuListRef(instance);
scrollTargetRef(instance);
}}
isLoading={isLoading}
maxHeight={maxHeight}
focusedOption={focusedOption}
>
{menuUI}
</MenuList>
)}
</ScrollManager>
</Menu>
)}
</MenuPlacer>
);
// positioning behaviour is almost identical for portalled and fixed,
// so we use the same component. the actual portalling logic is forked
// within the component based on `menuPosition`
return menuPortalTarget || menuPosition === 'fixed' ? (
<MenuPortal
{...commonProps}
appendTo={menuPortalTarget}
controlElement={this.controlRef}
menuPlacement={menuPlacement}
menuPosition={menuPosition}
>
{menuElement}
</MenuPortal>
) : (
menuElement
);
}
renderFormField() {
const { delimiter, isDisabled, isMulti, name } = this.props;
const { selectValue } = this.state;
if (!name || isDisabled) return;
if (isMulti) {
if (delimiter) {
const value = selectValue
.map(opt => this.getOptionValue(opt))
.join(delimiter);
return <input name={name} type="hidden" value={value} />;
} else {
const input =
selectValue.length > 0 ? (
selectValue.map((opt, i) => (
<input
key={`i-${i}`}
name={name}
type="hidden"
value={this.getOptionValue(opt)}
/>
))
) : (
<input name={name} type="hidden" />
);
return <div>{input}</div>;
}
} else {
const value = selectValue[0] ? this.getOptionValue(selectValue[0]) : '';
return <input name={name} type="hidden" value={value} />;
}
}
renderLiveRegion() {
const { commonProps } = this;
const {
ariaSelection,
focusedOption,
focusedValue,
isFocused,
selectValue,
} = this.state;
const focusableOptions = this.getFocusableOptions();
return (
<LiveRegion
{...commonProps}
ariaSelection={ariaSelection}
focusedOption={focusedOption}
focusedValue={focusedValue}
isFocused={isFocused}
selectValue={selectValue}
focusableOptions={focusableOptions}
/>
);
}
render() {
const {
Control,
IndicatorsContainer,
SelectContainer,
ValueContainer,
} = this.getComponents();
const { className, id, isDisabled, menuIsOpen } = this.props;
const { isFocused } = this.state;
const commonProps = (this.commonProps = this.getCommonProps());
return (
<SelectContainer
{...commonProps}
className={className}
innerProps={{
id: id,
onKeyDown: this.onKeyDown,
}}
isDisabled={isDisabled}
isFocused={isFocused}
>
{this.renderLiveRegion()}
<Control
{...commonProps}
innerRef={this.getControlRef}
innerProps={{
onMouseDown: this.onControlMouseDown,
onTouchEnd: this.onControlTouchEnd,
}}
isDisabled={isDisabled}
isFocused={isFocused}
menuIsOpen={menuIsOpen}
>
<ValueContainer {...commonProps} isDisabled={isDisabled}>
{this.renderPlaceholderOrValue()}
{this.renderInput()}
</ValueContainer>
<IndicatorsContainer {...commonProps} isDisabled={isDisabled}>
{this.renderClearIndicator()}
{this.renderLoadingIndicator()}
{this.renderIndicatorSeparator()}
{this.renderDropdownIndicator()}
</IndicatorsContainer>
</Control>
{this.renderMenu()}
{this.renderFormField()}
</SelectContainer>
);
}
}
|
:: Command execute :: | |
--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0063 ]-- |