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
 
@@ -1585,18 +1589,27 @@ function invokeOnChangeHandler(key, action, props, state, newState) {
1585
1589
  */
1586
1590
  function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1587
1591
  var prevStateRef = React__namespace.useRef({});
1588
- var actionRef = React__namespace.useRef();
1589
- var enhancedReducer = React__namespace.useCallback(function (state, action) {
1590
- actionRef.current = action;
1592
+ var enhancedReducer = React__namespace.useCallback(function (_ref, action) {
1593
+ var state = _ref.state;
1591
1594
  state = getState(state, action.props);
1592
1595
  var changes = reducer(state, action);
1593
1596
  var newState = action.props.stateReducer(state, _extends({}, action, {
1594
1597
  changes: changes
1595
1598
  }));
1596
- return _extends({}, state, newState);
1599
+ return {
1600
+ state: _extends({}, state, newState),
1601
+ lastAction: action
1602
+ };
1597
1603
  }, [reducer]);
1598
- var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, createInitialState),
1599
- state = _React$useReducer[0],
1604
+ var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, function (p) {
1605
+ return {
1606
+ state: createInitialState(p),
1607
+ lastAction: undefined
1608
+ };
1609
+ }),
1610
+ _React$useReducer$ = _React$useReducer[0],
1611
+ state = _React$useReducer$.state,
1612
+ lastAction = _React$useReducer$.lastAction,
1600
1613
  dispatch = _React$useReducer[1];
1601
1614
  var propsRef = useLatestRef(props);
1602
1615
  var dispatchWithProps = React__namespace.useCallback(function (action) {
@@ -1604,15 +1617,16 @@ function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
1604
1617
  props: propsRef.current
1605
1618
  }));
1606
1619
  }, [propsRef]);
1607
- var action = actionRef.current;
1608
1620
  React__namespace.useEffect(function () {
1609
- var prevState = getState(prevStateRef.current, action == null ? void 0 : action.props);
1610
- var shouldCallOnChangeProps = action && !isStateEqual(prevState, state);
1611
- if (shouldCallOnChangeProps) {
1612
- callOnChangeProps(action, action.props, prevState, state);
1621
+ if (lastAction) {
1622
+ var prevState = getState(prevStateRef.current, lastAction.props);
1623
+ var shouldCallOnChangeProps = !isStateEqual(prevState, state);
1624
+ if (shouldCallOnChangeProps) {
1625
+ callOnChangeProps(lastAction, lastAction.props, prevState, state);
1626
+ }
1613
1627
  }
1614
1628
  prevStateRef.current = state;
1615
- }, [state, action, isStateEqual]);
1629
+ }, [state, lastAction, isStateEqual]);
1616
1630
  return [state, dispatchWithProps];
1617
1631
  }
1618
1632
 
@@ -1673,6 +1687,8 @@ function useIsInitialMount() {
1673
1687
  isInitialMountRef.current = true;
1674
1688
  };
1675
1689
  }, []);
1690
+
1691
+ // eslint-disable-next-line react-hooks/refs
1676
1692
  return isInitialMountRef.current;
1677
1693
  }
1678
1694
 
@@ -1852,8 +1868,10 @@ function useElementIdsLegacy$1(_ref2) {
1852
1868
  getItemId = _ref2.getItemId,
1853
1869
  toggleButtonId = _ref2.toggleButtonId,
1854
1870
  inputId = _ref2.inputId;
1855
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
1856
- var baseId = baseIdRef.current;
1871
+ var _React$useState = React__namespace.useState(function () {
1872
+ return id != null ? id : "downshift-" + generateId();
1873
+ }),
1874
+ baseId = _React$useState[0];
1857
1875
  var elementIds = React__namespace.useMemo(function () {
1858
1876
  return {
1859
1877
  labelId: labelId != null ? labelId : baseId + "-label",
@@ -1927,10 +1945,10 @@ function getHighlightedIndexOnOpen(items, initialHighlightedIndex, defaultHighli
1927
1945
  }
1928
1946
 
1929
1947
  // initialHighlightedIndex will give value to highlightedIndex on initial state only.
1930
- if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && items[initialHighlightedIndex] && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex)) {
1948
+ if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && (initialHighlightedIndex === -1 || items[initialHighlightedIndex] !== undefined && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex))) {
1931
1949
  return initialHighlightedIndex;
1932
1950
  }
1933
- if (defaultHighlightedIndex !== undefined && items[defaultHighlightedIndex] && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex)) {
1951
+ if (defaultHighlightedIndex !== undefined && (defaultHighlightedIndex === -1 || items[defaultHighlightedIndex] !== undefined && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex))) {
1934
1952
  return defaultHighlightedIndex;
1935
1953
  }
1936
1954
  if (selectedItem) {
@@ -2002,7 +2020,7 @@ if (process.env.NODE_ENV !== 'production') {
2002
2020
  * @param environment The environment to add the event listeners to, for instance window.
2003
2021
  * @param handleBlur The function that is called if mouseDown or touchEnd occured outside the downshiftElements.
2004
2022
  * @param downshiftRefs The refs for the elements that should not trigger a blur action from mouseDown or touchEnd.
2005
- * @returns The mouse and touch events information.
2023
+ * @returns A ref holding the mouse and touch events information. Read `.current` only inside event handlers/effects.
2006
2024
  */
2007
2025
  function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2008
2026
  var mouseAndTouchTrackersRef = React__namespace.useRef({
@@ -2055,7 +2073,7 @@ function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
2055
2073
  environment.removeEventListener('touchend', onTouchEnd);
2056
2074
  };
2057
2075
  }, [environment, getDownshiftElements, handleBlur]);
2058
- return mouseAndTouchTrackersRef.current;
2076
+ return mouseAndTouchTrackersRef;
2059
2077
  }
2060
2078
 
2061
2079
  /* istanbul ignore next */
@@ -2110,31 +2128,28 @@ if (process.env.NODE_ENV !== 'production') {
2110
2128
  };
2111
2129
  }
2112
2130
 
2113
- // istanbul ignore next
2114
- var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2115
-
2116
2131
  /**
2117
2132
  * Utility hook that scrolls an item from a menu into view.
2118
2133
  * @param scrollIntoView The function that does the scroll.
2119
2134
  * @param highlightedIndex The index of the item that should be scrolled.
2120
2135
  * @param isOpen If the menu is open or not.
2121
- * @param menuElement The menu element.
2122
- * @param itemElements The object containing item elements.
2136
+ * @param menuRef The ref to the menu element.
2137
+ * @param itemsRef The ref to the object containing item elements.
2123
2138
  * @param getItemId The function to get the item id from index.
2124
2139
  * @returns Function that when called prevents the scroll.
2125
2140
  */
2126
- function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuElement, itemElements, getItemId) {
2141
+ function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, getItemId) {
2127
2142
  // used not to scroll on highlight by mouse.
2128
2143
  var shouldScrollRef = React__namespace.useRef(true);
2129
2144
  // Scroll on highlighted item if change comes from keyboard.
2130
2145
  useIsomorphicLayoutEffect(function () {
2131
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemElements).length) {
2146
+ if (highlightedIndex < 0 || !isOpen || !Object.keys(itemsRef.current).length) {
2132
2147
  return;
2133
2148
  }
2134
2149
  if (shouldScrollRef.current) {
2135
- var itemElement = itemElements[getItemId(highlightedIndex)];
2136
- if (itemElement && menuElement) {
2137
- scrollIntoView(itemElement, menuElement);
2150
+ var itemElement = itemsRef.current[getItemId(highlightedIndex)];
2151
+ if (itemElement && menuRef.current) {
2152
+ scrollIntoView(itemElement, menuRef.current);
2138
2153
  }
2139
2154
  } else {
2140
2155
  shouldScrollRef.current = true;
@@ -2402,7 +2417,7 @@ function downshiftSelectReducer(state, action) {
2402
2417
 
2403
2418
  var _excluded$3 = ["onClick"],
2404
2419
  _excluded2$3 = ["onMouseLeave", "refKey", "ref", "aria-label"],
2405
- _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref", "disabled"],
2420
+ _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref"],
2406
2421
  _excluded4$1 = ["item", "index", "onMouseMove", "onClick", "onMouseDown", "onPress", "refKey", "disabled", "ref"];
2407
2422
  useSelect.stateChangeTypes = stateChangeTypes$3;
2408
2423
  function useSelect(userProps) {
@@ -2412,7 +2427,9 @@ function useSelect(userProps) {
2412
2427
  validatePropTypes$1(userProps, useSelect, propTypes$3);
2413
2428
  // Props defaults and destructuring.
2414
2429
  var props = _extends({}, dropdownDefaultProps, userProps);
2415
- var scrollIntoView = props.scrollIntoView,
2430
+ var items = props.items,
2431
+ isItemDisabled = props.isItemDisabled,
2432
+ scrollIntoView = props.scrollIntoView,
2416
2433
  environment = props.environment,
2417
2434
  getA11yStatusMessage = props.getA11yStatusMessage;
2418
2435
  // Initial state depending on controlled props.
@@ -2433,17 +2450,18 @@ function useSelect(userProps) {
2433
2450
  var clearTimeoutRef = React.useRef(null);
2434
2451
  // prevent id re-generation between renders.
2435
2452
  var elementIds = useElementIds$1(props);
2436
- // utility callback to get item element.
2437
- var latest = useLatestRef({
2438
- state: state,
2439
- props: props
2440
- });
2453
+ /**
2454
+ * Ref to read `state` in handlers to preserve referential identity.
2455
+ * Only to be used in handlers and effects.
2456
+ * **never access this in getters**
2457
+ */
2458
+ var stateRef = useLatestRef(state);
2441
2459
 
2442
2460
  // Effects.
2443
2461
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
2444
2462
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
2445
2463
  // Scroll on highlighted item if change comes from keyboard.
2446
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
2464
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
2447
2465
  // Sets cleanup for the keysSoFar callback, debounced after 500ms.
2448
2466
  React.useEffect(function () {
2449
2467
  // init the clean function here as we need access to dispatch.
@@ -2480,12 +2498,12 @@ function useSelect(userProps) {
2480
2498
  // eslint-disable-next-line react-hooks/exhaustive-deps
2481
2499
  }, []);
2482
2500
  var handleBlurInTracker = React.useCallback(function handleBlur() {
2483
- if (latest.current.state.isOpen) {
2501
+ if (stateRef.current.isOpen) {
2484
2502
  dispatch({
2485
2503
  type: ToggleButtonBlur
2486
2504
  });
2487
2505
  }
2488
- }, [dispatch, latest]);
2506
+ }, [dispatch, stateRef]);
2489
2507
  var downshiftRefs = React.useMemo(function () {
2490
2508
  return [menuRef, toggleButtonRef];
2491
2509
  }, []);
@@ -2528,7 +2546,7 @@ function useSelect(userProps) {
2528
2546
  });
2529
2547
  },
2530
2548
  Escape: function Escape() {
2531
- if (latest.current.state.isOpen) {
2549
+ if (stateRef.current.isOpen) {
2532
2550
  dispatch({
2533
2551
  type: ToggleButtonKeyDownEscape
2534
2552
  });
@@ -2537,11 +2555,11 @@ function useSelect(userProps) {
2537
2555
  Enter: function Enter(event) {
2538
2556
  event.preventDefault();
2539
2557
  dispatch({
2540
- type: latest.current.state.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2558
+ type: stateRef.current.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
2541
2559
  });
2542
2560
  },
2543
2561
  PageUp: function PageUp(event) {
2544
- if (latest.current.state.isOpen) {
2562
+ if (stateRef.current.isOpen) {
2545
2563
  event.preventDefault();
2546
2564
  dispatch({
2547
2565
  type: ToggleButtonKeyDownPageUp
@@ -2549,7 +2567,7 @@ function useSelect(userProps) {
2549
2567
  }
2550
2568
  },
2551
2569
  PageDown: function PageDown(event) {
2552
- if (latest.current.state.isOpen) {
2570
+ if (stateRef.current.isOpen) {
2553
2571
  event.preventDefault();
2554
2572
  dispatch({
2555
2573
  type: ToggleButtonKeyDownPageDown
@@ -2558,7 +2576,7 @@ function useSelect(userProps) {
2558
2576
  },
2559
2577
  ' ': function _(event) {
2560
2578
  event.preventDefault();
2561
- var currentState = latest.current.state;
2579
+ var currentState = stateRef.current;
2562
2580
  if (!currentState.isOpen) {
2563
2581
  dispatch({
2564
2582
  type: ToggleButtonClick$1
@@ -2577,7 +2595,7 @@ function useSelect(userProps) {
2577
2595
  }
2578
2596
  }
2579
2597
  };
2580
- }, [dispatch, latest]);
2598
+ }, [dispatch, stateRef]);
2581
2599
 
2582
2600
  // Getter functions.
2583
2601
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -2626,19 +2644,17 @@ function useSelect(userProps) {
2626
2644
  _ref4$refKey = _ref4.refKey,
2627
2645
  refKey = _ref4$refKey === void 0 ? 'ref' : _ref4$refKey,
2628
2646
  ref = _ref4.ref,
2629
- disabled = _ref4.disabled,
2630
2647
  rest = _objectWithoutPropertiesLoose(_ref4, _excluded3$2);
2631
2648
  var _ref5 = otherProps != null ? otherProps : {},
2632
2649
  _ref5$suppressRefErro = _ref5.suppressRefError,
2633
2650
  suppressRefError = _ref5$suppressRefErro === void 0 ? false : _ref5$suppressRefErro;
2634
- var latestState = latest.current.state;
2635
2651
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
2636
2652
  dispatch({
2637
2653
  type: ToggleButtonClick$1
2638
2654
  });
2639
2655
  };
2640
2656
  var toggleButtonHandleBlur = function toggleButtonHandleBlur() {
2641
- if (latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
2657
+ if (stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
2642
2658
  dispatch({
2643
2659
  type: ToggleButtonBlur
2644
2660
  });
@@ -2657,8 +2673,8 @@ function useSelect(userProps) {
2657
2673
  };
2658
2674
  var toggleProps = _extends((_extends3 = {}, _extends3[refKey] = handleRefs(ref, function (toggleButtonNode) {
2659
2675
  toggleButtonRef.current = toggleButtonNode;
2660
- }), _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);
2661
- if (!disabled) {
2676
+ }), _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);
2677
+ if (!rest.disabled) {
2662
2678
  /* istanbul ignore if (react-native) */
2663
2679
  {
2664
2680
  Object.assign(toggleProps, {
@@ -2669,7 +2685,7 @@ function useSelect(userProps) {
2669
2685
  }
2670
2686
  setGetterPropCallInfo('getToggleButtonProps', suppressRefError, refKey, toggleButtonRef);
2671
2687
  return toggleProps;
2672
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers]);
2688
+ }, [dispatch, elementIds, isOpen, highlightedIndex, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers, stateRef]);
2673
2689
  var getItemProps = React.useCallback(function (itemProps) {
2674
2690
  var _extends4;
2675
2691
  var _ref6 = itemProps != null ? itemProps : {},
@@ -2687,15 +2703,12 @@ function useSelect(userProps) {
2687
2703
  if (disabledProp !== undefined) {
2688
2704
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useSelect.');
2689
2705
  }
2690
- var _latest$current = latest.current,
2691
- latestState = _latest$current.state,
2692
- latestProps = _latest$current.props;
2693
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
2706
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
2694
2707
  item = _getItemAndIndex[0],
2695
2708
  index = _getItemAndIndex[1];
2696
- var disabled = latestProps.isItemDisabled(item, index);
2709
+ var disabled = isItemDisabled(item, index);
2697
2710
  var itemHandleMouseMove = function itemHandleMouseMove() {
2698
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
2711
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
2699
2712
  return;
2700
2713
  }
2701
2714
  preventScroll();
@@ -2719,7 +2732,7 @@ function useSelect(userProps) {
2719
2732
  if (itemNode) {
2720
2733
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
2721
2734
  }
2722
- }), _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);
2735
+ }), _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);
2723
2736
  if (!disabled) {
2724
2737
  /* istanbul ignore next (react-native) */
2725
2738
  {
@@ -2729,7 +2742,7 @@ function useSelect(userProps) {
2729
2742
  }
2730
2743
  }
2731
2744
  return resultItemProps;
2732
- }, [latest, elementIds, mouseAndTouchTrackers, preventScroll, dispatch]);
2745
+ }, [items, isItemDisabled, selectedItem, elementIds, mouseAndTouchTrackers, preventScroll, dispatch, stateRef]);
2733
2746
 
2734
2747
  // Action functions.
2735
2748
  var toggleMenu = React.useCallback(function () {
@@ -3020,8 +3033,8 @@ function downshiftUseComboboxReducer(state, action) {
3020
3033
 
3021
3034
  var _excluded$2 = ["onMouseLeave", "refKey", "ref", "aria-label"],
3022
3035
  _excluded2$2 = ["item", "index", "refKey", "ref", "onMouseMove", "onMouseDown", "onClick", "onPress", "disabled"],
3023
- _excluded3$1 = ["onClick", "onPress", "refKey", "ref", "disabled"],
3024
- _excluded4 = ["aria-label", "disabled", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3036
+ _excluded3$1 = ["onClick", "onPress", "refKey", "ref"],
3037
+ _excluded4 = ["aria-label", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
3025
3038
  useCombobox.stateChangeTypes = stateChangeTypes$2;
3026
3039
  function useCombobox(userProps) {
3027
3040
  if (userProps === void 0) {
@@ -3031,6 +3044,7 @@ function useCombobox(userProps) {
3031
3044
  // Props defaults and destructuring.
3032
3045
  var props = _extends({}, dropdownDefaultProps, userProps);
3033
3046
  var items = props.items,
3047
+ isItemDisabled = props.isItemDisabled,
3034
3048
  scrollIntoView = props.scrollIntoView,
3035
3049
  environment = props.environment,
3036
3050
  getA11yStatusMessage = props.getA11yStatusMessage;
@@ -3054,17 +3068,18 @@ function useCombobox(userProps) {
3054
3068
  var elementIds = useElementIds$1(props);
3055
3069
  // used to keep track of how many items we had on previous cycle.
3056
3070
  var previousResultCountRef = React.useRef();
3057
- // utility callback to get item element.
3058
- var latest = useLatestRef({
3059
- state: state,
3060
- props: props
3061
- });
3071
+ /**
3072
+ * Ref to read `state` in handlers to preserve referential identity.
3073
+ * Only to be used in handlers and effects.
3074
+ * **never access this in getters**
3075
+ */
3076
+ var stateRef = useLatestRef(state);
3062
3077
 
3063
3078
  // Effects.
3064
3079
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
3065
3080
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
3066
3081
  // Scroll on highlighted item if change comes from keyboard.
3067
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
3082
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
3068
3083
  useControlPropsValidator({
3069
3084
  state: state,
3070
3085
  props: props
@@ -3083,12 +3098,12 @@ function useCombobox(userProps) {
3083
3098
  }
3084
3099
  });
3085
3100
  var handleBlurInTracker = React.useCallback(function handleBlur() {
3086
- if (latest.current.state.isOpen) {
3101
+ if (stateRef.current.isOpen) {
3087
3102
  dispatch({
3088
3103
  type: InputBlur
3089
3104
  });
3090
3105
  }
3091
- }, [dispatch, latest]);
3106
+ }, [dispatch, stateRef]);
3092
3107
  var downshiftRefs = React.useMemo(function () {
3093
3108
  return [menuRef, toggleButtonRef, inputRef];
3094
3109
  }, []);
@@ -3129,7 +3144,7 @@ function useCombobox(userProps) {
3129
3144
  });
3130
3145
  },
3131
3146
  Home: function Home(event) {
3132
- if (!latest.current.state.isOpen) {
3147
+ if (!stateRef.current.isOpen) {
3133
3148
  return;
3134
3149
  }
3135
3150
  event.preventDefault();
@@ -3138,7 +3153,7 @@ function useCombobox(userProps) {
3138
3153
  });
3139
3154
  },
3140
3155
  End: function End(event) {
3141
- if (!latest.current.state.isOpen) {
3156
+ if (!stateRef.current.isOpen) {
3142
3157
  return;
3143
3158
  }
3144
3159
  event.preventDefault();
@@ -3147,7 +3162,7 @@ function useCombobox(userProps) {
3147
3162
  });
3148
3163
  },
3149
3164
  Escape: function Escape(event) {
3150
- var latestState = latest.current.state;
3165
+ var latestState = stateRef.current;
3151
3166
  if (latestState.isOpen || latestState.inputValue || latestState.selectedItem || latestState.highlightedIndex > -1) {
3152
3167
  event.preventDefault();
3153
3168
  dispatch({
@@ -3156,7 +3171,7 @@ function useCombobox(userProps) {
3156
3171
  }
3157
3172
  },
3158
3173
  Enter: function Enter(event) {
3159
- var latestState = latest.current.state;
3174
+ var latestState = stateRef.current;
3160
3175
  // if closed or no highlighted index, do nothing.
3161
3176
  if (!latestState.isOpen || event.which === 229 // if IME composing, wait for next Enter keydown event.
3162
3177
  ) {
@@ -3168,7 +3183,7 @@ function useCombobox(userProps) {
3168
3183
  });
3169
3184
  },
3170
3185
  PageUp: function PageUp(event) {
3171
- if (latest.current.state.isOpen) {
3186
+ if (stateRef.current.isOpen) {
3172
3187
  event.preventDefault();
3173
3188
  dispatch({
3174
3189
  type: InputKeyDownPageUp
@@ -3176,7 +3191,7 @@ function useCombobox(userProps) {
3176
3191
  }
3177
3192
  },
3178
3193
  PageDown: function PageDown(event) {
3179
- if (latest.current.state.isOpen) {
3194
+ if (stateRef.current.isOpen) {
3180
3195
  event.preventDefault();
3181
3196
  dispatch({
3182
3197
  type: InputKeyDownPageDown
@@ -3184,7 +3199,7 @@ function useCombobox(userProps) {
3184
3199
  }
3185
3200
  }
3186
3201
  };
3187
- }, [dispatch, latest]);
3202
+ }, [dispatch, stateRef]);
3188
3203
 
3189
3204
  // Getter props.
3190
3205
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -3231,17 +3246,14 @@ function useCombobox(userProps) {
3231
3246
  if (disabledProp !== undefined) {
3232
3247
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useCombobox.');
3233
3248
  }
3234
- var _latest$current = latest.current,
3235
- latestProps = _latest$current.props,
3236
- latestState = _latest$current.state;
3237
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
3249
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
3238
3250
  item = _getItemAndIndex[0],
3239
3251
  index = _getItemAndIndex[1];
3240
- var disabled = latestProps.isItemDisabled(item, index);
3252
+ var disabled = isItemDisabled(item, index);
3241
3253
  var onSelectKey = 'onClick';
3242
3254
  var customClickHandler = onClick;
3243
3255
  var itemHandleMouseMove = function itemHandleMouseMove() {
3244
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
3256
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
3245
3257
  return;
3246
3258
  }
3247
3259
  preventScroll();
@@ -3265,11 +3277,11 @@ function useCombobox(userProps) {
3265
3277
  if (itemNode) {
3266
3278
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
3267
3279
  }
3268
- }), _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), {
3280
+ }), _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), {
3269
3281
  onMouseMove: callAllEventHandlers(onMouseMove, itemHandleMouseMove),
3270
3282
  onMouseDown: callAllEventHandlers(onMouseDown, itemHandleMouseDown)
3271
3283
  }, rest);
3272
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, preventScroll]);
3284
+ }, [dispatch, elementIds, items, isItemDisabled, highlightedIndex, mouseAndTouchTrackers, preventScroll, stateRef]);
3273
3285
  var getToggleButtonProps = React.useCallback(function (toggleButtonProps) {
3274
3286
  var _extends4;
3275
3287
  var _ref5 = toggleButtonProps != null ? toggleButtonProps : {},
@@ -3278,9 +3290,7 @@ function useCombobox(userProps) {
3278
3290
  var _ref5$refKey = _ref5.refKey,
3279
3291
  refKey = _ref5$refKey === void 0 ? 'ref' : _ref5$refKey,
3280
3292
  ref = _ref5.ref,
3281
- disabled = _ref5.disabled,
3282
3293
  rest = _objectWithoutPropertiesLoose(_ref5, _excluded3$1);
3283
- var latestState = latest.current.state;
3284
3294
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
3285
3295
  dispatch({
3286
3296
  type: ToggleButtonClick
@@ -3288,17 +3298,14 @@ function useCombobox(userProps) {
3288
3298
  };
3289
3299
  return _extends((_extends4 = {}, _extends4[refKey] = handleRefs(ref, function (toggleButtonNode) {
3290
3300
  toggleButtonRef.current = toggleButtonNode;
3291
- }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = latestState.isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !disabled && _extends({}, {
3301
+ }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !rest.disabled && _extends({}, {
3292
3302
  onClick: callAllEventHandlers(onClick, toggleButtonHandleClick)
3293
- }), {
3294
- disabled: disabled
3295
- }, rest);
3296
- }, [dispatch, latest, elementIds]);
3303
+ }), rest);
3304
+ }, [dispatch, isOpen, elementIds]);
3297
3305
  var getInputProps = React.useCallback(function (inputProps, otherProps) {
3298
3306
  var _extends5;
3299
3307
  var _ref6 = inputProps != null ? inputProps : {},
3300
3308
  ariaLabel = _ref6['aria-label'],
3301
- disabled = _ref6.disabled,
3302
3309
  onKeyDown = _ref6.onKeyDown,
3303
3310
  onChange = _ref6.onChange,
3304
3311
  onInput = _ref6.onInput,
@@ -3313,7 +3320,6 @@ function useCombobox(userProps) {
3313
3320
  _ref7$suppressRefErro = _ref7.suppressRefError,
3314
3321
  suppressRefError = _ref7$suppressRefErro === void 0 ? false : _ref7$suppressRefErro;
3315
3322
  setGetterPropCallInfo('getInputProps', suppressRefError, refKey, inputRef);
3316
- var latestState = latest.current.state;
3317
3323
  var inputHandleKeyDown = function inputHandleKeyDown(event) {
3318
3324
  var key = normalizeArrowKey(event);
3319
3325
  if (key && key in inputKeyDownHandlers) {
@@ -3329,7 +3335,7 @@ function useCombobox(userProps) {
3329
3335
  };
3330
3336
  var inputHandleBlur = function inputHandleBlur(event) {
3331
3337
  /* istanbul ignore else */
3332
- if (environment != null && environment.document && latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
3338
+ if (environment != null && environment.document && stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
3333
3339
  var isBlurByTabChange = event.relatedTarget === null && environment.document.activeElement !== environment.document.body;
3334
3340
  dispatch({
3335
3341
  type: InputBlur,
@@ -3346,14 +3352,14 @@ function useCombobox(userProps) {
3346
3352
  /* istanbul ignore next (preact) */
3347
3353
  var onChangeKey = 'onInput' ;
3348
3354
  var eventHandlers = {};
3349
- if (!disabled) {
3355
+ if (!rest.disabled) {
3350
3356
  var _eventHandlers;
3351
3357
  eventHandlers = (_eventHandlers = {}, _eventHandlers[onChangeKey] = callAllEventHandlers(onChange, onInput, inputHandleChange), _eventHandlers.onKeyDown = callAllEventHandlers(onKeyDown, inputHandleKeyDown), _eventHandlers.onBlur = callAllEventHandlers(onBlur, inputHandleBlur), _eventHandlers.onClick = callAllEventHandlers(onClick, inputHandleClick), _eventHandlers);
3352
3358
  }
3353
3359
  return _extends((_extends5 = {}, _extends5[refKey] = handleRefs(ref, function (inputNode) {
3354
3360
  inputRef.current = inputNode;
3355
- }), _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);
3356
- }, [dispatch, elementIds, environment, inputKeyDownHandlers, latest, mouseAndTouchTrackers, setGetterPropCallInfo]);
3361
+ }), _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);
3362
+ }, [dispatch, elementIds, environment, inputKeyDownHandlers, isOpen, highlightedIndex, inputValue, mouseAndTouchTrackers, setGetterPropCallInfo, stateRef]);
3357
3363
 
3358
3364
  // returns
3359
3365
  var toggleMenu = React.useCallback(function () {
@@ -3675,12 +3681,13 @@ function useMultipleSelection(userProps) {
3675
3681
  // Refs.
3676
3682
  var isInitialMount = useIsInitialMount();
3677
3683
  var dropdownRef = React.useRef(null);
3678
- var selectedItemRefs = React.useRef();
3679
- selectedItemRefs.current = [];
3680
- var latest = useLatestRef({
3681
- state: state,
3682
- props: props
3683
- });
3684
+ // Map of selected-item index -> DOM node. Populated by the ref callback in
3685
+ // getSelectedItemProps and read by the focus effect. Keyed by
3686
+ // index so we never reset it during render.
3687
+ var selectedItemRefs = React.useRef(null);
3688
+ if (selectedItemRefs.current === null) {
3689
+ selectedItemRefs.current = new Map();
3690
+ }
3684
3691
 
3685
3692
  // Effects.
3686
3693
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
@@ -3692,8 +3699,9 @@ function useMultipleSelection(userProps) {
3692
3699
  }
3693
3700
  if (activeIndex === -1 && dropdownRef.current) {
3694
3701
  dropdownRef.current.focus();
3695
- } else if (selectedItemRefs.current[activeIndex]) {
3696
- selectedItemRefs.current[activeIndex].focus();
3702
+ } else {
3703
+ var _selectedItemRefs$cur;
3704
+ (_selectedItemRefs$cur = selectedItemRefs.current.get(activeIndex)) == null || _selectedItemRefs$cur.focus();
3697
3705
  }
3698
3706
  // eslint-disable-next-line react-hooks/exhaustive-deps
3699
3707
  }, [activeIndex]);
@@ -3753,10 +3761,9 @@ function useMultipleSelection(userProps) {
3753
3761
  selectedItemProp = _ref3.selectedItem,
3754
3762
  indexProp = _ref3.index,
3755
3763
  rest = _objectWithoutPropertiesLoose(_ref3, _excluded$1);
3756
- var latestState = latest.current.state;
3757
- var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, latestState.selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3764
+ var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, selectedItems, 'Pass either item or index to getSelectedItemProps!'),
3758
3765
  index = _getItemAndIndex[1];
3759
- var isFocusable = index > -1 && index === latestState.activeIndex;
3766
+ var isFocusable = index > -1 && index === activeIndex;
3760
3767
  var selectedItemHandleClick = function selectedItemHandleClick() {
3761
3768
  dispatch({
3762
3769
  type: SelectedItemClick,
@@ -3771,10 +3778,12 @@ function useMultipleSelection(userProps) {
3771
3778
  };
3772
3779
  return _extends((_extends2 = {}, _extends2[refKey] = handleRefs(ref, function (selectedItemNode) {
3773
3780
  if (selectedItemNode) {
3774
- selectedItemRefs.current.push(selectedItemNode);
3781
+ selectedItemRefs.current.set(index, selectedItemNode);
3782
+ } else {
3783
+ selectedItemRefs.current["delete"](index);
3775
3784
  }
3776
3785
  }), _extends2.tabIndex = isFocusable ? 0 : -1, _extends2.onClick = callAllEventHandlers(onClick, selectedItemHandleClick), _extends2.onKeyDown = callAllEventHandlers(onKeyDown, selectedItemHandleKeyDown), _extends2), rest);
3777
- }, [dispatch, latest, selectedItemKeyDownHandlers]);
3786
+ }, [dispatch, selectedItems, activeIndex, selectedItemKeyDownHandlers]);
3778
3787
  var getDropdownProps = React.useCallback(function (_temp2, _temp3) {
3779
3788
  var _extends3;
3780
3789
  var _ref4 = _temp2 === void 0 ? {} : _temp2,
@@ -3962,8 +3971,10 @@ function useElementIdsLegacy(_ref2) {
3962
3971
  var id = _ref2.id,
3963
3972
  getTagId = _ref2.getTagId,
3964
3973
  tagGroupId = _ref2.tagGroupId;
3965
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
3966
- var baseId = baseIdRef.current;
3974
+ var _React$useState = React__namespace.useState(function () {
3975
+ return id != null ? id : "downshift-" + generateId();
3976
+ }),
3977
+ baseId = _React$useState[0];
3967
3978
  var elementIds = React__namespace.useMemo(function () {
3968
3979
  return {
3969
3980
  tagGroupId: tagGroupId != null ? tagGroupId : baseId + "-tag-group",
@@ -4060,10 +4071,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4060
4071
 
4061
4072
  /* Refs */
4062
4073
 
4063
- var latest = useLatestRef({
4064
- state: state,
4065
- props: props
4066
- });
4067
4074
  var elementIds = useElementIds({
4068
4075
  getTagId: props.getTagId,
4069
4076
  id: props.id,
@@ -4126,7 +4133,6 @@ var _useTagGroup = function useTagGroup(userProps) {
4126
4133
  if (!Number.isInteger(index) || index < 0) {
4127
4134
  throw new Error('Pass correct item index to getTagProps!');
4128
4135
  }
4129
- var latestState = latest.current.state;
4130
4136
  var handleClick = function handleClick() {
4131
4137
  dispatch({
4132
4138
  type: TagClick,
@@ -4140,8 +4146,8 @@ var _useTagGroup = function useTagGroup(userProps) {
4140
4146
  if (itemNode) {
4141
4147
  itemRefs.current[tagId] = itemNode;
4142
4148
  }
4143
- }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = latestState.activeIndex === index ? 0 : -1, _extends2), rest);
4144
- }, [dispatch, elementIds, latest, itemRefs]);
4149
+ }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = activeIndex === index ? 0 : -1, _extends2), rest);
4150
+ }, [dispatch, elementIds, activeIndex, itemRefs]);
4145
4151
  var getTagRemoveProps = React.useCallback(function (options) {
4146
4152
  var index = options.index,
4147
4153
  onClick = options.onClick,