UNPKG

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