downshift
Version:
🏎 A set of primitives to build simple, flexible, WAI-ARIA compliant React autocomplete, combobox or select dropdown components.
1,258 lines (1,172 loc) • 182 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('preact/compat'), require('prop-types')) :
typeof define === 'function' && define.amd ? define(['exports', 'preact/compat', 'prop-types'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Downshift = {}, global.React, global.PropTypes));
})(this, (function (exports, React, PropTypes) { 'use strict';
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _setPrototypeOf(t, e) {
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
return t.__proto__ = e, t;
}, _setPrototypeOf(t, e);
}
function _inheritsLoose(t, o) {
t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o);
}
var reactIs = {exports: {}};
var reactIs_development = {};
/**
* @license React
* react-is.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactIs_development;
function requireReactIs_development () {
if (hasRequiredReactIs_development) return reactIs_development;
hasRequiredReactIs_development = 1;
{
(function() {
// ATTENTION
// When adding new symbols to this file,
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
// The Symbol used to tag the ReactElement-like types.
var REACT_ELEMENT_TYPE = Symbol.for('react.element');
var REACT_PORTAL_TYPE = Symbol.for('react.portal');
var REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
var REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
var REACT_PROVIDER_TYPE = Symbol.for('react.provider');
var REACT_CONTEXT_TYPE = Symbol.for('react.context');
var REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context');
var REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
var REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
var REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
var REACT_MEMO_TYPE = Symbol.for('react.memo');
var REACT_LAZY_TYPE = Symbol.for('react.lazy');
var REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
// -----------------------------------------------------------------------------
var enableScopeAPI = false; // Experimental Create Event Handle API.
var enableCacheElement = false;
var enableTransitionTracing = false; // No known bugs, but needs performance testing
var enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber
// stuff. Intended to enable React core members to more easily debug scheduling
// issues in DEV builds.
var enableDebugTracing = false; // Track which Fiber(s) schedule render work.
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
}
function isValidElementType(type) {
if (typeof type === 'string' || typeof type === 'function') {
return true;
} // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing ) {
return true;
}
if (typeof type === 'object' && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) {
return true;
}
}
return false;
}
function typeOf(object) {
if (typeof object === 'object' && object !== null) {
var $$typeof = object.$$typeof;
switch ($$typeof) {
case REACT_ELEMENT_TYPE:
var type = object.type;
switch (type) {
case REACT_FRAGMENT_TYPE:
case REACT_PROFILER_TYPE:
case REACT_STRICT_MODE_TYPE:
case REACT_SUSPENSE_TYPE:
case REACT_SUSPENSE_LIST_TYPE:
return type;
default:
var $$typeofType = type && type.$$typeof;
switch ($$typeofType) {
case REACT_SERVER_CONTEXT_TYPE:
case REACT_CONTEXT_TYPE:
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
case REACT_PROVIDER_TYPE:
return $$typeofType;
default:
return $$typeof;
}
}
case REACT_PORTAL_TYPE:
return $$typeof;
}
}
return undefined;
}
var ContextConsumer = REACT_CONTEXT_TYPE;
var ContextProvider = REACT_PROVIDER_TYPE;
var Element = REACT_ELEMENT_TYPE;
var ForwardRef = REACT_FORWARD_REF_TYPE;
var Fragment = REACT_FRAGMENT_TYPE;
var Lazy = REACT_LAZY_TYPE;
var Memo = REACT_MEMO_TYPE;
var Portal = REACT_PORTAL_TYPE;
var Profiler = REACT_PROFILER_TYPE;
var StrictMode = REACT_STRICT_MODE_TYPE;
var Suspense = REACT_SUSPENSE_TYPE;
var SuspenseList = REACT_SUSPENSE_LIST_TYPE;
var hasWarnedAboutDeprecatedIsAsyncMode = false;
var hasWarnedAboutDeprecatedIsConcurrentMode = false; // AsyncMode should be deprecated
function isAsyncMode(object) {
{
if (!hasWarnedAboutDeprecatedIsAsyncMode) {
hasWarnedAboutDeprecatedIsAsyncMode = true; // Using console['warn'] to evade Babel and ESLint
console['warn']('The ReactIs.isAsyncMode() alias has been deprecated, ' + 'and will be removed in React 18+.');
}
}
return false;
}
function isConcurrentMode(object) {
{
if (!hasWarnedAboutDeprecatedIsConcurrentMode) {
hasWarnedAboutDeprecatedIsConcurrentMode = true; // Using console['warn'] to evade Babel and ESLint
console['warn']('The ReactIs.isConcurrentMode() alias has been deprecated, ' + 'and will be removed in React 18+.');
}
}
return false;
}
function isContextConsumer(object) {
return typeOf(object) === REACT_CONTEXT_TYPE;
}
function isContextProvider(object) {
return typeOf(object) === REACT_PROVIDER_TYPE;
}
function isElement(object) {
return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
function isForwardRef(object) {
return typeOf(object) === REACT_FORWARD_REF_TYPE;
}
function isFragment(object) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
}
function isLazy(object) {
return typeOf(object) === REACT_LAZY_TYPE;
}
function isMemo(object) {
return typeOf(object) === REACT_MEMO_TYPE;
}
function isPortal(object) {
return typeOf(object) === REACT_PORTAL_TYPE;
}
function isProfiler(object) {
return typeOf(object) === REACT_PROFILER_TYPE;
}
function isStrictMode(object) {
return typeOf(object) === REACT_STRICT_MODE_TYPE;
}
function isSuspense(object) {
return typeOf(object) === REACT_SUSPENSE_TYPE;
}
function isSuspenseList(object) {
return typeOf(object) === REACT_SUSPENSE_LIST_TYPE;
}
reactIs_development.ContextConsumer = ContextConsumer;
reactIs_development.ContextProvider = ContextProvider;
reactIs_development.Element = Element;
reactIs_development.ForwardRef = ForwardRef;
reactIs_development.Fragment = Fragment;
reactIs_development.Lazy = Lazy;
reactIs_development.Memo = Memo;
reactIs_development.Portal = Portal;
reactIs_development.Profiler = Profiler;
reactIs_development.StrictMode = StrictMode;
reactIs_development.Suspense = Suspense;
reactIs_development.SuspenseList = SuspenseList;
reactIs_development.isAsyncMode = isAsyncMode;
reactIs_development.isConcurrentMode = isConcurrentMode;
reactIs_development.isContextConsumer = isContextConsumer;
reactIs_development.isContextProvider = isContextProvider;
reactIs_development.isElement = isElement;
reactIs_development.isForwardRef = isForwardRef;
reactIs_development.isFragment = isFragment;
reactIs_development.isLazy = isLazy;
reactIs_development.isMemo = isMemo;
reactIs_development.isPortal = isPortal;
reactIs_development.isProfiler = isProfiler;
reactIs_development.isStrictMode = isStrictMode;
reactIs_development.isSuspense = isSuspense;
reactIs_development.isSuspenseList = isSuspenseList;
reactIs_development.isValidElementType = isValidElementType;
reactIs_development.typeOf = typeOf;
})();
}
return reactIs_development;
}
var hasRequiredReactIs;
function requireReactIs () {
if (hasRequiredReactIs) return reactIs.exports;
hasRequiredReactIs = 1;
{
reactIs.exports = requireReactIs_development();
}
return reactIs.exports;
}
var reactIsExports = requireReactIs();
var unknown = '__autocomplete_unknown__' ;
var mouseUp = '__autocomplete_mouseup__' ;
var itemMouseEnter = '__autocomplete_item_mouseenter__' ;
var keyDownArrowUp = '__autocomplete_keydown_arrow_up__' ;
var keyDownArrowDown = '__autocomplete_keydown_arrow_down__' ;
var keyDownEscape = '__autocomplete_keydown_escape__' ;
var keyDownEnter = '__autocomplete_keydown_enter__' ;
var keyDownHome = '__autocomplete_keydown_home__' ;
var keyDownEnd = '__autocomplete_keydown_end__' ;
var clickItem = '__autocomplete_click_item__' ;
var blurInput = '__autocomplete_blur_input__' ;
var changeInput = '__autocomplete_change_input__' ;
var keyDownSpaceButton = '__autocomplete_keydown_space_button__' ;
var clickButton = '__autocomplete_click_button__' ;
var blurButton = '__autocomplete_blur_button__' ;
var controlledPropUpdatedSelectedItem = '__autocomplete_controlled_prop_updated_selected_item__' ;
var touchEnd = '__autocomplete_touchend__' ;
var stateChangeTypes$4 = /*#__PURE__*/Object.freeze({
__proto__: null,
blurButton: blurButton,
blurInput: blurInput,
changeInput: changeInput,
clickButton: clickButton,
clickItem: clickItem,
controlledPropUpdatedSelectedItem: controlledPropUpdatedSelectedItem,
itemMouseEnter: itemMouseEnter,
keyDownArrowDown: keyDownArrowDown,
keyDownArrowUp: keyDownArrowUp,
keyDownEnd: keyDownEnd,
keyDownEnter: keyDownEnter,
keyDownEscape: keyDownEscape,
keyDownHome: keyDownHome,
keyDownSpaceButton: keyDownSpaceButton,
mouseUp: mouseUp,
touchEnd: touchEnd,
unknown: unknown
});
var idCounter = 0;
/**
* This generates a unique ID for an instance of Downshift
* @return {string} the unique ID
*/
function generateId() {
return String(idCounter++);
}
/**
* Resets idCounter to 0. Used for SSR.
*/
function resetIdCounter() {
// istanbul ignore next
if ('useId' in React__namespace) {
console.warn("It is not necessary to call resetIdCounter when using React 18+");
return;
}
idCounter = 0;
}
function useLatestRef(val) {
var ref = React__namespace.useRef(val);
// technically this is not "concurrent mode safe" because we're manipulating
// the value during render (so it's not idempotent). However, the places this
// hook is used is to support memoizing callbacks which will be called
// *during* render, so we need the latest values *during* render.
// If not for this, then we'd probably want to use useLayoutEffect instead.
ref.current = val;
return ref;
}
function handleRefs$1() {
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
refs[_key] = arguments[_key];
}
return function (node) {
refs.forEach(function (ref) {
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
});
};
}
/**
* This is intended to be used to compose event handlers.
* They are executed in order until one of them sets
* `event.preventDownshiftDefault = true`.
* @param fns the event handler functions
* @return the event handler to add to an element
*/
function callAllEventHandlers$1() {
for (var _len = arguments.length, fns = new Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];
}
return function (event) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return fns.some(function (fn) {
if (fn) {
fn.apply(void 0, [event].concat(args));
}
return event.preventDownshiftDefault || event.hasOwnProperty('nativeEvent') && event.nativeEvent.preventDownshiftDefault;
});
};
}
/**
* Simple debounce implementation. Will call the given
* function once after the time given has passed since
* it was last called.
*/
function debounce$1(fn, time) {
var timeoutId;
function cancel() {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
function wrapper() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
cancel();
timeoutId = setTimeout(function () {
timeoutId = null;
fn.apply(void 0, args);
}, time);
}
wrapper.cancel = cancel;
return wrapper;
}
var cleanupStatus = debounce$1(function (document) {
getStatusDiv(document).textContent = '';
}, 500);
/**
* Get the status node or create it if it does not already exist.
*/
function getStatusDiv(document) {
var statusDiv = document.getElementById('a11y-status-message');
if (statusDiv) {
return statusDiv;
}
statusDiv = document.createElement('div');
statusDiv.setAttribute('id', 'a11y-status-message');
statusDiv.setAttribute('role', 'status');
statusDiv.setAttribute('aria-live', 'polite');
statusDiv.setAttribute('aria-relevant', 'additions text');
Object.assign(statusDiv.style, {
border: '0',
clip: 'rect(0 0 0 0)',
height: '1px',
margin: '-1px',
overflow: 'hidden',
padding: '0',
position: 'absolute',
width: '1px'
});
document.body.appendChild(statusDiv);
return statusDiv;
}
/**
* Sets aria live status to a div element that's visually hidden.
*/
function setStatus(status, document) {
if (!status || !document) {
return;
}
var div = getStatusDiv(document);
div.textContent = status;
cleanupStatus(document);
}
/**
* Removes the status element from the DOM
*/
function cleanupStatusDiv(document) {
var statusDiv = document == null ? void 0 : document.getElementById('a11y-status-message');
if (statusDiv) {
statusDiv.remove();
}
}
function noop() {}
// eslint-disable-next-line import/no-mutable-exports
var validatePropTypes$1 = noop;
/* istanbul ignore next */
{
validatePropTypes$1 = function validatePropTypes(options, caller, propTypes) {
PropTypes.checkPropTypes(propTypes, options, 'prop', caller.name);
};
}
/**
* This will perform a shallow merge of the given state object
* with the state coming from props
* (for the controlled component scenario)
* This is used in state updater functions so they're referencing
* the right state regardless of where it comes from.
*
* @param state The state of the component/hook.
* @param props The props that may contain controlled values.
* @returns The merged controlled state.
*/
function getState(state, props) {
if (!props) {
return state;
}
var keys = Object.keys(state);
return keys.reduce(function (newState, key) {
if (props[key] !== undefined) {
newState[key] = props[key];
}
return newState;
}, _extends({}, state));
}
const t=t=>"object"==typeof t&&null!=t&&1===t.nodeType,e=(t,e)=>(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t),n=(t,n)=>{if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const o=getComputedStyle(t,null);return e(o.overflowY,n)||e(o.overflowX,n)||(t=>{const e=(t=>{if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}})(t);return !!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)})(t)}return false},o=(t,e,n,o,l,r,i,s)=>r<t&&i>e||r>t&&i<e?0:r<=t&&s<=n||i>=e&&s>=n?r-t-o:i>e&&s<n||r<t&&s>n?i-e+l:0,l=t=>{const e=t.parentElement;return null==e?t.getRootNode().host||null:e},r=(e,r)=>{var i,s,d,h;if("undefined"==typeof document)return [];const{inline:u,boundary:a,skipOverflowHiddenElements:g}=r,p="function"==typeof a?a:t=>t!==a;if(!t(e))throw new TypeError("Invalid target");const m=document.scrollingElement||document.documentElement,w=[];let W=e;for(;t(W)&&p(W);){if(W=l(W),W===m){w.push(W);break}null!=W&&W===document.body&&n(W)&&!n(document.documentElement)||null!=W&&n(W,g)&&w.push(W);}const b=null!=(s=null==(i=window.visualViewport)?void 0:i.width)?s:innerWidth,H=null!=(h=null==(d=window.visualViewport)?void 0:d.height)?h:innerHeight,{scrollX:y,scrollY:M}=window,{height:v,width:E,top:x,right:C,bottom:I,left:R}=e.getBoundingClientRect(),{top:T,right:B,left:V}=(t=>{const e=window.getComputedStyle(t);return {top:parseFloat(e.scrollMarginTop)||0,right:parseFloat(e.scrollMarginRight)||0,bottom:parseFloat(e.scrollMarginBottom)||0,left:parseFloat(e.scrollMarginLeft)||0}})(e);let k=x-T,D="center"===u?R+E/2-V+B:"end"===u?C+B:R-V;const L=[];for(let t=0;t<w.length;t++){const e=w[t],{height:l,width:r,top:i,right:s,bottom:d,left:h}=e.getBoundingClientRect();if(x>=0&&R>=0&&I<=H&&C<=b&&(e===m&&!n(e)||x>=i&&I<=d&&R>=h&&C<=s))return L;const a=getComputedStyle(e),g=parseInt(a.borderLeftWidth,10),p=parseInt(a.borderTopWidth,10),W=parseInt(a.borderRightWidth,10),T=parseInt(a.borderBottomWidth,10);let B=0,F=0;const V="offsetWidth"in e?e.offsetWidth-e.clientWidth-g-W:0,S="offsetHeight"in e?e.offsetHeight-e.clientHeight-p-T:0,X="offsetWidth"in e?0===e.offsetWidth?0:r/e.offsetWidth:0,Y="offsetHeight"in e?0===e.offsetHeight?0:l/e.offsetHeight:0;if(m===e)B=o(M,M+H,H,p,T,M+k,M+k+v,v),F="start"===u?D:"center"===u?D-b/2:"end"===u?D-b:o(y,y+b,b,g,W,y+D,y+D+E,E),B=Math.max(0,B+M),F=Math.max(0,F+y);else {B=o(i,d,l,p,T+S,k,k+v,v),F="start"===u?D-h-g:"center"===u?D-(h+r/2)+V/2:"end"===u?D-s+W+V:o(h,s,r,g,W+V,D,D+E,E);const{scrollLeft:t,scrollTop:n}=e;B=0===Y?0:Math.max(0,Math.min(n+B/Y,e.scrollHeight-l/Y+S)),F=0===X?0:Math.max(0,Math.min(t+F/X,e.scrollWidth-r/X+V)),k+=n-B,D+=t-F;}L.push({el:e,top:B,left:F});}return L};
/**
* Scroll node into view if necessary
* @param {HTMLElement} node the element that should scroll into view
* @param {HTMLElement} menuNode the menu element of the component
*/
function scrollIntoView(node, menuNode) {
if (!node) {
return;
}
var actions = r(node, {
boundary: menuNode});
actions.forEach(function (_ref) {
var el = _ref.el,
top = _ref.top,
left = _ref.left;
el.scrollTop = top;
el.scrollLeft = left;
});
}
/**
* Accepts a parameter and returns it if it's a function
* or a noop function if it's not. This allows us to
* accept a callback, but not worry about it if it's not
* passed.
* @param {Function} cb the callback
* @return {Function} a function
*/
function cbToCb(cb) {
return typeof cb === 'function' ? cb : noop;
}
/**
* @param {HTMLElement} parent the parent node
* @param {HTMLElement} child the child node
* @param {Window} environment The window context where downshift renders.
* @return {Boolean} whether the parent is the child or the child is in the parent
*/
function isOrContainsNode(parent, child, environment) {
var result = parent === child || child instanceof environment.Node && parent.contains && parent.contains(child);
return result;
}
/**
* Simple debounce implementation. Will call the given
* function once after the time given has passed since
* it was last called.
* @param {Function} fn the function to call after the time
* @param {Number} time the time to wait
* @return {Function} the debounced function
*/
function debounce(fn, time) {
var timeoutId;
function cancel() {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
function wrapper() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
cancel();
timeoutId = setTimeout(function () {
timeoutId = null;
fn.apply(void 0, args);
}, time);
}
wrapper.cancel = cancel;
return wrapper;
}
/**
* This is intended to be used to compose event handlers.
* They are executed in order until one of them sets
* `event.preventDownshiftDefault = true`.
* @param {...Function} fns the event handler functions
* @return {Function} the event handler to add to an element
*/
function callAllEventHandlers() {
for (var _len2 = arguments.length, fns = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
fns[_key2] = arguments[_key2];
}
return function (event) {
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
return fns.some(function (fn) {
if (fn) {
fn.apply(void 0, [event].concat(args));
}
return event.preventDownshiftDefault || event.hasOwnProperty('nativeEvent') && event.nativeEvent.preventDownshiftDefault;
});
};
}
function handleRefs() {
for (var _len4 = arguments.length, refs = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
refs[_key4] = arguments[_key4];
}
return function (node) {
refs.forEach(function (ref) {
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
});
};
}
/**
* Default implementation for status message. Only added when menu is open.
* Will specify if there are results in the list, and if so, how many,
* and what keys are relevant.
*
* @param {Object} param the downshift state and other relevant properties
* @return {String} the a11y status message
*/
function getA11yStatusMessage(_ref) {
var isOpen = _ref.isOpen,
resultCount = _ref.resultCount,
previousResultCount = _ref.previousResultCount;
if (!isOpen) {
return '';
}
if (!resultCount) {
return 'No results are available.';
}
if (resultCount !== previousResultCount) {
return resultCount + " result" + (resultCount === 1 ? ' is' : 's are') + " available, use up and down arrow keys to navigate. Press Enter key to select.";
}
return '';
}
/**
* Takes an argument and if it's an array, returns the first item in the array
* otherwise returns the argument
* @param {*} arg the maybe-array
* @param {*} defaultValue the value if arg is falsey not defined
* @return {*} the arg or it's first item
*/
function unwrapArray(arg, defaultValue) {
arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */arg[0] : arg;
if (!arg && defaultValue) {
return defaultValue;
} else {
return arg;
}
}
/**
* @param {Object} element (P)react element
* @return {Boolean} whether it's a DOM element
*/
function isDOMElement(element) {
/* istanbul ignore if */
{
// then this is preact or preact X
return typeof element.type === 'string' || typeof element.nodeName === 'string';
}
}
/**
* @param {Object} element (P)react element
* @return {Object} the props
*/
function getElementProps(element) {
// props for react, attributes for preact
/* istanbul ignore if */
{
return element.props || element.attributes;
}
}
/**
* Throws a helpful error message for required properties. Useful
* to be used as a default in destructuring or object params.
* @param {String} fnName the function name
* @param {String} propName the prop name
*/
function requiredProp(fnName, propName) {
// eslint-disable-next-line no-console
console.error("The property \"" + propName + "\" is required in \"" + fnName + "\"");
}
var stateKeys = ['highlightedIndex', 'inputValue', 'isOpen', 'selectedItem', 'type'];
/**
* @param {Object} state the state object
* @return {Object} state that is relevant to downshift
*/
function pickState(state) {
if (state === void 0) {
state = {};
}
var result = {};
stateKeys.forEach(function (k) {
if (state.hasOwnProperty(k)) {
result[k] = state[k];
}
});
return result;
}
/**
* This determines whether a prop is a "controlled prop" meaning it is
* state which is controlled by the outside of this component rather
* than within this component.
*
* @param {Object} props The props that may contain controlled values.
* @param {String} key the key to check
* @return {Boolean} whether it is a controlled controlled prop
*/
function isControlledProp(props, key) {
return props[key] !== undefined;
}
/**
* Normalizes the 'key' property of a KeyboardEvent in IE/Edge
* @param {Object} event a keyboardEvent object
* @return {String} keyboard key
*/
function normalizeArrowKey(event) {
var key = event.key,
keyCode = event.keyCode;
/* istanbul ignore next (ie) */
if (keyCode >= 37 && keyCode <= 40 && key.indexOf('Arrow') !== 0) {
return "Arrow" + key;
}
return key;
}
/**
* Simple check if the value passed is object literal
* @param {*} obj any things
* @return {Boolean} whether it's object literal
*/
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
/**
* Returns the next non-disabled highlightedIndex value.
*
* @param {number} start The current highlightedIndex.
* @param {number} offset The offset from the current highlightedIndex to start searching.
* @param {unknown[]} items The items array.
* @param {(item: unknown, index: number) => boolean} isItemDisabled Function that tells if an item is disabled or not.
* @param {boolean?} circular If the search reaches the end, if it can search again starting from the other end.
* @returns {number} The next highlightedIndex.
*/
function getHighlightedIndex(start, offset, items, isItemDisabled, circular) {
if (circular === void 0) {
circular = false;
}
var count = items.length;
if (count === 0) {
return -1;
}
var itemsLastIndex = count - 1;
if (typeof start !== 'number' || start < 0 || start > itemsLastIndex) {
start = offset > 0 ? -1 : itemsLastIndex + 1;
}
var current = start + offset;
if (current < 0) {
current = circular ? itemsLastIndex : 0;
} else if (current > itemsLastIndex) {
current = circular ? 0 : itemsLastIndex;
}
var highlightedIndex = getNonDisabledIndex(current, offset < 0, items, isItemDisabled, circular);
if (highlightedIndex === -1) {
return start >= count ? -1 : start;
}
return highlightedIndex;
}
/**
* Returns the next non-disabled highlightedIndex value.
*
* @param {number} start The current highlightedIndex.
* @param {boolean} backwards If true, it will search backwards from the start.
* @param {unknown[]} items The items array.
* @param {(item: unknown, index: number) => boolean} isItemDisabled Function that tells if an item is disabled or not.
* @param {boolean} circular If the search reaches the end, if it can search again starting from the other end.
* @returns {number} The next non-disabled index.
*/
function getNonDisabledIndex(start, backwards, items, isItemDisabled, circular) {
if (circular === void 0) {
circular = false;
}
var count = items.length;
if (backwards) {
for (var index = start; index >= 0; index--) {
if (!isItemDisabled(items[index], index)) {
return index;
}
}
} else {
for (var _index = start; _index < count; _index++) {
if (!isItemDisabled(items[_index], _index)) {
return _index;
}
}
}
if (circular) {
return getNonDisabledIndex(backwards ? count - 1 : 0, backwards, items, isItemDisabled);
}
return -1;
}
/**
* Checks if event target is within the downshift elements.
*
* @param {EventTarget} target Target to check.
* @param {HTMLElement[]} downshiftElements The elements that form downshift (list, toggle button etc).
* @param {Window} environment The window context where downshift renders.
* @param {boolean} checkActiveElement Whether to also check activeElement.
*
* @returns {boolean} Whether or not the target is within downshift elements.
*/
function targetWithinDownshift(target, downshiftElements, environment, checkActiveElement) {
if (checkActiveElement === void 0) {
checkActiveElement = true;
}
return environment && downshiftElements.some(function (contextNode) {
return contextNode && (isOrContainsNode(contextNode, target, environment) || checkActiveElement && isOrContainsNode(contextNode, environment.document.activeElement, environment));
});
}
// eslint-disable-next-line import/no-mutable-exports
var validateControlledUnchanged = noop;
/* istanbul ignore next */
{
validateControlledUnchanged = function validateControlledUnchanged(state, prevProps, nextProps) {
var warningDescription = "This prop should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled Downshift element for the lifetime of the component. More info: https://github.com/downshift-js/downshift#control-props";
Object.keys(state).forEach(function (propKey) {
if (prevProps[propKey] !== undefined && nextProps[propKey] === undefined) {
// eslint-disable-next-line no-console
console.error("downshift: A component has changed the controlled prop \"" + propKey + "\" to be uncontrolled. " + warningDescription);
} else if (prevProps[propKey] === undefined && nextProps[propKey] !== undefined) {
// eslint-disable-next-line no-console
console.error("downshift: A component has changed the uncontrolled prop \"" + propKey + "\" to be controlled. " + warningDescription);
}
});
};
}
var _excluded$4 = ["refKey", "ref"],
_excluded2$4 = ["onClick", "onPress", "onKeyDown", "onKeyUp", "onBlur"],
_excluded3$3 = ["onKeyDown", "onBlur", "onChange", "onInput", "onChangeText"],
_excluded4$2 = ["refKey", "ref"],
_excluded5 = ["onMouseMove", "onMouseDown", "onClick", "onPress", "index", "item"];
var Downshift = /*#__PURE__*/function () {
var Downshift = /*#__PURE__*/function (_Component) {
function Downshift(_props) {
var _this;
_this = _Component.call(this, _props) || this;
// fancy destructuring + defaults + aliases
// this basically says each value of state should either be set to
// the initial value or the default value if the initial value is not provided
_this.id = _this.props.id || "downshift-" + generateId();
_this.menuId = _this.props.menuId || _this.id + "-menu";
_this.labelId = _this.props.labelId || _this.id + "-label";
_this.inputId = _this.props.inputId || _this.id + "-input";
_this.getItemId = _this.props.getItemId || function (index) {
return _this.id + "-item-" + index;
};
_this.items = [];
// itemCount can be changed asynchronously
// from within downshift (so it can't come from a prop)
// this is why we store it as an instance and use
// getItemCount rather than just use items.length
// (to support windowing + async)
_this.itemCount = null;
_this.previousResultCount = 0;
_this.timeoutIds = [];
/**
* @param {Function} fn the function to call after the time
* @param {Number} time the time to wait
*/
_this.internalSetTimeout = function (fn, time) {
var id = setTimeout(function () {
_this.timeoutIds = _this.timeoutIds.filter(function (i) {
return i !== id;
});
fn();
}, time);
_this.timeoutIds.push(id);
};
_this.setItemCount = function (count) {
_this.itemCount = count;
};
_this.unsetItemCount = function () {
_this.itemCount = null;
};
_this.isItemDisabled = function (_item, index) {
var currentElementNode = _this.getItemNodeFromIndex(index);
return currentElementNode && currentElementNode.hasAttribute('disabled');
};
_this.setHighlightedIndex = function (highlightedIndex, otherStateToSet) {
if (highlightedIndex === void 0) {
highlightedIndex = _this.props.defaultHighlightedIndex;
}
if (otherStateToSet === void 0) {
otherStateToSet = {};
}
otherStateToSet = pickState(otherStateToSet);
_this.internalSetState(_extends({
highlightedIndex: highlightedIndex
}, otherStateToSet));
};
_this.clearSelection = function (cb) {
_this.internalSetState({
selectedItem: null,
inputValue: '',
highlightedIndex: _this.props.defaultHighlightedIndex,
isOpen: _this.props.defaultIsOpen
}, cb);
};
_this.selectItem = function (item, otherStateToSet, cb) {
otherStateToSet = pickState(otherStateToSet);
_this.internalSetState(_extends({
isOpen: _this.props.defaultIsOpen,
highlightedIndex: _this.props.defaultHighlightedIndex,
selectedItem: item,
inputValue: _this.props.itemToString(item)
}, otherStateToSet), cb);
};
_this.selectItemAtIndex = function (itemIndex, otherStateToSet, cb) {
var item = _this.items[itemIndex];
if (item == null) {
return;
}
_this.selectItem(item, otherStateToSet, cb);
};
_this.selectHighlightedItem = function (otherStateToSet, cb) {
return _this.selectItemAtIndex(_this.getState().highlightedIndex, otherStateToSet, cb);
};
// any piece of our state can live in two places:
// 1. Uncontrolled: it's internal (this.state)
// We will call this.setState to update that state
// 2. Controlled: it's external (this.props)
// We will call this.props.onStateChange to update that state
//
// In addition, we'll call this.props.onChange if the
// selectedItem is changed.
_this.internalSetState = function (stateToSet, cb) {
var isItemSelected, onChangeArg;
var onStateChangeArg = {};
var isStateToSetFunction = typeof stateToSet === 'function';
// we want to call `onInputValueChange` before the `setState` call
// so someone controlling the `inputValue` state gets notified of
// the input change as soon as possible. This avoids issues with
// preserving the cursor position.
// See https://github.com/downshift-js/downshift/issues/217 for more info.
if (!isStateToSetFunction && stateToSet.hasOwnProperty('inputValue')) {
_this.props.onInputValueChange(stateToSet.inputValue, _extends({}, _this.getStateAndHelpers(), stateToSet));
}
return _this.setState(function (state) {
var _newStateToSet;
state = _this.getState(state);
var newStateToSet = isStateToSetFunction ? stateToSet(state) : stateToSet;
// Your own function that could modify the state that will be set.
newStateToSet = _this.props.stateReducer(state, newStateToSet);
// checks if an item is selected, regardless of if it's different from
// what was selected before
// used to determine if onSelect and onChange callbacks should be called
isItemSelected = newStateToSet.hasOwnProperty('selectedItem');
// this keeps track of the object we want to call with setState
var nextState = {};
// we need to call on change if the outside world is controlling any of our state
// and we're trying to update that state. OR if the selection has changed and we're
// trying to update the selection
if (isItemSelected && newStateToSet.selectedItem !== state.selectedItem) {
onChangeArg = newStateToSet.selectedItem;
}
(_newStateToSet = newStateToSet).type || (_newStateToSet.type = unknown);
Object.keys(newStateToSet).forEach(function (key) {
// onStateChangeArg should only have the state that is
// actually changing
if (state[key] !== newStateToSet[key]) {
onStateChangeArg[key] = newStateToSet[key];
}
// the type is useful for the onStateChangeArg
// but we don't actually want to set it in internal state.
// this is an undocumented feature for now... Not all internalSetState
// calls support it and I'm not certain we want them to yet.
// But it enables users controlling the isOpen state to know when
// the isOpen state changes due to mouseup events which is quite handy.
if (key === 'type') {
return;
}
newStateToSet[key];
// if it's coming from props, then we don't care to set it internally
if (!isControlledProp(_this.props, key)) {
nextState[key] = newStateToSet[key];
}
});
// if stateToSet is a function, then we weren't able to call onInputValueChange
// earlier, so we'll call it now that we know what the inputValue state will be.
if (isStateToSetFunction && newStateToSet.hasOwnProperty('inputValue')) {
_this.props.onInputValueChange(newStateToSet.inputValue, _extends({}, _this.getStateAndHelpers(), newStateToSet));
}
return nextState;
}, function () {
// call the provided callback if it's a function
cbToCb(cb)();
// only call the onStateChange and onChange callbacks if
// we have relevant information to pass them.
var hasMoreStateThanType = Object.keys(onStateChangeArg).length > 1;
if (hasMoreStateThanType) {
_this.props.onStateChange(onStateChangeArg, _this.getStateAndHelpers());
}
if (isItemSelected) {
_this.props.onSelect(stateToSet.selectedItem, _this.getStateAndHelpers());
}
if (onChangeArg !== undefined) {
_this.props.onChange(onChangeArg, _this.getStateAndHelpers());
}
// this is currently undocumented and therefore subject to change
// We'll try to not break it, but just be warned.
_this.props.onUserAction(onStateChangeArg, _this.getStateAndHelpers());
});
};
//////////////////////////// ROOT
_this.rootRef = function (node) {
return _this._rootNode = node;
};
_this.getRootProps = function (_temp, _temp2) {
var _extends2;
var _ref = _temp === void 0 ? {} : _temp,
_ref$refKey = _ref.refKey,
refKey = _ref$refKey === void 0 ? 'ref' : _ref$refKey,
ref = _ref.ref,
rest = _objectWithoutPropertiesLoose(_ref, _excluded$4);
var _ref2 = _temp2 === void 0 ? {} : _temp2,
_ref2$suppressRefErro = _ref2.suppressRefError,
suppressRefError = _ref2$suppressRefErro === void 0 ? false : _ref2$suppressRefErro;
// this is used in the render to know whether the user has called getRootProps.
// It uses that to know whether to apply the props automatically
_this.getRootProps.called = true;
_this.getRootProps.refKey = refKey;
_this.getRootProps.suppressRefError = suppressRefError;
var _this$getState = _this.getState(),
isOpen = _this$getState.isOpen;
return _extends((_extends2 = {}, _extends2[refKey] = handleRefs(ref, _this.rootRef), _extends2.role = 'combobox', _extends2['aria-expanded'] = isOpen, _extends2['aria-haspopup'] = 'listbox', _extends2['aria-owns'] = isOpen ? _this.menuId : undefined, _extends2['aria-labelledby'] = _this.labelId, _extends2), rest);
};
//\\\\\\\\\\\\\\\\\\\\\\\\\\ ROOT
_this.keyDownHandlers = {
ArrowDown: function ArrowDown(event) {
var _this2 = this;
event.preventDefault();
if (this.getState().isOpen) {
var amount = event.shiftKey ? 5 : 1;
this.moveHighlightedIndex(amount, {
type: keyDownArrowDown
});
} else {
this.internalSetState({
isOpen: true,
type: keyDownArrowDown
}, function () {
var itemCount = _this2.getItemCount();
if (itemCount > 0) {
var _this2$getState = _this2.getState(),
highlightedIndex = _this2$getState.highlightedIndex;
var nextHighlightedIndex = getHighlightedIndex(highlightedIndex, 1, {
length: itemCount
}, _this2.isItemDisabled, true);
_this2.setHighlightedIndex(nextHighlightedIndex, {
type: keyDownArrowDown
});
}
});
}
},
ArrowUp: function ArrowUp(event) {
var _this3 = this;
event.preventDefault();
if (this.getState().isOpen) {
var amount = event.shiftKey ? -5 : -1;
this.moveHighlightedIndex(amount, {
type: keyDownArrowUp
});
} else {
this.internalSetState({
isOpen: true,
type: keyDownArrowUp
}, function () {
var itemCount = _this3.getItemCount();
if (itemCount > 0) {
var _this3$getState = _this3.getState(),
highlightedIndex = _this3$getState.highlightedIndex;
var nextHighlightedIndex = getHighlightedIndex(highlightedIndex, -1, {
length: itemCount
}, _this3.isItemDisabled, true);
_this3.setHighlightedIndex(nextHighlightedIndex, {
type: keyDownArrowUp
});
}
});
}
},
Enter: function Enter(event) {
if (event.which === 229) {
return;
}
var _this$getState2 = this.getState(),
isOpen = _this$getState2.isOpen,
highlightedIndex = _this$getState2.highlightedIndex;
if (isOpen && highlightedIndex != null) {
event.preventDefault();
var item = this.items[highlightedIndex];
var itemNode = this.getItemNodeFromIndex(highlightedIndex);
if (item == null || itemNode && itemNode.hasAttribute('disabled')) {
return;
}
this.selectHighlightedItem({
type: keyDownEnter
});
}
},
Escape: function Escape(event) {
event.preventDefault();
this.reset(_extends({
type: keyDownEscape
}, !this.state.isOpen && {
selectedItem: null,
inputValue: ''
}));
}
};
//////////////////////////// BUTTON
_this.buttonKeyDownHandlers = _extends({}, _this.keyDownHandlers, {
' ': function _(event) {
event.preventDefault();
this.toggleMenu({
type: keyDownSpaceButton
});
}
});
_this.inputKeyDownHandlers = _extends({}, _this.keyDownHandlers, {
Home: function Home(event) {
var _this$getState3 = this.getState(),
isOpen = _this$getState3.isOpen;
if (!isOpen) {
return;
}
event.preventDefault();
var itemCount = this.getItemCount();
if (itemCount <= 0 || !isOpen) {
return;
}
// get next non-disabled starting downwards from 0 if that's disabled.
var newHighlightedIndex = getNonDisabledIndex(0, false, {
length: itemCount
}, this.isItemDisabled);
this.setHighlightedIndex(newHighlightedIndex, {
type: keyDownHome
});
},
End: function End(event) {
var _this$getState4 = this.getState(),
isOpen = _this$getState4.isOpen;
if (!isOpen) {
return;
}
event.preventDefault();
var itemCount = this.getItemCount();
if (itemCount <= 0 || !isOpen) {
return;
}
// get next non-disabled starting upwards from last index if that's disabled.
var newHighlightedIndex = getNonDisabledIndex(itemCount - 1, true, {
length: itemCount
}, this.isItemDisabled);
this.setHighlightedIndex(newHighlightedIndex, {
type: keyDownEnd
});
}
});
_this.getToggleButtonProps = function (_temp3) {
var _ref3 = _temp3 === void 0 ? {} : _temp3,
onClick = _ref3.onClick;
_ref3.onPress;
var onKeyDown = _ref3.onKeyDown,
onKeyUp = _ref3.onKeyUp,
onBlur = _ref3.onBlur,
rest = _objectWithoutPropertiesLoose(_ref3, _excluded2$4);
var _this$getState5 = _this.getState(),
isOpen = _this$getState5.isOpen;
var enabledEventHandlers = {
onClick: callAllEventHandlers(onClick, _this.buttonHandleClick),
onKeyDown: callAllEventHandlers(onKeyDown, _this.buttonHandleKeyDown),
onKeyUp: callAllEventHandlers(onKeyUp, _this.buttonHandleKeyUp),
onBlur: callAllEventHandlers(onBlur, _this.buttonHandleBlur)
};
var eventHandlers = rest.disabled ? {} : enabledEventHandlers;
return _extends({
type: 'button',
role: 'button',
'aria-label': isOpen ? 'close menu' : 'open menu',
'aria-haspopup': true,
'data-toggle': true
}, eventHandlers, rest);
};
_this.buttonHandleKeyUp = function (event) {
// Prevent click event from emitting in Firefox
event.preventDefault();
};
_this.buttonHandleKeyDown = function (event) {
var key = normalizeArrowKey(event);
if (_this.buttonKeyDownHandlers[key]) {
_this.buttonKeyDownHandlers[key].call(_this, event);
}
};
_this.buttonHandleClick = function (event) {
event.preventDefault();
// handle odd case for Safari and Firefox which
// don't give the button the focus properly.
/* istanbul ignore if (can't reasonably test this) */
if (_this.props.environment) {
var _this$props$environme = _this.props.environment.document,
body = _this$props$environme.body,
activeElement = _this$props$environme.