@codecademy/gamut

72.3.072.3.1-alpha.59371c.0
dist/Form/SelectDropdown/SelectDropdown.js
~dist/Form/SelectDropdown/SelectDropdown.jsModified
+79−103
Index: package/dist/Form/SelectDropdown/SelectDropdown.js
===================================================================
--- package/dist/Form/SelectDropdown/SelectDropdown.js
+++ package/dist/Form/SelectDropdown/SelectDropdown.js
@@ -1,28 +1,23 @@
+import _styled from "@emotion/styled/base";
+import { screenReaderOnly } from '@codecademy/gamut-styles';
 import { useTheme } from '@emotion/react';
-import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
+import { useId, useMemo, useRef, useState } from 'react';
 import * as React from 'react';
-import { parseOptions } from '../utils';
-import { AbbreviatedSingleValue, CustomContainer, CustomInput, CustomValueContainer, DropdownButton, formatGroupLabel, formatOptionLabel, IconOption, MultiValueRemoveButton, MultiValueWithColorMode, onFocus, RemoveAllButton, SelectDropdownContext, TypedReactSelect } from './elements';
-import { getMemoizedStyles } from './styles';
-import { filterValueFromOptions, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from './utils';
-import { jsx as _jsx } from "react/jsx-runtime";
-const defaultProps = {
-  name: undefined,
-  components: {
-    DropdownIndicator: DropdownButton,
-    IndicatorSeparator: () => null,
-    ClearIndicator: RemoveAllButton,
-    SelectContainer: CustomContainer,
-    ValueContainer: CustomValueContainer,
-    MultiValue: MultiValueWithColorMode,
-    MultiValueRemove: MultiValueRemoveButton,
-    Option: IconOption,
-    SingleValue: AbbreviatedSingleValue,
-    Input: CustomInput
-  }
-};
-const onChangeAction = 'select-option';
+import { onFocus } from './core/accessibility';
+import { getDefaultComponents } from './core/constants';
+import { getMemoizedStyles } from './core/styles';
+import { resolveNoOptionsMessage } from './core/utils';
+import { formatGroupLabel, formatOptionLabel, SelectDropdownContext, TypedReactSelect } from './elements';
+import { useNoOptionsAnnouncement } from './hooks/useNoOptionsAnnouncement';
+import { useSelectHandlers } from './hooks/useSelectHandlers';
+import { useSelectOptions } from './hooks/useSelectOptions';
+import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+/** Announces the custom `validationMessage` menu text - see `useNoOptionsAnnouncement`. */
+const NoOptionsLiveRegion = /*#__PURE__*/_styled("div", {
+  target: "e2dtq0m0",
+  label: "NoOptionsLiveRegion"
+})(screenReaderOnly, process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL1NlbGVjdERyb3Bkb3duLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUF1QjRCIiwiZmlsZSI6Ii4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL1NlbGVjdERyb3Bkb3duLnRzeCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHNjcmVlblJlYWRlck9ubHkgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHsgdXNlVGhlbWUgfSBmcm9tICdAZW1vdGlvbi9yZWFjdCc7XG5pbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCc7XG5pbXBvcnQgeyB1c2VJZCwgdXNlTWVtbywgdXNlUmVmLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7IFN0eWxlc0NvbmZpZyB9IGZyb20gJ3JlYWN0LXNlbGVjdCc7XG5cbmltcG9ydCB7IG9uRm9jdXMgfSBmcm9tICcuL2NvcmUvYWNjZXNzaWJpbGl0eSc7XG5pbXBvcnQgeyBnZXREZWZhdWx0Q29tcG9uZW50cyB9IGZyb20gJy4vY29yZS9jb25zdGFudHMnO1xuaW1wb3J0IHsgZ2V0TWVtb2l6ZWRTdHlsZXMgfSBmcm9tICcuL2NvcmUvc3R5bGVzJztcbmltcG9ydCB7IHJlc29sdmVOb09wdGlvbnNNZXNzYWdlIH0gZnJvbSAnLi9jb3JlL3V0aWxzJztcbmltcG9ydCB7XG4gIGZvcm1hdEdyb3VwTGFiZWwsXG4gIGZvcm1hdE9wdGlvbkxhYmVsLFxuICBTZWxlY3REcm9wZG93bkNvbnRleHQsXG4gIFR5cGVkUmVhY3RTZWxlY3QsXG59IGZyb20gJy4vZWxlbWVudHMnO1xuaW1wb3J0IHsgdXNlTm9PcHRpb25zQW5ub3VuY2VtZW50IH0gZnJvbSAnLi9ob29rcy91c2VOb09wdGlvbnNBbm5vdW5jZW1lbnQnO1xuaW1wb3J0IHsgdXNlU2VsZWN0SGFuZGxlcnMgfSBmcm9tICcuL2hvb2tzL3VzZVNlbGVjdEhhbmRsZXJzJztcbmltcG9ydCB7IHVzZVNlbGVjdE9wdGlvbnMgfSBmcm9tICcuL2hvb2tzL3VzZVNlbGVjdE9wdGlvbnMnO1xuaW1wb3J0IHsgU2VsZWN0RHJvcGRvd25Qcm9wcyB9IGZyb20gJy4vdHlwZXMnO1xuXG4vKiogQW5ub3VuY2VzIHRoZSBjdXN0b20gYHZhbGlkYXRpb25NZXNzYWdlYCBtZW51IHRleHQgLSBzZWUgYHVzZU5vT3B0aW9uc0Fubm91bmNlbWVudGAuICovXG5jb25zdCBOb09wdGlvbnNMaXZlUmVnaW9uID0gc3R5bGVkLmRpdihzY3JlZW5SZWFkZXJPbmx5KTtcblxuLyoqXG4gKiBBIGZsZXhpYmxlIGRyb3Bkb3duIHNlbGVjdCBjb21wb25lbnQgYnVpbHQgb24gdG9wIG9mIHJlYWN0LXNlbGVjdC5cbiAqXG4gKiBTdXBwb3J0cyBib3RoIHNpbmdsZSBhbmQgbXVsdGktc2VsZWN0IG1vZGVzIHdpdGggY3VzdG9taXphYmxlIG9wdGlvbnMgaW5jbHVkaW5nXG4gKiBpY29ucywgc3VidGl0bGVzLCByaWdodCBsYWJlbHMsIGFuZCBhYmJyZXZpYXRpb25zLiBUaGUgY29tcG9uZW50IHByb3ZpZGVzXG4gKiBhY2Nlc3NpYmlsaXR5IGZlYXR1cmVzLCBrZXlib2FyZCBuYXZpZ2F0aW9uLCBhbmQgcmVzcG9uc2l2ZSBzdHlsaW5nLlxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0c3hcbiAqIC8vIEJhc2ljIHNpbmdsZSBzZWxlY3RcbiAqIDxTZWxlY3REcm9wZG93blxuICogICBuYW1lPVwiY291bnRyeVwiXG4gKiAgIG9wdGlvbnM9e1tcbiAqICAgICB7IGxhYmVsOiAnVW5pdGVkIFN0YXRlcycsIHZhbHVlOiAndXMnIH0sXG4gKiAgICAgeyBsYWJlbDogJ0NhbmFkYScsIHZhbHVlOiAnY2EnIH1cbiAqICAgXX1cbiAqICAgb25DaGFuZ2U9eyhvcHRpb24pID0+IGNvbnNvbGUubG9nKG9wdGlvbil9XG4gKiAvPlxuICpcbiAqIC8vIE11bHRpLXNlbGVjdCB3aXRoIGljb25zXG4gKiA8U2VsZWN0RHJvcGRvd25cbiAqICAgbmFtZT1cInNraWxsc1wiXG4gKiAgIG11bHRpcGxlXG4gKiAgIG9wdGlvbnM9e1tcbiAqICAgICB7IGxhYmVsOiAnUmVhY3QnLCB2YWx1ZTogJ3JlYWN0JywgaWNvbjogUmVhY3RJY29uIH0sXG4gKiAgICAgeyBsYWJlbDogJ1R5cGVTY3JpcHQnLCB2YWx1ZTogJ3RzJywgaWNvbjogVHlwZVNjcmlwdEljb24gfVxuICogICBdfVxuICogICBvbkNoYW5nZT17KG9wdGlvbnMpID0+IGNvbnNvbGUubG9nKG9wdGlvbnMpfVxuICogLz5cbiAqXG4gKiAvLyBHcm91cGVkIG9wdGlvbnMgd2l0aCBleHRlbmRlZCBmZWF0dXJlc1xuICogPFNlbGVjdERyb3Bkb3duXG4gKiAgIG5hbWU9XCJjYXRlZ29yeVwiXG4gKiAgIHBsYWNlaG9sZGVyPVwiRGVmYXVsdCBwbGFjZWhvbGRlciB0ZXh0XCJcbiAqICAgb3B0aW9ucz17W1xuICogICAgIHtcbiAqICAgICAgIGxhYmVsOiAnRnJvbnRlbmQnLFxuICogICAgICAgb3B0aW9uczogW1xuICogICAgICAgICB7IGxhYmVsOiAnUmVhY3QnLCB2YWx1ZTogJ3JlYWN0Jywgc3VidGl0bGU6ICdVSSBMaWJyYXJ5JyB9LFxuICogICAgICAgICB7IGxhYmVsOiAnVnVlJywgdmFsdWU6ICd2dWUnLCBzdWJ0aXRsZTogJ1Byb2dyZXNzaXZlIEZyYW1ld29yaycgfVxuICogICAgICAgXVxuICogICAgIH1cbiAqICAgXX1cbiAqIC8+XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNvbnN0IFNlbGVjdERyb3Bkb3duOiBSZWFjdC5GQzxTZWxlY3REcm9wZG93blByb3BzPiA9ICh7XG4gIGNyZWF0ZU9wdGlvblBvc2l0aW9uLFxuICBkaXNhYmxlZCxcbiAgZHJvcGRvd25XaWR0aCxcbiAgZXJyb3IsXG4gIGZvcm1hdENyZWF0ZUxhYmVsID0gKGlucHV0VmFsdWU6IHN0cmluZykgPT4gYEFkZCBcIiR7aW5wdXRWYWx1ZX1cImAsXG4gIGlkLFxuICBpbnB1dFByb3BzLFxuICBpbnB1dFdpZHRoLFxuICBpc0NyZWF0YWJsZSA9IGZhbHNlLFxuICBpc1NlYXJjaGFibGU6IGlzU2VhcmNoYWJsZVByb3AgPSBmYWxzZSxcbiAgaXNWYWxpZE5ld09wdGlvbixcbiAgbWVudUFsaWdubWVudCA9ICdsZWZ0JyxcbiAgbXVsdGlwbGUsXG4gIG5hbWUsXG4gIG9uQ2hhbmdlLFxuICBvbkNyZWF0ZU9wdGlvbixcbiAgb25JbnB1dENoYW5nZSxcbiAgb25NZW51Q2xvc2UsXG4gIG9wdGlvbnMsXG4gIHBsYWNlaG9sZGVyID0gJ1NlbGVjdCBhbiBvcHRpb24nLFxuICBzaG93bk9wdGlvbnNMaW1pdCA9IDYsXG4gIHNpemUsXG4gIHZhbGlkYXRpb25NZXNzYWdlLFxuICB2YWx1ZSxcbiAgekluZGV4LFxuICAuLi5yZXN0XG59KSA9PiB7XG4gIC8vIGlzU2VhcmNoYWJsZSBpcyBmb3JjZWQgdHJ1ZSB3aGVuIGlzQ3JlYXRhYmxlIGlzIHRydWUgKENyZWF0YWJsZVNlbGVjdCByZXF1aXJlcyBhIHRleHQgaW5wdXQpXG4gIGNvbnN0IGlzU2VhcmNoYWJsZSA9IGlzQ3JlYXRhYmxlIHx8IGlzU2VhcmNoYWJsZVByb3A7XG4gIGNvbnN0IHJhd0lucHV0SWQgPSB1c2VJZCgpO1xuICBjb25zdCBpbnB1dElkID0gbmFtZSA/PyBgJHtpZCA/PyByYXdJbnB1dElkfS1zZWxlY3QtZHJvcGRvd24tJHtyYXdJbnB1dElkfWA7XG5cbiAgY29uc3QgcmVtb3ZlQWxsQnV0dG9uUmVmID0gdXNlUmVmPEhUTUxEaXZFbGVtZW50PihudWxsKTtcbiAgY29uc3Qgc2VsZWN0SW5wdXRSZWYgPSB1c2VSZWY8SFRNTERpdkVsZW1lbnQ+KG51bGwpO1xuXG4gIGNvbnN0IFtjdXJyZW50Rm9jdXNlZFZhbHVlLCBzZXRDdXJyZW50Rm9jdXNlZFZhbHVlXSA9IHVzZVN0YXRlKHVuZGVmaW5lZCk7XG5cbiAgY29uc3QgeyBzZWxlY3RPcHRpb25zLCBwYXJzZWRWYWx1ZSB9ID0gdXNlU2VsZWN0T3B0aW9ucyh7XG4gICAgb3B0aW9ucyxcbiAgICBpZCxcbiAgICBzaXplLFxuICAgIHZhbHVlOiB2YWx1ZSBhcyBzdHJpbmcgfCBzdHJpbmdbXSB8IHVuZGVmaW5lZCxcbiAgfSk7XG5cbiAgY29uc3Qgbm9PcHRpb25zTWVzc2FnZSA9IHJlc29sdmVOb09wdGlvbnNNZXNzYWdlKHZhbGlkYXRpb25NZXNzYWdlKTtcbiAgY29uc3QgeyBub09wdGlvbnNNZXNzYWdlQ29tcG9uZW50LCBhbm5vdW5jZW1lbnQsIGNsZWFyQW5ub3VuY2VtZW50IH0gPVxuICAgIHVzZU5vT3B0aW9uc0Fubm91bmNlbWVudCgpO1xuICBjb25zdCBjb21wb25lbnRzID0gdXNlTWVtbyhcbiAgICAoKSA9PiBnZXREZWZhdWx0Q29tcG9uZW50cyhub09wdGlvbnNNZXNzYWdlQ29tcG9uZW50KSxcbiAgICBbbm9PcHRpb25zTWVzc2FnZUNvbXBvbmVudF1cbiAgKTtcbiAgY29uc3QgaGFuZGxlTWVudUNsb3NlID0gKCkgPT4ge1xuICAgIGNsZWFyQW5ub3VuY2VtZW50KCk7XG4gICAgb25NZW51Q2xvc2U/LigpO1xuICB9O1xuXG4gIGNvbnN0IHsgYWN0aXZhdGVkLCBtdWx0aVZhbHVlcywgY2hhbmdlSGFuZGxlciwga2V5UHJlc3NIYW5kbGVyIH0gPVxuICAgIHVzZVNlbGVjdEhhbmRsZXJzKHtcbiAgICAgIG9uQ2hhbmdlLFxuICAgICAgbXVsdGlwbGUsXG4gICAgICBvbkNyZWF0ZU9wdGlvbixcbiAgICAgIHNlbGVjdE9wdGlvbnMsXG4gICAgICB2YWx1ZSxcbiAgICAgIGN1cnJlbnRGb2N1c2VkVmFsdWUsXG4gICAgICByZW1vdmVBbGxCdXR0b25SZWYsXG4gICAgfSk7XG5cbiAgY29uc3QgdGhlbWUgPSB1c2VUaGVtZSgpO1xuICBjb25zdCBtZW1vaXplZFN0eWxlcyA9IHVzZU1lbW8oKCk6IFN0eWxlc0NvbmZpZzxhbnksIGZhbHNlPiA9PiB7XG4gICAgcmV0dXJuIGdldE1lbW9pemVkU3R5bGVzKHRoZW1lLCB6SW5kZXgpO1xuICB9LCBbdGhlbWUsIHpJbmRleF0pO1xuXG4gIHJldHVybiAoXG4gICAgPFNlbGVjdERyb3Bkb3duQ29udGV4dC5Qcm92aWRlclxuICAgICAgdmFsdWU9e3tcbiAgICAgICAgY3VycmVudEZvY3VzZWRWYWx1ZSxcbiAgICAgICAgc2V0Q3VycmVudEZvY3VzZWRWYWx1ZSxcbiAgICAgICAgcmVtb3ZlQWxsQnV0dG9uUmVmLFxuICAgICAgICBzZWxlY3RJbnB1dFJlZixcbiAgICAgIH19XG4gICAgPlxuICAgICAgPFR5cGVkUmVhY3RTZWxlY3RcbiAgICAgICAgYWN0aXZhdGVkPXthY3RpdmF0ZWR9XG4gICAgICAgIGFyaWEtbGl2ZT1cImFzc2VydGl2ZVwiXG4gICAgICAgIGFyaWFMaXZlTWVzc2FnZXM9e3sgb25Gb2N1cyB9fVxuICAgICAgICBjb21wb25lbnRzPXtjb21wb25lbnRzfVxuICAgICAgICBjcmVhdGVPcHRpb25Qb3NpdGlvbj17Y3JlYXRlT3B0aW9uUG9zaXRpb259XG4gICAgICAgIGRyb3Bkb3duV2lkdGg9e2Ryb3Bkb3duV2lkdGh9XG4gICAgICAgIGVycm9yPXtCb29sZWFuKGVycm9yKX1cbiAgICAgICAgZm9ybWF0Q3JlYXRlTGFiZWw9e2Zvcm1hdENyZWF0ZUxhYmVsfVxuICAgICAgICBmb3JtYXRHcm91cExhYmVsPXtmb3JtYXRHcm91cExhYmVsfVxuICAgICAgICBmb3JtYXRPcHRpb25MYWJlbD17Zm9ybWF0T3B0aW9uTGFiZWx9XG4gICAgICAgIGlkPXtpZCB8fCByZXN0Lmh0bWxGb3IgfHwgcmF3SW5wdXRJZH1cbiAgICAgICAgaW5wdXRJZD17aW5wdXRJZH1cbiAgICAgICAgaW5wdXRQcm9wcz17eyAuLi5pbnB1dFByb3BzIH19XG4gICAgICAgIGlucHV0V2lkdGg9e2lucHV0V2lkdGh9XG4gICAgICAgIGlzQ3JlYXRhYmxlPXtpc0NyZWF0YWJsZX1cbiAgICAgICAgaXNEaXNhYmxlZD17ZGlzYWJsZWR9XG4gICAgICAgIGlzTXVsdGk9e211bHRpcGxlfVxuICAgICAgICBpc09wdGlvbkRpc2FibGVkPXsob3B0aW9uKSA9PiBvcHRpb24uZGlzYWJsZWR9XG4gICAgICAgIGlzU2VhcmNoYWJsZT17aXNTZWFyY2hhYmxlfVxuICAgICAgICBpc1ZhbGlkTmV3T3B0aW9uPXtpc1ZhbGlkTmV3T3B0aW9ufVxuICAgICAgICBtZW51QWxpZ25tZW50PXttZW51QWxpZ25tZW50fVxuICAgICAgICBuYW1lPXtuYW1lfVxuICAgICAgICBub09wdGlvbnNNZXNzYWdlPXtub09wdGlvbnNNZXNzYWdlfVxuICAgICAgICBvcHRpb25zPXtzZWxlY3RPcHRpb25zfVxuICAgICAgICBwbGFjZWhvbGRlcj17cGxhY2Vob2xkZXJ9XG4gICAgICAgIHNlbGVjdFJlZj17c2VsZWN0SW5wdXRSZWZ9XG4gICAgICAgIHNob3duT3B0aW9uc0xpbWl0PXtzaG93bk9wdGlvbnNMaW1pdH1cbiAgICAgICAgc2l6ZT17c2l6ZX1cbiAgICAgICAgc3R5bGVzPXttZW1vaXplZFN0eWxlc31cbiAgICAgICAgdmFsdWU9e211bHRpcGxlID8gbXVsdGlWYWx1ZXMgOiBwYXJzZWRWYWx1ZX1cbiAgICAgICAgb25DaGFuZ2U9e2NoYW5nZUhhbmRsZXJ9XG4gICAgICAgIG9uSW5wdXRDaGFuZ2U9e29uSW5wdXRDaGFuZ2V9XG4gICAgICAgIG9uS2V5RG93bj17bXVsdGlwbGUgPyBrZXlQcmVzc0hhbmRsZXIgOiB1bmRlZmluZWR9XG4gICAgICAgIG9uTWVudUNsb3NlPXtoYW5kbGVNZW51Q2xvc2V9XG4gICAgICAgIHsuLi5yZXN0fVxuICAgICAgLz5cbiAgICAgIDxOb09wdGlvbnNMaXZlUmVnaW9uIGFyaWEtbGl2ZT1cInBvbGl0ZVwiIHJvbGU9XCJzdGF0dXNcIj5cbiAgICAgICAge2Fubm91bmNlbWVudH1cbiAgICAgIDwvTm9PcHRpb25zTGl2ZVJlZ2lvbj5cbiAgICA8L1NlbGVjdERyb3Bkb3duQ29udGV4dC5Qcm92aWRlcj5cbiAgKTtcbn07XG4iXX0= */");
 
 /**
  * A flexible dropdown select component built on top of react-select.
  *
@@ -69,149 +64,130 @@
  * />
  * ```
  */
 export const SelectDropdown = ({
+  createOptionPosition,
   disabled,
   dropdownWidth,
   error,
+  formatCreateLabel = inputValue => `Add "${inputValue}"`,
   id,
   inputProps,
   inputWidth,
-  isSearchable = false,
+  isCreatable = false,
+  isSearchable: isSearchableProp = false,
+  isValidNewOption,
   menuAlignment = 'left',
   multiple,
   name,
   onChange,
+  onCreateOption,
+  onInputChange,
+  onMenuClose,
   options,
   placeholder = 'Select an option',
   shownOptionsLimit = 6,
   size,
+  validationMessage,
   value,
   zIndex,
   ...rest
 }) => {
+  // isSearchable is forced true when isCreatable is true (CreatableSelect requires a text input)
+  const isSearchable = isCreatable || isSearchableProp;
   const rawInputId = useId();
-  const inputId = name ?? `${id}-select-dropdown-${rawInputId}`;
-  const [activated, setActivated] = useState(false);
-  const [currentFocusedValue, setCurrentFocusedValue] = useState(undefined);
-
-  // these are used to programatically manage the focus state of our multi-select options + 'Remove all' button
+  const inputId = name ?? `${id ?? rawInputId}-select-dropdown-${rawInputId}`;
   const removeAllButtonRef = useRef(null);
   const selectInputRef = useRef(null);
-  const selectOptions = useMemo(() => {
-    if (!options || Array.isArray(options) && !options.length || typeof options === 'object' && !Array.isArray(options) && Object.keys(options).length === 0) {
-      return [];
-    }
-    if (isOptionsGrouped(options)) {
-      return options;
-    }
-    return parseOptions({
-      options,
-      id,
-      size
-    });
-  }, [options, id, size]);
-  const parsedValue = useMemo(() => {
-    if (isOptionsGrouped(selectOptions)) {
-      for (const group of selectOptions) {
-        if (group.options) {
-          const foundOption = group.options.find(option => option.value === value);
-          if (foundOption) return foundOption;
-        }
-      }
-      return undefined;
-    }
-    return selectOptions.find(option => option.value === value);
-  }, [selectOptions, value]);
-  const [multiValues, setMultiValues] = useState(multiple &&
-  // To keep this efficient for non-multiSelect
-  filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions)));
-
-  // If the caller changes the initial value, let's update our value to match.
-  useEffect(() => {
-    const newMultiValues = filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions));
-    if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
-
-    //
-    // We only update this when our passed in options or value changes, not multiValues.
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [options, value]);
-  const changeHandler = useCallback(optionEvent => {
-    setActivated(true);
-
-    // We have to do this because the version of typescript we have doesn't have the transitivity of these type guards yet. But, we will soon!
-    // Should probably come with: https://codecademy.atlassian.net/browse/GM-354
-    const onChangeProps = {
-      onChange,
-      multiple
-    };
-    if (isSingleSelectProps(onChangeProps)) {
-      const singleOptionEvent = optionEvent;
-      onChangeProps.onChange?.(singleOptionEvent, {
-        action: onChangeAction,
-        option: singleOptionEvent
-      });
-    }
-    if (isMultipleSelectProps(onChangeProps)) {
-      setMultiValues(optionEvent);
-      onChangeProps.onChange?.(optionEvent, {
-        action: onChangeAction,
-        option: undefined // At the moment this isn't used, but when multi select is built for real, boom (https://codecademy.atlassian.net/browse/GM-354)
-      });
-    }
-  }, [onChange, multiple]);
-  const keyPressHandler = e => {
-    if (multiple && e.key === 'Enter' && currentFocusedValue && multiValues) {
-      const newMultiValues = removeValueFromSelectedOptions(multiValues, currentFocusedValue);
-      if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
-    }
-    if (removeAllButtonRef.current !== null && e.key === 'ArrowRight' && multiValues && currentFocusedValue === multiValues[multiValues.length - 1].value) {
-      removeAllButtonRef.current.focus();
-    }
+  const [currentFocusedValue, setCurrentFocusedValue] = useState(undefined);
+  const {
+    selectOptions,
+    parsedValue
+  } = useSelectOptions({
+    options,
+    id,
+    size,
+    value: value
+  });
+  const noOptionsMessage = resolveNoOptionsMessage(validationMessage);
+  const {
+    noOptionsMessageComponent,
+    announcement,
+    clearAnnouncement
+  } = useNoOptionsAnnouncement();
+  const components = useMemo(() => getDefaultComponents(noOptionsMessageComponent), [noOptionsMessageComponent]);
+  const handleMenuClose = () => {
+    clearAnnouncement();
+    onMenuClose?.();
   };
+  const {
+    activated,
+    multiValues,
+    changeHandler,
+    keyPressHandler
+  } = useSelectHandlers({
+    onChange,
+    multiple,
+    onCreateOption,
+    selectOptions,
+    value,
+    currentFocusedValue,
+    removeAllButtonRef
+  });
   const theme = useTheme();
   const memoizedStyles = useMemo(() => {
     return getMemoizedStyles(theme, zIndex);
   }, [theme, zIndex]);
-  return /*#__PURE__*/_jsx(SelectDropdownContext.Provider, {
+  return /*#__PURE__*/_jsxs(SelectDropdownContext.Provider, {
     value: {
       currentFocusedValue,
       setCurrentFocusedValue,
       removeAllButtonRef,
       selectInputRef
     },
-    children: /*#__PURE__*/_jsx(TypedReactSelect, {
-      ...defaultProps,
+    children: [/*#__PURE__*/_jsx(TypedReactSelect, {
       activated: activated,
       "aria-live": "assertive",
       ariaLiveMessages: {
         onFocus
       },
+      components: components,
+      createOptionPosition: createOptionPosition,
       dropdownWidth: dropdownWidth,
       error: Boolean(error),
+      formatCreateLabel: formatCreateLabel,
       formatGroupLabel: formatGroupLabel,
       formatOptionLabel: formatOptionLabel,
       id: id || rest.htmlFor || rawInputId,
       inputId: inputId,
       inputProps: {
         ...inputProps
       },
       inputWidth: inputWidth,
+      isCreatable: isCreatable,
       isDisabled: disabled,
       isMulti: multiple,
       isOptionDisabled: option => option.disabled,
       isSearchable: isSearchable,
+      isValidNewOption: isValidNewOption,
       menuAlignment: menuAlignment,
       name: name,
+      noOptionsMessage: noOptionsMessage,
       options: selectOptions,
       placeholder: placeholder,
       selectRef: selectInputRef,
       shownOptionsLimit: shownOptionsLimit,
       size: size,
       styles: memoizedStyles,
       value: multiple ? multiValues : parsedValue,
       onChange: changeHandler,
-      onKeyDown: multiple ? e => keyPressHandler(e) : undefined,
+      onInputChange: onInputChange,
+      onKeyDown: multiple ? keyPressHandler : undefined,
+      onMenuClose: handleMenuClose,
       ...rest
-    })
+    }), /*#__PURE__*/_jsx(NoOptionsLiveRegion, {
+      "aria-live": "polite",
+      role: "status",
+      children: announcement
+    })]
   });
 };
\ No newline at end of file