wix-style-react
Version:
wix-style-react
706 lines • 30.5 kB
JavaScript
import PropTypes from 'prop-types';
import React from 'react';
import Loader from '../Loader/Loader';
import InfiniteScroll from '../utils/InfiniteScroll';
import scrollIntoView from '../utils/scrollIntoView';
import { DATA_HOOKS, DATA_OPTION, DATA_SHOWN, DATA_DIRECTION, DROPDOWN_LAYOUT_DIRECTIONS, OPTION_DATA_HOOKS, DROPDOWN_LAYOUT_LOADER, DATA_SELECTED_OPTION_ID, } from './DataAttr';
import { st, classes } from './DropdownLayout.st.css';
import { filterObject } from '../utils/filterObject';
import ReactDOM from 'react-dom';
import { listItemSectionBuilder } from '../ListItemSection';
import { listItemSelectBuilder } from '../ListItemSelect';
import { listItemActionBuilder } from '../ListItemAction';
import { isString } from '../utils/StringUtils';
const MOUSE_EVENTS_SUPPORTED = ['mouseup', 'touchend'];
const KEY = {
arrowLeft: 'ArrowLeft',
arrowUp: 'ArrowUp',
arrowRight: 'ArrowRight',
arrowDown: 'ArrowDown',
escape: 'Escape',
tab: 'Tab',
enter: 'Enter',
space: ' ',
home: 'Home',
end: 'End',
};
const ListType = {
action: 'action',
select: 'select',
};
const modulu = (n, m) => {
const remain = n % m;
return remain >= 0 ? remain : remain + m;
};
const getUnit = value => (isString(value) ? value : `${value}px`);
const NOT_HOVERED_INDEX = -1;
export const DIVIDER_OPTION_VALUE = '-';
class DropdownLayout extends React.PureComponent {
constructor(props) {
super(props);
this.focusableItemsIdsList = [];
this.savedOnClicks = [];
this.children = {};
// Deprecated
this._onMouseEventsHandler = e => {
if (!this._checkIfEventOnElements(e, [ReactDOM.findDOMNode(this)])) {
this._onClickOutside(e);
}
};
this._onClickOutside = event => {
const { visible, onClickOutside } = this.props;
if (visible && onClickOutside) {
onClickOutside(event);
}
};
this._onSelect = (index, e) => {
const { options, onSelect, listType } = this.props;
if (listType !== ListType.select) {
e.stopPropagation();
this._onClose();
return;
}
const chosenOption = options[index];
if (chosenOption) {
const sameOptionWasPicked = chosenOption.id === this.state.selectedId;
if (onSelect) {
e.stopPropagation();
onSelect(chosenOption, sameOptionWasPicked);
}
}
if (!this._isControlled()) {
this.setState({ selectedId: chosenOption && chosenOption.id });
}
return !!onSelect && chosenOption;
};
this._onActionClick = e => {
const onClick = this.savedOnClicks.find(({ id }) => id === e.id).onClick;
onClick && onClick(e);
};
this._saveOnClicks = () => {
this.savedOnClicks = this.props.options.map(({ id, onClick }) => ({
id,
onClick,
}));
};
this._onMouseEnter = index => {
if (this._isSelectableOption(this.props.options[index])) {
this._markOption(index);
}
};
this._onMouseLeave = () => this._markOption(NOT_HOVERED_INDEX);
this._focusOnOption = () => {
const { focusOnOption, options } = this.props;
const markedIndex = options.findIndex(option => option.id === focusOnOption);
if (markedIndex !== -1) {
this._markOptionAtIndex(markedIndex);
}
else {
// Remove focus
this._markOption(markedIndex);
}
};
this._markOptionAtIndex = markedIndex => {
const { infiniteScroll } = this.props;
this._markOption(markedIndex);
const menuElement = this.options;
const hoveredElement = infiniteScroll
? this.options.childNodes[0].childNodes[markedIndex]
: this.options.childNodes[markedIndex];
scrollIntoView(menuElement, hoveredElement);
};
/**
* Handle keydown events for the DropdownLayout, mostly for accessibility
*
* @param {SyntheticEvent} event - The keydown event triggered by React
* @returns {boolean} - Whether the event was handled by the component
*/
this._onSelectListKeyDown = event => {
if (!this.props.visible ||
this.props.isComposing ||
this.props.listType !== ListType.select) {
return false;
}
switch (event.key) {
case KEY.arrowDown: {
this._markNextStep(1);
event.preventDefault();
break;
}
case KEY.arrowUp: {
this._markNextStep(-1);
event.preventDefault();
break;
}
case KEY.space:
case KEY.enter: {
if (!this._onSelect(this.state.hovered, event)) {
return false;
}
break;
}
case KEY.tab: {
if (this.props.closeOnSelect) {
return this._onSelect(this.state.hovered, event);
}
else {
if (this._onSelect(this.state.hovered, event)) {
event.preventDefault();
return true;
}
else {
return false;
}
}
}
case KEY.escape: {
this._onClose();
break;
}
default: {
return false;
}
}
event.stopPropagation();
return true;
};
this._focus = (focusedItemId, e) => {
e && e.preventDefault();
const element = this.children[focusedItemId];
if (!element) {
return;
}
const native = element.focus;
const focusableHOC = element.wrappedComponentRef;
const callback = native
? element.focus
: focusableHOC
? focusableHOC.innerComponentRef.focus
: () => ({});
this.setState({ focusedItemId }, () => callback());
};
this._handleActionListNavigation = (event, id) => {
const length = this.focusableItemsIdsList.length;
let focusedItemId = this.state.focusedItemId;
const { key } = event;
const currentMenuItemIndex = this.focusableItemsIdsList.indexOf(id);
const firstMenuItem = this.focusableItemsIdsList[0];
const lastMenuItem = this.focusableItemsIdsList[length - 1];
if (key === KEY.arrowLeft || key === KEY.arrowUp) {
focusedItemId =
id === 0
? lastMenuItem
: this.focusableItemsIdsList[currentMenuItemIndex - 1];
}
if (key === KEY.arrowRight || key === KEY.arrowDown) {
focusedItemId =
currentMenuItemIndex === length - 1
? firstMenuItem
: this.focusableItemsIdsList[currentMenuItemIndex + 1];
}
if (key === KEY.home) {
focusedItemId = firstMenuItem;
}
if (key === KEY.end) {
focusedItemId = lastMenuItem;
}
if (focusedItemId !== this.state.focusedItemId) {
this._focus(focusedItemId, event);
}
};
this._onActionListKeyDown = (event, id) => {
if (this.props.listType !== ListType.action) {
return;
}
const { key } = event;
if (key === KEY.space || key === KEY.enter) {
event.preventDefault();
this._onActionClick({ id: this.state.focusedItemId });
this._onClose();
}
else if (key === KEY.escape || key === KEY.tab) {
this._onClose();
}
else {
this._handleActionListNavigation(event, id);
}
event.stopPropagation();
};
this._onClose = () => {
this._markOption(NOT_HOVERED_INDEX);
if (this.props.onClose) {
this.props.onClose();
}
};
this._wrapWithInfiniteScroll = scrollableElement => {
if (!this.options) {
this.loadedWithUndefinedOptions = true;
}
return (React.createElement(InfiniteScroll, { useWindow: true, dataHook: DATA_HOOKS.INFINITE_SCROLL_CONTAINER, scrollElement: this.options, loadMore: this.props.loadMore, hasMore: this.props.hasMore, data: this.props.options, loader: React.createElement("div", { className: classes.loader },
React.createElement(Loader, { dataHook: DROPDOWN_LAYOUT_LOADER, size: "small" })) }, scrollableElement));
};
/** for testing purposes only */
this._getDataAttributes = () => {
const { visible, dropDirectionUp } = this.props;
const { selectedId } = this.state;
return filterObject({
'data-hook': DATA_HOOKS.CONTENT_CONTAINER,
[DATA_SHOWN]: visible,
[DATA_SELECTED_OPTION_ID]: selectedId === 0 ? `${selectedId}` : selectedId,
[DATA_DIRECTION]: dropDirectionUp
? DROPDOWN_LAYOUT_DIRECTIONS.UP
: DROPDOWN_LAYOUT_DIRECTIONS.DOWN,
}, (key, value) => !!value);
};
// For testing purposes only
this._getItemDataAttr = ({ hovered, selected, disabled }) => {
const { itemHeight, selectedHighlight } = this.props;
return filterObject({
[DATA_OPTION.DISABLED]: disabled,
[DATA_OPTION.SELECTED]: selected && selectedHighlight,
[DATA_OPTION.HOVERED]: hovered,
/* deprecated */
[DATA_OPTION.SIZE]: itemHeight,
}, (key, value) => !!value);
};
this.containerRef = React.createRef();
this.state = {
hovered: NOT_HOVERED_INDEX,
selectedId: props.selectedId,
focusedItemId: null,
};
}
componentDidMount() {
if (this.loadedWithUndefinedOptions && this.options) {
this.forceUpdate();
}
const { focusOnSelectedOption, scrollToOption, autoFocus } = this.props;
if (focusOnSelectedOption) {
this._focusOnSelectedOption();
}
else if (this.props.hasOwnProperty('focusOnOption')) {
this._focusOnOption();
}
if (scrollToOption) {
this._scrollToOption();
}
this._markOptionByProperty(this.props);
// Deprecated
MOUSE_EVENTS_SUPPORTED.forEach(eventName => {
document.addEventListener(eventName, this._onMouseEventsHandler, true);
});
this._boundEvents = MOUSE_EVENTS_SUPPORTED;
if (autoFocus) {
this._focusFirstOption();
}
}
componentWillUnmount() {
if (this._boundEvents && typeof document !== 'undefined') {
this._boundEvents.forEach(eventName => {
document.removeEventListener(eventName, this._onMouseEventsHandler, true);
});
}
}
componentDidUpdate(prevProps) {
const { focusOnOption } = this.props;
if (prevProps.focusOnOption !== focusOnOption) {
this._focusOnOption();
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.visible !== nextProps.visible) {
this._markOption(NOT_HOVERED_INDEX);
}
if (this.props.selectedId !== nextProps.selectedId) {
this.setState({ selectedId: nextProps.selectedId });
}
// make sure the same item is hovered if options changed
if (this.state.hovered !== NOT_HOVERED_INDEX &&
(!nextProps.options[this.state.hovered] ||
this.props.options[this.state.hovered].id !==
nextProps.options[this.state.hovered].id)) {
this._markOption(this._findIndex(nextProps.options, item => item.id === this.props.options[this.state.hovered].id));
}
this._markOptionByProperty(nextProps);
}
_focusFirstOption() {
this._focus(this.focusableItemsIdsList[0]);
}
// Deprecated
_checkIfEventOnElements(e, elem) {
let current = e.target;
while (current.parentNode) {
if (elem.indexOf(current) > -1) {
return true;
}
current = current.parentNode;
}
return current !== document;
}
// Deprecated
_renderTopArrow() {
const { withArrow, visible } = this.props;
return withArrow && visible ? (React.createElement("div", { "data-hook": DATA_HOOKS.TOP_ARROW, className: classes.arrow })) : null;
}
_convertOptionToListItemSectionBuilder({ option, idx }) {
const { value, id, title: isTitle } = option;
if (value === DIVIDER_OPTION_VALUE) {
return listItemSectionBuilder({
dataHook: OPTION_DATA_HOOKS.DIVIDER,
id: id || idx,
type: 'divider',
});
}
if (isTitle) {
return listItemSectionBuilder({
dataHook: OPTION_DATA_HOOKS.TITLE,
id,
type: 'subheader',
title: value,
});
}
}
_convertOptionToListItemActionBuilder({ option, idx }) {
const { id, value, disabled, optionTitle, title, ...rest } = option;
return listItemActionBuilder({
id: id !== undefined ? id : idx,
ref: ref => (this.children[id] = ref),
tabIndex: id === this.state.focusedItemId && !disabled ? '0' : '-1',
disabled,
title: optionTitle,
role: 'menuitem',
...rest,
});
}
_isControlled() {
return (typeof this.props.selectedId !== 'undefined' &&
typeof this.props.onSelect !== 'undefined');
}
_focusOnSelectedOption() {
if (this.selectedOption) {
this.options.scrollTop = Math.max(this.selectedOption.offsetTop - this.selectedOption.offsetHeight, 0);
}
}
_setSelectedOptionNode(optionNode, option) {
if (option.id === this.state.selectedId) {
this.selectedOption = optionNode;
}
}
_markOption(index, options) {
const { onOptionMarked } = this.props;
options = options || this.props.options;
this.setState({ hovered: index });
onOptionMarked && onOptionMarked(options[index] || null);
}
_getMarkedIndex() {
const { options } = this.props;
const useHoverIndex = this.state.hovered > NOT_HOVERED_INDEX;
const useSelectedIdIndex = typeof this.state.selectedId !== 'undefined';
let markedIndex;
if (useHoverIndex) {
markedIndex = this.state.hovered;
}
else if (useSelectedIdIndex) {
markedIndex = options.findIndex(option => option.id === this.state.selectedId);
}
else {
markedIndex = NOT_HOVERED_INDEX;
}
return markedIndex;
}
_markNextStep(step) {
const { options } = this.props;
if (!options.some(this._isSelectableOption)) {
return;
}
let markedIndex = this._getMarkedIndex();
do {
markedIndex = Math.abs(modulu(Math.max(markedIndex + step, -1), options.length));
} while (!this._isSelectableOption(options[markedIndex]));
this._markOptionAtIndex(markedIndex);
}
_scrollToOption() {
const { scrollToOption, options } = this.props;
const optionIndex = options.findIndex(option => option.id === scrollToOption);
const optionNode = this.options.childNodes[optionIndex];
if (!optionNode) {
return;
}
this.options.scrollTop = Math.max(optionNode.offsetTop - optionNode.offsetHeight, 0);
}
_renderNode(node) {
return node ? React.createElement("div", null, node) : null;
}
_convertCustomOptionToBuilder({ option }) {
const { value, id, disabled, overrideOptionStyle, overrideStyle } = option;
if (overrideStyle) {
return {
id,
disabled,
overrideStyle,
value: () => React.createElement("div", { "data-hook": DATA_HOOKS.OPTION }, value),
};
}
if (overrideOptionStyle) {
return {
id,
disabled,
overrideOptionStyle,
value: () => React.createElement("div", { "data-hook": DATA_HOOKS.OPTION }, value),
};
}
}
_convertOptionToListItemSelectBuilder({ option }) {
const { value, id, disabled } = option;
const { selectedId } = this.state;
const { itemHeight, selectedHighlight } = this.props;
return listItemSelectBuilder({
id,
title: React.createElement("div", { "data-hook": DATA_HOOKS.OPTION }, value),
disabled,
selected: id === selectedId && selectedHighlight,
className: st(classes.selectableOption, { itemHeight }),
});
}
_isBuilderOption({ option }) {
const { value } = option;
return typeof value === 'function';
}
_isCustomOption({ option }) {
const { overrideOptionStyle, overrideStyle } = option;
return overrideOptionStyle || overrideStyle;
}
_isActionOption({ option }) {
return option.value === ListType.action;
}
_isItemSection({ option }) {
const { value, title: isTitle } = option;
return value === DIVIDER_OPTION_VALUE || isTitle;
}
_convertOptionToBuilder(option, idx) {
if (this._isBuilderOption({ option })) {
return option;
}
else if (this._isActionOption({ option })) {
return this._convertOptionToListItemActionBuilder({ option, idx });
}
else if (this._isItemSection({ option })) {
return this._convertOptionToListItemSectionBuilder({ option, idx });
}
else if (this._isCustomOption({ option })) {
return this._convertCustomOptionToBuilder({ option });
}
else {
return this._convertOptionToListItemSelectBuilder({ option });
}
}
_renderOption({ option, idx }) {
const builderOption = this._convertOptionToBuilder(option, idx);
const content = this._renderOptionContent({
option: builderOption,
idx,
hasLink: !!option.linkTo,
});
const isActionItem = this.props.listType === ListType.action;
return option.linkTo ? (React.createElement("a", { className: classes.linkItem, key: idx, "data-hook": DATA_HOOKS.LINK_ITEM, href: option.linkTo, role: isActionItem ? undefined : 'option', "aria-selected": isActionItem || option.disabled
? undefined
: option.id === this.state.selectedId, "aria-hidden": option.disabled }, content)) : (content);
}
_renderOptionContent({ option, idx, hasLink }) {
const { itemHeight, selectedHighlight, listType } = this.props;
const { selectedId, hovered } = this.state;
const { id, disabled, overrideStyle, overrideOptionStyle } = option;
const optionState = {
selected: id === selectedId,
hovered: idx === hovered,
disabled,
};
if (!disabled) {
this.focusableItemsIdsList = [...this.focusableItemsIdsList, id];
}
const isActionItem = listType === ListType.action;
return (React.createElement("div", { "aria-selected": hasLink || isActionItem || disabled ? undefined : optionState.selected, "aria-hidden": disabled, ...this._getItemDataAttr({ ...optionState }), role: hasLink || isActionItem ? undefined : 'option', className: overrideOptionStyle
? null
: st(classes.option, {
...optionState,
selected: optionState.selected && selectedHighlight,
itemHeight,
overrideStyle,
}), ref: node => this._setSelectedOptionNode(node, option), onClick: !disabled ? e => this._onSelect(idx, e) : null, key: idx, onMouseEnter: () => this._onMouseEnter(idx), onMouseLeave: this._onMouseLeave, "data-hook": `dropdown-item-${id}`, onKeyDown: e => this._onActionListKeyDown(e, id) }, option.value(optionState)));
}
_markOptionByProperty(props) {
if (this.state.hovered === NOT_HOVERED_INDEX && props.markedOption) {
const selectableOptions = props.options.filter(this._isSelectableOption);
if (selectableOptions.length) {
const idToMark = props.markedOption === true
? selectableOptions[0].id
: props.markedOption;
this._markOption(this._findIndex(props.options, item => item.id === idToMark), props.options);
}
}
}
_findIndex(arr, predicate) {
return (Array.isArray(arr) ? arr : []).findIndex(predicate);
}
_isSelectableOption(option) {
return (option &&
option.value !== DIVIDER_OPTION_VALUE &&
!option.disabled &&
!option.title);
}
_renderOptions() {
this.focusableItemsIdsList = [];
this._saveOnClicks();
return this.props.options.map((option, idx) => this._renderOption({ option, idx }));
}
render() {
const { className, visible, dropDirectionUp, tabIndex, onMouseEnter, onMouseLeave, onMouseDown, fixedHeader, withArrow, fixedFooter, inContainer, overflow, maxHeightPixels, minWidthPixels, infiniteScroll, dataHook, listType, } = this.props;
const renderedOptions = this._renderOptions();
return (React.createElement("div", { "data-list-type": listType, "data-hook": dataHook, className: st(classes.root, {
visible,
withArrow,
direction: dropDirectionUp
? DROPDOWN_LAYOUT_DIRECTIONS.UP
: DROPDOWN_LAYOUT_DIRECTIONS.DOWN,
containerStyles: !inContainer,
}, className), tabIndex: tabIndex, onKeyDown: this._onSelectListKeyDown, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, ref: this.containerRef },
React.createElement("div", { ...this._getDataAttributes(), className: classes.contentContainer, style: {
overflow,
maxHeight: getUnit(maxHeightPixels),
minWidth: getUnit(minWidthPixels),
} },
this._renderNode(fixedHeader),
React.createElement("div", { className: classes.options, style: {
maxHeight: getUnit(parseInt(maxHeightPixels, 10) - 35),
overflow,
}, ref: _options => (this.options = _options), "data-hook": DATA_HOOKS.DROPDOWN_LAYOUT_OPTIONS, role: listType === ListType.select ? 'listbox' : 'menu' }, infiniteScroll
? this._wrapWithInfiniteScroll(renderedOptions)
: renderedOptions),
this._renderNode(fixedFooter)),
this._renderTopArrow()));
}
}
const optionPropTypes = PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func])
.isRequired,
disabled: PropTypes.bool,
/** @deprecated*/
overrideStyle: PropTypes.bool,
/** @deprecated*/
title: PropTypes.bool,
overrideOptionStyle: PropTypes.bool,
/* the string displayed within the input when the option is selected */
label: PropTypes.string,
});
export function optionValidator(props, propName, componentName) {
const option = props[propName];
// Notice: We don't use Proptypes.oneOf() to check for either option OR divider, because then the failure message would be less informative.
if (typeof option === 'object' && option.value === DIVIDER_OPTION_VALUE) {
return;
}
const optionError = PropTypes.checkPropTypes({ option: optionPropTypes }, { option }, 'option', componentName);
if (optionError) {
return optionError;
}
}
DropdownLayout.propTypes = {
/** A single CSS class name to be appended to the root element. */
className: PropTypes.string,
/** @deprecated */
dropDirectionUp: PropTypes.bool,
/** Scroll view to the selected option on opening the dropdown */
focusOnSelectedOption: PropTypes.bool,
/** Callback function called whenever the user press the `Escape` keyboard.*/
onClose: PropTypes.func,
/** Callback function called whenever the user selects a different option in the list */
onSelect: PropTypes.func,
/** Callback function called whenever an option becomes focused (hovered/active). Receives the relevant option object from the original props.options array. */
onOptionMarked: PropTypes.func,
/** Set overflow of container */
overflow: PropTypes.string,
/** Should show or hide the component */
visible: PropTypes.bool,
/** Array of objects:
* - id `<string / number>` *required*: the id of the option, should be unique.
* - value `<function / string / node>` *required*: can be a string, react element or a builder function.
* - disabled `<bool>` *default value- false*: whether this option is disabled or not
* - linkTo `<string>`: when provided the option will be an anchor to the given value
* - title `<bool>` *default value- false* **deprecated**: please use `listItemSectionBuilder` for rendering a title.
* - overrideStyle `<bool>` *default value- false* **deprecated**: please use `overrideOptionStyle` for override option styles.
* - overrideOptionStyle `<bool>` *default value- false* - when set to `true`, the option will be responsible to its own styles. No styles will be applied from the DropdownLayout itself.
* - label `<string>`: the string displayed within an input when the option is selected. This is used when using `<DropdownLayout/>` with an `<Input/>`.
*/
options: PropTypes.arrayOf(optionValidator),
/** The id of the selected option in the list */
selectedId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Specifies the tab order of the component. */
tabIndex: PropTypes.number,
/** @deprecated Do not use this prop. */
onClickOutside: PropTypes.func,
/** A fixed header to the list */
fixedHeader: PropTypes.node,
/** A fixed footer to the list */
fixedFooter: PropTypes.node,
/** Set the max height of the dropdownLayout in pixels */
maxHeightPixels: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Set the min width of the dropdownLayout in pixels */
minWidthPixels: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** @deprecated Do not use this prop. */
withArrow: PropTypes.bool,
/** Closes DropdownLayout on option selection */
closeOnSelect: PropTypes.bool,
/** Callback function called whenever the user entered with the mouse to the dropdown layout.*/
onMouseEnter: PropTypes.func,
/** Callback function called whenever the user exited with the mouse from the dropdown layout.*/
onMouseLeave: PropTypes.func,
/** @deprecated Do not use this prop. */
itemHeight: PropTypes.oneOf(['small', 'big']),
/** Whether the selected option will be highlighted when dropdown reopened. */
selectedHighlight: PropTypes.bool,
/** Whether the `<DropdownLayout/>` is in a container component. If `true`, some styles such as shadows, positioning and padding will be added the the component contentContainer. */
inContainer: PropTypes.bool,
/** Set this prop for lazy loading of the dropdown layout items.*/
infiniteScroll: PropTypes.bool,
/** A callback called when more items are requested to be rendered. */
loadMore: PropTypes.func,
/** Whether there are more items to be loaded. */
hasMore: PropTypes.bool,
/** Sets the default hover behavior when:
* 1. `false` means no default
* 2. `true` means to hover the first selectable option
* 3. Any number/string represents the id of option to hover
*/
markedOption: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
PropTypes.number,
]),
/** Marks (not selects) and scrolls view to the option on opening the dropdown by option id */
focusOnOption: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Scrolls to the specified option when dropdown is opened without marking it */
scrollToOption: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** Defines type of behavior applied in list */
listType: PropTypes.oneOf([ListType.action, ListType.select]),
/** Specifies whether first list item should be focused */
autoFocus: PropTypes.bool,
};
DropdownLayout.defaultProps = {
options: [],
tabIndex: 0,
maxHeightPixels: 260,
closeOnSelect: true,
itemHeight: 'small',
selectedHighlight: true,
inContainer: false,
infiniteScroll: false,
loadMore: null,
hasMore: false,
markedOption: false,
overflow: 'auto',
listType: ListType.select,
};
DropdownLayout.displayName = 'DropdownLayout';
DropdownLayout.NONE_SELECTED_ID = NOT_HOVERED_INDEX;
export default DropdownLayout;
//# sourceMappingURL=DropdownLayout.js.map