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
@@ -1451,14 +1451,18 @@
1451
1451
  idCounter = 0;
1452
1452
  }
1453
1453
 
1454
- function useLatestRef(val) {
1455
- var ref = React__namespace.useRef(val);
1456
- // technically this is not "concurrent mode safe" because we're manipulating
1457
- // the value during render (so it's not idempotent). However, the places this
1458
- // hook is used is to support memoizing callbacks which will be called
1459
- // *during* render, so we need the latest values *during* render.
1460
- // If not for this, then we'd probably want to use useLayoutEffect instead.
1461
- ref.current = val;
1454
+ // istanbul ignore next
1455
+ var canUseDOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
1456
+
1457
+ // React Native supports layout effects even though it does not have a DOM.
1458
+ // istanbul ignore next
1459
+ var useIsomorphicLayoutEffect = canUseDOM || false ? React__namespace.useLayoutEffect : React__namespace.useEffect;
1460
+
1461
+ function useLatestRef(value) {
1462
+ var ref = React__namespace.useRef(value);
1463
+ useIsomorphicLayoutEffect(function () {
1464
+ ref.current = value;
1465
+ }, [value]);
1462
1466
  return ref;
1463
1467
  }
1464
1468
 
@@ -2977,18 +2981,27 @@
2977
2981
  */
2978
2982
  function useEnhancedReducer(reducer, props, createInitialState, isStateEqual) {
2979
2983
  var prevStateRef = React__namespace.useRef({});
2980
- var actionRef = React__namespace.useRef();
2981
- var enhancedReducer = React__namespace.useCallback(function (state, action) {
2982
- actionRef.current = action;
2984
+ var enhancedReducer = React__namespace.useCallback(function (_ref, action) {
2985
+ var state = _ref.state;
2983
2986
  state = getState(state, action.props);
2984
2987
  var changes = reducer(state, action);
2985
2988
  var newState = action.props.stateReducer(state, _extends({}, action, {
2986
2989
  changes: changes
2987
2990
  }));
2988
- return _extends({}, state, newState);
2991
+ return {
2992
+ state: _extends({}, state, newState),
2993
+ lastAction: action
2994
+ };
2989
2995
  }, [reducer]);
2990
- var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, createInitialState),
2991
- state = _React$useReducer[0],
2996
+ var _React$useReducer = React__namespace.useReducer(enhancedReducer, props, function (p) {
2997
+ return {
2998
+ state: createInitialState(p),
2999
+ lastAction: undefined
3000
+ };
3001
+ }),
3002
+ _React$useReducer$ = _React$useReducer[0],
3003
+ state = _React$useReducer$.state,
3004
+ lastAction = _React$useReducer$.lastAction,
2992
3005
  dispatch = _React$useReducer[1];
2993
3006
  var propsRef = useLatestRef(props);
2994
3007
  var dispatchWithProps = React__namespace.useCallback(function (action) {
@@ -2996,15 +3009,16 @@
2996
3009
  props: propsRef.current
2997
3010
  }));
2998
3011
  }, [propsRef]);
2999
- var action = actionRef.current;
3000
3012
  React__namespace.useEffect(function () {
3001
- var prevState = getState(prevStateRef.current, action == null ? void 0 : action.props);
3002
- var shouldCallOnChangeProps = action && !isStateEqual(prevState, state);
3003
- if (shouldCallOnChangeProps) {
3004
- callOnChangeProps(action, action.props, prevState, state);
3013
+ if (lastAction) {
3014
+ var prevState = getState(prevStateRef.current, lastAction.props);
3015
+ var shouldCallOnChangeProps = !isStateEqual(prevState, state);
3016
+ if (shouldCallOnChangeProps) {
3017
+ callOnChangeProps(lastAction, lastAction.props, prevState, state);
3018
+ }
3005
3019
  }
3006
3020
  prevStateRef.current = state;
3007
- }, [state, action, isStateEqual]);
3021
+ }, [state, lastAction, isStateEqual]);
3008
3022
  return [state, dispatchWithProps];
3009
3023
  }
3010
3024
 
@@ -3065,6 +3079,8 @@
3065
3079
  isInitialMountRef.current = true;
3066
3080
  };
3067
3081
  }, []);
3082
+
3083
+ // eslint-disable-next-line react-hooks/refs
3068
3084
  return isInitialMountRef.current;
3069
3085
  }
3070
3086
 
@@ -3244,8 +3260,10 @@
3244
3260
  getItemId = _ref2.getItemId,
3245
3261
  toggleButtonId = _ref2.toggleButtonId,
3246
3262
  inputId = _ref2.inputId;
3247
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
3248
- var baseId = baseIdRef.current;
3263
+ var _React$useState = React__namespace.useState(function () {
3264
+ return id != null ? id : "downshift-" + generateId();
3265
+ }),
3266
+ baseId = _React$useState[0];
3249
3267
  var elementIds = React__namespace.useMemo(function () {
3250
3268
  return {
3251
3269
  labelId: labelId != null ? labelId : baseId + "-label",
@@ -3319,10 +3337,10 @@
3319
3337
  }
3320
3338
 
3321
3339
  // initialHighlightedIndex will give value to highlightedIndex on initial state only.
3322
- if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && items[initialHighlightedIndex] && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex)) {
3340
+ if (initialHighlightedIndex !== undefined && highlightedIndex === initialHighlightedIndex && (initialHighlightedIndex === -1 || items[initialHighlightedIndex] !== undefined && !isItemDisabled(items[initialHighlightedIndex], initialHighlightedIndex))) {
3323
3341
  return initialHighlightedIndex;
3324
3342
  }
3325
- if (defaultHighlightedIndex !== undefined && items[defaultHighlightedIndex] && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex)) {
3343
+ if (defaultHighlightedIndex !== undefined && (defaultHighlightedIndex === -1 || items[defaultHighlightedIndex] !== undefined && !isItemDisabled(items[defaultHighlightedIndex], defaultHighlightedIndex))) {
3326
3344
  return defaultHighlightedIndex;
3327
3345
  }
3328
3346
  if (selectedItem) {
@@ -3394,7 +3412,7 @@
3394
3412
  * @param environment The environment to add the event listeners to, for instance window.
3395
3413
  * @param handleBlur The function that is called if mouseDown or touchEnd occured outside the downshiftElements.
3396
3414
  * @param downshiftRefs The refs for the elements that should not trigger a blur action from mouseDown or touchEnd.
3397
- * @returns The mouse and touch events information.
3415
+ * @returns A ref holding the mouse and touch events information. Read `.current` only inside event handlers/effects.
3398
3416
  */
3399
3417
  function useMouseAndTouchTracker(environment, handleBlur, downshiftRefs) {
3400
3418
  var mouseAndTouchTrackersRef = React__namespace.useRef({
@@ -3447,7 +3465,7 @@
3447
3465
  environment.removeEventListener('touchend', onTouchEnd);
3448
3466
  };
3449
3467
  }, [environment, getDownshiftElements, handleBlur]);
3450
- return mouseAndTouchTrackersRef.current;
3468
+ return mouseAndTouchTrackersRef;
3451
3469
  }
3452
3470
 
3453
3471
  /* istanbul ignore next */
@@ -3502,31 +3520,28 @@
3502
3520
  };
3503
3521
  }
3504
3522
 
3505
- // istanbul ignore next
3506
- var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
3507
-
3508
3523
  /**
3509
3524
  * Utility hook that scrolls an item from a menu into view.
3510
3525
  * @param scrollIntoView The function that does the scroll.
3511
3526
  * @param highlightedIndex The index of the item that should be scrolled.
3512
3527
  * @param isOpen If the menu is open or not.
3513
- * @param menuElement The menu element.
3514
- * @param itemElements The object containing item elements.
3528
+ * @param menuRef The ref to the menu element.
3529
+ * @param itemsRef The ref to the object containing item elements.
3515
3530
  * @param getItemId The function to get the item id from index.
3516
3531
  * @returns Function that when called prevents the scroll.
3517
3532
  */
3518
- function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuElement, itemElements, getItemId) {
3533
+ function useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, getItemId) {
3519
3534
  // used not to scroll on highlight by mouse.
3520
3535
  var shouldScrollRef = React__namespace.useRef(true);
3521
3536
  // Scroll on highlighted item if change comes from keyboard.
3522
3537
  useIsomorphicLayoutEffect(function () {
3523
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemElements).length) {
3538
+ if (highlightedIndex < 0 || !isOpen || !Object.keys(itemsRef.current).length) {
3524
3539
  return;
3525
3540
  }
3526
3541
  if (shouldScrollRef.current) {
3527
- var itemElement = itemElements[getItemId(highlightedIndex)];
3528
- if (itemElement && menuElement) {
3529
- scrollIntoView(itemElement, menuElement);
3542
+ var itemElement = itemsRef.current[getItemId(highlightedIndex)];
3543
+ if (itemElement && menuRef.current) {
3544
+ scrollIntoView(itemElement, menuRef.current);
3530
3545
  }
3531
3546
  } else {
3532
3547
  shouldScrollRef.current = true;
@@ -3794,7 +3809,7 @@
3794
3809
 
3795
3810
  var _excluded$3 = ["onClick"],
3796
3811
  _excluded2$3 = ["onMouseLeave", "refKey", "ref", "aria-label"],
3797
- _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref", "disabled"],
3812
+ _excluded3$2 = ["onBlur", "onClick", "onPress", "onKeyDown", "refKey", "ref"],
3798
3813
  _excluded4$1 = ["item", "index", "onMouseMove", "onClick", "onMouseDown", "onPress", "refKey", "disabled", "ref"];
3799
3814
  useSelect.stateChangeTypes = stateChangeTypes$3;
3800
3815
  function useSelect(userProps) {
@@ -3804,7 +3819,9 @@
3804
3819
  validatePropTypes$1(userProps, useSelect, propTypes$3);
3805
3820
  // Props defaults and destructuring.
3806
3821
  var props = _extends({}, dropdownDefaultProps, userProps);
3807
- var scrollIntoView = props.scrollIntoView,
3822
+ var items = props.items,
3823
+ isItemDisabled = props.isItemDisabled,
3824
+ scrollIntoView = props.scrollIntoView,
3808
3825
  environment = props.environment,
3809
3826
  getA11yStatusMessage = props.getA11yStatusMessage;
3810
3827
  // Initial state depending on controlled props.
@@ -3825,17 +3842,18 @@
3825
3842
  var clearTimeoutRef = React.useRef(null);
3826
3843
  // prevent id re-generation between renders.
3827
3844
  var elementIds = useElementIds$1(props);
3828
- // utility callback to get item element.
3829
- var latest = useLatestRef({
3830
- state: state,
3831
- props: props
3832
- });
3845
+ /**
3846
+ * Ref to read `state` in handlers to preserve referential identity.
3847
+ * Only to be used in handlers and effects.
3848
+ * **never access this in getters**
3849
+ */
3850
+ var stateRef = useLatestRef(state);
3833
3851
 
3834
3852
  // Effects.
3835
3853
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
3836
3854
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
3837
3855
  // Scroll on highlighted item if change comes from keyboard.
3838
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
3856
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
3839
3857
  // Sets cleanup for the keysSoFar callback, debounced after 500ms.
3840
3858
  React.useEffect(function () {
3841
3859
  // init the clean function here as we need access to dispatch.
@@ -3872,12 +3890,12 @@
3872
3890
  // eslint-disable-next-line react-hooks/exhaustive-deps
3873
3891
  }, []);
3874
3892
  var handleBlurInTracker = React.useCallback(function handleBlur() {
3875
- if (latest.current.state.isOpen) {
3893
+ if (stateRef.current.isOpen) {
3876
3894
  dispatch({
3877
3895
  type: ToggleButtonBlur
3878
3896
  });
3879
3897
  }
3880
- }, [dispatch, latest]);
3898
+ }, [dispatch, stateRef]);
3881
3899
  var downshiftRefs = React.useMemo(function () {
3882
3900
  return [menuRef, toggleButtonRef];
3883
3901
  }, []);
@@ -3920,7 +3938,7 @@
3920
3938
  });
3921
3939
  },
3922
3940
  Escape: function Escape() {
3923
- if (latest.current.state.isOpen) {
3941
+ if (stateRef.current.isOpen) {
3924
3942
  dispatch({
3925
3943
  type: ToggleButtonKeyDownEscape
3926
3944
  });
@@ -3929,11 +3947,11 @@
3929
3947
  Enter: function Enter(event) {
3930
3948
  event.preventDefault();
3931
3949
  dispatch({
3932
- type: latest.current.state.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
3950
+ type: stateRef.current.isOpen ? ToggleButtonKeyDownEnter : ToggleButtonClick$1
3933
3951
  });
3934
3952
  },
3935
3953
  PageUp: function PageUp(event) {
3936
- if (latest.current.state.isOpen) {
3954
+ if (stateRef.current.isOpen) {
3937
3955
  event.preventDefault();
3938
3956
  dispatch({
3939
3957
  type: ToggleButtonKeyDownPageUp
@@ -3941,7 +3959,7 @@
3941
3959
  }
3942
3960
  },
3943
3961
  PageDown: function PageDown(event) {
3944
- if (latest.current.state.isOpen) {
3962
+ if (stateRef.current.isOpen) {
3945
3963
  event.preventDefault();
3946
3964
  dispatch({
3947
3965
  type: ToggleButtonKeyDownPageDown
@@ -3950,7 +3968,7 @@
3950
3968
  },
3951
3969
  ' ': function _(event) {
3952
3970
  event.preventDefault();
3953
- var currentState = latest.current.state;
3971
+ var currentState = stateRef.current;
3954
3972
  if (!currentState.isOpen) {
3955
3973
  dispatch({
3956
3974
  type: ToggleButtonClick$1
@@ -3969,7 +3987,7 @@
3969
3987
  }
3970
3988
  }
3971
3989
  };
3972
- }, [dispatch, latest]);
3990
+ }, [dispatch, stateRef]);
3973
3991
 
3974
3992
  // Getter functions.
3975
3993
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -4018,19 +4036,17 @@
4018
4036
  _ref4$refKey = _ref4.refKey,
4019
4037
  refKey = _ref4$refKey === void 0 ? 'ref' : _ref4$refKey,
4020
4038
  ref = _ref4.ref,
4021
- disabled = _ref4.disabled,
4022
4039
  rest = _objectWithoutPropertiesLoose(_ref4, _excluded3$2);
4023
4040
  var _ref5 = otherProps != null ? otherProps : {},
4024
4041
  _ref5$suppressRefErro = _ref5.suppressRefError,
4025
4042
  suppressRefError = _ref5$suppressRefErro === void 0 ? false : _ref5$suppressRefErro;
4026
- var latestState = latest.current.state;
4027
4043
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
4028
4044
  dispatch({
4029
4045
  type: ToggleButtonClick$1
4030
4046
  });
4031
4047
  };
4032
4048
  var toggleButtonHandleBlur = function toggleButtonHandleBlur() {
4033
- if (latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
4049
+ if (stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
4034
4050
  dispatch({
4035
4051
  type: ToggleButtonBlur
4036
4052
  });
@@ -4049,8 +4065,8 @@
4049
4065
  };
4050
4066
  var toggleProps = _extends((_extends3 = {}, _extends3[refKey] = handleRefs(ref, function (toggleButtonNode) {
4051
4067
  toggleButtonRef.current = toggleButtonNode;
4052
- }), _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);
4053
- if (!disabled) {
4068
+ }), _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);
4069
+ if (!rest.disabled) {
4054
4070
  /* istanbul ignore if (react-native) */
4055
4071
  {
4056
4072
  Object.assign(toggleProps, {
@@ -4061,7 +4077,7 @@
4061
4077
  }
4062
4078
  setGetterPropCallInfo('getToggleButtonProps', suppressRefError, refKey, toggleButtonRef);
4063
4079
  return toggleProps;
4064
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers]);
4080
+ }, [dispatch, elementIds, isOpen, highlightedIndex, mouseAndTouchTrackers, setGetterPropCallInfo, toggleButtonKeyDownHandlers, stateRef]);
4065
4081
  var getItemProps = React.useCallback(function (itemProps) {
4066
4082
  var _extends4;
4067
4083
  var _ref6 = itemProps != null ? itemProps : {},
@@ -4079,15 +4095,12 @@
4079
4095
  if (disabledProp !== undefined) {
4080
4096
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useSelect.');
4081
4097
  }
4082
- var _latest$current = latest.current,
4083
- latestState = _latest$current.state,
4084
- latestProps = _latest$current.props;
4085
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
4098
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
4086
4099
  item = _getItemAndIndex[0],
4087
4100
  index = _getItemAndIndex[1];
4088
- var disabled = latestProps.isItemDisabled(item, index);
4101
+ var disabled = isItemDisabled(item, index);
4089
4102
  var itemHandleMouseMove = function itemHandleMouseMove() {
4090
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
4103
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
4091
4104
  return;
4092
4105
  }
4093
4106
  preventScroll();
@@ -4111,7 +4124,7 @@
4111
4124
  if (itemNode) {
4112
4125
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
4113
4126
  }
4114
- }), _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);
4127
+ }), _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);
4115
4128
  if (!disabled) {
4116
4129
  /* istanbul ignore next (react-native) */
4117
4130
  {
@@ -4121,7 +4134,7 @@
4121
4134
  }
4122
4135
  }
4123
4136
  return resultItemProps;
4124
- }, [latest, elementIds, mouseAndTouchTrackers, preventScroll, dispatch]);
4137
+ }, [items, isItemDisabled, selectedItem, elementIds, mouseAndTouchTrackers, preventScroll, dispatch, stateRef]);
4125
4138
 
4126
4139
  // Action functions.
4127
4140
  var toggleMenu = React.useCallback(function () {
@@ -4412,8 +4425,8 @@
4412
4425
 
4413
4426
  var _excluded$2 = ["onMouseLeave", "refKey", "ref", "aria-label"],
4414
4427
  _excluded2$2 = ["item", "index", "refKey", "ref", "onMouseMove", "onMouseDown", "onClick", "onPress", "disabled"],
4415
- _excluded3$1 = ["onClick", "onPress", "refKey", "ref", "disabled"],
4416
- _excluded4 = ["aria-label", "disabled", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
4428
+ _excluded3$1 = ["onClick", "onPress", "refKey", "ref"],
4429
+ _excluded4 = ["aria-label", "onKeyDown", "onChange", "onInput", "onBlur", "onChangeText", "onClick", "refKey", "ref"];
4417
4430
  useCombobox.stateChangeTypes = stateChangeTypes$2;
4418
4431
  function useCombobox(userProps) {
4419
4432
  if (userProps === void 0) {
@@ -4423,6 +4436,7 @@
4423
4436
  // Props defaults and destructuring.
4424
4437
  var props = _extends({}, dropdownDefaultProps, userProps);
4425
4438
  var items = props.items,
4439
+ isItemDisabled = props.isItemDisabled,
4426
4440
  scrollIntoView = props.scrollIntoView,
4427
4441
  environment = props.environment,
4428
4442
  getA11yStatusMessage = props.getA11yStatusMessage;
@@ -4446,17 +4460,18 @@
4446
4460
  var elementIds = useElementIds$1(props);
4447
4461
  // used to keep track of how many items we had on previous cycle.
4448
4462
  var previousResultCountRef = React.useRef();
4449
- // utility callback to get item element.
4450
- var latest = useLatestRef({
4451
- state: state,
4452
- props: props
4453
- });
4463
+ /**
4464
+ * Ref to read `state` in handlers to preserve referential identity.
4465
+ * Only to be used in handlers and effects.
4466
+ * **never access this in getters**
4467
+ */
4468
+ var stateRef = useLatestRef(state);
4454
4469
 
4455
4470
  // Effects.
4456
4471
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
4457
4472
  useA11yMessageStatus(getA11yStatusMessage, state, [isOpen, highlightedIndex, selectedItem, inputValue], environment);
4458
4473
  // Scroll on highlighted item if change comes from keyboard.
4459
- var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef.current, itemsRef.current, elementIds.getItemId);
4474
+ var preventScroll = useScrollIntoView(scrollIntoView, highlightedIndex, isOpen, menuRef, itemsRef, elementIds.getItemId);
4460
4475
  useControlPropsValidator({
4461
4476
  state: state,
4462
4477
  props: props
@@ -4475,12 +4490,12 @@
4475
4490
  }
4476
4491
  });
4477
4492
  var handleBlurInTracker = React.useCallback(function handleBlur() {
4478
- if (latest.current.state.isOpen) {
4493
+ if (stateRef.current.isOpen) {
4479
4494
  dispatch({
4480
4495
  type: InputBlur
4481
4496
  });
4482
4497
  }
4483
- }, [dispatch, latest]);
4498
+ }, [dispatch, stateRef]);
4484
4499
  var downshiftRefs = React.useMemo(function () {
4485
4500
  return [menuRef, toggleButtonRef, inputRef];
4486
4501
  }, []);
@@ -4521,7 +4536,7 @@
4521
4536
  });
4522
4537
  },
4523
4538
  Home: function Home(event) {
4524
- if (!latest.current.state.isOpen) {
4539
+ if (!stateRef.current.isOpen) {
4525
4540
  return;
4526
4541
  }
4527
4542
  event.preventDefault();
@@ -4530,7 +4545,7 @@
4530
4545
  });
4531
4546
  },
4532
4547
  End: function End(event) {
4533
- if (!latest.current.state.isOpen) {
4548
+ if (!stateRef.current.isOpen) {
4534
4549
  return;
4535
4550
  }
4536
4551
  event.preventDefault();
@@ -4539,7 +4554,7 @@
4539
4554
  });
4540
4555
  },
4541
4556
  Escape: function Escape(event) {
4542
- var latestState = latest.current.state;
4557
+ var latestState = stateRef.current;
4543
4558
  if (latestState.isOpen || latestState.inputValue || latestState.selectedItem || latestState.highlightedIndex > -1) {
4544
4559
  event.preventDefault();
4545
4560
  dispatch({
@@ -4548,7 +4563,7 @@
4548
4563
  }
4549
4564
  },
4550
4565
  Enter: function Enter(event) {
4551
- var latestState = latest.current.state;
4566
+ var latestState = stateRef.current;
4552
4567
  // if closed or no highlighted index, do nothing.
4553
4568
  if (!latestState.isOpen || event.which === 229 // if IME composing, wait for next Enter keydown event.
4554
4569
  ) {
@@ -4560,7 +4575,7 @@
4560
4575
  });
4561
4576
  },
4562
4577
  PageUp: function PageUp(event) {
4563
- if (latest.current.state.isOpen) {
4578
+ if (stateRef.current.isOpen) {
4564
4579
  event.preventDefault();
4565
4580
  dispatch({
4566
4581
  type: InputKeyDownPageUp
@@ -4568,7 +4583,7 @@
4568
4583
  }
4569
4584
  },
4570
4585
  PageDown: function PageDown(event) {
4571
- if (latest.current.state.isOpen) {
4586
+ if (stateRef.current.isOpen) {
4572
4587
  event.preventDefault();
4573
4588
  dispatch({
4574
4589
  type: InputKeyDownPageDown
@@ -4576,7 +4591,7 @@
4576
4591
  }
4577
4592
  }
4578
4593
  };
4579
- }, [dispatch, latest]);
4594
+ }, [dispatch, stateRef]);
4580
4595
 
4581
4596
  // Getter props.
4582
4597
  var getLabelProps = React.useCallback(function (labelProps) {
@@ -4623,17 +4638,14 @@
4623
4638
  if (disabledProp !== undefined) {
4624
4639
  console.warn('Passing "disabled" as an argument to getItemProps is not supported anymore. Please use the isItemDisabled prop from useCombobox.');
4625
4640
  }
4626
- var _latest$current = latest.current,
4627
- latestProps = _latest$current.props,
4628
- latestState = _latest$current.state;
4629
- var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, latestProps.items, 'Pass either item or index to getItemProps!'),
4641
+ var _getItemAndIndex = getItemAndIndex(itemProp, indexProp, items, 'Pass either item or index to getItemProps!'),
4630
4642
  item = _getItemAndIndex[0],
4631
4643
  index = _getItemAndIndex[1];
4632
- var disabled = latestProps.isItemDisabled(item, index);
4644
+ var disabled = isItemDisabled(item, index);
4633
4645
  var onSelectKey = 'onClick';
4634
4646
  var customClickHandler = onClick;
4635
4647
  var itemHandleMouseMove = function itemHandleMouseMove() {
4636
- if (mouseAndTouchTrackers.isTouchEnd || index === latestState.highlightedIndex) {
4648
+ if (mouseAndTouchTrackers.current.isTouchEnd || index === stateRef.current.highlightedIndex) {
4637
4649
  return;
4638
4650
  }
4639
4651
  preventScroll();
@@ -4657,11 +4669,11 @@
4657
4669
  if (itemNode) {
4658
4670
  itemsRef.current[elementIds.getItemId(index)] = itemNode;
4659
4671
  }
4660
- }), _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), {
4672
+ }), _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), {
4661
4673
  onMouseMove: callAllEventHandlers(onMouseMove, itemHandleMouseMove),
4662
4674
  onMouseDown: callAllEventHandlers(onMouseDown, itemHandleMouseDown)
4663
4675
  }, rest);
4664
- }, [dispatch, elementIds, latest, mouseAndTouchTrackers, preventScroll]);
4676
+ }, [dispatch, elementIds, items, isItemDisabled, highlightedIndex, mouseAndTouchTrackers, preventScroll, stateRef]);
4665
4677
  var getToggleButtonProps = React.useCallback(function (toggleButtonProps) {
4666
4678
  var _extends4;
4667
4679
  var _ref5 = toggleButtonProps != null ? toggleButtonProps : {},
@@ -4670,9 +4682,7 @@
4670
4682
  var _ref5$refKey = _ref5.refKey,
4671
4683
  refKey = _ref5$refKey === void 0 ? 'ref' : _ref5$refKey,
4672
4684
  ref = _ref5.ref,
4673
- disabled = _ref5.disabled,
4674
4685
  rest = _objectWithoutPropertiesLoose(_ref5, _excluded3$1);
4675
- var latestState = latest.current.state;
4676
4686
  var toggleButtonHandleClick = function toggleButtonHandleClick() {
4677
4687
  dispatch({
4678
4688
  type: ToggleButtonClick
@@ -4680,17 +4690,14 @@
4680
4690
  };
4681
4691
  return _extends((_extends4 = {}, _extends4[refKey] = handleRefs(ref, function (toggleButtonNode) {
4682
4692
  toggleButtonRef.current = toggleButtonNode;
4683
- }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = latestState.isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !disabled && _extends({}, {
4693
+ }), _extends4['aria-controls'] = elementIds.menuId, _extends4['aria-expanded'] = isOpen, _extends4.id = elementIds.toggleButtonId, _extends4.tabIndex = -1, _extends4), !rest.disabled && _extends({}, {
4684
4694
  onClick: callAllEventHandlers(onClick, toggleButtonHandleClick)
4685
- }), {
4686
- disabled: disabled
4687
- }, rest);
4688
- }, [dispatch, latest, elementIds]);
4695
+ }), rest);
4696
+ }, [dispatch, isOpen, elementIds]);
4689
4697
  var getInputProps = React.useCallback(function (inputProps, otherProps) {
4690
4698
  var _extends5;
4691
4699
  var _ref6 = inputProps != null ? inputProps : {},
4692
4700
  ariaLabel = _ref6['aria-label'],
4693
- disabled = _ref6.disabled,
4694
4701
  onKeyDown = _ref6.onKeyDown,
4695
4702
  onChange = _ref6.onChange,
4696
4703
  onInput = _ref6.onInput,
@@ -4705,7 +4712,6 @@
4705
4712
  _ref7$suppressRefErro = _ref7.suppressRefError,
4706
4713
  suppressRefError = _ref7$suppressRefErro === void 0 ? false : _ref7$suppressRefErro;
4707
4714
  setGetterPropCallInfo('getInputProps', suppressRefError, refKey, inputRef);
4708
- var latestState = latest.current.state;
4709
4715
  var inputHandleKeyDown = function inputHandleKeyDown(event) {
4710
4716
  var key = normalizeArrowKey(event);
4711
4717
  if (key && key in inputKeyDownHandlers) {
@@ -4721,7 +4727,7 @@
4721
4727
  };
4722
4728
  var inputHandleBlur = function inputHandleBlur(event) {
4723
4729
  /* istanbul ignore else */
4724
- if (environment != null && environment.document && latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
4730
+ if (environment != null && environment.document && stateRef.current.isOpen && !mouseAndTouchTrackers.current.isMouseDown) {
4725
4731
  var isBlurByTabChange = event.relatedTarget === null && environment.document.activeElement !== environment.document.body;
4726
4732
  dispatch({
4727
4733
  type: InputBlur,
@@ -4738,14 +4744,14 @@
4738
4744
  /* istanbul ignore next (preact) */
4739
4745
  var onChangeKey = 'onChange';
4740
4746
  var eventHandlers = {};
4741
- if (!disabled) {
4747
+ if (!rest.disabled) {
4742
4748
  var _eventHandlers;
4743
4749
  eventHandlers = (_eventHandlers = {}, _eventHandlers[onChangeKey] = callAllEventHandlers(onChange, onInput, inputHandleChange), _eventHandlers.onKeyDown = callAllEventHandlers(onKeyDown, inputHandleKeyDown), _eventHandlers.onBlur = callAllEventHandlers(onBlur, inputHandleBlur), _eventHandlers.onClick = callAllEventHandlers(onClick, inputHandleClick), _eventHandlers);
4744
4750
  }
4745
4751
  return _extends((_extends5 = {}, _extends5[refKey] = handleRefs(ref, function (inputNode) {
4746
4752
  inputRef.current = inputNode;
4747
- }), _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);
4748
- }, [dispatch, elementIds, environment, inputKeyDownHandlers, latest, mouseAndTouchTrackers, setGetterPropCallInfo]);
4753
+ }), _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);
4754
+ }, [dispatch, elementIds, environment, inputKeyDownHandlers, isOpen, highlightedIndex, inputValue, mouseAndTouchTrackers, setGetterPropCallInfo, stateRef]);
4749
4755
 
4750
4756
  // returns
4751
4757
  var toggleMenu = React.useCallback(function () {
@@ -5067,12 +5073,13 @@
5067
5073
  // Refs.
5068
5074
  var isInitialMount = useIsInitialMount();
5069
5075
  var dropdownRef = React.useRef(null);
5070
- var selectedItemRefs = React.useRef();
5071
- selectedItemRefs.current = [];
5072
- var latest = useLatestRef({
5073
- state: state,
5074
- props: props
5075
- });
5076
+ // Map of selected-item index -> DOM node. Populated by the ref callback in
5077
+ // getSelectedItemProps and read by the focus effect. Keyed by
5078
+ // index so we never reset it during render.
5079
+ var selectedItemRefs = React.useRef(null);
5080
+ if (selectedItemRefs.current === null) {
5081
+ selectedItemRefs.current = new Map();
5082
+ }
5076
5083
 
5077
5084
  // Effects.
5078
5085
  // Adds an a11y aria live status message if getA11yStatusMessage is passed.
@@ -5084,8 +5091,9 @@
5084
5091
  }
5085
5092
  if (activeIndex === -1 && dropdownRef.current) {
5086
5093
  dropdownRef.current.focus();
5087
- } else if (selectedItemRefs.current[activeIndex]) {
5088
- selectedItemRefs.current[activeIndex].focus();
5094
+ } else {
5095
+ var _selectedItemRefs$cur;
5096
+ (_selectedItemRefs$cur = selectedItemRefs.current.get(activeIndex)) == null || _selectedItemRefs$cur.focus();
5089
5097
  }
5090
5098
  // eslint-disable-next-line react-hooks/exhaustive-deps
5091
5099
  }, [activeIndex]);
@@ -5145,10 +5153,9 @@
5145
5153
  selectedItemProp = _ref3.selectedItem,
5146
5154
  indexProp = _ref3.index,
5147
5155
  rest = _objectWithoutPropertiesLoose(_ref3, _excluded$1);
5148
- var latestState = latest.current.state;
5149
- var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, latestState.selectedItems, 'Pass either item or index to getSelectedItemProps!'),
5156
+ var _getItemAndIndex = getItemAndIndex(selectedItemProp, indexProp, selectedItems, 'Pass either item or index to getSelectedItemProps!'),
5150
5157
  index = _getItemAndIndex[1];
5151
- var isFocusable = index > -1 && index === latestState.activeIndex;
5158
+ var isFocusable = index > -1 && index === activeIndex;
5152
5159
  var selectedItemHandleClick = function selectedItemHandleClick() {
5153
5160
  dispatch({
5154
5161
  type: SelectedItemClick,
@@ -5163,10 +5170,12 @@
5163
5170
  };
5164
5171
  return _extends((_extends2 = {}, _extends2[refKey] = handleRefs(ref, function (selectedItemNode) {
5165
5172
  if (selectedItemNode) {
5166
- selectedItemRefs.current.push(selectedItemNode);
5173
+ selectedItemRefs.current.set(index, selectedItemNode);
5174
+ } else {
5175
+ selectedItemRefs.current["delete"](index);
5167
5176
  }
5168
5177
  }), _extends2.tabIndex = isFocusable ? 0 : -1, _extends2.onClick = callAllEventHandlers(onClick, selectedItemHandleClick), _extends2.onKeyDown = callAllEventHandlers(onKeyDown, selectedItemHandleKeyDown), _extends2), rest);
5169
- }, [dispatch, latest, selectedItemKeyDownHandlers]);
5178
+ }, [dispatch, selectedItems, activeIndex, selectedItemKeyDownHandlers]);
5170
5179
  var getDropdownProps = React.useCallback(function (_temp2, _temp3) {
5171
5180
  var _extends3;
5172
5181
  var _ref4 = _temp2 === void 0 ? {} : _temp2,
@@ -5354,8 +5363,10 @@
5354
5363
  var id = _ref2.id,
5355
5364
  getTagId = _ref2.getTagId,
5356
5365
  tagGroupId = _ref2.tagGroupId;
5357
- var baseIdRef = React__namespace.useRef(id != null ? id : "downshift-" + generateId());
5358
- var baseId = baseIdRef.current;
5366
+ var _React$useState = React__namespace.useState(function () {
5367
+ return id != null ? id : "downshift-" + generateId();
5368
+ }),
5369
+ baseId = _React$useState[0];
5359
5370
  var elementIds = React__namespace.useMemo(function () {
5360
5371
  return {
5361
5372
  tagGroupId: tagGroupId != null ? tagGroupId : baseId + "-tag-group",
@@ -5452,10 +5463,6 @@
5452
5463
 
5453
5464
  /* Refs */
5454
5465
 
5455
- var latest = useLatestRef({
5456
- state: state,
5457
- props: props
5458
- });
5459
5466
  var elementIds = useElementIds({
5460
5467
  getTagId: props.getTagId,
5461
5468
  id: props.id,
@@ -5518,7 +5525,6 @@
5518
5525
  if (!Number.isInteger(index) || index < 0) {
5519
5526
  throw new Error('Pass correct item index to getTagProps!');
5520
5527
  }
5521
- var latestState = latest.current.state;
5522
5528
  var handleClick = function handleClick() {
5523
5529
  dispatch({
5524
5530
  type: TagClick,
@@ -5532,8 +5538,8 @@
5532
5538
  if (itemNode) {
5533
5539
  itemRefs.current[tagId] = itemNode;
5534
5540
  }
5535
- }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = latestState.activeIndex === index ? 0 : -1, _extends2), rest);
5536
- }, [dispatch, elementIds, latest, itemRefs]);
5541
+ }), _extends2['aria-labelledby'] = tagId, _extends2.role = 'option', _extends2.id = tagId, _extends2.onClick = callAllEventHandlers(onClick, handleClick), _extends2.tabIndex = activeIndex === index ? 0 : -1, _extends2), rest);
5542
+ }, [dispatch, elementIds, activeIndex, itemRefs]);
5537
5543
  var getTagRemoveProps = React.useCallback(function (options) {
5538
5544
  var index = options.index,
5539
5545
  onClick = options.onClick,