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
 
@@ -1622,18 +1626,27 @@ function invokeOnChangeHandler(key, action, props, state, newState) {
1622
1626
  */
1623
1627
  function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1624
1628
  var prevStateRef = React__namespace.useRef({});
1625
- var actionRef = React__namespace.useRef();
1626
- var enhancedReducer = React__namespace.useCallback(function (state, action) {
1627
- actionRef.current = action;
1629
+ var enhancedReducer = React__namespace.useCallback(function (_ref, action) {
1630
+ var state = _ref.state;
1628
1631
  state = getState(state, action.props);
1629
1632
  var changes = reducer(state, action);
1630
1633
  var newState = action.props.stateReducer(state, _extends({}, action, {
1631
1634
  changes: changes
1632
1635
  }));
1633
- return _extends({}, state, newState);
1636
+ return {
1637
+ state: _extends({}, state, newState),
1638
+ lastAction: action
1639
+ };
1634
1640
  }, [reducer]);
1635
- var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, createInitialState),
1636
- state = _React$useReducer[0],
1641
+ var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, function (p) {
1642
+ return {
1643
+ state: createInitialState(p),
1644
+ lastAction: undefined
1645
+ };
1646
+ }),
1647
+ _React$useReducer$ = _React$useReducer[0],
1648
+ state = _React$useReducer$.state,
1649
+ lastAction = _React$useReducer$.lastAction,
1637
1650
  dispatch = _React$useReducer[1];
1638
1651
  var propsRef = useLatestRef(props);
1639
1652
  var dispatchWithProps = React__namespace.useCallback(function (action) {
@@ -1641,15 +1654,16 @@ function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1641
1654
  props: propsRef.current
1642
1655
  }));
1643
1656
  }, [propsRef]);
1644
- var action = actionRef.current;
1645
1657
  React__namespace.useEffect(function () {
1646
- var prevState = getState(prevStateRef.current, action == null ? void 0 : action.props);
1647
- var shouldCallOnChangeProps = action && !isStateEqual(prevState, state);
1648
- if (shouldCallOnChangeProps) {
1649
- callOnChangeProps(action, action.props, prevState, state);
1658
+ if (lastAction) {
1659
+ var prevState = getState(prevStateRef.current, lastAction.props);
1660
+ var shouldCallOnChangeProps = !isStateEqual(prevState, state);
1661
+ if (shouldCallOnChangeProps) {
1662
+ callOnChangeProps(lastAction, lastAction.props, prevState, state);
1663
+ }
1650
1664
  }
1651
1665
  prevStateRef.current = state;
1652
- }, [state, action, isStateEqual]);
1666
+ }, [state, lastAction, isStateEqual]);
1653
1667
  return [state, dispatchWithProps];
1654
1668
  }
1655
1669
 
@@ -1710,6 +1724,8 @@ function useIsInitialMount() {
1710
1724
  isInitialMountRef.current = true;
1711
1725
  };
1712
1726
  }, []);
1727
+
1728
+ // eslint-disable-next-line react-hooks/refs
1713
1729
  return isInitialMountRef.current;
1714
1730
  }
1715
1731
 
@@ -1889,8 +1905,10 @@ function useElementIdsLegacy$1(_ref2) {
1889
1905
  getItemId = _ref2.getItemId,
1890
1906
  toggleButtonId = _ref2.toggleButtonId,
1891
1907
  inputId = _ref2.inputId;
1892
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
1893
- var baseId = baseIdRef.current;
1908
+ var _React$useState = React__namespace.useState(function () {
1909
+ return id != null ? id : "downshift-" + generateId();
1910
+ }),
1911
+ baseId = _React$useState[0];
1894
1912
  var elementIds = React__namespace.useMemo(function () {
1895
1913
  return {
1896
1914
  labelId: labelId != null ? labelId : baseId + "-label",
@@ -1964,10 +1982,10 @@ function getHighlightedIndexOnOpen(items, initialHighlightedIndex, defaultHighli
1964
1982
  }
1965
1983
 
1966
1984
  // initialHighlightedIndex will give value to highlightedIndex on initial state only.
1967
- if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && items[initialHighlightedIndex] && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex)) {
1985
+ if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && (initialHighlightedIndex === -1 || items[initialHighlightedIndex] !== undefined && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex))) {
1968
1986
  return initialHighlightedIndex;
1969
1987
  }
1970
- if (defaultHighlightedIndex !== undefined && items[defaultHighlightedIndex] && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex)) {
1988
+ if (defaultHighlightedIndex !== undefined && (defaultHighlightedIndex === -1 || items[defaultHighlightedIndex] !== undefined && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex))) {
1971
1989
  return defaultHighlightedIndex;
1972
1990
  }
1973
1991
  if (selectedItem) {
@@ -2039,7 +2057,7 @@ if (process.env.NODE_ENV !== 'production') {
2039
2057
  * @param environment The environment to add the event listeners to, for instance window.
2040
2058
  * @param handleBlur The function that is called if mouseDown or touchEnd occured outside the downshiftElements.
2041
2059
  * @param downshiftRefs The refs for the elements that should not trigger a blur action from mouseDown or touchEnd.
2042
- * @returns The mouse and touch events information.
2060
+ * @returns A ref holding the mouse and touch events information. Read `.current` only inside event handlers/effects.
2043
2061
  */
2044
2062
  function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2045
2063
  var mouseAndTouchTrackersRef = React__namespace.useRef({
@@ -2092,7 +2110,7 @@ function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2092
2110
  environment.removeEventListener('touchend', onTouchEnd);
2093
2111
  };
2094
2112
  }, [environment, getDownshiftElements, handleBlur]);
2095
- return mouseAndTouchTrackersRef.current;
2113
+ return mouseAndTouchTrackersRef;
2096
2114
  }
2097
2115
 
2098
2116
  /* istanbul ignore next */
@@ -2147,31 +2165,28 @@ if (process.env.NODE_ENV !== 'production') {
2147
2165
  };
2148
2166
  }
2149
2167
 
2150
- // istanbul ignore next
2151
- var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2152
-
2153
2168
  /**
2154
2169
  * Utility hook that scrolls an item from a menu into view.
2155
2170
  * @param scrollIntoView The function that does the scroll.
2156
2171
  * @param highlightedIndex The index of the item that should be scrolled.
2157
2172
  * @param isOpen If the menu is open or not.
2158
- * @param menuElement The menu element.
2159
- * @param itemElements The object containing item elements.
2173
+ * @param menuRef The ref to the menu element.
2174
+ * @param itemsRef The ref to the object containing item elements.
2160
2175
  * @param getItemId The function to get the item id from index.
2161
2176
  * @returns Function that when called prevents the scroll.
2162
2177
  */
2163
- function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuElement, itemElements, getItemId) {
2178
+ function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, getItemId) {
2164
2179
  // used not to scroll on highlight by mouse.
2165
2180
  var shouldScrollRef = React__namespace.useRef(true);
2166
2181
  // Scroll on highlighted item if change comes from keyboard.
2167
2182
  useIsomorphicLayoutEffect(function () {
2168
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemElements).length) {
2183
+ if (highlightedIndex < 0 || !isOpen || !Object.keys(itemsRef.current).length) {
2169
2184
  return;
2170
2185
  }
2171
2186
  if (shouldScrollRef.current) {
2172
- var itemElement = itemElements[getItemId(highlightedIndex)];
2173
- if (itemElement && menuElement) {
2174
- scrollIntoView(itemElement, menuElement);
2187
+ var itemElement = itemsRef.current[getItemId(highlightedIndex)];
2188
+ if (itemElement && menuRef.current) {
2189
+ scrollIntoView(itemElement, menuRef.current);
2175
2190
  }
2176
2191
  } else {
2177
2192
  shouldScrollRef.current = true;
@@ -2439,7 +2454,7 @@ function downshiftSelectReducer(state, action) {
2439
2454
 
2440
2455
  var _excluded$3 = ["onClick"],
2441
2456
  _excluded2$3 = ["onMouseLeave", "refKey", "ref", "aria-label"],
2442
- _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref", "disabled"],
2457
+ _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref"],
2443
2458
  _excluded4$1 = ["item", "index", "onMouseMove", "onClick", "onMouseDown", "onPress", "refKey", "disabled", "ref"];
2444
2459
  useSelect.stateChangeTypes = stateChangeTypes$3;
2445
2460
  function useSelect(userProps) {
@@ -2449,7 +2464,9 @@ function useSelect(userProps) {
2449
2464
  validatePropTypes$1(userProps, useSelect, propTypes$3);
2450
2465
  // Props defaults and destructuring.
2451
2466
  var props = _extends({}, dropdownDefaultProps, userProps);
2452
- var scrollIntoView = props.scrollIntoView,
2467
+ var items = props.items,
2468
+ isItemDisabled = props.isItemDisabled,
2469
+ scrollIntoView = props.scrollIntoView,
2453
2470
  environment = props.environment,
2454
2471
  getA11yStatusMessage = props.getA11yStatusMessage;
2455
2472
  // Initial state depending on controlled props.
@@ -2470,17 +2487,18 @@ function useSelect(userProps) {
2470
2487
  var clearTimeoutRef = React.useRef(null);
2471
2488
  // prevent id re-generation between renders.
2472
2489
  var elementIds = useElementIds$1(props);
2473
- // utility callback to get item element.
2474
- var latest = useLatestRef({
2475
- state: state,
2476
- props: props
2477
- });
2490
+ /**
2491
+ * Ref to read `state` in handlers to preserve referential identity.
2492
+ * Only to be used in handlers and effects.
2493
+ * **never access this in getters**
2494
+ */
2495
+ var stateRef = useLatestRef(state);
2478
2496
 
2479
2497
  // Effects.
2480
2498
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
2481
2499
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
2482
2500
  // Scroll on highlighted item if change comes from keyboard.
2483
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
2501
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
2484
2502
  // Sets cleanup for the keysSoFar callback, debounced after 500ms.
2485
2503
  React.useEffect(function () {
2486
2504
  // init the clean function here as we need access to dispatch.
@@ -2517,12 +2535,12 @@ function useSelect(userProps) {
2517
2535
  // eslint-disable-next-line react-hooks/exhaustive-deps
2518
2536
  }, []);
2519
2537
  var handleBlurInTracker = React.useCallback(function handleBlur() {
2520
- if (latest.current.state.isOpen) {
2538
+ if (stateRef.current.isOpen) {
2521
2539
  dispatch({
2522
2540
  type: ToggleButtonBlur
2523
2541
  });
2524
2542
  }
2525
- }, [dispatch, latest]);
2543
+ }, [dispatch, stateRef]);
2526
2544
  var downshiftRefs = React.useMemo(function () {
2527
2545
  return [menuRef, toggleButtonRef];
2528
2546
  }, []);
@@ -2565,7 +2583,7 @@ function useSelect(userProps) {
2565
2583
  });
2566
2584
  },
2567
2585
  Escape: function Escape() {
2568
- if (latest.current.state.isOpen) {
2586
+ if (stateRef.current.isOpen) {
2569
2587
  dispatch({
2570
2588
  type: ToggleButtonKeyDownEscape
2571
2589
  });
@@ -2574,11 +2592,11 @@ function useSelect(userProps) {
2574
2592
  Enter: function Enter(event) {
2575
2593
  event.preventDefault();
2576
2594
  dispatch({
2577
- type: latest.current.state.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2595
+ type: stateRef.current.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2578
2596
  });
2579
2597
  },
2580
2598
  PageUp: function PageUp(event) {
2581
- if (latest.current.state.isOpen) {
2599
+ if (stateRef.current.isOpen) {
2582
2600
  event.preventDefault();
2583
2601
  dispatch({
2584
2602
  type: ToggleButtonKeyDownPageUp
@@ -2586,7 +2604,7 @@ function useSelect(userProps) {
2586
2604
  }
2587
2605
  },
2588
2606
  PageDown: function PageDown(event) {
2589
- if (latest.current.state.isOpen) {
2607
+ if (stateRef.current.isOpen) {
2590
2608
  event.preventDefault();
2591
2609
  dispatch({
2592
2610
  type: ToggleButtonKeyDownPageDown
@@ -2595,7 +2613,7 @@ function useSelect(userProps) {
2595
2613
  },
2596
2614
  ' ': function _(event) {
2597
2615
  event.preventDefault();
2598
- var currentState = latest.current.state;
2616
+ var currentState = stateRef.current;
2599
2617
  if (!currentState.isOpen) {
2600
2618
  dispatch({
2601
2619
  type: ToggleButtonClick$1
@@ -2614,7 +2632,7 @@ function useSelect(userProps) {
2614
2632
  }
2615
2633
  }
2616
2634
  };
2617
- }, [dispatch, latest]);
2635
+ }, [dispatch, stateRef]);
2618
2636
 
2619
2637
  // Getter functions.
2620
2638
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -2663,19 +2681,17 @@ function useSelect(userProps) {
2663
2681
  var _ref4$refKey = _ref4.refKey,
2664
2682
  refKey = _ref4$refKey === void 0 ? 'ref' : _ref4$refKey,
2665
2683
  ref = _ref4.ref,
2666
- disabled = _ref4.disabled,
2667
2684
  rest = _objectWithoutPropertiesLoose(_ref4, _excluded3$2);
2668
2685
  var _ref5 = otherProps != null ? otherProps : {},
2669
2686
  _ref5$suppressRefErro = _ref5.suppressRefError,
2670
2687
  suppressRefError = _ref5$suppressRefErro === void 0 ? false : _ref5$suppressRefErro;
2671
- var latestState = latest.current.state;
2672
2688
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
2673
2689
  dispatch({
2674
2690
  type: ToggleButtonClick$1
2675
2691
  });
2676
2692
  };
2677
2693
  var toggleButtonHandleBlur = function toggleButtonHandleBlur() {
2678
- if (latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
2694
+ if (stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
2679
2695
  dispatch({
2680
2696
  type: ToggleButtonBlur
2681
2697
  });
@@ -2683,8 +2699,8 @@ function useSelect(userProps) {
2683
2699
  };
2684
2700
  var toggleProps = _extends((_extends3 = {}, _extends3[refKey] = handleRefs(ref, function (toggleButtonNode) {
2685
2701
  toggleButtonRef.current = toggleButtonNode;
2686
- }), _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);
2687
- if (!disabled) {
2702
+ }), _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);
2703
+ if (!rest.disabled) {
2688
2704
  /* istanbul ignore if (react-native) */
2689
2705
  {
2690
2706
  Object.assign(toggleProps, {
@@ -2694,7 +2710,7 @@ function useSelect(userProps) {
2694
2710
  }
2695
2711
  setGetterPropCallInfo('getToggleButtonProps', suppressRefError, refKey, toggleButtonRef);
2696
2712
  return toggleProps;
2697
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers]);
2713
+ }, [dispatch, elementIds, isOpen, highlightedIndex, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers, stateRef]);
2698
2714
  var getItemProps = React.useCallback(function (itemProps) {
2699
2715
  var _extends4;
2700
2716
  var _ref6 = itemProps != null ? itemProps : {},
@@ -2712,15 +2728,12 @@ function useSelect(userProps) {
2712
2728
  if (disabledProp !== undefined) {
2713
2729
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useSelect.');
2714
2730
  }
2715
- var _latest$current = latest.current,
2716
- latestState = _latest$current.state,
2717
- latestProps = _latest$current.props;
2718
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
2731
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
2719
2732
  item = _getItemAndIndex[0],
2720
2733
  index = _getItemAndIndex[1];
2721
- var disabled = latestProps.isItemDisabled(item, index);
2734
+ var disabled = isItemDisabled(item, index);
2722
2735
  var itemHandleMouseMove = function itemHandleMouseMove() {
2723
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
2736
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
2724
2737
  return;
2725
2738
  }
2726
2739
  preventScroll();
@@ -2744,7 +2757,7 @@ function useSelect(userProps) {
2744
2757
  if (itemNode) {
2745
2758
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
2746
2759
  }
2747
- }), _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);
2760
+ }), _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);
2748
2761
  if (!disabled) {
2749
2762
  /* istanbul ignore next (react-native) */
2750
2763
  {
@@ -2754,7 +2767,7 @@ function useSelect(userProps) {
2754
2767
  }
2755
2768
  }
2756
2769
  return resultItemProps;
2757
- }, [latest, elementIds, mouseAndTouchTrackers, preventScroll, dispatch]);
2770
+ }, [items, isItemDisabled, selectedItem, elementIds, mouseAndTouchTrackers, preventScroll, dispatch, stateRef]);
2758
2771
 
2759
2772
  // Action functions.
2760
2773
  var toggleMenu = React.useCallback(function () {
@@ -3045,8 +3058,8 @@ function downshiftUseComboboxReducer(state, action) {
3045
3058
 
3046
3059
  var _excluded$2 = ["onMouseLeave", "refKey", "ref", "aria-label"],
3047
3060
  _excluded2$2 = ["item", "index", "refKey", "ref", "onMouseMove", "onMouseDown", "onClick", "onPress", "disabled"],
3048
- _excluded3$1 = ["onClick", "onPress", "refKey", "ref", "disabled"],
3049
- _excluded4 = ["aria-label", "disabled", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3061
+ _excluded3$1 = ["onClick", "onPress", "refKey", "ref"],
3062
+ _excluded4 = ["aria-label", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3050
3063
  useCombobox.stateChangeTypes = stateChangeTypes$2;
3051
3064
  function useCombobox(userProps) {
3052
3065
  if (userProps === void 0) {
@@ -3056,6 +3069,7 @@ function useCombobox(userProps) {
3056
3069
  // Props defaults and destructuring.
3057
3070
  var props = _extends({}, dropdownDefaultProps, userProps);
3058
3071
  var items = props.items,
3072
+ isItemDisabled = props.isItemDisabled,
3059
3073
  scrollIntoView = props.scrollIntoView,
3060
3074
  environment = props.environment,
3061
3075
  getA11yStatusMessage = props.getA11yStatusMessage;
@@ -3079,17 +3093,18 @@ function useCombobox(userProps) {
3079
3093
  var elementIds = useElementIds$1(props);
3080
3094
  // used to keep track of how many items we had on previous cycle.
3081
3095
  var previousResultCountRef = React.useRef();
3082
- // utility callback to get item element.
3083
- var latest = useLatestRef({
3084
- state: state,
3085
- props: props
3086
- });
3096
+ /**
3097
+ * Ref to read `state` in handlers to preserve referential identity.
3098
+ * Only to be used in handlers and effects.
3099
+ * **never access this in getters**
3100
+ */
3101
+ var stateRef = useLatestRef(state);
3087
3102
 
3088
3103
  // Effects.
3089
3104
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
3090
3105
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
3091
3106
  // Scroll on highlighted item if change comes from keyboard.
3092
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
3107
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
3093
3108
  useControlPropsValidator({
3094
3109
  state: state,
3095
3110
  props: props
@@ -3108,12 +3123,12 @@ function useCombobox(userProps) {
3108
3123
  }
3109
3124
  });
3110
3125
  var handleBlurInTracker = React.useCallback(function handleBlur() {
3111
- if (latest.current.state.isOpen) {
3126
+ if (stateRef.current.isOpen) {
3112
3127
  dispatch({
3113
3128
  type: InputBlur
3114
3129
  });
3115
3130
  }
3116
- }, [dispatch, latest]);
3131
+ }, [dispatch, stateRef]);
3117
3132
  var downshiftRefs = React.useMemo(function () {
3118
3133
  return [menuRef, toggleButtonRef, inputRef];
3119
3134
  }, []);
@@ -3154,7 +3169,7 @@ function useCombobox(userProps) {
3154
3169
  });
3155
3170
  },
3156
3171
  Home: function Home(event) {
3157
- if (!latest.current.state.isOpen) {
3172
+ if (!stateRef.current.isOpen) {
3158
3173
  return;
3159
3174
  }
3160
3175
  event.preventDefault();
@@ -3163,7 +3178,7 @@ function useCombobox(userProps) {
3163
3178
  });
3164
3179
  },
3165
3180
  End: function End(event) {
3166
- if (!latest.current.state.isOpen) {
3181
+ if (!stateRef.current.isOpen) {
3167
3182
  return;
3168
3183
  }
3169
3184
  event.preventDefault();
@@ -3172,7 +3187,7 @@ function useCombobox(userProps) {
3172
3187
  });
3173
3188
  },
3174
3189
  Escape: function Escape(event) {
3175
- var latestState = latest.current.state;
3190
+ var latestState = stateRef.current;
3176
3191
  if (latestState.isOpen || latestState.inputValue || latestState.selectedItem || latestState.highlightedIndex > -1) {
3177
3192
  event.preventDefault();
3178
3193
  dispatch({
@@ -3181,7 +3196,7 @@ function useCombobox(userProps) {
3181
3196
  }
3182
3197
  },
3183
3198
  Enter: function Enter(event) {
3184
- var latestState = latest.current.state;
3199
+ var latestState = stateRef.current;
3185
3200
  // if closed or no highlighted index, do nothing.
3186
3201
  if (!latestState.isOpen || event.which === 229 // if IME composing, wait for next Enter keydown event.
3187
3202
  ) {
@@ -3193,7 +3208,7 @@ function useCombobox(userProps) {
3193
3208
  });
3194
3209
  },
3195
3210
  PageUp: function PageUp(event) {
3196
- if (latest.current.state.isOpen) {
3211
+ if (stateRef.current.isOpen) {
3197
3212
  event.preventDefault();
3198
3213
  dispatch({
3199
3214
  type: InputKeyDownPageUp
@@ -3201,7 +3216,7 @@ function useCombobox(userProps) {
3201
3216
  }
3202
3217
  },
3203
3218
  PageDown: function PageDown(event) {
3204
- if (latest.current.state.isOpen) {
3219
+ if (stateRef.current.isOpen) {
3205
3220
  event.preventDefault();
3206
3221
  dispatch({
3207
3222
  type: InputKeyDownPageDown
@@ -3209,7 +3224,7 @@ function useCombobox(userProps) {
3209
3224
  }
3210
3225
  }
3211
3226
  };
3212
- }, [dispatch, latest]);
3227
+ }, [dispatch, stateRef]);
3213
3228
 
3214
3229
  // Getter props.
3215
3230
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -3256,17 +3271,14 @@ function useCombobox(userProps) {
3256
3271
  if (disabledProp !== undefined) {
3257
3272
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useCombobox.');
3258
3273
  }
3259
- var _latest$current = latest.current,
3260
- latestProps = _latest$current.props,
3261
- latestState = _latest$current.state;
3262
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
3274
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
3263
3275
  item = _getItemAndIndex[0],
3264
3276
  index = _getItemAndIndex[1];
3265
- var disabled = latestProps.isItemDisabled(item, index);
3277
+ var disabled = isItemDisabled(item, index);
3266
3278
  var onSelectKey = /* istanbul ignore next (react-native) */'onPress' ;
3267
3279
  var customClickHandler = onClick;
3268
3280
  var itemHandleMouseMove = function itemHandleMouseMove() {
3269
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
3281
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
3270
3282
  return;
3271
3283
  }
3272
3284
  preventScroll();
@@ -3290,11 +3302,11 @@ function useCombobox(userProps) {
3290
3302
  if (itemNode) {
3291
3303
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
3292
3304
  }
3293
- }), _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), {
3305
+ }), _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), {
3294
3306
  onMouseMove: callAllEventHandlers(onMouseMove, itemHandleMouseMove),
3295
3307
  onMouseDown: callAllEventHandlers(onMouseDown, itemHandleMouseDown)
3296
3308
  }, rest);
3297
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, preventScroll]);
3309
+ }, [dispatch, elementIds, items, isItemDisabled, highlightedIndex, mouseAndTouchTrackers, preventScroll, stateRef]);
3298
3310
  var getToggleButtonProps = React.useCallback(function (toggleButtonProps) {
3299
3311
  var _extends4;
3300
3312
  var _ref5 = toggleButtonProps != null ? toggleButtonProps : {};
@@ -3303,9 +3315,7 @@ function useCombobox(userProps) {
3303
3315
  _ref5$refKey = _ref5.refKey,
3304
3316
  refKey = _ref5$refKey === void 0 ? 'ref' : _ref5$refKey,
3305
3317
  ref = _ref5.ref,
3306
- disabled = _ref5.disabled,
3307
3318
  rest = _objectWithoutPropertiesLoose(_ref5, _excluded3$1);
3308
- var latestState = latest.current.state;
3309
3319
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
3310
3320
  dispatch({
3311
3321
  type: ToggleButtonClick
@@ -3313,17 +3323,14 @@ function useCombobox(userProps) {
3313
3323
  };
3314
3324
  return _extends((_extends4 = {}, _extends4[refKey] = handleRefs(ref, function (toggleButtonNode) {
3315
3325
  toggleButtonRef.current = toggleButtonNode;
3316
- }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = latestState.isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !disabled && _extends({}, /* istanbul ignore next (react-native) */{
3326
+ }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !rest.disabled && _extends({}, /* istanbul ignore next (react-native) */{
3317
3327
  onPress: callAllEventHandlers(onPress, toggleButtonHandleClick)
3318
- } ), {
3319
- disabled: disabled
3320
- }, rest);
3321
- }, [dispatch, latest, elementIds]);
3328
+ } ), rest);
3329
+ }, [dispatch, isOpen, elementIds]);
3322
3330
  var getInputProps = React.useCallback(function (inputProps, otherProps) {
3323
3331
  var _extends5;
3324
3332
  var _ref6 = inputProps != null ? inputProps : {},
3325
3333
  ariaLabel = _ref6['aria-label'],
3326
- disabled = _ref6.disabled,
3327
3334
  onKeyDown = _ref6.onKeyDown,
3328
3335
  onChange = _ref6.onChange,
3329
3336
  onInput = _ref6.onInput,
@@ -3338,7 +3345,6 @@ function useCombobox(userProps) {
3338
3345
  _ref7$suppressRefErro = _ref7.suppressRefError,
3339
3346
  suppressRefError = _ref7$suppressRefErro === void 0 ? false : _ref7$suppressRefErro;
3340
3347
  setGetterPropCallInfo('getInputProps', suppressRefError, refKey, inputRef);
3341
- var latestState = latest.current.state;
3342
3348
  var inputHandleKeyDown = function inputHandleKeyDown(event) {
3343
3349
  var key = normalizeArrowKey(event);
3344
3350
  if (key && key in inputKeyDownHandlers) {
@@ -3354,7 +3360,7 @@ function useCombobox(userProps) {
3354
3360
  };
3355
3361
  var inputHandleBlur = function inputHandleBlur(event) {
3356
3362
  /* istanbul ignore else */
3357
- if (environment != null && environment.document && latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
3363
+ if (environment != null && environment.document && stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
3358
3364
  var isBlurByTabChange = event.relatedTarget === null && environment.document.activeElement !== environment.document.body;
3359
3365
  dispatch({
3360
3366
  type: InputBlur,
@@ -3371,14 +3377,14 @@ function useCombobox(userProps) {
3371
3377
  /* istanbul ignore next (preact) */
3372
3378
  var onChangeKey = 'onChange';
3373
3379
  var eventHandlers = {};
3374
- if (!disabled) {
3380
+ if (!rest.disabled) {
3375
3381
  var _eventHandlers;
3376
3382
  eventHandlers = (_eventHandlers = {}, _eventHandlers[onChangeKey] = callAllEventHandlers(onChange, onInput, inputHandleChange), _eventHandlers.onKeyDown = callAllEventHandlers(onKeyDown, inputHandleKeyDown), _eventHandlers.onBlur = callAllEventHandlers(onBlur, inputHandleBlur), _eventHandlers.onClick = callAllEventHandlers(onClick, inputHandleClick), _eventHandlers);
3377
3383
  }
3378
3384
  return _extends((_extends5 = {}, _extends5[refKey] = handleRefs(ref, function (inputNode) {
3379
3385
  inputRef.current = inputNode;
3380
- }), _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);
3381
- }, [dispatch, elementIds, environment, inputKeyDownHandlers, latest, mouseAndTouchTrackers, setGetterPropCallInfo]);
3386
+ }), _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);
3387
+ }, [dispatch, elementIds, environment, inputKeyDownHandlers, isOpen, highlightedIndex, inputValue, mouseAndTouchTrackers, setGetterPropCallInfo, stateRef]);
3382
3388
 
3383
3389
  // returns
3384
3390
  var toggleMenu = React.useCallback(function () {
@@ -3700,12 +3706,13 @@ function useMultipleSelection(userProps) {
3700
3706
  // Refs.
3701
3707
  var isInitialMount = useIsInitialMount();
3702
3708
  var dropdownRef = React.useRef(null);
3703
- var selectedItemRefs = React.useRef();
3704
- selectedItemRefs.current = [];
3705
- var latest = useLatestRef({
3706
- state: state,
3707
- props: props
3708
- });
3709
+ // Map of selected-item index -> DOM node. Populated by the ref callback in
3710
+ // getSelectedItemProps and read by the focus effect. Keyed by
3711
+ // index so we never reset it during render.
3712
+ var selectedItemRefs = React.useRef(null);
3713
+ if (selectedItemRefs.current === null) {
3714
+ selectedItemRefs.current = new Map();
3715
+ }
3709
3716
 
3710
3717
  // Effects.
3711
3718
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
@@ -3717,8 +3724,9 @@ function useMultipleSelection(userProps) {
3717
3724
  }
3718
3725
  if (activeIndex === -1 && dropdownRef.current) {
3719
3726
  dropdownRef.current.focus();
3720
- } else if (selectedItemRefs.current[activeIndex]) {
3721
- selectedItemRefs.current[activeIndex].focus();
3727
+ } else {
3728
+ var _selectedItemRefs$cur;
3729
+ (_selectedItemRefs$cur = selectedItemRefs.current.get(activeIndex)) == null || _selectedItemRefs$cur.focus();
3722
3730
  }
3723
3731
  // eslint-disable-next-line react-hooks/exhaustive-deps
3724
3732
  }, [activeIndex]);
@@ -3778,10 +3786,9 @@ function useMultipleSelection(userProps) {
3778
3786
  selectedItemProp = _ref3.selectedItem,
3779
3787
  indexProp = _ref3.index,
3780
3788
  rest = _objectWithoutPropertiesLoose(_ref3, _excluded$1);
3781
- var latestState = latest.current.state;
3782
- var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, latestState.selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3789
+ var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3783
3790
  index = _getItemAndIndex[1];
3784
- var isFocusable = index > -1 && index === latestState.activeIndex;
3791
+ var isFocusable = index > -1 && index === activeIndex;
3785
3792
  var selectedItemHandleClick = function selectedItemHandleClick() {
3786
3793
  dispatch({
3787
3794
  type: SelectedItemClick,
@@ -3796,10 +3803,12 @@ function useMultipleSelection(userProps) {
3796
3803
  };
3797
3804
  return _extends((_extends2 = {}, _extends2[refKey] = handleRefs(ref, function (selectedItemNode) {
3798
3805
  if (selectedItemNode) {
3799
- selectedItemRefs.current.push(selectedItemNode);
3806
+ selectedItemRefs.current.set(index, selectedItemNode);
3807
+ } else {
3808
+ selectedItemRefs.current["delete"](index);
3800
3809
  }
3801
3810
  }), _extends2.tabIndex = isFocusable ? 0 : -1, _extends2.onClick = callAllEventHandlers(onClick, selectedItemHandleClick), _extends2.onKeyDown = callAllEventHandlers(onKeyDown, selectedItemHandleKeyDown), _extends2), rest);
3802
- }, [dispatch, latest, selectedItemKeyDownHandlers]);
3811
+ }, [dispatch, selectedItems, activeIndex, selectedItemKeyDownHandlers]);
3803
3812
  var getDropdownProps = React.useCallback(function (_temp2, _temp3) {
3804
3813
  var _extends3;
3805
3814
  var _ref4 = _temp2 === void 0 ? {} : _temp2,
@@ -3987,8 +3996,10 @@ function useElementIdsLegacy(_ref2) {
3987
3996
  var id = _ref2.id,
3988
3997
  getTagId = _ref2.getTagId,
3989
3998
  tagGroupId = _ref2.tagGroupId;
3990
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
3991
- var baseId = baseIdRef.current;
3999
+ var _React$useState = React__namespace.useState(function () {
4000
+ return id != null ? id : "downshift-" + generateId();
4001
+ }),
4002
+ baseId = _React$useState[0];
3992
4003
  var elementIds = React__namespace.useMemo(function () {
3993
4004
  return {
3994
4005
  tagGroupId: tagGroupId != null ? tagGroupId : baseId + "-tag-group",
@@ -4085,10 +4096,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4085
4096
 
4086
4097
  /* Refs */
4087
4098
 
4088
- var latest = useLatestRef({
4089
- state: state,
4090
- props: props
4091
- });
4092
4099
  var elementIds = useElementIds({
4093
4100
  getTagId: props.getTagId,
4094
4101
  id: props.id,
@@ -4151,7 +4158,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4151
4158
  if (!Number.isInteger(index) || index < 0) {
4152
4159
  throw new Error('Pass correct item index to getTagProps!');
4153
4160
  }
4154
- var latestState = latest.current.state;
4155
4161
  var handleClick = function handleClick() {
4156
4162
  dispatch({
4157
4163
  type: TagClick,
@@ -4165,8 +4171,8 @@ var _useTagGroup = function useTagGroup(userProps) {
4165
4171
  if (itemNode) {
4166
4172
  itemRefs.current[tagId] = itemNode;
4167
4173
  }
4168
- }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = latestState.activeIndex === index ? 0 : -1, _extends2), rest);
4169
- }, [dispatch, elementIds, latest, itemRefs]);
4174
+ }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = activeIndex === index ? 0 : -1, _extends2), rest);
4175
+ }, [dispatch, elementIds, activeIndex, itemRefs]);
4170
4176
  var getTagRemoveProps = React.useCallback(function (options) {
4171
4177
  var index = options.index,
4172
4178
  onClick = options.onClick,