UNPKG

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