UNPKG

@emsipl/react-select-search

Version:
291 lines (262 loc) 9.5 kB
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); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import React, { forwardRef, memo, useRef, useEffect, useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import useSelect from './useSelect'; import { optionType } from './types'; import Option from './Components/Option'; import isSelected from './lib/isSelected'; const SelectSearch = /*#__PURE__*/forwardRef(({ value: defaultValue, disabled, placeholder, multiple, search, autoFocus, autoComplete, options: defaultOptions, id, onChange, printOptions, closeOnSelect, className, renderValue, renderOption, renderGroupHeader, getOptions, fuse, emptyMessage, menuPlacement }, ref) => { const selectRef = useRef(null); const mainRef = useRef(null); const [optionsStyle, setOptionsStyle] = useState({}); const [snapshot, valueProps, optionProps] = useSelect({ options: defaultOptions, value: defaultValue, multiple, disabled, fuse, search, onChange, getOptions, closeOnSelect, closable: !multiple || printOptions === 'on-focus', allowEmpty: !!placeholder }); const { focus, highlighted, value, options, searching, displayValue, search: searchValue } = snapshot; const cls = useCallback(key => { if (typeof className === 'function') { return className(key); } if (key.indexOf('container') === 0) { return key.replace('container', className); } if (key.indexOf('is-') === 0 || key.indexOf('has-') === 0) { return key; } return className.split(' ')[0] + "__" + key; }, [className]); const renderEmptyMessage = useCallback(() => { if (emptyMessage === null) { return null; } const content = typeof emptyMessage === 'function' ? emptyMessage() : emptyMessage; return /*#__PURE__*/React.createElement("li", { className: cls('not-found') }, content); }, [emptyMessage, cls]); const wrapperClass = [cls('container'), disabled ? cls('is-disabled') : false, searching ? cls('is-loading') : false, focus ? cls('has-focus') : false].filter(single => !!single).join(' '); const inputValue = focus && search ? searchValue : displayValue; useEffect(() => { const { current } = selectRef; if (!current || multiple || highlighted < 0 && !value) { return; } const query = highlighted > -1 ? "[data-index=\"" + highlighted + "\"]" : "[data-value=\"" + escape(value.value) + "\"]"; const selected = current.querySelector(query); if (selected) { const rect = current.getBoundingClientRect(); const selectedRect = selected.getBoundingClientRect(); current.scrollTop = selected.offsetTop - rect.height / 2 + selectedRect.height / 2; } }, [focus, value, highlighted, selectRef, multiple]); let shouldRenderOptions; switch (printOptions) { case 'never': shouldRenderOptions = false; break; case 'always': shouldRenderOptions = true; break; case 'on-focus': shouldRenderOptions = focus; break; default: shouldRenderOptions = !disabled && (focus || multiple); break; } const getMenuPlacement = () => { switch (menuPlacement) { case 'top': return 'top'; case 'auto': if (selectRef.current && mainRef.current) { let optionsRect = selectRef.current.getBoundingClientRect(); let mainRect = mainRef.current.getBoundingClientRect(); let spaceBelow = window.innerHeight - mainRect.bottom - optionsRect.height; let spaceAbove = mainRect.top - optionsRect.height; if (spaceBelow < 0 && spaceAbove > spaceBelow) { return 'top'; } } default: return 'bottom'; } }; const updateOptionsStyle = () => { if (!selectRef.current || !mainRef.current) { return; } const placement = getMenuPlacement(); const rect = selectRef.current.getBoundingClientRect(); const newStyle = {}; if (placement === 'top') { newStyle.top = -rect.height - 10; } if (newStyle.top !== optionsStyle.top) { setOptionsStyle(newStyle); } }; const setSelectRef = r => { selectRef.current = r; updateOptionsStyle(); }; const setMainRef = r => { mainRef.current = r; updateOptionsStyle(); if (ref) { if (typeof ref === 'function') { ref(mainRef.current); } else { ref.current = mainRef.current; } } }; return /*#__PURE__*/React.createElement("div", { ref: setMainRef, className: wrapperClass, id: id }, (!multiple || placeholder || search) && /*#__PURE__*/React.createElement("div", { className: cls('value') }, renderValue(_objectSpread(_objectSpread({}, valueProps), {}, { placeholder, autoFocus, autoComplete, value: inputValue }), snapshot, cls('input'))), shouldRenderOptions && /*#__PURE__*/React.createElement("div", { className: cls('select'), ref: setSelectRef, style: optionsStyle, onMouseDown: e => e.preventDefault() }, /*#__PURE__*/React.createElement("ul", { className: cls('options') }, options.length > 0 ? options.map(option => { const isGroup = option.type === 'group'; const items = isGroup ? option.items : [option]; const base = { cls, optionProps, renderOption }; const rendered = items.map(o => /*#__PURE__*/React.createElement(Option, _extends({ key: o.value, selected: isSelected(o, value), highlighted: highlighted === o.index }, base, o))); if (isGroup) { return /*#__PURE__*/React.createElement("li", { role: "none", className: cls('row'), key: option.groupId }, /*#__PURE__*/React.createElement("div", { className: cls('group') }, /*#__PURE__*/React.createElement("div", { className: cls('group-header') }, renderGroupHeader(option.name)), /*#__PURE__*/React.createElement("ul", { className: cls('options') }, rendered))); } return rendered; }) : renderEmptyMessage() || null))); }); SelectSearch.defaultProps = { className: 'select-search', disabled: false, search: false, multiple: false, placeholder: null, id: null, autoFocus: false, autoComplete: 'on', value: '', onChange: () => {}, printOptions: 'auto', closeOnSelect: true, renderOption: (domProps, option, snapshot, className) => /*#__PURE__*/ // eslint-disable-next-line react/button-has-type React.createElement("button", _extends({ type: "button", className: className }, domProps), option.name), renderGroupHeader: name => name, renderValue: (valueProps, snapshot, className) => /*#__PURE__*/React.createElement("input", _extends({}, valueProps, { className: className })), fuse: { keys: ['name', 'groupName'], threshold: 0.3 }, getOptions: null, emptyMessage: null, menuPlacement: 'bottom' }; SelectSearch.propTypes = process.env.NODE_ENV !== "production" ? { options: PropTypes.arrayOf(optionType).isRequired, getOptions: PropTypes.func, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]), className: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), multiple: PropTypes.bool, search: PropTypes.bool, disabled: PropTypes.bool, placeholder: PropTypes.string, id: PropTypes.string, autoComplete: PropTypes.string, autoFocus: PropTypes.bool, onChange: PropTypes.func, printOptions: PropTypes.oneOf(['auto', 'always', 'never', 'on-focus']), closeOnSelect: PropTypes.bool, renderOption: PropTypes.func, renderGroupHeader: PropTypes.func, renderValue: PropTypes.func, fuse: PropTypes.oneOfType([PropTypes.bool, PropTypes.shape({ keys: PropTypes.arrayOf(PropTypes.string), threshold: PropTypes.number })]), emptyMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), menuPlacement: PropTypes.oneOf(['bottom', 'top', 'auto']) } : {}; export default /*#__PURE__*/memo(SelectSearch);