react-responsive-combo-box
Version:
Easy and Responsive Combo-box
410 lines (360 loc) • 13.3 kB
JavaScript
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var initialState = {
isFocus: false,
focusIndex: -1
};
var focusReducer = function focusReducer(state, action) {
switch (action.type) {
case 'setFocusIndex':
return _extends({}, state, {
focusIndex: action.focusIndex
});
case 'toggleFocus':
return _extends({}, state, {
isFocus: action.isFocus
});
default:
return state;
}
};
var styles = {"comboBox":"_3tcyg","comboBoxInput":"_3LDgJ","comboBoxPopover":"_WbEAz","comboBoxList":"_r3jpc","comboBoxOption":"_2iQTD","rightElement":"_1GXAI","leftElement":"_2e4AG"};
var useScroll = function useScroll(focusedIndex, scrollableContainer, listContainer) {
React.useEffect(function () {
if (listContainer.current && scrollableContainer.current && focusedIndex >= 0) {
if (focusedIndex === 0) scrollableContainer.current.scrollTo({
top: 0
});
var children = listContainer.current.childNodes;
var focusedChild = children && children.length ? children[focusedIndex] : null;
if (focusedChild && focusedChild.getBoundingClientRect) {
var _focusedChild$getBoun = focusedChild.getBoundingClientRect(),
optionHeight = _focusedChild$getBoun.height;
var _scrollableContainer$ = scrollableContainer.current.getBoundingClientRect(),
listHeight = _scrollableContainer$.height;
var scrollTop = scrollableContainer.current.scrollTop;
var isAbove = focusedChild.offsetTop <= scrollTop;
var isInView = focusedChild.offsetTop >= scrollTop && focusedChild.offsetTop + optionHeight <= scrollTop + listHeight;
if (!isInView) {
if (isAbove) {
scrollableContainer.current.scrollTo({
top: focusedChild.offsetTop
});
} else {
scrollableContainer.current.scrollTo({
top: focusedChild.offsetTop - listHeight + optionHeight
});
}
}
}
}
}, [focusedIndex, listContainer, scrollableContainer]);
};
var UP_ARROW = 38;
var DOWN_ARROW = 40;
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
var ComboBox = function ComboBox(_ref) {
var comboBoxOptions = _ref.options,
onChange = _ref.onChange,
defaultValue = _ref.defaultValue,
placeholder = _ref.placeholder,
onSelect = _ref.onSelect,
onOptionsChange = _ref.onOptionsChange,
optionsListMaxHeight = _ref.optionsListMaxHeight,
renderOptions = _ref.renderOptions,
style = _ref.style,
inputClassName = _ref.inputClassName,
className = _ref.className,
listClassName = _ref.listClassName,
optionsClassName = _ref.optionsClassName,
popoverClassName = _ref.popoverClassName,
highlightColor = _ref.highlightColor,
selectedOptionColor = _ref.selectedOptionColor,
enableAutocomplete = _ref.enableAutocomplete,
inputStyles = _ref.inputStyles,
name = _ref.name,
onBlur = _ref.onBlur,
_ref$editable = _ref.editable,
editable = _ref$editable === void 0 ? true : _ref$editable,
renderRightElement = _ref.renderRightElement,
renderLeftElement = _ref.renderLeftElement;
var optionMaxHeight = optionsListMaxHeight || 200;
var suggestionListPositionStyles = {};
var _useState = React.useState(comboBoxOptions),
options = _useState[0],
setOptions = _useState[1];
var _useState2 = React.useState(defaultValue || ''),
inputValue = _useState2[0],
setInputValue = _useState2[1];
var _useReducer = React.useReducer(focusReducer, initialState),
state = _useReducer[0],
dispatch = _useReducer[1];
var isFocus = state.isFocus,
focusIndex = state.focusIndex;
var _useState3 = React.useState(false),
isMouseInsideOptions = _useState3[0],
setIsMouseInsideOptions = _useState3[1];
var _useState4 = React.useState(false),
IsOptionsPositionedTop = _useState4[0],
setIsOptionsPositionedTop = _useState4[1];
var _useState5 = React.useState(-1),
selectedOptionIndex = _useState5[0],
setSelectedOptionIndex = _useState5[1];
var dropdownRef = React.useRef(null);
var optionsListRef = React.useRef(null);
React.useEffect(function () {
setOptions(comboBoxOptions);
}, [comboBoxOptions]);
React.useEffect(function () {
if (!isFocus) setInputValue(defaultValue || '');
dispatch({
type: 'setFocusIndex',
focusIndex: defaultValue ? options.indexOf(defaultValue.toString()) : -1
});
setSelectedOptionIndex(defaultValue ? options.indexOf(defaultValue.toString()) : -1);
}, [defaultValue]);
useScroll(focusIndex, dropdownRef, optionsListRef);
React.useEffect(function () {
var _optionsContainerElem, _optionsContainerElem2;
var optionsContainerElement = dropdownRef.current;
var offsetBottom = window.innerHeight - (optionsContainerElement === null || optionsContainerElement === void 0 ? void 0 : (_optionsContainerElem = optionsContainerElement.offsetParent) === null || _optionsContainerElem === void 0 ? void 0 : _optionsContainerElem.getBoundingClientRect().top);
if (optionMaxHeight > offsetBottom && (optionsContainerElement === null || optionsContainerElement === void 0 ? void 0 : (_optionsContainerElem2 = optionsContainerElement.offsetParent) === null || _optionsContainerElem2 === void 0 ? void 0 : _optionsContainerElem2.getBoundingClientRect().top) > offsetBottom) {
setIsOptionsPositionedTop(true);
} else {
setIsOptionsPositionedTop(false);
}
}, [isFocus]);
if (IsOptionsPositionedTop) suggestionListPositionStyles = {
bottom: '100%',
marginBottom: '5px'
};else suggestionListPositionStyles = {
top: '100%',
marginTop: '5px'
};
var blurHandler = function blurHandler(event) {
if (!isMouseInsideOptions) dispatch({
type: 'toggleFocus',
isFocus: false
});
if (onBlur) onBlur(event);
};
var updateValue = function updateValue(index) {
if (index === void 0) {
index = focusIndex;
}
if (index !== -1) {
setInputValue(options[index]);
if (onOptionsChange) onOptionsChange(options[index]);
}
};
var resetFocusIndex = function resetFocusIndex() {
comboBoxOptions.forEach(function (option, index) {
if (option === options[focusIndex]) dispatch({
type: 'setFocusIndex',
focusIndex: index
});
});
};
var selectSuggestionHandler = function selectSuggestionHandler() {
updateValue();
dispatch({
type: 'toggleFocus',
isFocus: false
});
setSelectedOptionIndex(focusIndex);
resetFocusIndex();
setOptions(comboBoxOptions);
if (onSelect) onSelect(options[focusIndex]);
};
var keyHandler = function keyHandler(event) {
var optionsContainerElement = dropdownRef.current;
var newFocusIndex = focusIndex;
switch (event.keyCode) {
case DOWN_ARROW:
{
event.preventDefault();
if (!isFocus) {
dispatch({
type: 'toggleFocus',
isFocus: true
});
} else {
if (focusIndex >= options.length - 1) {
newFocusIndex = 0;
optionsContainerElement.scrollTop = 0;
} else {
newFocusIndex = focusIndex + 1;
}
}
dispatch({
type: 'setFocusIndex',
focusIndex: newFocusIndex
});
if (onOptionsChange) onOptionsChange(options[newFocusIndex]);
dropdownRef.current = optionsContainerElement;
break;
}
case UP_ARROW:
{
event.preventDefault();
if (!isFocus) {
dispatch({
type: 'toggleFocus',
isFocus: true
});
} else {
if (focusIndex <= 0) {
newFocusIndex = options.length - 1;
if (optionsContainerElement) optionsContainerElement.scrollTop = optionsContainerElement.scrollHeight;
} else {
newFocusIndex = focusIndex - 1;
}
}
dispatch({
type: 'setFocusIndex',
focusIndex: newFocusIndex
});
if (onOptionsChange) onOptionsChange(options[newFocusIndex]);
dropdownRef.current = optionsContainerElement;
break;
}
case ENTER_KEY:
{
event.preventDefault();
if (focusIndex > -1 && focusIndex < options.length) selectSuggestionHandler();
break;
}
case ESCAPE_KEY:
{
event.target.blur();
dispatch({
type: 'toggleFocus',
isFocus: false
});
break;
}
}
};
var filterSuggestion = function filterSuggestion(filterText) {
if (filterText.length === 0) setOptions(comboBoxOptions);else {
var filteredSuggestion = comboBoxOptions.filter(function (option) {
return option.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
});
setOptions(filteredSuggestion);
}
};
var inputChangeHandler = function inputChangeHandler(event) {
if (onChange) onChange(event);
setInputValue(event.target.value);
if (enableAutocomplete) filterSuggestion(event.target.value);
};
var inputClickHandler = function inputClickHandler() {
dispatch({
type: 'toggleFocus',
isFocus: true
});
dispatch({
type: 'setFocusIndex',
focusIndex: options.indexOf(inputValue.toString())
});
};
var focusHandler = function focusHandler() {
dispatch({
type: 'toggleFocus',
isFocus: true
});
};
var mouseEnterHandler = function mouseEnterHandler(index) {
dispatch({
type: 'setFocusIndex',
focusIndex: index
});
if (onOptionsChange) onOptionsChange(options[index]);
};
var backgroundColorSelector = function backgroundColorSelector(optionIndex) {
if (optionIndex === focusIndex && optionIndex === selectedOptionIndex) return {
backgroundColor: selectedOptionColor || '#63b3ed'
};else if (optionIndex === focusIndex) {
return {
backgroundColor: highlightColor || '#bee3f8'
};
} else if (optionIndex === selectedOptionIndex) {
return {
backgroundColor: selectedOptionColor || '#63b3ed'
};
} else return {};
};
return React__default.createElement("div", {
className: className ? styles.comboBox + " " + className : styles.comboBox,
style: style
}, renderLeftElement && React__default.createElement("div", {
className: styles.leftElement
}, renderLeftElement()), React__default.createElement("input", {
onFocus: focusHandler,
onChange: inputChangeHandler,
placeholder: placeholder || '',
onKeyDown: keyHandler,
value: inputValue,
className: inputClassName ? styles.comboBoxInput + " " + inputClassName : styles.comboBoxInput,
onBlur: blurHandler,
name: name,
style: _extends({}, inputStyles, {
cursor: editable ? 'text' : 'pointer',
paddingLeft: renderLeftElement ? 30 : 10
}),
readOnly: !editable,
onClick: inputClickHandler
}), renderRightElement && React__default.createElement("div", {
className: styles.rightElement
}, renderRightElement()), React__default.createElement("div", {
className: popoverClassName ? styles.comboBoxPopover + " " + popoverClassName : styles.comboBoxPopover,
style: _extends({
opacity: isFocus ? 1 : 0,
visibility: isFocus ? 'visible' : 'hidden',
maxHeight: isFocus ? optionMaxHeight : 0
}, suggestionListPositionStyles),
ref: dropdownRef,
onMouseEnter: function onMouseEnter() {
return setIsMouseInsideOptions(true);
},
onMouseLeave: function onMouseLeave() {
return setIsMouseInsideOptions(false);
}
}, React__default.createElement("ul", {
className: listClassName ? styles.comboBoxList + " " + listClassName : styles.comboBoxList,
ref: optionsListRef
}, options.map(function (option, index) {
return React__default.createElement("li", {
className: optionsClassName ? styles.comboBoxOption + " " + optionsClassName : styles.comboBoxOption,
key: option,
style: _extends({}, backgroundColorSelector(index)),
onClick: function onClick() {
return selectSuggestionHandler();
},
onMouseDown: function onMouseDown(e) {
return e.preventDefault();
},
onMouseEnter: function onMouseEnter() {
return mouseEnterHandler(index);
}
}, renderOptions ? renderOptions(option) : option);
}))));
};
module.exports = ComboBox;
//# sourceMappingURL=index.js.map