downshift 9.4.0-alpha.2 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/downshift.cjs.cjs +132 -126
  2. package/dist/downshift.d.ts +60 -56
  3. package/dist/downshift.esm.mjs +132 -126
  4. package/dist/downshift.native.cjs.cjs +129 -126
  5. package/dist/downshift.nativeweb.cjs.cjs +132 -126
  6. package/dist/downshift.umd.js +132 -126
  7. package/dist/downshift.umd.js.map +1 -1
  8. package/dist/downshift.umd.min.js +1 -1
  9. package/dist/downshift.umd.min.js.map +1 -1
  10. package/dist/hooks/testUtils/interactions.d.ts +7 -7
  11. package/dist/hooks/useCombobox/__tests__/utils/renderCombobox.d.ts +3 -59
  12. package/dist/hooks/useCombobox/__tests__/utils/renderUseCombobox.d.ts +1 -1
  13. package/dist/hooks/useCombobox/index.types.d.ts +0 -1
  14. package/dist/hooks/useCombobox/utils/getInitialState.d.ts +1 -6
  15. package/dist/hooks/useCombobox/utils/propTypes.d.ts +1 -43
  16. package/dist/hooks/useSelect/__tests__/utils/getItemIndexByCharacter.d.ts +1 -1
  17. package/dist/hooks/useSelect/__tests__/utils/renderSelect.d.ts +2 -58
  18. package/dist/hooks/useSelect/__tests__/utils/renderUseSelect.d.ts +1 -1
  19. package/dist/hooks/useSelect/__tests__/utils/stateChangeTestCases.d.ts +29 -50
  20. package/dist/hooks/useSelect/utils/propTypes.d.ts +1 -38
  21. package/dist/hooks/useTagGroup/__tests__/utils/renderTagGroup.d.ts +1 -63
  22. package/dist/hooks/useTagGroup/__tests__/utils/renderUseTagGroup.d.ts +2 -2
  23. package/dist/hooks/utils/dropdownDefaultProps.d.ts +3 -4
  24. package/dist/hooks/utils/useMouseAndTouchTracker.d.ts +3 -3
  25. package/dist/hooks/utils/useScrollIntoView.d.ts +4 -3
  26. package/dist/utils/getHighlightedIndex.d.ts +1 -1
  27. package/dist/utils/getNonDisabledIndex.d.ts +1 -1
  28. package/dist/utils/index.d.ts +1 -0
  29. package/dist/utils/useIsomorphicLayoutEffect.d.ts +2 -0
  30. package/dist/utils/useLatestRef.d.ts +1 -1
  31. package/package.json +15 -2
  32. package/preact/dist/downshift.cjs.cjs +132 -126
  33. package/preact/dist/downshift.esm.mjs +132 -126
  34. package/preact/dist/downshift.umd.js +132 -126
  35. package/preact/dist/downshift.umd.js.map +1 -1
  36. package/preact/dist/downshift.umd.min.js +1 -1
  37. package/preact/dist/downshift.umd.min.js.map +1 -1
  38. package/dist/hooks/useMultipleSelection/index.d.ts +0 -50
  39. package/dist/hooks/useMultipleSelection/reducer.d.ts +0 -1
  40. package/dist/hooks/useMultipleSelection/stateChangeTypes.d.ts +0 -13
  41. package/dist/hooks/useMultipleSelection/utils.d.ts +0 -44
  42. package/dist/types.d.ts +0 -12
  43. package/preact/dist/downshift.cjs.js +0 -4265
  44. package/preact/dist/downshift.esm.js +0 -4238
@@ -90,14 +90,18 @@ function resetIdCounter() {
90
90
  idCounter = 0;
91
91
  }
92
92
 
93
- function useLatestRef(val) {
94
- var ref = React__namespace.useRef(val);
95
- // technically this is not "concurrent mode safe" because we're manipulating
96
- // the value during render (so it's not idempotent). However, the places this
97
- // hook is used is to support memoizing callbacks which will be called
98
- // *during* render, so we need the latest values *during* render.
99
- // If not for this, then we'd probably want to use useLayoutEffect instead.
100
- ref.current = val;
93
+ // istanbul ignore next
94
+ var canUseDOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
95
+
96
+ // React Native supports layout effects even though it does not have a DOM.
97
+ // istanbul ignore next
98
+ var useIsomorphicLayoutEffect = canUseDOM || false ? React__namespace.useLayoutEffect : React__namespace.useEffect;
99
+
100
+ function useLatestRef(value) {
101
+ var ref = React__namespace.useRef(value);
102
+ useIsomorphicLayoutEffect(function () {
103
+ ref.current = value;
104
+ }, [value]);
101
105
  return ref;
102
106
  }
103
107
 
@@ -1624,18 +1628,27 @@ function invokeOnChangeHandler(key, action, props, state, newState) {
1624
1628
  */
1625
1629
  function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1626
1630
  var prevStateRef = React__namespace.useRef({});
1627
- var actionRef = React__namespace.useRef();
1628
- var enhancedReducer = React__namespace.useCallback(function (state, action) {
1629
- actionRef.current = action;
1631
+ var enhancedReducer = React__namespace.useCallback(function (_ref, action) {
1632
+ var state = _ref.state;
1630
1633
  state = getState(state, action.props);
1631
1634
  var changes = reducer(state, action);
1632
1635
  var newState = action.props.stateReducer(state, _extends({}, action, {
1633
1636
  changes: changes
1634
1637
  }));
1635
- return _extends({}, state, newState);
1638
+ return {
1639
+ state: _extends({}, state, newState),
1640
+ lastAction: action
1641
+ };
1636
1642
  }, [reducer]);
1637
- var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, createInitialState),
1638
- state = _React$useReducer[0],
1643
+ var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, function (p) {
1644
+ return {
1645
+ state: createInitialState(p),
1646
+ lastAction: undefined
1647
+ };
1648
+ }),
1649
+ _React$useReducer$ = _React$useReducer[0],
1650
+ state = _React$useReducer$.state,
1651
+ lastAction = _React$useReducer$.lastAction,
1639
1652
  dispatch = _React$useReducer[1];
1640
1653
  var propsRef = useLatestRef(props);
1641
1654
  var dispatchWithProps = React__namespace.useCallback(function (action) {
@@ -1643,15 +1656,16 @@ function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1643
1656
  props: propsRef.current
1644
1657
  }));
1645
1658
  }, [propsRef]);
1646
- var action = actionRef.current;
1647
1659
  React__namespace.useEffect(function () {
1648
- var prevState = getState(prevStateRef.current, action == null ? void 0 : action.props);
1649
- var shouldCallOnChangeProps = action && !isStateEqual(prevState, state);
1650
- if (shouldCallOnChangeProps) {
1651
- callOnChangeProps(action, action.props, prevState, state);
1660
+ if (lastAction) {
1661
+ var prevState = getState(prevStateRef.current, lastAction.props);
1662
+ var shouldCallOnChangeProps = !isStateEqual(prevState, state);
1663
+ if (shouldCallOnChangeProps) {
1664
+ callOnChangeProps(lastAction, lastAction.props, prevState, state);
1665
+ }
1652
1666
  }
1653
1667
  prevStateRef.current = state;
1654
- }, [state, action, isStateEqual]);
1668
+ }, [state, lastAction, isStateEqual]);
1655
1669
  return [state, dispatchWithProps];
1656
1670
  }
1657
1671
 
@@ -1712,6 +1726,8 @@ function useIsInitialMount() {
1712
1726
  isInitialMountRef.current = true;
1713
1727
  };
1714
1728
  }, []);
1729
+
1730
+ // eslint-disable-next-line react-hooks/refs
1715
1731
  return isInitialMountRef.current;
1716
1732
  }
1717
1733
 
@@ -1891,8 +1907,10 @@ function useElementIdsLegacy$1(_ref2) {
1891
1907
  getItemId = _ref2.getItemId,
1892
1908
  toggleButtonId = _ref2.toggleButtonId,
1893
1909
  inputId = _ref2.inputId;
1894
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
1895
- var baseId = baseIdRef.current;
1910
+ var _React$useState = React__namespace.useState(function () {
1911
+ return id != null ? id : "downshift-" + generateId();
1912
+ }),
1913
+ baseId = _React$useState[0];
1896
1914
  var elementIds = React__namespace.useMemo(function () {
1897
1915
  return {
1898
1916
  labelId: labelId != null ? labelId : baseId + "-label",
@@ -1966,10 +1984,10 @@ function getHighlightedIndexOnOpen(items, initialHighlightedIndex, defaultHighli
1966
1984
  }
1967
1985
 
1968
1986
  // initialHighlightedIndex will give value to highlightedIndex on initial state only.
1969
- if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && items[initialHighlightedIndex] && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex)) {
1987
+ if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && (initialHighlightedIndex === -1 || items[initialHighlightedIndex] !== undefined && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex))) {
1970
1988
  return initialHighlightedIndex;
1971
1989
  }
1972
- if (defaultHighlightedIndex !== undefined && items[defaultHighlightedIndex] && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex)) {
1990
+ if (defaultHighlightedIndex !== undefined && (defaultHighlightedIndex === -1 || items[defaultHighlightedIndex] !== undefined && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex))) {
1973
1991
  return defaultHighlightedIndex;
1974
1992
  }
1975
1993
  if (selectedItem) {
@@ -2041,7 +2059,7 @@ if (process.env.NODE_ENV !== 'production') {
2041
2059
  * @param environment The environment to add the event listeners to, for instance window.
2042
2060
  * @param handleBlur The function that is called if mouseDown or touchEnd occured outside the downshiftElements.
2043
2061
  * @param downshiftRefs The refs for the elements that should not trigger a blur action from mouseDown or touchEnd.
2044
- * @returns The mouse and touch events information.
2062
+ * @returns A ref holding the mouse and touch events information. Read `.current` only inside event handlers/effects.
2045
2063
  */
2046
2064
  function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2047
2065
  var mouseAndTouchTrackersRef = React__namespace.useRef({
@@ -2094,7 +2112,7 @@ function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2094
2112
  environment.removeEventListener('touchend', onTouchEnd);
2095
2113
  };
2096
2114
  }, [environment, getDownshiftElements, handleBlur]);
2097
- return mouseAndTouchTrackersRef.current;
2115
+ return mouseAndTouchTrackersRef;
2098
2116
  }
2099
2117
 
2100
2118
  /* istanbul ignore next */
@@ -2149,31 +2167,28 @@ if (process.env.NODE_ENV !== 'production') {
2149
2167
  };
2150
2168
  }
2151
2169
 
2152
- // istanbul ignore next
2153
- var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2154
-
2155
2170
  /**
2156
2171
  * Utility hook that scrolls an item from a menu into view.
2157
2172
  * @param scrollIntoView The function that does the scroll.
2158
2173
  * @param highlightedIndex The index of the item that should be scrolled.
2159
2174
  * @param isOpen If the menu is open or not.
2160
- * @param menuElement The menu element.
2161
- * @param itemElements The object containing item elements.
2175
+ * @param menuRef The ref to the menu element.
2176
+ * @param itemsRef The ref to the object containing item elements.
2162
2177
  * @param getItemId The function to get the item id from index.
2163
2178
  * @returns Function that when called prevents the scroll.
2164
2179
  */
2165
- function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuElement, itemElements, getItemId) {
2180
+ function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, getItemId) {
2166
2181
  // used not to scroll on highlight by mouse.
2167
2182
  var shouldScrollRef = React__namespace.useRef(true);
2168
2183
  // Scroll on highlighted item if change comes from keyboard.
2169
2184
  useIsomorphicLayoutEffect(function () {
2170
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemElements).length) {
2185
+ if (highlightedIndex < 0 || !isOpen || !Object.keys(itemsRef.current).length) {
2171
2186
  return;
2172
2187
  }
2173
2188
  if (shouldScrollRef.current) {
2174
- var itemElement = itemElements[getItemId(highlightedIndex)];
2175
- if (itemElement && menuElement) {
2176
- scrollIntoView(itemElement, menuElement);
2189
+ var itemElement = itemsRef.current[getItemId(highlightedIndex)];
2190
+ if (itemElement && menuRef.current) {
2191
+ scrollIntoView(itemElement, menuRef.current);
2177
2192
  }
2178
2193
  } else {
2179
2194
  shouldScrollRef.current = true;
@@ -2441,7 +2456,7 @@ function downshiftSelectReducer(state, action) {
2441
2456
 
2442
2457
  var _excluded$3 = ["onClick"],
2443
2458
  _excluded2$3 = ["onMouseLeave", "refKey", "ref", "aria-label"],
2444
- _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref", "disabled"],
2459
+ _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref"],
2445
2460
  _excluded4$1 = ["item", "index", "onMouseMove", "onClick", "onMouseDown", "onPress", "refKey", "disabled", "ref"];
2446
2461
  useSelect.stateChangeTypes = stateChangeTypes$3;
2447
2462
  function useSelect(userProps) {
@@ -2451,7 +2466,9 @@ function useSelect(userProps) {
2451
2466
  validatePropTypes$1(userProps, useSelect, propTypes$3);
2452
2467
  // Props defaults and destructuring.
2453
2468
  var props = _extends({}, dropdownDefaultProps, userProps);
2454
- var scrollIntoView = props.scrollIntoView,
2469
+ var items = props.items,
2470
+ isItemDisabled = props.isItemDisabled,
2471
+ scrollIntoView = props.scrollIntoView,
2455
2472
  environment = props.environment,
2456
2473
  getA11yStatusMessage = props.getA11yStatusMessage;
2457
2474
  // Initial state depending on controlled props.
@@ -2472,17 +2489,18 @@ function useSelect(userProps) {
2472
2489
  var clearTimeoutRef = React.useRef(null);
2473
2490
  // prevent id re-generation between renders.
2474
2491
  var elementIds = useElementIds$1(props);
2475
- // utility callback to get item element.
2476
- var latest = useLatestRef({
2477
- state: state,
2478
- props: props
2479
- });
2492
+ /**
2493
+ * Ref to read `state` in handlers to preserve referential identity.
2494
+ * Only to be used in handlers and effects.
2495
+ * **never access this in getters**
2496
+ */
2497
+ var stateRef = useLatestRef(state);
2480
2498
 
2481
2499
  // Effects.
2482
2500
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
2483
2501
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
2484
2502
  // Scroll on highlighted item if change comes from keyboard.
2485
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
2503
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
2486
2504
  // Sets cleanup for the keysSoFar callback, debounced after 500ms.
2487
2505
  React.useEffect(function () {
2488
2506
  // init the clean function here as we need access to dispatch.
@@ -2519,12 +2537,12 @@ function useSelect(userProps) {
2519
2537
  // eslint-disable-next-line react-hooks/exhaustive-deps
2520
2538
  }, []);
2521
2539
  var handleBlurInTracker = React.useCallback(function handleBlur() {
2522
- if (latest.current.state.isOpen) {
2540
+ if (stateRef.current.isOpen) {
2523
2541
  dispatch({
2524
2542
  type: ToggleButtonBlur
2525
2543
  });
2526
2544
  }
2527
- }, [dispatch, latest]);
2545
+ }, [dispatch, stateRef]);
2528
2546
  var downshiftRefs = React.useMemo(function () {
2529
2547
  return [menuRef, toggleButtonRef];
2530
2548
  }, []);
@@ -2567,7 +2585,7 @@ function useSelect(userProps) {
2567
2585
  });
2568
2586
  },
2569
2587
  Escape: function Escape() {
2570
- if (latest.current.state.isOpen) {
2588
+ if (stateRef.current.isOpen) {
2571
2589
  dispatch({
2572
2590
  type: ToggleButtonKeyDownEscape
2573
2591
  });
@@ -2576,11 +2594,11 @@ function useSelect(userProps) {
2576
2594
  Enter: function Enter(event) {
2577
2595
  event.preventDefault();
2578
2596
  dispatch({
2579
- type: latest.current.state.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2597
+ type: stateRef.current.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2580
2598
  });
2581
2599
  },
2582
2600
  PageUp: function PageUp(event) {
2583
- if (latest.current.state.isOpen) {
2601
+ if (stateRef.current.isOpen) {
2584
2602
  event.preventDefault();
2585
2603
  dispatch({
2586
2604
  type: ToggleButtonKeyDownPageUp
@@ -2588,7 +2606,7 @@ function useSelect(userProps) {
2588
2606
  }
2589
2607
  },
2590
2608
  PageDown: function PageDown(event) {
2591
- if (latest.current.state.isOpen) {
2609
+ if (stateRef.current.isOpen) {
2592
2610
  event.preventDefault();
2593
2611
  dispatch({
2594
2612
  type: ToggleButtonKeyDownPageDown
@@ -2597,7 +2615,7 @@ function useSelect(userProps) {
2597
2615
  },
2598
2616
  ' ': function _(event) {
2599
2617
  event.preventDefault();
2600
- var currentState = latest.current.state;
2618
+ var currentState = stateRef.current;
2601
2619
  if (!currentState.isOpen) {
2602
2620
  dispatch({
2603
2621
  type: ToggleButtonClick$1
@@ -2616,7 +2634,7 @@ function useSelect(userProps) {
2616
2634
  }
2617
2635
  }
2618
2636
  };
2619
- }, [dispatch, latest]);
2637
+ }, [dispatch, stateRef]);
2620
2638
 
2621
2639
  // Getter functions.
2622
2640
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -2665,19 +2683,17 @@ function useSelect(userProps) {
2665
2683
  _ref4$refKey = _ref4.refKey,
2666
2684
  refKey = _ref4$refKey === void 0 ? 'ref' : _ref4$refKey,
2667
2685
  ref = _ref4.ref,
2668
- disabled = _ref4.disabled,
2669
2686
  rest = _objectWithoutPropertiesLoose(_ref4, _excluded3$2);
2670
2687
  var _ref5 = otherProps != null ? otherProps : {},
2671
2688
  _ref5$suppressRefErro = _ref5.suppressRefError,
2672
2689
  suppressRefError = _ref5$suppressRefErro === void 0 ? false : _ref5$suppressRefErro;
2673
- var latestState = latest.current.state;
2674
2690
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
2675
2691
  dispatch({
2676
2692
  type: ToggleButtonClick$1
2677
2693
  });
2678
2694
  };
2679
2695
  var toggleButtonHandleBlur = function toggleButtonHandleBlur() {
2680
- if (latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
2696
+ if (stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
2681
2697
  dispatch({
2682
2698
  type: ToggleButtonBlur
2683
2699
  });
@@ -2696,8 +2712,8 @@ function useSelect(userProps) {
2696
2712
  };
2697
2713
  var toggleProps = _extends((_extends3 = {}, _extends3[refKey] = handleRefs(ref, function (toggleButtonNode) {
2698
2714
  toggleButtonRef.current = toggleButtonNode;
2699
- }), _extends3['aria-activedescendant'] = latestState.isOpen && latestState.highlightedIndex > -1 ? elementIds.getItemId(latestState.highlightedIndex) : '', _extends3['aria-controls'] = elementIds.menuId, _extends3['aria-expanded'] = latest.current.state.isOpen, _extends3['aria-haspopup'] = 'listbox', _extends3['aria-labelledby'] = rest['aria-label'] ? undefined : "" + elementIds.labelId, _extends3.id = elementIds.toggleButtonId, _extends3.role = 'combobox', _extends3.tabIndex = 0, _extends3.onBlur = callAllEventHandlers(onBlur, toggleButtonHandleBlur), _extends3.disabled = disabled, _extends3), rest);
2700
- if (!disabled) {
2715
+ }), _extends3['aria-activedescendant'] = isOpen && highlightedIndex > -1 ? elementIds.getItemId(highlightedIndex) : '', _extends3['aria-controls'] = elementIds.menuId, _extends3['aria-expanded'] = isOpen, _extends3['aria-haspopup'] = 'listbox', _extends3['aria-labelledby'] = rest['aria-label'] ? undefined : "" + elementIds.labelId, _extends3.id = elementIds.toggleButtonId, _extends3.role = 'combobox', _extends3.tabIndex = 0, _extends3.onBlur = callAllEventHandlers(onBlur, toggleButtonHandleBlur), _extends3), rest);
2716
+ if (!rest.disabled) {
2701
2717
  /* istanbul ignore if (react-native) */
2702
2718
  {
2703
2719
  Object.assign(toggleProps, {
@@ -2708,7 +2724,7 @@ function useSelect(userProps) {
2708
2724
  }
2709
2725
  setGetterPropCallInfo('getToggleButtonProps', suppressRefError, refKey, toggleButtonRef);
2710
2726
  return toggleProps;
2711
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers]);
2727
+ }, [dispatch, elementIds, isOpen, highlightedIndex, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers, stateRef]);
2712
2728
  var getItemProps = React.useCallback(function (itemProps) {
2713
2729
  var _extends4;
2714
2730
  var _ref6 = itemProps != null ? itemProps : {},
@@ -2726,15 +2742,12 @@ function useSelect(userProps) {
2726
2742
  if (disabledProp !== undefined) {
2727
2743
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useSelect.');
2728
2744
  }
2729
- var _latest$current = latest.current,
2730
- latestState = _latest$current.state,
2731
- latestProps = _latest$current.props;
2732
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
2745
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
2733
2746
  item = _getItemAndIndex[0],
2734
2747
  index = _getItemAndIndex[1];
2735
- var disabled = latestProps.isItemDisabled(item, index);
2748
+ var disabled = isItemDisabled(item, index);
2736
2749
  var itemHandleMouseMove = function itemHandleMouseMove() {
2737
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
2750
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
2738
2751
  return;
2739
2752
  }
2740
2753
  preventScroll();
@@ -2758,7 +2771,7 @@ function useSelect(userProps) {
2758
2771
  if (itemNode) {
2759
2772
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
2760
2773
  }
2761
- }), _extends4['aria-disabled'] = disabled, _extends4['aria-selected'] = item === latestState.selectedItem, _extends4.id = elementIds.getItemId(index), _extends4.role = 'option', _extends4.onMouseMove = callAllEventHandlers(onMouseMove, itemHandleMouseMove), _extends4.onMouseDown = callAllEventHandlers(onMouseDown, itemHandleMouseDown), _extends4), rest);
2774
+ }), _extends4['aria-disabled'] = disabled, _extends4['aria-selected'] = item === selectedItem, _extends4.id = elementIds.getItemId(index), _extends4.role = 'option', _extends4.onMouseMove = callAllEventHandlers(onMouseMove, itemHandleMouseMove), _extends4.onMouseDown = callAllEventHandlers(onMouseDown, itemHandleMouseDown), _extends4), rest);
2762
2775
  if (!disabled) {
2763
2776
  /* istanbul ignore next (react-native) */
2764
2777
  {
@@ -2768,7 +2781,7 @@ function useSelect(userProps) {
2768
2781
  }
2769
2782
  }
2770
2783
  return resultItemProps;
2771
- }, [latest, elementIds, mouseAndTouchTrackers, preventScroll, dispatch]);
2784
+ }, [items, isItemDisabled, selectedItem, elementIds, mouseAndTouchTrackers, preventScroll, dispatch, stateRef]);
2772
2785
 
2773
2786
  // Action functions.
2774
2787
  var toggleMenu = React.useCallback(function () {
@@ -3059,8 +3072,8 @@ function downshiftUseComboboxReducer(state, action) {
3059
3072
 
3060
3073
  var _excluded$2 = ["onMouseLeave", "refKey", "ref", "aria-label"],
3061
3074
  _excluded2$2 = ["item", "index", "refKey", "ref", "onMouseMove", "onMouseDown", "onClick", "onPress", "disabled"],
3062
- _excluded3$1 = ["onClick", "onPress", "refKey", "ref", "disabled"],
3063
- _excluded4 = ["aria-label", "disabled", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3075
+ _excluded3$1 = ["onClick", "onPress", "refKey", "ref"],
3076
+ _excluded4 = ["aria-label", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3064
3077
  useCombobox.stateChangeTypes = stateChangeTypes$2;
3065
3078
  function useCombobox(userProps) {
3066
3079
  if (userProps === void 0) {
@@ -3070,6 +3083,7 @@ function useCombobox(userProps) {
3070
3083
  // Props defaults and destructuring.
3071
3084
  var props = _extends({}, dropdownDefaultProps, userProps);
3072
3085
  var items = props.items,
3086
+ isItemDisabled = props.isItemDisabled,
3073
3087
  scrollIntoView = props.scrollIntoView,
3074
3088
  environment = props.environment,
3075
3089
  getA11yStatusMessage = props.getA11yStatusMessage;
@@ -3093,17 +3107,18 @@ function useCombobox(userProps) {
3093
3107
  var elementIds = useElementIds$1(props);
3094
3108
  // used to keep track of how many items we had on previous cycle.
3095
3109
  var previousResultCountRef = React.useRef();
3096
- // utility callback to get item element.
3097
- var latest = useLatestRef({
3098
- state: state,
3099
- props: props
3100
- });
3110
+ /**
3111
+ * Ref to read `state` in handlers to preserve referential identity.
3112
+ * Only to be used in handlers and effects.
3113
+ * **never access this in getters**
3114
+ */
3115
+ var stateRef = useLatestRef(state);
3101
3116
 
3102
3117
  // Effects.
3103
3118
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
3104
3119
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
3105
3120
  // Scroll on highlighted item if change comes from keyboard.
3106
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
3121
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
3107
3122
  useControlPropsValidator({
3108
3123
  state: state,
3109
3124
  props: props
@@ -3122,12 +3137,12 @@ function useCombobox(userProps) {
3122
3137
  }
3123
3138
  });
3124
3139
  var handleBlurInTracker = React.useCallback(function handleBlur() {
3125
- if (latest.current.state.isOpen) {
3140
+ if (stateRef.current.isOpen) {
3126
3141
  dispatch({
3127
3142
  type: InputBlur
3128
3143
  });
3129
3144
  }
3130
- }, [dispatch, latest]);
3145
+ }, [dispatch, stateRef]);
3131
3146
  var downshiftRefs = React.useMemo(function () {
3132
3147
  return [menuRef, toggleButtonRef, inputRef];
3133
3148
  }, []);
@@ -3168,7 +3183,7 @@ function useCombobox(userProps) {
3168
3183
  });
3169
3184
  },
3170
3185
  Home: function Home(event) {
3171
- if (!latest.current.state.isOpen) {
3186
+ if (!stateRef.current.isOpen) {
3172
3187
  return;
3173
3188
  }
3174
3189
  event.preventDefault();
@@ -3177,7 +3192,7 @@ function useCombobox(userProps) {
3177
3192
  });
3178
3193
  },
3179
3194
  End: function End(event) {
3180
- if (!latest.current.state.isOpen) {
3195
+ if (!stateRef.current.isOpen) {
3181
3196
  return;
3182
3197
  }
3183
3198
  event.preventDefault();
@@ -3186,7 +3201,7 @@ function useCombobox(userProps) {
3186
3201
  });
3187
3202
  },
3188
3203
  Escape: function Escape(event) {
3189
- var latestState = latest.current.state;
3204
+ var latestState = stateRef.current;
3190
3205
  if (latestState.isOpen || latestState.inputValue || latestState.selectedItem || latestState.highlightedIndex > -1) {
3191
3206
  event.preventDefault();
3192
3207
  dispatch({
@@ -3195,7 +3210,7 @@ function useCombobox(userProps) {
3195
3210
  }
3196
3211
  },
3197
3212
  Enter: function Enter(event) {
3198
- var latestState = latest.current.state;
3213
+ var latestState = stateRef.current;
3199
3214
  // if closed or no highlighted index, do nothing.
3200
3215
  if (!latestState.isOpen || event.which === 229 // if IME composing, wait for next Enter keydown event.
3201
3216
  ) {
@@ -3207,7 +3222,7 @@ function useCombobox(userProps) {
3207
3222
  });
3208
3223
  },
3209
3224
  PageUp: function PageUp(event) {
3210
- if (latest.current.state.isOpen) {
3225
+ if (stateRef.current.isOpen) {
3211
3226
  event.preventDefault();
3212
3227
  dispatch({
3213
3228
  type: InputKeyDownPageUp
@@ -3215,7 +3230,7 @@ function useCombobox(userProps) {
3215
3230
  }
3216
3231
  },
3217
3232
  PageDown: function PageDown(event) {
3218
- if (latest.current.state.isOpen) {
3233
+ if (stateRef.current.isOpen) {
3219
3234
  event.preventDefault();
3220
3235
  dispatch({
3221
3236
  type: InputKeyDownPageDown
@@ -3223,7 +3238,7 @@ function useCombobox(userProps) {
3223
3238
  }
3224
3239
  }
3225
3240
  };
3226
- }, [dispatch, latest]);
3241
+ }, [dispatch, stateRef]);
3227
3242
 
3228
3243
  // Getter props.
3229
3244
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -3270,17 +3285,14 @@ function useCombobox(userProps) {
3270
3285
  if (disabledProp !== undefined) {
3271
3286
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useCombobox.');
3272
3287
  }
3273
- var _latest$current = latest.current,
3274
- latestProps = _latest$current.props,
3275
- latestState = _latest$current.state;
3276
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
3288
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
3277
3289
  item = _getItemAndIndex[0],
3278
3290
  index = _getItemAndIndex[1];
3279
- var disabled = latestProps.isItemDisabled(item, index);
3291
+ var disabled = isItemDisabled(item, index);
3280
3292
  var onSelectKey = 'onClick';
3281
3293
  var customClickHandler = onClick;
3282
3294
  var itemHandleMouseMove = function itemHandleMouseMove() {
3283
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
3295
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
3284
3296
  return;
3285
3297
  }
3286
3298
  preventScroll();
@@ -3304,11 +3316,11 @@ function useCombobox(userProps) {
3304
3316
  if (itemNode) {
3305
3317
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
3306
3318
  }
3307
- }), _extends3['aria-disabled'] = disabled, _extends3['aria-selected'] = index === latestState.highlightedIndex, _extends3.id = elementIds.getItemId(index), _extends3.role = 'option', _extends3), !disabled && (_ref4 = {}, _ref4[onSelectKey] = callAllEventHandlers(customClickHandler, itemHandleClick), _ref4), {
3319
+ }), _extends3['aria-disabled'] = disabled, _extends3['aria-selected'] = index === highlightedIndex, _extends3.id = elementIds.getItemId(index), _extends3.role = 'option', _extends3), !disabled && (_ref4 = {}, _ref4[onSelectKey] = callAllEventHandlers(customClickHandler, itemHandleClick), _ref4), {
3308
3320
  onMouseMove: callAllEventHandlers(onMouseMove, itemHandleMouseMove),
3309
3321
  onMouseDown: callAllEventHandlers(onMouseDown, itemHandleMouseDown)
3310
3322
  }, rest);
3311
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, preventScroll]);
3323
+ }, [dispatch, elementIds, items, isItemDisabled, highlightedIndex, mouseAndTouchTrackers, preventScroll, stateRef]);
3312
3324
  var getToggleButtonProps = React.useCallback(function (toggleButtonProps) {
3313
3325
  var _extends4;
3314
3326
  var _ref5 = toggleButtonProps != null ? toggleButtonProps : {},
@@ -3317,9 +3329,7 @@ function useCombobox(userProps) {
3317
3329
  var _ref5$refKey = _ref5.refKey,
3318
3330
  refKey = _ref5$refKey === void 0 ? 'ref' : _ref5$refKey,
3319
3331
  ref = _ref5.ref,
3320
- disabled = _ref5.disabled,
3321
3332
  rest = _objectWithoutPropertiesLoose(_ref5, _excluded3$1);
3322
- var latestState = latest.current.state;
3323
3333
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
3324
3334
  dispatch({
3325
3335
  type: ToggleButtonClick
@@ -3327,17 +3337,14 @@ function useCombobox(userProps) {
3327
3337
  };
3328
3338
  return _extends((_extends4 = {}, _extends4[refKey] = handleRefs(ref, function (toggleButtonNode) {
3329
3339
  toggleButtonRef.current = toggleButtonNode;
3330
- }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = latestState.isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !disabled && _extends({}, {
3340
+ }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !rest.disabled && _extends({}, {
3331
3341
  onClick: callAllEventHandlers(onClick, toggleButtonHandleClick)
3332
- }), {
3333
- disabled: disabled
3334
- }, rest);
3335
- }, [dispatch, latest, elementIds]);
3342
+ }), rest);
3343
+ }, [dispatch, isOpen, elementIds]);
3336
3344
  var getInputProps = React.useCallback(function (inputProps, otherProps) {
3337
3345
  var _extends5;
3338
3346
  var _ref6 = inputProps != null ? inputProps : {},
3339
3347
  ariaLabel = _ref6['aria-label'],
3340
- disabled = _ref6.disabled,
3341
3348
  onKeyDown = _ref6.onKeyDown,
3342
3349
  onChange = _ref6.onChange,
3343
3350
  onInput = _ref6.onInput,
@@ -3352,7 +3359,6 @@ function useCombobox(userProps) {
3352
3359
  _ref7$suppressRefErro = _ref7.suppressRefError,
3353
3360
  suppressRefError = _ref7$suppressRefErro === void 0 ? false : _ref7$suppressRefErro;
3354
3361
  setGetterPropCallInfo('getInputProps', suppressRefError, refKey, inputRef);
3355
- var latestState = latest.current.state;
3356
3362
  var inputHandleKeyDown = function inputHandleKeyDown(event) {
3357
3363
  var key = normalizeArrowKey(event);
3358
3364
  if (key && key in inputKeyDownHandlers) {
@@ -3368,7 +3374,7 @@ function useCombobox(userProps) {
3368
3374
  };
3369
3375
  var inputHandleBlur = function inputHandleBlur(event) {
3370
3376
  /* istanbul ignore else */
3371
- if (environment != null && environment.document && latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
3377
+ if (environment != null && environment.document && stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
3372
3378
  var isBlurByTabChange = event.relatedTarget === null && environment.document.activeElement !== environment.document.body;
3373
3379
  dispatch({
3374
3380
  type: InputBlur,
@@ -3385,14 +3391,14 @@ function useCombobox(userProps) {
3385
3391
  /* istanbul ignore next (preact) */
3386
3392
  var onChangeKey = 'onChange';
3387
3393
  var eventHandlers = {};
3388
- if (!disabled) {
3394
+ if (!rest.disabled) {
3389
3395
  var _eventHandlers;
3390
3396
  eventHandlers = (_eventHandlers = {}, _eventHandlers[onChangeKey] = callAllEventHandlers(onChange, onInput, inputHandleChange), _eventHandlers.onKeyDown = callAllEventHandlers(onKeyDown, inputHandleKeyDown), _eventHandlers.onBlur = callAllEventHandlers(onBlur, inputHandleBlur), _eventHandlers.onClick = callAllEventHandlers(onClick, inputHandleClick), _eventHandlers);
3391
3397
  }
3392
3398
  return _extends((_extends5 = {}, _extends5[refKey] = handleRefs(ref, function (inputNode) {
3393
3399
  inputRef.current = inputNode;
3394
- }), _extends5['aria-activedescendant'] = latestState.isOpen && latestState.highlightedIndex > -1 ? elementIds.getItemId(latestState.highlightedIndex) : '', _extends5['aria-autocomplete'] = 'list', _extends5['aria-controls'] = elementIds.menuId, _extends5['aria-expanded'] = latestState.isOpen, _extends5['aria-labelledby'] = ariaLabel ? undefined : elementIds.labelId, _extends5['aria-label'] = ariaLabel, _extends5.autoComplete = 'off', _extends5.disabled = disabled, _extends5.id = elementIds.inputId, _extends5.role = 'combobox', _extends5.value = latestState.inputValue, _extends5), eventHandlers, rest);
3395
- }, [dispatch, elementIds, environment, inputKeyDownHandlers, latest, mouseAndTouchTrackers, setGetterPropCallInfo]);
3400
+ }), _extends5['aria-activedescendant'] = isOpen && highlightedIndex > -1 ? elementIds.getItemId(highlightedIndex) : '', _extends5['aria-autocomplete'] = 'list', _extends5['aria-controls'] = elementIds.menuId, _extends5['aria-expanded'] = isOpen, _extends5['aria-labelledby'] = ariaLabel ? undefined : elementIds.labelId, _extends5['aria-label'] = ariaLabel, _extends5.autoComplete = 'off', _extends5.id = elementIds.inputId, _extends5.role = 'combobox', _extends5.value = inputValue, _extends5), eventHandlers, rest);
3401
+ }, [dispatch, elementIds, environment, inputKeyDownHandlers, isOpen, highlightedIndex, inputValue, mouseAndTouchTrackers, setGetterPropCallInfo, stateRef]);
3396
3402
 
3397
3403
  // returns
3398
3404
  var toggleMenu = React.useCallback(function () {
@@ -3714,12 +3720,13 @@ function useMultipleSelection(userProps) {
3714
3720
  // Refs.
3715
3721
  var isInitialMount = useIsInitialMount();
3716
3722
  var dropdownRef = React.useRef(null);
3717
- var selectedItemRefs = React.useRef();
3718
- selectedItemRefs.current = [];
3719
- var latest = useLatestRef({
3720
- state: state,
3721
- props: props
3722
- });
3723
+ // Map of selected-item index -> DOM node. Populated by the ref callback in
3724
+ // getSelectedItemProps and read by the focus effect. Keyed by
3725
+ // index so we never reset it during render.
3726
+ var selectedItemRefs = React.useRef(null);
3727
+ if (selectedItemRefs.current === null) {
3728
+ selectedItemRefs.current = new Map();
3729
+ }
3723
3730
 
3724
3731
  // Effects.
3725
3732
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
@@ -3731,8 +3738,9 @@ function useMultipleSelection(userProps) {
3731
3738
  }
3732
3739
  if (activeIndex === -1 && dropdownRef.current) {
3733
3740
  dropdownRef.current.focus();
3734
- } else if (selectedItemRefs.current[activeIndex]) {
3735
- selectedItemRefs.current[activeIndex].focus();
3741
+ } else {
3742
+ var _selectedItemRefs$cur;
3743
+ (_selectedItemRefs$cur = selectedItemRefs.current.get(activeIndex)) == null || _selectedItemRefs$cur.focus();
3736
3744
  }
3737
3745
  // eslint-disable-next-line react-hooks/exhaustive-deps
3738
3746
  }, [activeIndex]);
@@ -3792,10 +3800,9 @@ function useMultipleSelection(userProps) {
3792
3800
  selectedItemProp = _ref3.selectedItem,
3793
3801
  indexProp = _ref3.index,
3794
3802
  rest = _objectWithoutPropertiesLoose(_ref3, _excluded$1);
3795
- var latestState = latest.current.state;
3796
- var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, latestState.selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3803
+ var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3797
3804
  index = _getItemAndIndex[1];
3798
- var isFocusable = index > -1 && index === latestState.activeIndex;
3805
+ var isFocusable = index > -1 && index === activeIndex;
3799
3806
  var selectedItemHandleClick = function selectedItemHandleClick() {
3800
3807
  dispatch({
3801
3808
  type: SelectedItemClick,
@@ -3810,10 +3817,12 @@ function useMultipleSelection(userProps) {
3810
3817
  };
3811
3818
  return _extends((_extends2 = {}, _extends2[refKey] = handleRefs(ref, function (selectedItemNode) {
3812
3819
  if (selectedItemNode) {
3813
- selectedItemRefs.current.push(selectedItemNode);
3820
+ selectedItemRefs.current.set(index, selectedItemNode);
3821
+ } else {
3822
+ selectedItemRefs.current["delete"](index);
3814
3823
  }
3815
3824
  }), _extends2.tabIndex = isFocusable ? 0 : -1, _extends2.onClick = callAllEventHandlers(onClick, selectedItemHandleClick), _extends2.onKeyDown = callAllEventHandlers(onKeyDown, selectedItemHandleKeyDown), _extends2), rest);
3816
- }, [dispatch, latest, selectedItemKeyDownHandlers]);
3825
+ }, [dispatch, selectedItems, activeIndex, selectedItemKeyDownHandlers]);
3817
3826
  var getDropdownProps = React.useCallback(function (_temp2, _temp3) {
3818
3827
  var _extends3;
3819
3828
  var _ref4 = _temp2 === void 0 ? {} : _temp2,
@@ -4001,8 +4010,10 @@ function useElementIdsLegacy(_ref2) {
4001
4010
  var id = _ref2.id,
4002
4011
  getTagId = _ref2.getTagId,
4003
4012
  tagGroupId = _ref2.tagGroupId;
4004
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
4005
- var baseId = baseIdRef.current;
4013
+ var _React$useState = React__namespace.useState(function () {
4014
+ return id != null ? id : "downshift-" + generateId();
4015
+ }),
4016
+ baseId = _React$useState[0];
4006
4017
  var elementIds = React__namespace.useMemo(function () {
4007
4018
  return {
4008
4019
  tagGroupId: tagGroupId != null ? tagGroupId : baseId + "-tag-group",
@@ -4099,10 +4110,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4099
4110
 
4100
4111
  /* Refs */
4101
4112
 
4102
- var latest = useLatestRef({
4103
- state: state,
4104
- props: props
4105
- });
4106
4113
  var elementIds = useElementIds({
4107
4114
  getTagId: props.getTagId,
4108
4115
  id: props.id,
@@ -4165,7 +4172,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4165
4172
  if (!Number.isInteger(index) || index < 0) {
4166
4173
  throw new Error('Pass correct item index to getTagProps!');
4167
4174
  }
4168
- var latestState = latest.current.state;
4169
4175
  var handleClick = function handleClick() {
4170
4176
  dispatch({
4171
4177
  type: TagClick,
@@ -4179,8 +4185,8 @@ var _useTagGroup = function useTagGroup(userProps) {
4179
4185
  if (itemNode) {
4180
4186
  itemRefs.current[tagId] = itemNode;
4181
4187
  }
4182
- }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = latestState.activeIndex === index ? 0 : -1, _extends2), rest);
4183
- }, [dispatch, elementIds, latest, itemRefs]);
4188
+ }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = activeIndex === index ? 0 : -1, _extends2), rest);
4189
+ }, [dispatch, elementIds, activeIndex, itemRefs]);
4184
4190
  var getTagRemoveProps = React.useCallback(function (options) {
4185
4191
  var index = options.index,
4186
4192
  onClick = options.onClick,