downshift 6.0.2 → 6.0.6

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <h1 align="center">
2
2
  downshift 🏎
3
3
  <br>
4
- <img src="https://downshift-js.com/logo/downshift.svg" alt="downshift logo" title="downshift logo" width="300">
4
+ <img src="https://github.com/downshift-js/downshift/blob/master/other/public/logo/downshift.svg" alt="downshift logo" title="downshift logo" width="300">
5
5
  <br>
6
6
  </h1>
7
7
  <p align="center" style="font-size: 1.2rem;">Primitives to build simple, flexible, WAI-ARIA compliant React
@@ -38,29 +38,33 @@ use case.
38
38
 
39
39
  ## This solution
40
40
 
41
- This library provides its users two main sets of solutions: the `Downshift`
42
- component and a set of hooks. The component is still the main part of the
43
- library, providing the autocomplete/combobox logic as a render prop. The hooks
44
- are newer and are going to be the way forward to providing accessibile
45
- experiences. Right now we support `useSelect` for `select` components,
46
- `useCombobox` for `combobox/autocomplete` inputs and `useMultipleSelection` to
47
- make the selection of multiple items easier for both the `select` and `combobox`
48
- elements.
49
-
50
- Since both `useCombobox` and the component `Downshift` aim to provide
51
- accessibility to a combobox, we suggest trying the new `useCombobox` first. If
52
- you feel that `Downshift` still covers your use cases better then use the
53
- component instead. However, if these use cases should also be covered in
54
- `useCombobox`, make sure to create an issue to help us improve the hook. Both
55
- the hooks and the component are actively maintained but we are cool kids from
56
- the future and prefer to share `React` logic via hooks.
41
+ The library offers a couple of solutions. The first solution, which is the one
42
+ we recommend you to try first, is a set of React hooks. Each hook provides the
43
+ stateful logic needed to make the corresponding component functional and
44
+ accessible. Navigate to the documentation for each by using the links in the
45
+ list below.
46
+
47
+ - [useSelect][useselect-readme] for a custom select component.
48
+ - [useCombobox][combobox-readme] for a combobox/autocomplete input.
49
+ - [useMultipleSelection][multiple-selection-readme] for selecting multiple items
50
+ in a select or a combobox, as well as deleting items from selection or
51
+ navigating between the selected items.
52
+
53
+ The second solution is the `Downshift` component, which can also be used to
54
+ create accessible combobox and select components, providing the logic in the
55
+ form of a render prop. It served as inspiration for developing the hooks and it
56
+ has been around for a while. It established a successful pattern for making
57
+ components accessible and functional while giving developers complete freedom
58
+ when building the UI.
57
59
 
58
60
  The `README` on this page convers only the component while each hook has its own
59
- `README` file. Check the [hooks section](#the-react-hooks-api) for links to
60
- each. Both the component and the hooks are similar in many concepts so you can
61
- always switch between the documentation pages in order to find information.
61
+ `README` page. You can navigate to the [hooks page][hooks-readme] or go directly
62
+ to the hook you need by using the links in the list above.
62
63
 
63
- ### Downshift component
64
+ For examples on how to use the hooks or the Downshift component, check out our
65
+ [docsite][docsite]!
66
+
67
+ ### Downshift
64
68
 
65
69
  This is a component that controls user interactions and state for you so you can
66
70
  create autocomplete/combobox or select dropdown components. It uses a [render
@@ -76,33 +80,6 @@ harder to contribute to.
76
80
  > NOTE: The original use case of this component is autocomplete, however the API
77
81
  > is powerful and flexible enough to build things like dropdowns as well.
78
82
 
79
- ### The React Hooks API
80
-
81
- `Downshift` proved to be a versatile component to create not only combobox
82
- inputs, but also custom select elements and even multiple selection experiences.
83
- However, additional code was needed to make these experiences fully accessible,
84
- so we decided to create dedicated React hooks for them. Each hook handles a
85
- specific dropdown variation and aims to make it fully accessible.
86
-
87
- You can check the progress in the [hooks page][hooks-readme]. The hooks
88
- published so far are:
89
-
90
- - [useSelect][useselect-readme] for a custom select dropdown.
91
- - [useCombobox][combobox-readme] for a combobox/autocomplete input.
92
- - [useMultipleSelection][multiple-selection-readme] for allowing the selection
93
- of multiple items in a select or a combobox.
94
-
95
- For examples on how to use the hooks check out our [docsite][docsite]!
96
-
97
- ### Bundle size concerns
98
-
99
- Adding the hooks into this repo increased the bundle size considerably. However,
100
- since we create the bundle with `Rollup` and export both `<Downshift>` and the
101
- hooks as modules, you should be able to have the library treeshaked (pruned) and
102
- receive only the code you need. Since version `3.4.8`
103
- [BundlePhobia][bundle-phobia-link] marked `Downshift` as both `tree-shakeable`
104
- and `side-effect free`.
105
-
106
83
  ## Table of Contents
107
84
 
108
85
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@@ -183,7 +160,7 @@ npm install --save downshift
183
160
 
184
161
  ## Usage
185
162
 
186
- > [Try it out in the browser](https://codesandbox.io/s/simple-downshift-with-getrootprops-example-24s13)
163
+ > [Try it out in the browser][code-sandbox-try-it-out]
187
164
 
188
165
  ```jsx
189
166
  import * as React from 'react'
@@ -253,8 +230,7 @@ render(
253
230
  )
254
231
  ```
255
232
 
256
- The previous example without `getRootProps` is
257
- [here](https://codesandbox.io/s/n9095).
233
+ There is also an [example without getRootProps][code-sandbox-no-get-root-props].
258
234
 
259
235
  > Warning: The example without `getRootProps` is not fully accessible with
260
236
  > screen readers as it's not possible to achieve the HTML structure suggested by
@@ -1090,14 +1066,21 @@ platforms:
1090
1066
  ## Examples
1091
1067
 
1092
1068
  > 🚨 We're in the process of moving all examples to the
1093
- > [downshift-examples](https://github.com/kentcdodds/downshift-examples) repo
1069
+ > [downshift-examples](https://github.com/downshift-js/downshift-examples) repo
1094
1070
  > (which you can open, interact with, and contribute back to live on
1095
1071
  > [codesandbox](https://codesandbox.io/s/github/kentcdodds/downshift-examples))
1096
1072
 
1073
+ > 🚨 We're also in the process of updating our examples from the
1074
+ > [downshift-docs](https://github.com/downshift-js/downshift-docs) repo which is
1075
+ > actually used to create our docsite at [downshit-js.com][docsite]). Make sure to
1076
+ > check it out for the most relevant Downshift examples or try out the new hooks
1077
+ > that aim to replace Downshift.
1078
+
1097
1079
  **Ordered Examples:**
1098
1080
 
1099
1081
  If you're just learning downshift, review these in order:
1100
1082
 
1083
+ 0. [basic automplete with getRootProps](https://codesandbox.io/s/github/kentcdodds/downshift-examples?file=/src/downshift/ordered-examples/00-get-root-props-example.js) - the same as example #1 but using the correct HTML structure as suggested by ARIA-WCAG.
1101
1084
  1. [basic autocomplete](https://codesandbox.io/s/github/kentcdodds/downshift-examples/tree/master/?module=%2Fsrc%2Fordered-examples%2F01-basic-autocomplete.js&moduleview=1) -
1102
1085
  very bare bones, not styled at all. Good place to start.
1103
1086
  2. [styled autocomplete](https://codesandbox.io/s/github/kentcdodds/downshift-examples/tree/master/?module=%2Fsrc%2Fordered-examples%2F02-complete-autocomplete.js&moduleview=1) -
@@ -1110,14 +1093,6 @@ If you're just learning downshift, review these in order:
1110
1093
  Shows how to create a MultiDownshift component that allows for an array of
1111
1094
  selectedItems for multiple selection using a state reducer
1112
1095
 
1113
- **Hooks Examples:**
1114
-
1115
- We are also in the process of updating our [docsite][docsite] and we aim to add
1116
- the examples that illustrate the use of hooks there. This is a great opportunity
1117
- to contribute, by converting existing `Downshift` examples to either `useSelect`
1118
- or `useCombobox` or by adding any other scenarios that you feel are
1119
- representative.
1120
-
1121
1096
  **Other Examples:**
1122
1097
 
1123
1098
  Check out these examples of more advanced use/edge cases:
@@ -1237,7 +1212,7 @@ You can implement these other solutions using `downshift`, but if you'd prefer
1237
1212
  to use these out of the box solutions, then that's fine too:
1238
1213
 
1239
1214
  - [`react-select`](https://github.com/JedWatson/react-select)
1240
- - [`react-autocomplete`](https://github.com/reactjs/react-autocomplete)
1215
+ - [`react-autosuggest`](https://github.com/moroshko/react-autosuggest)
1241
1216
 
1242
1217
  ## Bindings for ReasonML
1243
1218
 
@@ -1422,6 +1397,10 @@ Thanks goes to these people ([emoji key][emojis]):
1422
1397
  <td align="center"><a href="http://dataart.com"><img src="https://avatars1.githubusercontent.com/u/5685800?v=4" width="100px;" alt=""/><br /><sub><b>Sergey Skrynnikov</b></sub></a><br /><a href="https://github.com/downshift-js/downshift/commits?author=IwalkAlone" title="Code">💻</a> <a href="https://github.com/downshift-js/downshift/commits?author=IwalkAlone" title="Tests">⚠️</a></td>
1423
1398
  <td align="center"><a href="https://www.linkedin.com/in/vvoyer"><img src="https://avatars0.githubusercontent.com/u/123822?v=4" width="100px;" alt=""/><br /><sub><b>Vincent Voyer</b></sub></a><br /><a href="https://github.com/downshift-js/downshift/commits?author=vvo" title="Documentation">📖</a></td>
1424
1399
  <td align="center"><a href="https://github.com/limejoe"><img src="https://avatars2.githubusercontent.com/u/7977551?v=4" width="100px;" alt=""/><br /><sub><b>limejoe</b></sub></a><br /><a href="https://github.com/downshift-js/downshift/commits?author=limejoe" title="Code">💻</a> <a href="https://github.com/downshift-js/downshift/issues?q=author%3Alimejoe" title="Bug reports">🐛</a></td>
1400
+ <td align="center"><a href="https://github.com/k88manish"><img src="https://avatars2.githubusercontent.com/u/19614770?v=4" width="100px;" alt=""/><br /><sub><b>Manish Kumar</b></sub></a><br /><a href="https://github.com/downshift-js/downshift/commits?author=k88manish" title="Code">💻</a></td>
1401
+ </tr>
1402
+ <tr>
1403
+ <td align="center"><a href="https://github.com/fcrezza"><img src="https://avatars2.githubusercontent.com/u/48123020?v=4" width="100px;" alt=""/><br /><sub><b>Anang Fachreza</b></sub></a><br /><a href="https://github.com/downshift-js/downshift/commits?author=fcrezza" title="Documentation">📖</a> <a href="#example-fcrezza" title="Examples">💡</a></td>
1425
1404
  </tr>
1426
1405
  </table>
1427
1406
 
@@ -1503,3 +1482,7 @@ MIT
1503
1482
  [select-aria]:
1504
1483
  https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
1505
1484
  [docsite]: https://downshift-js.com/
1485
+ [code-sandbox-try-it-out]:
1486
+ https://codesandbox.io/s/github/kentcdodds/downshift-examples?file=/src/downshift/ordered-examples/00-get-root-props-example.js
1487
+ [code-sandbox-no-get-root-props]:
1488
+ https://codesandbox.io/s/github/kentcdodds/downshift-examples?file=/src/downshift/ordered-examples/01-basic-autocomplete.js
@@ -2088,6 +2088,69 @@ function useGetterPropsCalledChecker() {
2088
2088
  }, []);
2089
2089
  return setGetterPropCallInfo;
2090
2090
  }
2091
+ function useA11yMessageSetter(getA11yMessage, dependencyArray, _ref2) {
2092
+ var isInitialMount = _ref2.isInitialMount,
2093
+ previousResultCount = _ref2.previousResultCount,
2094
+ highlightedIndex = _ref2.highlightedIndex,
2095
+ items = _ref2.items,
2096
+ environment = _ref2.environment,
2097
+ rest = _objectWithoutPropertiesLoose(_ref2, ["isInitialMount", "previousResultCount", "highlightedIndex", "items", "environment"]);
2098
+
2099
+ // Sets a11y status message on changes in state.
2100
+ react.useEffect(function () {
2101
+ if (isInitialMount) {
2102
+ return;
2103
+ }
2104
+
2105
+ updateA11yStatus(function () {
2106
+ return getA11yMessage(_extends({
2107
+ highlightedIndex: highlightedIndex,
2108
+ highlightedItem: items[highlightedIndex],
2109
+ resultCount: items.length,
2110
+ previousResultCount: previousResultCount
2111
+ }, rest));
2112
+ }, environment.document); // eslint-disable-next-line react-hooks/exhaustive-deps
2113
+ }, dependencyArray);
2114
+ }
2115
+ function useScrollIntoView(_ref3) {
2116
+ var highlightedIndex = _ref3.highlightedIndex,
2117
+ isOpen = _ref3.isOpen,
2118
+ itemRefs = _ref3.itemRefs,
2119
+ getItemNodeFromIndex = _ref3.getItemNodeFromIndex,
2120
+ menuElement = _ref3.menuElement,
2121
+ scrollIntoViewProp = _ref3.scrollIntoView;
2122
+ // used not to scroll on highlight by mouse.
2123
+ var shouldScrollRef = react.useRef(true); // Scroll on highlighted item if change comes from keyboard.
2124
+
2125
+ react.useEffect(function () {
2126
+ if (highlightedIndex < 0 || !isOpen || !Object.keys(itemRefs.current).length) {
2127
+ return;
2128
+ }
2129
+
2130
+ if (shouldScrollRef.current === false) {
2131
+ shouldScrollRef.current = true;
2132
+ } else {
2133
+ scrollIntoViewProp(getItemNodeFromIndex(highlightedIndex), menuElement);
2134
+ } // eslint-disable-next-line react-hooks/exhaustive-deps
2135
+
2136
+ }, [highlightedIndex]);
2137
+ return shouldScrollRef;
2138
+ }
2139
+ function useControlPropsValidator(_ref4) {
2140
+ var isInitialMount = _ref4.isInitialMount,
2141
+ props = _ref4.props,
2142
+ state = _ref4.state;
2143
+ // used for checking when props are moving from controlled to uncontrolled.
2144
+ var prevPropsRef = react.useRef(props);
2145
+ react.useEffect(function () {
2146
+ if (isInitialMount) {
2147
+ return;
2148
+ }
2149
+
2150
+ validateControlledUnchanged(state, prevPropsRef.current, props);
2151
+ prevPropsRef.current = props;
2152
+ }, [state, props, isInitialMount]);
2153
+ }
2091
2154
 
2092
2155
  function getItemIndexByCharacterKey(keysSoFar, highlightedIndex, items, itemToStringParam, getItemNodeFromIndex) {
2093
2156
  var lowerCasedItemStrings = items.map(function (item) {
@@ -2454,9 +2517,7 @@ function useSelect(userProps) {
2454
2517
  var toggleButtonRef = react.useRef(null);
2455
2518
  var menuRef = react.useRef(null);
2456
2519
  var itemRefs = react.useRef();
2457
- itemRefs.current = {}; // used not to scroll when highlight by mouse.
2458
-
2459
- var shouldScrollRef = react.useRef(true); // used not to trigger menu blur action in some scenarios.
2520
+ itemRefs.current = {}; // used not to trigger menu blur action in some scenarios.
2460
2521
 
2461
2522
  var shouldBlurRef = react.useRef(true); // used to keep the inputValue clearTimeout object between renders.
2462
2523
 
@@ -2465,9 +2526,7 @@ function useSelect(userProps) {
2465
2526
  var elementIdsRef = react.useRef(getElementIds(props)); // used to keep track of how many items we had on previous cycle.
2466
2527
 
2467
2528
  var previousResultCountRef = react.useRef();
2468
- var isInitialMountRef = react.useRef(true); // used for checking when props are moving from controlled to uncontrolled.
2469
-
2470
- var prevPropsRef = react.useRef(props); // utility callback to get item element.
2529
+ var isInitialMountRef = react.useRef(true); // utility callback to get item element.
2471
2530
 
2472
2531
  var latest = useLatestRef({
2473
2532
  state: state,
@@ -2480,45 +2539,30 @@ function useSelect(userProps) {
2480
2539
  // Sets a11y status message on changes in state.
2481
2540
 
2482
2541
 
2483
- react.useEffect(function () {
2484
- if (isInitialMountRef.current) {
2485
- return;
2486
- }
2487
-
2488
- var previousResultCount = previousResultCountRef.current;
2489
- updateA11yStatus(function () {
2490
- return getA11yStatusMessage({
2491
- isOpen: isOpen,
2492
- highlightedIndex: highlightedIndex,
2493
- selectedItem: selectedItem,
2494
- inputValue: inputValue,
2495
- highlightedItem: items[highlightedIndex],
2496
- resultCount: items.length,
2497
- itemToString: itemToString,
2498
- previousResultCount: previousResultCount
2499
- });
2500
- }, environment.document); // eslint-disable-next-line react-hooks/exhaustive-deps
2501
- }, [isOpen, highlightedIndex, inputValue, items]); // Sets a11y status message on changes in selectedItem.
2502
-
2503
- react.useEffect(function () {
2504
- if (isInitialMountRef.current) {
2505
- return;
2506
- }
2507
-
2508
- var previousResultCount = previousResultCountRef.current;
2509
- updateA11yStatus(function () {
2510
- return getA11ySelectionMessage({
2511
- isOpen: isOpen,
2512
- highlightedIndex: highlightedIndex,
2513
- selectedItem: selectedItem,
2514
- inputValue: inputValue,
2515
- highlightedItem: items[highlightedIndex],
2516
- resultCount: items.length,
2517
- itemToString: itemToString,
2518
- previousResultCount: previousResultCount
2519
- });
2520
- }, environment.document); // eslint-disable-next-line react-hooks/exhaustive-deps
2521
- }, [selectedItem]); // Sets cleanup for the keysSoFar after 500ms.
2542
+ useA11yMessageSetter(getA11yStatusMessage, [isOpen, highlightedIndex, inputValue, items], _extends({
2543
+ isInitialMount: isInitialMountRef.current,
2544
+ previousResultCount: previousResultCountRef.current,
2545
+ items: items,
2546
+ environment: environment,
2547
+ itemToString: itemToString
2548
+ }, state)); // Sets a11y status message on changes in selectedItem.
2549
+
2550
+ useA11yMessageSetter(getA11ySelectionMessage, [selectedItem], _extends({
2551
+ isInitialMount: isInitialMountRef.current,
2552
+ previousResultCount: previousResultCountRef.current,
2553
+ items: items,
2554
+ environment: environment,
2555
+ itemToString: itemToString
2556
+ }, state)); // Scroll on highlighted item if change comes from keyboard.
2557
+
2558
+ var shouldScrollRef = useScrollIntoView({
2559
+ menuElement: menuRef.current,
2560
+ highlightedIndex: highlightedIndex,
2561
+ isOpen: isOpen,
2562
+ itemRefs: itemRefs,
2563
+ scrollIntoView: scrollIntoView,
2564
+ getItemNodeFromIndex: getItemNodeFromIndex
2565
+ }); // Sets cleanup for the keysSoFar after 500ms.
2522
2566
 
2523
2567
  react.useEffect(function () {
2524
2568
  // init the clean function here as we need access to dispatch.
@@ -2535,8 +2579,13 @@ function useSelect(userProps) {
2535
2579
  return;
2536
2580
  }
2537
2581
 
2538
- clearTimeoutRef.current(dispatch); // eslint-disable-next-line react-hooks/exhaustive-deps
2539
- }, [inputValue]);
2582
+ clearTimeoutRef.current(dispatch);
2583
+ }, [dispatch, inputValue]);
2584
+ useControlPropsValidator({
2585
+ isInitialMount: isInitialMountRef.current,
2586
+ props: props,
2587
+ state: state
2588
+ });
2540
2589
  /* Controls the focus on the menu or the toggle button. */
2541
2590
 
2542
2591
  react.useEffect(function () {
@@ -2569,35 +2618,14 @@ function useSelect(userProps) {
2569
2618
  }
2570
2619
  } // eslint-disable-next-line react-hooks/exhaustive-deps
2571
2620
 
2572
- }, [isOpen]); // Scroll on highlighted item if change comes from keyboard.
2573
-
2574
- react.useEffect(function () {
2575
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemRefs.current).length) {
2576
- return;
2577
- }
2578
-
2579
- if (shouldScrollRef.current === false) {
2580
- shouldScrollRef.current = true;
2581
- } else {
2582
- scrollIntoView(getItemNodeFromIndex(highlightedIndex), menuRef.current);
2583
- } // eslint-disable-next-line react-hooks/exhaustive-deps
2584
-
2585
- }, [highlightedIndex]);
2621
+ }, [isOpen]);
2586
2622
  react.useEffect(function () {
2587
2623
  if (isInitialMountRef.current) {
2588
2624
  return;
2589
2625
  }
2590
2626
 
2591
2627
  previousResultCountRef.current = items.length;
2592
- });
2593
- react.useEffect(function () {
2594
- if (isInitialMountRef.current) {
2595
- return;
2596
- }
2597
-
2598
- validateControlledUnchanged(state, prevPropsRef.current, props);
2599
- prevPropsRef.current = props;
2600
- }, [state, props]); // Add mouse/touch events to document.
2628
+ }); // Add mouse/touch events to document.
2601
2629
 
2602
2630
  var mouseAndTouchTrackersRef = useMouseAndTouchTracker(isOpen, [menuRef, toggleButtonRef], environment, function () {
2603
2631
  dispatch({
@@ -2891,7 +2919,7 @@ function useSelect(userProps) {
2891
2919
  }
2892
2920
 
2893
2921
  return itemProps;
2894
- }, [dispatch, latest]);
2922
+ }, [dispatch, latest, shouldScrollRef]);
2895
2923
  return {
2896
2924
  // prop getters.
2897
2925
  getToggleButtonProps: getToggleButtonProps,
@@ -3117,7 +3145,7 @@ function downshiftUseComboboxReducer(state, action) {
3117
3145
  break;
3118
3146
 
3119
3147
  case InputKeyDownEnter:
3120
- changes = _extends({}, state.highlightedIndex >= 0 && {
3148
+ changes = _extends({}, state.isOpen && state.highlightedIndex >= 0 && {
3121
3149
  selectedItem: props.items[state.highlightedIndex],
3122
3150
  isOpen: getDefaultValue(props, 'isOpen'),
3123
3151
  highlightedIndex: getDefaultValue(props, 'highlightedIndex'),
@@ -3136,25 +3164,28 @@ function downshiftUseComboboxReducer(state, action) {
3136
3164
  break;
3137
3165
 
3138
3166
  case InputKeyDownHome:
3139
- changes = {
3167
+ changes = _extends({}, state.isOpen && {
3140
3168
  highlightedIndex: getNextNonDisabledIndex(1, 0, props.items.length, action.getItemNodeFromIndex, false)
3141
- };
3169
+ });
3142
3170
  break;
3143
3171
 
3144
3172
  case InputKeyDownEnd:
3145
- changes = {
3173
+ changes = _extends({}, state.isOpen && {
3146
3174
  highlightedIndex: getNextNonDisabledIndex(-1, props.items.length - 1, props.items.length, action.getItemNodeFromIndex, false)
3147
- };
3175
+ });
3148
3176
  break;
3149
3177
 
3150
3178
  case InputBlur:
3151
- changes = _extends({
3152
- isOpen: false,
3153
- highlightedIndex: -1
3154
- }, state.highlightedIndex >= 0 && action.selectItem && {
3155
- selectedItem: props.items[state.highlightedIndex],
3156
- inputValue: props.itemToString(props.items[state.highlightedIndex])
3157
- });
3179
+ if (state.isOpen) {
3180
+ changes = _extends({
3181
+ isOpen: false,
3182
+ highlightedIndex: -1
3183
+ }, state.highlightedIndex >= 0 && action.selectItem && {
3184
+ selectedItem: props.items[state.highlightedIndex],
3185
+ inputValue: props.itemToString(props.items[state.highlightedIndex])
3186
+ });
3187
+ }
3188
+
3158
3189
  break;
3159
3190
 
3160
3191
  case InputChange:
@@ -3272,16 +3303,12 @@ function useCombobox(userProps) {
3272
3303
  var inputRef = react.useRef(null);
3273
3304
  var toggleButtonRef = react.useRef(null);
3274
3305
  var comboboxRef = react.useRef(null);
3275
- itemRefs.current = {}; // used not to scroll on highlight by mouse.
3276
-
3277
- var shouldScrollRef = react.useRef(true);
3306
+ itemRefs.current = {};
3278
3307
  var isInitialMountRef = react.useRef(true); // prevent id re-generation between renders.
3279
3308
 
3280
3309
  var elementIdsRef = react.useRef(getElementIds$1(props)); // used to keep track of how many items we had on previous cycle.
3281
3310
 
3282
- var previousResultCountRef = react.useRef(); // used for checking when props are moving from controlled to uncontrolled.
3283
-
3284
- var prevPropsRef = react.useRef(props); // utility callback to get item element.
3311
+ var previousResultCountRef = react.useRef(); // utility callback to get item element.
3285
3312
 
3286
3313
  var latest = useLatestRef({
3287
3314
  state: state,
@@ -3294,58 +3321,35 @@ function useCombobox(userProps) {
3294
3321
  // Sets a11y status message on changes in state.
3295
3322
 
3296
3323
 
3297
- react.useEffect(function () {
3298
- if (isInitialMountRef.current) {
3299
- return;
3300
- }
3301
-
3302
- var previousResultCount = previousResultCountRef.current;
3303
- updateA11yStatus(function () {
3304
- return getA11yStatusMessage({
3305
- isOpen: isOpen,
3306
- highlightedIndex: highlightedIndex,
3307
- selectedItem: selectedItem,
3308
- inputValue: inputValue,
3309
- highlightedItem: items[highlightedIndex],
3310
- resultCount: items.length,
3311
- itemToString: itemToString,
3312
- previousResultCount: previousResultCount
3313
- });
3314
- }, environment.document); // eslint-disable-next-line react-hooks/exhaustive-deps
3315
- }, [isOpen, highlightedIndex, inputValue, items]); // Sets a11y status message on changes in selectedItem.
3316
-
3317
- react.useEffect(function () {
3318
- if (isInitialMountRef.current) {
3319
- return;
3320
- }
3321
-
3322
- var previousResultCount = previousResultCountRef.current;
3323
- updateA11yStatus(function () {
3324
- return getA11ySelectionMessage({
3325
- isOpen: isOpen,
3326
- highlightedIndex: highlightedIndex,
3327
- selectedItem: selectedItem,
3328
- inputValue: inputValue,
3329
- highlightedItem: items[highlightedIndex],
3330
- resultCount: items.length,
3331
- itemToString: itemToString,
3332
- previousResultCount: previousResultCount
3333
- });
3334
- }, environment.document); // eslint-disable-next-line react-hooks/exhaustive-deps
3335
- }, [selectedItem]); // Scroll on highlighted item if change comes from keyboard.
3336
-
3337
- react.useEffect(function () {
3338
- if (highlightedIndex < 0 || !isOpen || !Object.keys(itemRefs.current).length) {
3339
- return;
3340
- }
3341
-
3342
- if (shouldScrollRef.current === false) {
3343
- shouldScrollRef.current = true;
3344
- } else {
3345
- scrollIntoView(getItemNodeFromIndex(highlightedIndex), menuRef.current);
3346
- } // eslint-disable-next-line react-hooks/exhaustive-deps
3347
-
3348
- }, [highlightedIndex]); // Controls the focus on the menu or the toggle button.
3324
+ useA11yMessageSetter(getA11yStatusMessage, [isOpen, highlightedIndex, inputValue, items], _extends({
3325
+ isInitialMount: isInitialMountRef.current,
3326
+ previousResultCount: previousResultCountRef.current,
3327
+ items: items,
3328
+ environment: environment,
3329
+ itemToString: itemToString
3330
+ }, state)); // Sets a11y status message on changes in selectedItem.
3331
+
3332
+ useA11yMessageSetter(getA11ySelectionMessage, [selectedItem], _extends({
3333
+ isInitialMount: isInitialMountRef.current,
3334
+ previousResultCount: previousResultCountRef.current,
3335
+ items: items,
3336
+ environment: environment,
3337
+ itemToString: itemToString
3338
+ }, state)); // Scroll on highlighted item if change comes from keyboard.
3339
+
3340
+ var shouldScrollRef = useScrollIntoView({
3341
+ menuElement: menuRef.current,
3342
+ highlightedIndex: highlightedIndex,
3343
+ isOpen: isOpen,
3344
+ itemRefs: itemRefs,
3345
+ scrollIntoView: scrollIntoView,
3346
+ getItemNodeFromIndex: getItemNodeFromIndex
3347
+ });
3348
+ useControlPropsValidator({
3349
+ isInitialMount: isInitialMountRef.current,
3350
+ props: props,
3351
+ state: state
3352
+ }); // Controls the focus on the input on open.
3349
3353
 
3350
3354
  react.useEffect(function () {
3351
3355
  // Don't focus menu on first render.
@@ -3365,15 +3369,7 @@ function useCombobox(userProps) {
3365
3369
  }
3366
3370
 
3367
3371
  previousResultCountRef.current = items.length;
3368
- });
3369
- react.useEffect(function () {
3370
- if (isInitialMountRef.current) {
3371
- return;
3372
- }
3373
-
3374
- validateControlledUnchanged(state, prevPropsRef.current, props);
3375
- prevPropsRef.current = props;
3376
- }, [state, props]); // Add mouse/touch events to document.
3372
+ }); // Add mouse/touch events to document.
3377
3373
 
3378
3374
  var mouseAndTouchTrackersRef = useMouseAndTouchTracker(isOpen, [comboboxRef, menuRef, toggleButtonRef], environment, function () {
3379
3375
  dispatch({
@@ -3433,13 +3429,14 @@ function useCombobox(userProps) {
3433
3429
 
3434
3430
  var latestState = latest.current.state;
3435
3431
 
3436
- if (latestState.isOpen && latestState.highlightedIndex > -1) {
3432
+ if (latestState.isOpen) {
3437
3433
  event.preventDefault();
3438
- dispatch({
3439
- type: InputKeyDownEnter,
3440
- getItemNodeFromIndex: getItemNodeFromIndex
3441
- });
3442
3434
  }
3435
+
3436
+ dispatch({
3437
+ type: InputKeyDownEnter,
3438
+ getItemNodeFromIndex: getItemNodeFromIndex
3439
+ });
3443
3440
  }
3444
3441
  };
3445
3442
  }, [dispatch, latest]); // Getter props.
@@ -3524,7 +3521,7 @@ function useCombobox(userProps) {
3524
3521
  inputRef.current.focus();
3525
3522
  }
3526
3523
  }), _ref4), rest);
3527
- }, [dispatch, latest]);
3524
+ }, [dispatch, latest, shouldScrollRef]);
3528
3525
  var getToggleButtonProps = react.useCallback(function (_temp4) {
3529
3526
  var _extends4;
3530
3527
 
@@ -4004,9 +4001,7 @@ function useMultipleSelection(userProps) {
4004
4001
  var dropdownRef = react.useRef(null);
4005
4002
  var previousSelectedItemsRef = react.useRef(selectedItems);
4006
4003
  var selectedItemRefs = react.useRef();
4007
- selectedItemRefs.current = []; // used for checking when props are moving from controlled to uncontrolled.
4008
-
4009
- var prevPropsRef = react.useRef(props);
4004
+ selectedItemRefs.current = [];
4010
4005
  var latest = useLatestRef({
4011
4006
  state: state,
4012
4007
  props: props
@@ -4046,14 +4041,11 @@ function useMultipleSelection(userProps) {
4046
4041
  selectedItemRefs.current[activeIndex].focus();
4047
4042
  }
4048
4043
  }, [activeIndex]);
4049
- react.useEffect(function () {
4050
- if (isInitialMountRef.current) {
4051
- return;
4052
- }
4053
-
4054
- validateControlledUnchanged(state, prevPropsRef.current, props);
4055
- prevPropsRef.current = props;
4056
- }, [state, props]);
4044
+ useControlPropsValidator({
4045
+ isInitialMount: isInitialMountRef.current,
4046
+ props: props,
4047
+ state: state
4048
+ });
4057
4049
  var setGetterPropCallInfo = useGetterPropsCalledChecker('getDropdownProps'); // Make initial ref false.
4058
4050
 
4059
4051
  react.useEffect(function () {