UNPKG

downshift

Version:

A set of primitives to build simple, flexible, WAI-ARIA compliant React autocomplete components

1,186 lines (1,011 loc) 39.3 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var defineProperty = function (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; }; var _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; }; var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var objectWithoutProperties = function (obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; // istanbul ignore next var statusDiv = typeof document === 'undefined' ? null : document.getElementById('a11y-status-message'); var statuses = []; function setStatus(status) { var isSameAsLast = statuses[statuses.length - 1] === status; if (isSameAsLast) { statuses = [].concat(toConsumableArray(statuses), [status]); } else { statuses = [status]; } var div = getStatusDiv(); div.innerHTML = '' + statuses.filter(Boolean).map(getStatusHtml).join(''); } function getStatusHtml(status, index) { var display = index === statuses.length - 1 ? 'block' : 'none'; return '<div style="display:' + display + ';">' + status + '</div>'; } function getStatusDiv() { if (statusDiv) { return statusDiv; } statusDiv = document.createElement('div'); statusDiv.setAttribute('id', 'a11y-status-message'); statusDiv.setAttribute('role', 'status'); statusDiv.setAttribute('aria-live', 'assertive'); statusDiv.setAttribute('aria-relevant', 'additions text'); Object.assign(statusDiv.style, { border: '0', clip: 'rect(0 0 0 0)', height: '1px', margin: '-1px', overflow: 'hidden', padding: '0', position: 'absolute', width: '1px' }); document.body.appendChild(statusDiv); return statusDiv; } var idCounter = 1; /** * Accepts a parameter and returns it if it's a function * or a noop function if it's not. This allows us to * accept a callback, but not worry about it if it's not * passed. * @param {Function} cb the callback * @return {Function} a function */ function cbToCb(cb) { return typeof cb === 'function' ? cb : noop; } function noop() {} function findParent(finder, node, rootNode) { if (node !== null && node !== rootNode.parentNode) { if (finder(node)) { return node; } else { return findParent(finder, node.parentNode, rootNode); } } else { return null; } } /** * Get the closest element that scrolls * @param {HTMLElement} node - the child element to start searching for scroll parent at * @param {HTMLElement} rootNode - the root element of the component * @return {HTMLElement} the closest parentNode that scrolls */ var getClosestScrollParent = findParent.bind(null, function (node) { return node.scrollHeight > node.clientHeight; }); /** * Scroll node into view if necessary * @param {HTMLElement} node - the element that should scroll into view * @param {HTMLElement} rootNode - the root element of the component * @param {Boolean} alignToTop - align element to the top of the visible area of the scrollable ancestor */ function scrollIntoView(node, rootNode) { var scrollParent = getClosestScrollParent(node, rootNode); if (scrollParent === null) { return; } var scrollParentStyles = getComputedStyle(scrollParent); var scrollParentRect = scrollParent.getBoundingClientRect(); var scrollParentBorderTopWidth = parseInt(scrollParentStyles.borderTopWidth, 10); var scrollParentBorderBottomWidth = parseInt(scrollParentStyles.borderBottomWidth, 10); var scrollParentTop = scrollParentRect.top + scrollParentBorderTopWidth; var nodeRect = node.getBoundingClientRect(); var nodeOffsetTop = nodeRect.top + scrollParent.scrollTop; var nodeTop = nodeOffsetTop - scrollParentTop; if (nodeTop < scrollParent.scrollTop) { // the item is above the scrollable area scrollParent.scrollTop = nodeTop; } else if (nodeTop + nodeRect.height + scrollParentBorderTopWidth + scrollParentBorderBottomWidth > scrollParent.scrollTop + scrollParentRect.height) { // the item is below the scrollable area scrollParent.scrollTop = nodeTop + nodeRect.height - scrollParentRect.height + scrollParentBorderTopWidth + scrollParentBorderBottomWidth; } // the item is within the scrollable area (do nothing) } /** * Simple debounce implementation. Will call the given * function once after the time given has passed since * it was last called. * @param {Function} fn the function to call after the time * @param {Number} time the time to wait * @return {Function} the debounced function */ function debounce(fn, time) { var timeoutId = void 0; return wrapper; function wrapper() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(function () { timeoutId = null; fn.apply(undefined, args); }, time); } } /** * This is intended to be used to compose event handlers * They are executed in order until one of them calls * `event.preventDefault()`. Not sure this is the best * way to do this, but it seems legit... * @param {Function} fns the event hanlder functions * @return {Function} the event handler to add to an element */ function composeEventHandlers() { for (var _len2 = arguments.length, fns = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { fns[_key2] = arguments[_key2]; } return function (event) { for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { args[_key3 - 1] = arguments[_key3]; } return fns.some(function (fn) { fn && fn.apply(undefined, [event].concat(args)); return event.defaultPrevented; }); }; } /** * This generates a unique ID for all autocomplete inputs * @param {String} prefix the prefix for the id * @return {String} the unique ID */ function generateId(prefix) { return prefix + '-' + idCounter++; } /** * Returns the first argument that is not undefined * @param {...*} args the arguments * @return {*} the defined value */ function firstDefined() { for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { args[_key4] = arguments[_key4]; } return args.find(function (a) { return typeof a !== 'undefined'; }); } function isNumber(thing) { // not NaN and is a number type // eslint-disable-next-line no-self-compare return thing === thing && typeof thing === 'number'; } // eslint-disable-next-line complexity function getA11yStatusMessage(_ref) { var isOpen = _ref.isOpen, highlightedItem = _ref.highlightedItem, selectedItem = _ref.selectedItem, resultCount = _ref.resultCount, previousResultCount = _ref.previousResultCount, itemToString = _ref.itemToString; if (!isOpen) { if (selectedItem) { return itemToString(selectedItem); } else { return ''; } } var resultCountChanged = resultCount !== previousResultCount; if (!resultCount) { return 'No results.'; } else if (!highlightedItem || resultCountChanged) { return resultCount + ' ' + (resultCount === 1 ? 'result is' : 'results are') + ' available, use up and down arrow keys to navigate.'; } return itemToString(highlightedItem); } /** * Takes an argument and if it's an array, returns the first item in the array * otherwise returns the argument * @param {*} arg the maybe-array * @param {*} defaultValue the value if arg is falsey not defined * @return {*} the arg or it's first item */ function unwrapArray(arg, defaultValue) { arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */arg[0] : arg; if (!arg && defaultValue) { return defaultValue; } else { return arg; } } /** * @param {Object} element (P)react element * @return {Boolean} whether it's a DOM element */ function isDOMElement(element) { /* istanbul ignore if */ if (element.nodeName) { // then this is preact return typeof element.nodeName === 'string'; } else { // then we assume this is react return typeof element.type === 'string'; } } /** * @param {Object} element (P)react element * @return {Object} the props */ function getElementProps(element) { // props for react, attributes for preact return element.props || /* istanbul ignore next (preact) */element.attributes; } /** * Throws a helpful error message for required properties. Useful * to be used as a default in destructuring or object params. * @param {String} fnName the function name * @param {String} propName the prop name */ function requiredProp(fnName, propName) { throw new Error('The property "' + propName + '" is required in "' + fnName + '"'); } var stateKeys = ['highlightedIndex', 'inputValue', 'isOpen', 'selectedItem', 'type']; /** * @param {Object} state The state object * @return {Object} State that is relevant to downshift */ function pickState() { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var result = {}; stateKeys.forEach(function (k) { if (state.hasOwnProperty(k)) { result[k] = state[k]; } }); return result; } /* eslint camelcase:0 */ var Downshift$1 = function (_Component) { inherits(Downshift, _Component); function Downshift() { var _ref; classCallCheck(this, Downshift); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var _this = possibleConstructorReturn(this, (_ref = Downshift.__proto__ || Object.getPrototypeOf(Downshift)).call.apply(_ref, [this].concat(args))); _initialiseProps.call(_this); var state = _this.getState({ highlightedIndex: _this.props.defaultHighlightedIndex, isOpen: _this.props.defaultIsOpen, inputValue: _this.props.defaultInputValue, selectedItem: _this.props.defaultSelectedItem }); if (state.selectedItem) { state.inputValue = _this.props.itemToString(state.selectedItem); } _this.state = state; return _this; } // this is an experimental feature // so we're not going to document this yet // nor are we going to test it. // We will try to avoid breaking it, but // we make no guarantees. // If you need it, we recommend that you lock // down your version of downshift (don't use a // version range) to avoid surprise breakages. createClass(Downshift, [{ key: 'getState', /** * Gets the state based on internal state or props * If a state value is passed via props, then that * is the value given, otherwise it's retrieved from * stateToMerge * * This will perform a shallow merge of the given state object * with the state coming from props * (for the controlled component scenario) * This is used in state updater functions so they're referencing * the right state regardless of where it comes from. * * @param {Object} stateToMerge defaults to this.state * @return {Object} the state */ value: function getState() { var _this2 = this; var stateToMerge = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state; return Object.keys(stateToMerge).reduce(function (state, key) { state[key] = _this2.isControlledProp(key) ? _this2.props[key] : stateToMerge[key]; return state; }, {}); } /** * This determines whether a prop is a "controlled prop" meaning it is * state which is controlled by the outside of this component rather * than within this component. * @param {String} key the key to check * @return {Boolean} whether it is a controlled controlled prop */ }, { key: 'isControlledProp', value: function isControlledProp(key) { return this.props[key] !== undefined; } }, { key: 'getItemCount', value: function getItemCount() { if (this.props.itemCount === undefined) { return this.items.length; } else { return this.props.itemCount; } } // eslint-disable-next-line complexity }, { key: 'internalSetState', // any piece of our state can live in two places: // 1. Uncontrolled: it's internal (this.state) // We will call this.setState to update that state // 2. Controlled: it's external (this.props) // We will call this.props.onStateChange to update that state // // In addition, we'll call this.props.onChange if the // selectedItem is changed. value: function internalSetState(stateToSet, cb) { var _this3 = this; var onChangeArg = void 0; var onStateChangeArg = {}; return this.setState(function (state) { state = _this3.getState(state); stateToSet = typeof stateToSet === 'function' ? stateToSet(state) : stateToSet; // this keeps track of the object we want to call with setState var nextState = {}; // this is just used to tell whether the state changed var nextFullState = {}; // we need to call on change if the outside world is controlling any of our state // and we're trying to update that state. OR if the selection has changed and we're // trying to update the selection if (stateToSet.hasOwnProperty('selectedItem') && stateToSet.selectedItem !== state.selectedItem) { onChangeArg = stateToSet.selectedItem; } stateToSet.type = stateToSet.type || Downshift.stateChangeTypes.unknown; Object.keys(stateToSet).forEach(function (key) { // onStateChangeArg should only have the state that is // actually changing if (state[key] !== stateToSet[key]) { onStateChangeArg[key] = stateToSet[key]; } // the type is useful for the onStateChangeArg // but we don't actually want to set it in internal state. // this is an undocumented feature for now... Not all internalSetState // calls support it and I'm not certain we want them to yet. // But it enables users controlling the isOpen state to know when // the isOpen state changes due to mouseup events which is quite handy. if (key === 'type') { return; } nextFullState[key] = stateToSet[key]; // if it's coming from props, then we don't care to set it internally if (!_this3.isControlledProp(key)) { nextState[key] = stateToSet[key]; } }); return nextState; }, function () { // call the provided callback if it's a callback cbToCb(cb)(); // only call the onStateChange and onChange callbacks if // we have relevant information to pass them. var hasMoreStateThanType = Object.keys(onStateChangeArg).length > 1; if (hasMoreStateThanType) { _this3.props.onStateChange(onStateChangeArg, _this3.getStateAndHelpers()); } if (onChangeArg !== undefined) { _this3.props.onChange(onChangeArg, _this3.getStateAndHelpers()); } // this is currently undocumented and therefore subject to change // We'll try to not break it, but just be warned. _this3.props.onUserAction(onStateChangeArg, _this3.getStateAndHelpers()); }); } }, { key: 'getStateAndHelpers', value: function getStateAndHelpers() { var _getState = this.getState(), highlightedIndex = _getState.highlightedIndex, inputValue = _getState.inputValue, selectedItem = _getState.selectedItem, isOpen = _getState.isOpen; var itemToString = this.props.itemToString; var getRootProps = this.getRootProps, getButtonProps = this.getButtonProps, getLabelProps = this.getLabelProps, getInputProps = this.getInputProps, getItemProps = this.getItemProps, openMenu = this.openMenu, closeMenu = this.closeMenu, toggleMenu = this.toggleMenu, selectItem = this.selectItem, selectItemAtIndex = this.selectItemAtIndex, selectHighlightedItem = this.selectHighlightedItem, setHighlightedIndex = this.setHighlightedIndex, clearSelection = this.clearSelection, reset = this.reset; return { // prop getters getRootProps: getRootProps, getButtonProps: getButtonProps, getLabelProps: getLabelProps, getInputProps: getInputProps, getItemProps: getItemProps, // actions reset: reset, openMenu: openMenu, closeMenu: closeMenu, toggleMenu: toggleMenu, selectItem: selectItem, selectItemAtIndex: selectItemAtIndex, selectHighlightedItem: selectHighlightedItem, setHighlightedIndex: setHighlightedIndex, clearSelection: clearSelection, itemToString: itemToString, // state highlightedIndex: highlightedIndex, inputValue: inputValue, isOpen: isOpen, selectedItem: selectedItem }; } //////////////////////////// ROOT //\\\\\\\\\\\\\\\\\\\\\\\\\\ ROOT //////////////////////////// BUTTON //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ BUTTON /////////////////////////////// LABEL //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ LABEL /////////////////////////////// INPUT }, { key: 'getItemId', //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ INPUT /////////////////////////////// ITEM value: function getItemId(index) { return this.id + '-item-' + index; } }, { key: 'getItemIndexFromId', value: function getItemIndexFromId(id) { if (id) { return Number(id.split(this.id + '-item-')[1]); } else { return null; } } //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ITEM }, { key: 'componentDidMount', value: function componentDidMount() { var _this4 = this; // the _isMounted property is because we have `updateStatus` in a `debounce` // and we don't want to update the status if the component has been umounted this._isMounted = true; // this.isMouseDown helps us track whether the mouse is currently held down. // This is useful when the user clicks on an item in the list, but holds the mouse // down long enough for the list to disappear (because the blur event fires on the input) // this.isMouseDown is used in the blur handler on the input to determine whether the blur event should // trigger hiding the menu. var onMouseDown = function onMouseDown() { _this4.isMouseDown = true; }; var onMouseUp = function onMouseUp(event) { _this4.isMouseDown = false; if ((event.target === _this4._rootNode || !_this4._rootNode.contains(event.target)) && _this4.getState().isOpen) { _this4.reset({ type: Downshift.stateChangeTypes.mouseUp }); } }; window.addEventListener('mousedown', onMouseDown); window.addEventListener('mouseup', onMouseUp); this.cleanup = function () { _this4._isMounted = false; window.removeEventListener('mousedown', onMouseDown); window.removeEventListener('mouseup', onMouseUp); }; } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { if (this.isControlledProp('selectedItem') && this.props.selectedItem !== prevProps.selectedItem) { this.internalSetState({ type: Downshift.stateChangeTypes.controlledPropUpdatedSelectedItem, inputValue: this.props.itemToString(this.props.selectedItem) }); } this.updateStatus(); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.cleanup(); // avoids memory leak } }, { key: 'render', value: function render() { var children = unwrapArray(this.props.children, noop); // because the items are rerendered every time we call the children // we clear this out each render and this.items = []; // we reset this so we know whether the user calls getRootProps during // this render. If they do then we don't need to do anything, // if they don't then we need to clone the element they return and // apply the props for them. this.getRootProps.called = false; this.getRootProps.refKey = undefined; // we do something similar for getLabelProps this.getLabelProps.called = false; // and something similar for getInputProps this.getInputProps.called = false; var element = unwrapArray(children(this.getStateAndHelpers())); if (!element) { return null; } if (this.getRootProps.called) { validateGetRootPropsCalledCorrectly(element, this.getRootProps); return element; } else if (isDOMElement(element)) { // they didn't apply the root props, but we can clone // this and apply the props ourselves return React.cloneElement(element, this.getRootProps(getElementProps(element))); } else { // they didn't apply the root props, but they need to // otherwise we can't query around the autocomplete throw new Error('downshift: If you return a non-DOM element, you must use apply the getRootProps function'); } } }]); return Downshift; }(Component); Downshift$1.propTypes = { children: PropTypes.func, defaultHighlightedIndex: PropTypes.number, defaultSelectedItem: PropTypes.any, defaultInputValue: PropTypes.string, defaultIsOpen: PropTypes.bool, getA11yStatusMessage: PropTypes.func, itemToString: PropTypes.func, onChange: PropTypes.func, onStateChange: PropTypes.func, onUserAction: PropTypes.func, onClick: PropTypes.func, itemCount: PropTypes.number, // things we keep in state for uncontrolled components // but can accept as props for controlled components /* eslint-disable react/no-unused-prop-types */ selectedItem: PropTypes.any, isOpen: PropTypes.bool, inputValue: PropTypes.string, highlightedIndex: PropTypes.number /* eslint-enable */ }; Downshift$1.defaultProps = { defaultHighlightedIndex: null, defaultSelectedItem: null, defaultInputValue: '', defaultIsOpen: false, getA11yStatusMessage: getA11yStatusMessage, itemToString: function itemToString(i) { return i == null ? '' : String(i); }, onStateChange: function onStateChange() {}, onUserAction: function onUserAction() {}, onChange: function onChange() {} }; Downshift$1.stateChangeTypes = { unknown: '__autocomplete_unknown__', mouseUp: '__autocomplete_mouseup__', itemMouseEnter: '__autocomplete_item_mouseenter__', keyDownArrowUp: '__autocomplete_keydown_arrow_up__', keyDownArrowDown: '__autocomplete_keydown_arrow_down__', keyDownEscape: '__autocomplete_keydown_escape__', keyDownEnter: '__autocomplete_keydown_enter__', blurInput: '__autocomplete_blur_input__', changeInput: '__autocomplete_change_input__', keyDownSpaceButton: '__autocomplete_keydown_space_button__', clickButton: '__autocomplete_click_button__', controlledPropUpdatedSelectedItem: '__autocomplete_controlled_prop_updated_selected_item__' }; var _initialiseProps = function _initialiseProps() { var _this5 = this; this.id = generateId('downshift'); this.root_handleClick = composeEventHandlers(this.props.onClick, this.root_handleClick); this.input = null; this.items = []; this.previousResultCount = 0; this.getItemNodeFromIndex = function (index) { return document.getElementById(_this5.getItemId(index)); }; this.setHighlightedIndex = function () { var highlightedIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this5.props.defaultHighlightedIndex; var otherStateToSet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; otherStateToSet = pickState(otherStateToSet); _this5.internalSetState(_extends({ highlightedIndex: highlightedIndex }, otherStateToSet), function () { var node = _this5.getItemNodeFromIndex(_this5.getState().highlightedIndex); var rootNode = _this5._rootNode; scrollIntoView(node, rootNode); }); }; this.openAndHighlightDefaultIndex = function () { var otherStateToSet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this5.setHighlightedIndex(undefined, _extends({ isOpen: true }, otherStateToSet)); }; this.highlightDefaultIndex = function () { var otherStateToSet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this5.setHighlightedIndex(undefined, otherStateToSet); }; this.moveHighlightedIndex = function (amount, otherStateToSet) { if (_this5.getState().isOpen) { _this5.changeHighlightedIndex(amount, otherStateToSet); } else { _this5.openAndHighlightDefaultIndex(otherStateToSet); } }; this.changeHighlightedIndex = function (moveAmount, otherStateToSet) { var itemsLastIndex = _this5.getItemCount() - 1; if (itemsLastIndex < 0) { return; } var _getState2 = _this5.getState(), highlightedIndex = _getState2.highlightedIndex; var baseIndex = highlightedIndex; if (baseIndex === null) { baseIndex = moveAmount > 0 ? -1 : itemsLastIndex + 1; } var newIndex = baseIndex + moveAmount; if (newIndex < 0) { newIndex = itemsLastIndex; } else if (newIndex > itemsLastIndex) { newIndex = 0; } _this5.setHighlightedIndex(newIndex, otherStateToSet); }; this.clearSelection = function (cb) { _this5.internalSetState({ selectedItem: null, inputValue: '', isOpen: false }, function () { var inputNode = _this5._rootNode.querySelector('#' + _this5.inputId); inputNode && inputNode.focus && inputNode.focus(); cbToCb(cb)(); }); }; this.selectItem = function (item, otherStateToSet, cb) { otherStateToSet = pickState(otherStateToSet); _this5.internalSetState(_extends({ isOpen: false, highlightedIndex: null, selectedItem: item, inputValue: _this5.props.itemToString(item) }, otherStateToSet), cbToCb(cb)); }; this.selectItemAtIndex = function (itemIndex, otherStateToSet, cb) { var item = _this5.items[itemIndex]; if (!item) { return; } _this5.selectItem(item, otherStateToSet, cb); }; this.selectHighlightedItem = function (otherStateToSet, cb) { return _this5.selectItemAtIndex(_this5.getState().highlightedIndex, otherStateToSet, cb); }; this.rootRef = function (node) { return _this5._rootNode = node; }; this.getRootProps = function () { var _babelHelpers$extends; var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _ref3$refKey = _ref3.refKey, refKey = _ref3$refKey === undefined ? 'ref' : _ref3$refKey, onClick = _ref3.onClick, rest = objectWithoutProperties(_ref3, ['refKey', 'onClick']); // this is used in the render to know whether the user has called getRootProps. // It uses that to know whether to apply the props automatically _this5.getRootProps.called = true; _this5.getRootProps.refKey = refKey; return _extends((_babelHelpers$extends = {}, defineProperty(_babelHelpers$extends, refKey, _this5.rootRef), defineProperty(_babelHelpers$extends, 'onClick', composeEventHandlers(onClick, _this5.root_handleClick)), _babelHelpers$extends), rest); }; this.root_handleClick = function (event) { event.preventDefault(); var itemParent = findParent(function (node) { var index = _this5.getItemIndexFromId(node.getAttribute('id')); return isNumber(index); }, event.target, _this5._rootNode); if (itemParent) { _this5.selectItemAtIndex(_this5.getItemIndexFromId(itemParent.getAttribute('id'))); } }; this.keyDownHandlers = { ArrowDown: function ArrowDown(event) { event.preventDefault(); var amount = event.shiftKey ? 5 : 1; this.moveHighlightedIndex(amount, { type: Downshift$1.stateChangeTypes.keyDownArrowDown }); }, ArrowUp: function ArrowUp(event) { event.preventDefault(); var amount = event.shiftKey ? -5 : -1; this.moveHighlightedIndex(amount, { type: Downshift$1.stateChangeTypes.keyDownArrowUp }); }, Enter: function Enter(event) { if (this.getState().isOpen) { event.preventDefault(); this.selectHighlightedItem({ type: Downshift$1.stateChangeTypes.keyDownEnter }); } }, Escape: function Escape(event) { event.preventDefault(); this.reset({ type: Downshift$1.stateChangeTypes.keyDownEscape }); } }; this.buttonKeyDownHandlers = _extends({}, this.keyDownHandlers, { ' ': function _(event) { event.preventDefault(); this.toggleMenu({ type: Downshift$1.stateChangeTypes.keyDownSpaceButton }); } }); this.getButtonProps = function () { var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var onClick = _ref4.onClick, onKeyDown = _ref4.onKeyDown, rest = objectWithoutProperties(_ref4, ['onClick', 'onKeyDown']); var _getState3 = _this5.getState(), isOpen = _getState3.isOpen; return _extends({ role: 'button', 'aria-label': isOpen ? 'close menu' : 'open menu', 'aria-expanded': isOpen, 'aria-haspopup': true, onClick: composeEventHandlers(onClick, _this5.button_handleClick), onKeyDown: composeEventHandlers(onKeyDown, _this5.button_handleKeyDown) }, rest); }; this.button_handleKeyDown = function (event) { if (_this5.buttonKeyDownHandlers[event.key]) { _this5.buttonKeyDownHandlers[event.key].call(_this5, event); } }; this.button_handleClick = function (event) { event.preventDefault(); _this5.toggleMenu({ type: Downshift$1.stateChangeTypes.clickButton }); }; this.getLabelProps = function () { var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this5.getLabelProps.called = true; if (_this5.getInputProps.called && props.htmlFor && props.htmlFor !== _this5.inputId) { throw new Error('downshift: You provided the htmlFor of "' + props.htmlFor + '" for your label, but the id of your input is "' + _this5.inputId + '". You must either remove the id from your input or set the htmlFor of the label equal to the input id.'); } _this5.inputId = firstDefined(_this5.inputId, props.htmlFor, generateId('downshift-input')); return _extends({}, props, { htmlFor: _this5.inputId }); }; this.getInputProps = function () { var _babelHelpers$extends2; var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var onKeyDown = _ref5.onKeyDown, onBlur = _ref5.onBlur, onChange = _ref5.onChange, onInput = _ref5.onInput, rest = objectWithoutProperties(_ref5, ['onKeyDown', 'onBlur', 'onChange', 'onInput']); _this5.getInputProps.called = true; if (_this5.getLabelProps.called && rest.id && rest.id !== _this5.inputId) { throw new Error('downshift: You provided the id of "' + rest.id + '" for your input, but the htmlFor of your label is "' + _this5.inputId + '". You must either remove the id from your input or set the htmlFor of the label equal to the input id.'); } _this5.inputId = firstDefined(_this5.inputId, rest.id, generateId('downshift-input')); var onChangeKey = undefined === 'true' /* istanbul ignore next (preact) */ ? 'onInput' : 'onChange'; var _getState4 = _this5.getState(), inputValue = _getState4.inputValue, isOpen = _getState4.isOpen, highlightedIndex = _getState4.highlightedIndex; return _extends((_babelHelpers$extends2 = { role: 'combobox', 'aria-autocomplete': 'list', 'aria-expanded': isOpen, 'aria-activedescendant': typeof highlightedIndex === 'number' && highlightedIndex >= 0 ? _this5.getItemId(highlightedIndex) : null, autoComplete: 'off', value: inputValue }, defineProperty(_babelHelpers$extends2, onChangeKey, composeEventHandlers(onChange, onInput, _this5.input_handleChange)), defineProperty(_babelHelpers$extends2, 'onKeyDown', composeEventHandlers(onKeyDown, _this5.input_handleKeyDown)), defineProperty(_babelHelpers$extends2, 'onBlur', composeEventHandlers(onBlur, _this5.input_handleBlur)), _babelHelpers$extends2), rest, { id: _this5.inputId }); }; this.input_handleKeyDown = function (event) { if (event.key && _this5.keyDownHandlers[event.key]) { _this5.keyDownHandlers[event.key].call(_this5, event); } }; this.input_handleChange = function (event) { _this5.internalSetState({ type: Downshift$1.stateChangeTypes.changeInput, isOpen: true, inputValue: event.target.value }); }; this.input_handleBlur = function () { if (!_this5.isMouseDown) { _this5.reset({ type: Downshift$1.stateChangeTypes.blurInput }); } }; this.getItemProps = function () { var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var onMouseEnter = _ref6.onMouseEnter, index = _ref6.index, _ref6$item = _ref6.item, item = _ref6$item === undefined ? requiredProp('getItemProps', 'item') : _ref6$item, rest = objectWithoutProperties(_ref6, ['onMouseEnter', 'index', 'item']); if (index === undefined) { _this5.items.push(item); index = _this5.items.indexOf(item); } else { _this5.items[index] = item; } return _extends({ id: _this5.getItemId(index), onMouseEnter: composeEventHandlers(onMouseEnter, function () { _this5.setHighlightedIndex(index, { type: Downshift$1.stateChangeTypes.itemMouseEnter }); }) }, rest); }; this.reset = function () { var otherStateToSet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var cb = arguments[1]; otherStateToSet = pickState(otherStateToSet); _this5.internalSetState(function (_ref7) { var selectedItem = _ref7.selectedItem; return _extends({ isOpen: false, highlightedIndex: null, inputValue: _this5.props.itemToString(selectedItem) }, otherStateToSet); }, cbToCb(cb)); }; this.toggleMenu = function () { var otherStateToSet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var cb = arguments[1]; otherStateToSet = pickState(otherStateToSet); _this5.internalSetState(function (_ref8) { var isOpen = _ref8.isOpen; return _extends({ isOpen: !isOpen }, otherStateToSet); }, function () { var _getState5 = _this5.getState(), isOpen = _getState5.isOpen; if (isOpen) { _this5.highlightDefaultIndex(); } cbToCb(cb)(); }); }; this.openMenu = function (cb) { _this5.internalSetState({ isOpen: true }, cbToCb(cb)); }; this.closeMenu = function (cb) { _this5.internalSetState({ isOpen: false }, cbToCb(cb)); }; this.updateStatus = debounce(function () { if (!_this5._isMounted) { return; } var state = _this5.getState(); var item = _this5.items[state.highlightedIndex] || {}; var resultCount = _this5.getItemCount(); var status = _this5.props.getA11yStatusMessage(_extends({ itemToString: _this5.props.itemToString, previousResultCount: _this5.previousResultCount, resultCount: resultCount, highlightedItem: item }, state)); _this5.previousResultCount = resultCount; setStatus(status); }, 200); }; function validateGetRootPropsCalledCorrectly(element, _ref2) { var refKey = _ref2.refKey; var refKeySpecified = refKey !== 'ref'; var isComposite = !isDOMElement(element); if (isComposite && !refKeySpecified) { throw new Error('downshift: You returned a non-DOM element. You must specify a refKey in getRootProps'); } else if (!isComposite && refKeySpecified) { throw new Error('downshift: You returned a DOM element. You should not specify a refKey in getRootProps. You specified "' + refKey + '"'); } if (!getElementProps(element).hasOwnProperty(refKey)) { throw new Error('downshift: You must apply the ref prop "' + refKey + '" from getRootProps onto your root element.'); } if (!getElementProps(element).hasOwnProperty('onClick')) { throw new Error('downshift: You must apply the "onClick" prop from getRootProps onto your root element.'); } } /* * Fix importing in typescript after rollup compilation * https://github.com/rollup/rollup/issues/1156 * https://github.com/Microsoft/TypeScript/issues/13017#issuecomment-268657860 */ Downshift$1.default = Downshift$1; export default Downshift$1;