UNPKG

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