UNPKG

34.7 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var React = require('react');
6var React__default = _interopDefault(React);
7var PropTypes = _interopDefault(require('prop-types'));
8
9var classCallCheck = function (instance, Constructor) {
10 if (!(instance instanceof Constructor)) {
11 throw new TypeError("Cannot call a class as a function");
12 }
13};
14
15var createClass = function () {
16 function defineProperties(target, props) {
17 for (var i = 0; i < props.length; i++) {
18 var descriptor = props[i];
19 descriptor.enumerable = descriptor.enumerable || false;
20 descriptor.configurable = true;
21 if ("value" in descriptor) descriptor.writable = true;
22 Object.defineProperty(target, descriptor.key, descriptor);
23 }
24 }
25
26 return function (Constructor, protoProps, staticProps) {
27 if (protoProps) defineProperties(Constructor.prototype, protoProps);
28 if (staticProps) defineProperties(Constructor, staticProps);
29 return Constructor;
30 };
31}();
32
33
34
35
36
37var defineProperty = function (obj, key, value) {
38 if (key in obj) {
39 Object.defineProperty(obj, key, {
40 value: value,
41 enumerable: true,
42 configurable: true,
43 writable: true
44 });
45 } else {
46 obj[key] = value;
47 }
48
49 return obj;
50};
51
52var _extends = Object.assign || function (target) {
53 for (var i = 1; i < arguments.length; i++) {
54 var source = arguments[i];
55
56 for (var key in source) {
57 if (Object.prototype.hasOwnProperty.call(source, key)) {
58 target[key] = source[key];
59 }
60 }
61 }
62
63 return target;
64};
65
66
67
68var inherits = function (subClass, superClass) {
69 if (typeof superClass !== "function" && superClass !== null) {
70 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
71 }
72
73 subClass.prototype = Object.create(superClass && superClass.prototype, {
74 constructor: {
75 value: subClass,
76 enumerable: false,
77 writable: true,
78 configurable: true
79 }
80 });
81 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
82};
83
84
85
86
87
88
89
90
91
92var objectWithoutProperties = function (obj, keys) {
93 var target = {};
94
95 for (var i in obj) {
96 if (keys.indexOf(i) >= 0) continue;
97 if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
98 target[i] = obj[i];
99 }
100
101 return target;
102};
103
104var possibleConstructorReturn = function (self, call) {
105 if (!self) {
106 throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
107 }
108
109 return call && (typeof call === "object" || typeof call === "function") ? call : self;
110};
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130var toConsumableArray = function (arr) {
131 if (Array.isArray(arr)) {
132 for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
133
134 return arr2;
135 } else {
136 return Array.from(arr);
137 }
138};
139
140// istanbul ignore next
141var statusDiv = typeof document === 'undefined' ? null : document.getElementById('a11y-status-message');
142
143var statuses = [];
144
145function setStatus(status) {
146 var isSameAsLast = statuses[statuses.length - 1] === status;
147 if (isSameAsLast) {
148 statuses = [].concat(toConsumableArray(statuses), [status]);
149 } else {
150 statuses = [status];
151 }
152 var div = getStatusDiv();
153 div.innerHTML = '' + statuses.filter(Boolean).map(getStatusHtml).join('');
154}
155
156function getStatusHtml(status, index) {
157 var display = index === statuses.length - 1 ? 'block' : 'none';
158 return '<div style="display:' + display + ';">' + status + '</div>';
159}
160
161function getStatusDiv() {
162 if (statusDiv) {
163 return statusDiv;
164 }
165 statusDiv = document.createElement('div');
166 statusDiv.setAttribute('id', 'a11y-status-message');
167 statusDiv.setAttribute('role', 'status');
168 statusDiv.setAttribute('aria-live', 'assertive');
169 statusDiv.setAttribute('aria-relevant', 'additions text');
170 Object.assign(statusDiv.style, {
171 border: '0',
172 clip: 'rect(0 0 0 0)',
173 height: '1px',
174 margin: '-1px',
175 overflow: 'hidden',
176 padding: '0',
177 position: 'absolute',
178 width: '1px'
179 });
180 document.body.appendChild(statusDiv);
181 return statusDiv;
182}
183
184var idCounter = 1;
185
186/**
187 * Accepts a parameter and returns it if it's a function
188 * or a noop function if it's not. This allows us to
189 * accept a callback, but not worry about it if it's not
190 * passed.
191 * @param {Function} cb the callback
192 * @return {Function} a function
193 */
194function cbToCb(cb) {
195 return typeof cb === 'function' ? cb : noop;
196}
197function noop() {}
198
199function findParent(finder, node, rootNode) {
200 if (node !== null && node !== rootNode.parentNode) {
201 if (finder(node)) {
202 return node;
203 } else {
204 return findParent(finder, node.parentNode, rootNode);
205 }
206 } else {
207 return null;
208 }
209}
210
211/**
212* Get the closest element that scrolls
213* @param {HTMLElement} node - the child element to start searching for scroll parent at
214* @param {HTMLElement} rootNode - the root element of the component
215* @return {HTMLElement} the closest parentNode that scrolls
216*/
217var getClosestScrollParent = findParent.bind(null, function (node) {
218 return node.scrollHeight > node.clientHeight;
219});
220
221/**
222 * Scroll node into view if necessary
223 * @param {HTMLElement} node - the element that should scroll into view
224 * @param {HTMLElement} rootNode - the root element of the component
225 * @param {Boolean} alignToTop - align element to the top of the visible area of the scrollable ancestor
226 */
227function scrollIntoView(node, rootNode) {
228 var scrollParent = getClosestScrollParent(node, rootNode);
229 if (scrollParent === null) {
230 return;
231 }
232 var scrollParentStyles = getComputedStyle(scrollParent);
233 var scrollParentRect = scrollParent.getBoundingClientRect();
234 var scrollParentBorderTopWidth = parseInt(scrollParentStyles.borderTopWidth, 10);
235 var scrollParentTop = scrollParentRect.top + scrollParentBorderTopWidth;
236 var nodeRect = node.getBoundingClientRect();
237 var nodeOffsetTop = nodeRect.top + scrollParent.scrollTop;
238 var nodeTop = nodeOffsetTop - scrollParentTop;
239 if (nodeTop < scrollParent.scrollTop) {
240 // the item is above the scrollable area
241 scrollParent.scrollTop = nodeTop;
242 } else if (nodeTop + nodeRect.height > scrollParent.scrollTop + scrollParentRect.height) {
243 // the item is below the scrollable area
244 scrollParent.scrollTop = nodeTop + nodeRect.height - scrollParentRect.height;
245 }
246 // the item is within the scrollable area (do nothing)
247}
248
249/**
250 * Simple debounce implementation. Will call the given
251 * function once after the time given has passed since
252 * it was last called.
253 * @param {Function} fn the function to call after the time
254 * @param {Number} time the time to wait
255 * @return {Function} the debounced function
256 */
257function debounce(fn, time) {
258 var timeoutId = void 0;
259 return wrapper;
260 function wrapper() {
261 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
262 args[_key] = arguments[_key];
263 }
264
265 if (timeoutId) {
266 clearTimeout(timeoutId);
267 }
268 timeoutId = setTimeout(function () {
269 timeoutId = null;
270 fn.apply(undefined, args);
271 }, time);
272 }
273}
274
275/**
276 * This is intended to be used to compose event handlers
277 * They are executed in order until one of them calls
278 * `event.preventDefault()`. Not sure this is the best
279 * way to do this, but it seems legit...
280 * @param {Function} fns the event hanlder functions
281 * @return {Function} the event handler to add to an element
282 */
283function composeEventHandlers() {
284 for (var _len2 = arguments.length, fns = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
285 fns[_key2] = arguments[_key2];
286 }
287
288 return function (event) {
289 for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
290 args[_key3 - 1] = arguments[_key3];
291 }
292
293 return fns.some(function (fn) {
294 fn && fn.apply(undefined, [event].concat(args));
295 return event.defaultPrevented;
296 });
297 };
298}
299
300/**
301 * This generates a unique ID for all autocomplete inputs
302 * @param {String} prefix the prefix for the id
303 * @return {String} the unique ID
304 */
305function generateId(prefix) {
306 return prefix + '-' + idCounter++;
307}
308
309/**
310 * Returns the first argument that is not undefined
311 * @param {...*} args the arguments
312 * @return {*} the defined value
313 */
314function firstDefined() {
315 for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
316 args[_key4] = arguments[_key4];
317 }
318
319 return args.find(function (a) {
320 return typeof a !== 'undefined';
321 });
322}
323
324function isNumber(thing) {
325 // not NaN and is a number type
326 // eslint-disable-next-line no-self-compare
327 return thing === thing && typeof thing === 'number';
328}
329
330// eslint-disable-next-line complexity
331function getA11yStatusMessage(_ref) {
332 var isOpen = _ref.isOpen,
333 highlightedItem = _ref.highlightedItem,
334 selectedItem = _ref.selectedItem,
335 resultCount = _ref.resultCount,
336 previousResultCount = _ref.previousResultCount,
337 itemToString = _ref.itemToString;
338
339 if (!isOpen) {
340 if (selectedItem) {
341 return itemToString(selectedItem);
342 } else {
343 return '';
344 }
345 }
346 var resultCountChanged = resultCount !== previousResultCount;
347 if (!resultCount) {
348 return 'No results.';
349 } else if (!highlightedItem || resultCountChanged) {
350 return resultCount + ' ' + (resultCount === 1 ? 'result is' : 'results are') + ' available, use up and down arrow keys to navigate.';
351 }
352 return itemToString(highlightedItem);
353}
354
355/**
356 * Takes an argument and if it's an array, returns the first item in the array
357 * otherwise returns the argument
358 * @param {*} arg the maybe-array
359 * @param {*} defaultValue the value if arg is falsey not defined
360 * @return {*} the arg or it's first item
361 */
362function unwrapArray(arg, defaultValue) {
363 arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */arg[0] : arg;
364 if (!arg && defaultValue) {
365 return defaultValue;
366 } else {
367 return arg;
368 }
369}
370
371/**
372 * @param {Object} element (P)react element
373 * @return {Boolean} whether it's a DOM element
374 */
375function isDOMElement(element) {
376 /* istanbul ignore if */
377 if (element.nodeName) {
378 // then this is preact
379 return typeof element.nodeName === 'string';
380 } else {
381 // then we assume this is react
382 return typeof element.type === 'string';
383 }
384}
385
386/**
387 * @param {Object} element (P)react element
388 * @return {Object} the props
389 */
390function getElementProps(element) {
391 // props for react, attributes for preact
392 return element.props || /* istanbul ignore next (preact) */element.attributes;
393}
394
395/* eslint camelcase:0 */
396
397var Downshift$1 = function (_Component) {
398 inherits(Downshift, _Component);
399
400 function Downshift() {
401 var _ref;
402
403 classCallCheck(this, Downshift);
404
405 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
406 args[_key] = arguments[_key];
407 }
408
409 var _this = possibleConstructorReturn(this, (_ref = Downshift.__proto__ || Object.getPrototypeOf(Downshift)).call.apply(_ref, [this].concat(args)));
410
411 _initialiseProps.call(_this);
412
413 _this.id = generateId('downshift');
414 var state = _this.getState({
415 highlightedIndex: _this.props.defaultHighlightedIndex,
416 isOpen: _this.props.defaultIsOpen,
417 inputValue: _this.props.defaultInputValue,
418 selectedItem: _this.props.defaultSelectedItem
419 });
420 if (state.selectedItem) {
421 state.inputValue = _this.props.itemToString(state.selectedItem);
422 }
423 _this.state = state;
424 _this.root_handleClick = composeEventHandlers(_this.props.onClick, _this.root_handleClick);
425 return _this;
426 }
427
428 // this is an experimental feature
429 // so we're not going to document this yet
430
431
432 createClass(Downshift, [{
433 key: 'getState',
434
435
436 /**
437 * Gets the state based on internal state or props
438 * If a state value is passed via props, then that
439 * is the value given, otherwise it's retrieved from
440 * stateToMerge
441 *
442 * This will perform a shallow merge of the given state object
443 * with the state coming from props
444 * (for the controlled component scenario)
445 * This is used in state updater functions so they're referencing
446 * the right state regardless of where it comes from.
447 *
448 * @param {Object} stateToMerge defaults to this.state
449 * @return {Object} the state
450 */
451 value: function getState() {
452 var _this2 = this;
453
454 var stateToMerge = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state;
455
456 return Object.keys(stateToMerge).reduce(function (state, key) {
457 state[key] = _this2.isStateProp(key) ? _this2.props[key] : stateToMerge[key];
458 return state;
459 }, {});
460 }
461
462 /**
463 * This determines whether a prop is a "controlled prop" meaning it is
464 * state which is controlled by the outside of this component rather
465 * than within this component.
466 * @param {String} key the key to check
467 * @return {Boolean} whether it is a controlled controlled prop
468 */
469
470 }, {
471 key: 'isStateProp',
472 value: function isStateProp(key) {
473 return this.props[key] !== undefined;
474 }
475 }, {
476 key: 'getItemCount',
477 value: function getItemCount() {
478 if (this.props.itemCount === undefined) {
479 return this.items.length;
480 } else {
481 return this.props.itemCount;
482 }
483 }
484
485 // eslint-disable-next-line complexity
486
487 }, {
488 key: 'internalSetState',
489
490
491 // any piece of our state can live in two places:
492 // 1. Uncontrolled: it's internal (this.state)
493 // We will call this.setState to update that state
494 // 2. Controlled: it's external (this.props)
495 // We will call this.props.onStateChange to update that state
496 //
497 // In addition, we'll call this.props.onChange if the
498 // selectedItem is changed.
499 value: function internalSetState(stateToSet, cb) {
500 var _this3 = this;
501
502 var onChangeArg = void 0;
503 var onStateChangeArg = {};
504 return this.setState(function (state) {
505 state = _this3.getState(state);
506 stateToSet = typeof stateToSet === 'function' ? stateToSet(state) : stateToSet;
507 // this keeps track of the object we want to call with setState
508 var nextState = {};
509 // this is just used to tell whether the state changed
510 var nextFullState = {};
511 // we need to call on change if the outside world is controlling any of our state
512 // and we're trying to update that state. OR if the selection has changed and we're
513 // trying to update the selection
514 if (stateToSet.hasOwnProperty('selectedItem') && stateToSet.selectedItem !== state.selectedItem) {
515 onChangeArg = stateToSet.selectedItem;
516 }
517 Object.keys(stateToSet).forEach(function (key) {
518 // the type is useful for the onStateChangeArg
519 // but we don't actually want to set it in internal state.
520 // this is an undocumented feature for now... Not all internalSetState
521 // calls support it and I'm not certain we want them to yet.
522 // But it enables users controlling the isOpen state to know when
523 // the isOpen state changes due to mouseup events which is quite handy.
524 if (key === 'type') {
525 return;
526 }
527 // onStateChangeArg should only have the state that is
528 // actually changing
529 if (state[key] !== stateToSet[key]) {
530 onStateChangeArg[key] = stateToSet[key];
531 }
532 nextFullState[key] = stateToSet[key];
533 // if it's coming from props, then we don't care to set it internally
534 if (!_this3.isStateProp(key)) {
535 nextState[key] = stateToSet[key];
536 }
537 });
538 return nextState;
539 }, function () {
540 // call the provided callback if it's a callback
541 cbToCb(cb)();
542
543 // only call the onStateChange and onChange callbacks if
544 // we have relevant information to pass them.
545 if (Object.keys(onStateChangeArg).length) {
546 _this3.props.onStateChange(onStateChangeArg, _this3.getState());
547 }
548 if (onChangeArg !== undefined) {
549 _this3.props.onChange(onChangeArg, _this3.getState());
550 }
551 });
552 }
553 }, {
554 key: 'getControllerStateAndHelpers',
555 value: function getControllerStateAndHelpers() {
556 var _getState = this.getState(),
557 highlightedIndex = _getState.highlightedIndex,
558 inputValue = _getState.inputValue,
559 selectedItem = _getState.selectedItem,
560 isOpen = _getState.isOpen;
561
562 var getRootProps = this.getRootProps,
563 getButtonProps = this.getButtonProps,
564 getLabelProps = this.getLabelProps,
565 getInputProps = this.getInputProps,
566 getItemProps = this.getItemProps,
567 openMenu = this.openMenu,
568 closeMenu = this.closeMenu,
569 toggleMenu = this.toggleMenu,
570 selectItem = this.selectItem,
571 selectItemAtIndex = this.selectItemAtIndex,
572 selectHighlightedItem = this.selectHighlightedItem,
573 setHighlightedIndex = this.setHighlightedIndex,
574 clearSelection = this.clearSelection;
575
576 return {
577 // prop getters
578 getRootProps: getRootProps,
579 getButtonProps: getButtonProps,
580 getLabelProps: getLabelProps,
581 getInputProps: getInputProps,
582 getItemProps: getItemProps,
583
584 // actions
585 openMenu: openMenu,
586 closeMenu: closeMenu,
587 toggleMenu: toggleMenu,
588 selectItem: selectItem,
589 selectItemAtIndex: selectItemAtIndex,
590 selectHighlightedItem: selectHighlightedItem,
591 setHighlightedIndex: setHighlightedIndex,
592 clearSelection: clearSelection,
593
594 // state
595 highlightedIndex: highlightedIndex,
596 inputValue: inputValue,
597 isOpen: isOpen,
598 selectedItem: selectedItem
599 };
600 }
601
602 //////////////////////////// ROOT
603
604 //\\\\\\\\\\\\\\\\\\\\\\\\\\ ROOT
605
606 //////////////////////////// BUTTON
607
608 //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ BUTTON
609
610 /////////////////////////////// LABEL
611
612 //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ LABEL
613
614 /////////////////////////////// INPUT
615
616 }, {
617 key: 'getItemId',
618
619 //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ INPUT
620
621 /////////////////////////////// ITEM
622 value: function getItemId(index) {
623 return this.id + '-item-' + index;
624 }
625 }, {
626 key: 'getItemIndexFromId',
627 value: function getItemIndexFromId(id) {
628 if (id) {
629 return Number(id.split(this.id + '-item-')[1]);
630 } else {
631 return null;
632 }
633 }
634 //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ITEM
635
636 }, {
637 key: 'componentDidMount',
638 value: function componentDidMount() {
639 var _this4 = this;
640
641 // the _isMounted property is because we have `updateStatus` in a `debounce`
642 // and we don't want to update the status if the component has been umounted
643 this._isMounted = true;
644 // this.isMouseDown helps us track whether the mouse is currently held down.
645 // This is useful when the user clicks on an item in the list, but holds the mouse
646 // down long enough for the list to disappear (because the blur event fires on the input)
647 // this.isMouseDown is used in the blur handler on the input to determine whether the blur event should
648 // trigger hiding the menu.
649 var onMouseDown = function onMouseDown() {
650 _this4.isMouseDown = true;
651 };
652 var onMouseUp = function onMouseUp(event) {
653 _this4.isMouseDown = false;
654 if ((event.target === _this4._rootNode || !_this4._rootNode.contains(event.target)) && _this4.getState().isOpen) {
655 _this4.reset(Downshift.stateChangeTypes.mouseUp);
656 }
657 };
658 window.addEventListener('mousedown', onMouseDown);
659 window.addEventListener('mouseup', onMouseUp);
660
661 this.cleanup = function () {
662 _this4._isMounted = false;
663 window.removeEventListener('mousedown', onMouseDown);
664 window.removeEventListener('mouseup', onMouseUp);
665 };
666 }
667 }, {
668 key: 'componentDidUpdate',
669 value: function componentDidUpdate(prevProps) {
670 if (this.isStateProp('selectedItem') && this.props.selectedItem !== prevProps.selectedItem) {
671 this.internalSetState({
672 inputValue: this.props.itemToString(this.props.selectedItem)
673 });
674 }
675 this.updateStatus();
676 }
677 }, {
678 key: 'componentWillUnmount',
679 value: function componentWillUnmount() {
680 this.cleanup(); // avoids memory leak
681 }
682 }, {
683 key: 'render',
684 value: function render() {
685 var children = unwrapArray(this.props.children, noop);
686 // because the items are rerendered every time we call the children
687 // we clear this out each render and
688 this.items = [];
689 // we reset this so we know whether the user calls getRootProps during
690 // this render. If they do then we don't need to do anything,
691 // if they don't then we need to clone the element they return and
692 // apply the props for them.
693 this.getRootProps.called = false;
694 this.getRootProps.refKey = undefined;
695 // we do something similar for getLabelProps
696 this.getLabelProps.called = false;
697 // and something similar for getInputProps
698 this.getInputProps.called = false;
699 var element = unwrapArray(children(this.getControllerStateAndHelpers()));
700 if (!element) {
701 return null;
702 }
703 if (this.getRootProps.called) {
704 validateGetRootPropsCalledCorrectly(element, this.getRootProps);
705 return element;
706 } else if (isDOMElement(element)) {
707 // they didn't apply the root props, but we can clone
708 // this and apply the props ourselves
709 return React__default.cloneElement(element, this.getRootProps(getElementProps(element)));
710 } else {
711 // they didn't apply the root props, but they need to
712 // otherwise we can't query around the autocomplete
713 throw new Error('downshift: If you return a non-DOM element, you must use apply the getRootProps function');
714 }
715 }
716 }]);
717 return Downshift;
718}(React.Component);
719
720Downshift$1.propTypes = {
721 children: PropTypes.func,
722 defaultHighlightedIndex: PropTypes.number,
723 defaultSelectedItem: PropTypes.any,
724 defaultInputValue: PropTypes.string,
725 defaultIsOpen: PropTypes.bool,
726 getA11yStatusMessage: PropTypes.func,
727 itemToString: PropTypes.func,
728 onChange: PropTypes.func,
729 onStateChange: PropTypes.func,
730 onClick: PropTypes.func,
731 itemCount: PropTypes.number,
732 // things we keep in state for uncontrolled components
733 // but can accept as props for controlled components
734 /* eslint-disable react/no-unused-prop-types */
735 selectedItem: PropTypes.any,
736 isOpen: PropTypes.bool,
737 inputValue: PropTypes.string,
738 highlightedIndex: PropTypes.number
739 /* eslint-enable */
740};
741Downshift$1.defaultProps = {
742 defaultHighlightedIndex: null,
743 defaultSelectedItem: null,
744 defaultInputValue: '',
745 defaultIsOpen: false,
746 getA11yStatusMessage: getA11yStatusMessage,
747 itemToString: function itemToString(i) {
748 return i == null ? '' : String(i);
749 },
750 onStateChange: function onStateChange() {},
751 onChange: function onChange() {} };
752Downshift$1.stateChangeTypes = {
753 mouseUp: '__autocomplete_mouseup__'
754};
755
756var _initialiseProps = function _initialiseProps() {
757 var _this5 = this;
758
759 this.input = null;
760 this.items = [];
761 this.previousResultCount = 0;
762
763 this.getItemNodeFromIndex = function (index) {
764 return document.getElementById(_this5.getItemId(index));
765 };
766
767 this.setHighlightedIndex = function () {
768 var highlightedIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this5.props.defaultHighlightedIndex;
769
770 _this5.internalSetState({ highlightedIndex: highlightedIndex }, function () {
771 var node = _this5.getItemNodeFromIndex(_this5.getState().highlightedIndex);
772 var rootNode = _this5._rootNode;
773 scrollIntoView(node, rootNode);
774 });
775 };
776
777 this.highlightIndex = function (index) {
778 _this5.openMenu(function () {
779 _this5.setHighlightedIndex(index);
780 });
781 };
782
783 this.moveHighlightedIndex = function (amount) {
784 if (_this5.getState().isOpen) {
785 _this5.changeHighlighedIndex(amount);
786 } else {
787 _this5.highlightIndex();
788 }
789 };
790
791 this.changeHighlighedIndex = function (moveAmount) {
792 var itemsLastIndex = _this5.getItemCount() - 1;
793 if (itemsLastIndex < 0) {
794 return;
795 }
796
797 var _getState2 = _this5.getState(),
798 highlightedIndex = _getState2.highlightedIndex;
799
800 var baseIndex = highlightedIndex;
801 if (baseIndex === null) {
802 baseIndex = moveAmount > 0 ? -1 : itemsLastIndex + 1;
803 }
804 var newIndex = baseIndex + moveAmount;
805 if (newIndex < 0) {
806 newIndex = itemsLastIndex;
807 } else if (newIndex > itemsLastIndex) {
808 newIndex = 0;
809 }
810 _this5.setHighlightedIndex(newIndex);
811 };
812
813 this.clearSelection = function () {
814 _this5.internalSetState({
815 selectedItem: null,
816 inputValue: '',
817 isOpen: false
818 }, function () {
819 var inputNode = _this5._rootNode.querySelector('#' + _this5.inputId);
820 inputNode && inputNode.focus && inputNode.focus();
821 });
822 };
823
824 this.selectItem = function (item) {
825 _this5.internalSetState({
826 isOpen: false,
827 highlightedIndex: null,
828 selectedItem: item,
829 inputValue: _this5.props.itemToString(item)
830 });
831 };
832
833 this.selectItemAtIndex = function (itemIndex) {
834 var item = _this5.items[itemIndex];
835 if (!item) {
836 return;
837 }
838 _this5.selectItem(item);
839 };
840
841 this.selectHighlightedItem = function () {
842 return _this5.selectItemAtIndex(_this5.getState().highlightedIndex);
843 };
844
845 this.rootRef = function (node) {
846 return _this5._rootNode = node;
847 };
848
849 this.getRootProps = function () {
850 var _babelHelpers$extends;
851
852 var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
853
854 var _ref3$refKey = _ref3.refKey,
855 refKey = _ref3$refKey === undefined ? 'ref' : _ref3$refKey,
856 onClick = _ref3.onClick,
857 rest = objectWithoutProperties(_ref3, ['refKey', 'onClick']);
858
859 // this is used in the render to know whether the user has called getRootProps.
860 // It uses that to know whether to apply the props automatically
861 _this5.getRootProps.called = true;
862 _this5.getRootProps.refKey = refKey;
863 return _extends((_babelHelpers$extends = {}, defineProperty(_babelHelpers$extends, refKey, _this5.rootRef), defineProperty(_babelHelpers$extends, 'onClick', composeEventHandlers(onClick, _this5.root_handleClick)), _babelHelpers$extends), rest);
864 };
865
866 this.root_handleClick = function (event) {
867 event.preventDefault();
868 var itemParent = findParent(function (node) {
869 var index = _this5.getItemIndexFromId(node.getAttribute('id'));
870 return isNumber(index);
871 }, event.target, _this5._rootNode);
872 if (itemParent) {
873 _this5.selectItemAtIndex(_this5.getItemIndexFromId(itemParent.getAttribute('id')));
874 }
875 };
876
877 this.keyDownHandlers = {
878 ArrowDown: function ArrowDown(event) {
879 event.preventDefault();
880 var amount = event.shiftKey ? 5 : 1;
881 this.moveHighlightedIndex(amount);
882 },
883 ArrowUp: function ArrowUp(event) {
884 event.preventDefault();
885 var amount = event.shiftKey ? -5 : -1;
886 this.moveHighlightedIndex(amount);
887 },
888 Enter: function Enter(event) {
889 event.preventDefault();
890 if (this.getState().isOpen) {
891 this.selectHighlightedItem();
892 }
893 },
894 Escape: function Escape(event) {
895 event.preventDefault();
896 this.reset();
897 }
898 };
899 this.buttonKeyDownHandlers = _extends({}, this.keyDownHandlers, {
900 ' ': function _(event) {
901 event.preventDefault();
902 this.toggleMenu();
903 }
904 });
905
906 this.getButtonProps = function () {
907 var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
908
909 var onClick = _ref4.onClick,
910 onKeyDown = _ref4.onKeyDown,
911 rest = objectWithoutProperties(_ref4, ['onClick', 'onKeyDown']);
912
913 var _getState3 = _this5.getState(),
914 isOpen = _getState3.isOpen;
915
916 return _extends({
917 role: 'button',
918 'aria-label': isOpen ? 'close menu' : 'open menu',
919 'aria-expanded': isOpen,
920 'aria-haspopup': true,
921 onClick: composeEventHandlers(onClick, _this5.button_handleClick),
922 onKeyDown: composeEventHandlers(onKeyDown, _this5.button_handleKeyDown)
923 }, rest);
924 };
925
926 this.button_handleKeyDown = function (event) {
927 if (_this5.buttonKeyDownHandlers[event.key]) {
928 _this5.buttonKeyDownHandlers[event.key].call(_this5, event);
929 }
930 };
931
932 this.button_handleClick = function (event) {
933 event.preventDefault();
934 _this5.toggleMenu();
935 };
936
937 this.getLabelProps = function () {
938 var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
939
940 _this5.getLabelProps.called = true;
941 if (_this5.getInputProps.called && props.htmlFor && props.htmlFor !== _this5.inputId) {
942 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.');
943 }
944 _this5.inputId = firstDefined(_this5.inputId, props.htmlFor, generateId('downshift-input'));
945 return _extends({}, props, {
946 htmlFor: _this5.inputId
947 });
948 };
949
950 this.getInputProps = function () {
951 var _babelHelpers$extends2;
952
953 var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
954
955 var onKeyDown = _ref5.onKeyDown,
956 onBlur = _ref5.onBlur,
957 onChange = _ref5.onChange,
958 onInput = _ref5.onInput,
959 rest = objectWithoutProperties(_ref5, ['onKeyDown', 'onBlur', 'onChange', 'onInput']);
960
961 _this5.getInputProps.called = true;
962 if (_this5.getLabelProps.called && rest.id && rest.id !== _this5.inputId) {
963 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.');
964 }
965 _this5.inputId = firstDefined(_this5.inputId, rest.id, generateId('downshift-input'));
966 var onChangeKey = 'onChange';
967
968 var _getState4 = _this5.getState(),
969 inputValue = _getState4.inputValue,
970 isOpen = _getState4.isOpen,
971 highlightedIndex = _getState4.highlightedIndex;
972
973 return _extends((_babelHelpers$extends2 = {
974 role: 'combobox',
975 'aria-autocomplete': 'list',
976 'aria-expanded': isOpen,
977 'aria-activedescendant': typeof highlightedIndex === 'number' && highlightedIndex >= 0 ? _this5.getItemId(highlightedIndex) : null,
978 autoComplete: 'off',
979 value: inputValue
980 }, 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, {
981 id: _this5.inputId
982 });
983 };
984
985 this.input_handleKeyDown = function (event) {
986 if (event.key && _this5.keyDownHandlers[event.key]) {
987 _this5.keyDownHandlers[event.key].call(_this5, event);
988 }
989 };
990
991 this.input_handleChange = function (event) {
992 _this5.internalSetState({ isOpen: true, inputValue: event.target.value });
993 };
994
995 this.input_handleBlur = function () {
996 if (!_this5.isMouseDown) {
997 _this5.reset();
998 }
999 };
1000
1001 this.getItemProps = function () {
1002 var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1003
1004 var onMouseEnter = _ref6.onMouseEnter,
1005 item = _ref6.item,
1006 index = _ref6.index,
1007 rest = objectWithoutProperties(_ref6, ['onMouseEnter', 'item', 'index']);
1008
1009 _this5.items[index] = item;
1010 return _extends({
1011 id: _this5.getItemId(index),
1012 onMouseEnter: composeEventHandlers(onMouseEnter, function () {
1013 _this5.setHighlightedIndex(index);
1014 })
1015 }, rest);
1016 };
1017
1018 this.reset = function (type) {
1019 _this5.internalSetState(function (_ref7) {
1020 var selectedItem = _ref7.selectedItem;
1021 return {
1022 type: type,
1023 isOpen: false,
1024 highlightedIndex: null,
1025 inputValue: _this5.props.itemToString(selectedItem)
1026 };
1027 });
1028 };
1029
1030 this.toggleMenu = function (newState, cb) {
1031 _this5.internalSetState(function (_ref8) {
1032 var isOpen = _ref8.isOpen;
1033
1034 var nextIsOpen = !isOpen;
1035 if (typeof newState === 'boolean') {
1036 nextIsOpen = newState;
1037 }
1038 return { isOpen: nextIsOpen };
1039 }, function () {
1040 var _getState5 = _this5.getState(),
1041 isOpen = _getState5.isOpen;
1042
1043 if (isOpen) {
1044 _this5.setHighlightedIndex();
1045 }
1046 cbToCb(cb)();
1047 });
1048 };
1049
1050 this.openMenu = function (cb) {
1051 _this5.toggleMenu(true, cb);
1052 };
1053
1054 this.closeMenu = function (cb) {
1055 _this5.toggleMenu(false, cb);
1056 };
1057
1058 this.updateStatus = debounce(function () {
1059 if (!_this5._isMounted) {
1060 return;
1061 }
1062 var state = _this5.getState();
1063 var item = _this5.items[state.highlightedIndex] || {};
1064 var resultCount = _this5.getItemCount();
1065 var status = _this5.props.getA11yStatusMessage(_extends({
1066 itemToString: _this5.props.itemToString,
1067 previousResultCount: _this5.previousResultCount,
1068 resultCount: resultCount,
1069 highlightedItem: item
1070 }, state));
1071 _this5.previousResultCount = resultCount;
1072 setStatus(status);
1073 }, 200);
1074};
1075
1076function validateGetRootPropsCalledCorrectly(element, _ref2) {
1077 var refKey = _ref2.refKey;
1078
1079 var refKeySpecified = refKey !== 'ref';
1080 var isComposite = !isDOMElement(element);
1081 if (isComposite && !refKeySpecified) {
1082 throw new Error('downshift: You returned a non-DOM element. You must specify a refKey in getRootProps');
1083 } else if (!isComposite && refKeySpecified) {
1084 throw new Error('downshift: You returned a DOM element. You should not specify a refKey in getRootProps. You specified "' + refKey + '"');
1085 }
1086 if (!getElementProps(element).hasOwnProperty(refKey)) {
1087 throw new Error('downshift: You must apply the ref prop "' + refKey + '" from getRootProps onto your root element.');
1088 }
1089 if (!getElementProps(element).hasOwnProperty('onClick')) {
1090 throw new Error('downshift: You must apply the "onClick" prop from getRootProps onto your root element.');
1091 }
1092}
1093
1094module.exports = Downshift$1;