@codecademy/gamut

72.2.472.2.5-alpha.619ec9.0
dist/Form/SelectDropdown/SelectDropdown.js
~dist/Form/SelectDropdown/SelectDropdown.jsModified
+71−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+XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNvbnN0IFNlbGVjdERyb3Bkb3duOiBSZWFjdC5GQzxTZWxlY3REcm9wZG93blByb3BzPiA9ICh7XG4gIGRpc2FibGVkLFxuICBkcm9wZG93bldpZHRoLFxuICBlcnJvcixcbiAgZm9ybWF0Q3JlYXRlTGFiZWwgPSAoaW5wdXRWYWx1ZTogc3RyaW5nKSA9PiBgQWRkIFwiJHtpbnB1dFZhbHVlfVwiYCxcbiAgaWQsXG4gIGlucHV0UHJvcHMsXG4gIGlucHV0V2lkdGgsXG4gIGlzQ3JlYXRhYmxlID0gZmFsc2UsXG4gIGlzU2VhcmNoYWJsZTogaXNTZWFyY2hhYmxlUHJvcCA9IGZhbHNlLFxuICBpc1ZhbGlkTmV3T3B0aW9uLFxuICBtZW51QWxpZ25tZW50ID0gJ2xlZnQnLFxuICBtdWx0aXBsZSxcbiAgbmFtZSxcbiAgb25DaGFuZ2UsXG4gIG9uQ3JlYXRlT3B0aW9uLFxuICBvbklucHV0Q2hhbmdlLFxuICBvcHRpb25zLFxuICBwbGFjZWhvbGRlciA9ICdTZWxlY3QgYW4gb3B0aW9uJyxcbiAgc2hvd25PcHRpb25zTGltaXQgPSA2LFxuICBzaXplLFxuICB2YWxpZGF0aW9uTWVzc2FnZSxcbiAgdmFsdWUsXG4gIHpJbmRleCxcbiAgLi4ucmVzdFxufSkgPT4ge1xuICAvLyBpc1NlYXJjaGFibGUgaXMgZm9yY2VkIHRydWUgd2hlbiBpc0NyZWF0YWJsZSBpcyB0cnVlIChDcmVhdGFibGVTZWxlY3QgcmVxdWlyZXMgYSB0ZXh0IGlucHV0KVxuICBjb25zdCBpc1NlYXJjaGFibGUgPSBpc0NyZWF0YWJsZSB8fCBpc1NlYXJjaGFibGVQcm9wO1xuICBjb25zdCByYXdJbnB1dElkID0gdXNlSWQoKTtcbiAgY29uc3QgaW5wdXRJZCA9IG5hbWUgPz8gYCR7aWR9LXNlbGVjdC1kcm9wZG93bi0ke3Jhd0lucHV0SWR9YDtcblxuICBjb25zdCByZW1vdmVBbGxCdXR0b25SZWYgPSB1c2VSZWY8SFRNTERpdkVsZW1lbnQ+KG51bGwpO1xuICBjb25zdCBzZWxlY3RJbnB1dFJlZiA9IHVzZVJlZjxIVE1MRGl2RWxlbWVudD4obnVsbCk7XG5cbiAgY29uc3QgW2N1cnJlbnRGb2N1c2VkVmFsdWUsIHNldEN1cnJlbnRGb2N1c2VkVmFsdWVdID0gdXNlU3RhdGUodW5kZWZpbmVkKTtcblxuICBjb25zdCB7IHNlbGVjdE9wdGlvbnMsIHBhcnNlZFZhbHVlIH0gPSB1c2VTZWxlY3RPcHRpb25zKHtcbiAgICBvcHRpb25zLFxuICAgIGlkLFxuICAgIHNpemUsXG4gICAgdmFsdWU6IHZhbHVlIGFzIHN0cmluZyB8IHN0cmluZ1tdIHwgdW5kZWZpbmVkLFxuICB9KTtcblxuICBjb25zdCBub09wdGlvbnNNZXNzYWdlID0gcmVzb2x2ZU5vT3B0aW9uc01lc3NhZ2UodmFsaWRhdGlvbk1lc3NhZ2UpO1xuICBjb25zdCB7IG5vT3B0aW9uc01lc3NhZ2VDb21wb25lbnQsIGFubm91bmNlbWVudCwgYW5ub3VuY2VtZW50S2V5IH0gPVxuICAgIHVzZU5vT3B0aW9uc0Fubm91bmNlbWVudCgpO1xuICBjb25zdCBjb21wb25lbnRzID0gdXNlTWVtbyhcbiAgICAoKSA9PiBnZXREZWZhdWx0Q29tcG9uZW50cyhub09wdGlvbnNNZXNzYWdlQ29tcG9uZW50KSxcbiAgICBbbm9PcHRpb25zTWVzc2FnZUNvbXBvbmVudF1cbiAgKTtcblxuICBjb25zdCB7IGFjdGl2YXRlZCwgbXVsdGlWYWx1ZXMsIGNoYW5nZUhhbmRsZXIsIGtleVByZXNzSGFuZGxlciB9ID1cbiAgICB1c2VTZWxlY3RIYW5kbGVycyh7XG4gICAgICBvbkNoYW5nZSxcbiAgICAgIG11bHRpcGxlLFxuICAgICAgb25DcmVhdGVPcHRpb24sXG4gICAgICBzZWxlY3RPcHRpb25zLFxuICAgICAgdmFsdWUsXG4gICAgICBjdXJyZW50Rm9jdXNlZFZhbHVlLFxuICAgICAgcmVtb3ZlQWxsQnV0dG9uUmVmLFxuICAgIH0pO1xuXG4gIGNvbnN0IHRoZW1lID0gdXNlVGhlbWUoKTtcbiAgY29uc3QgbWVtb2l6ZWRTdHlsZXMgPSB1c2VNZW1vKCgpOiBTdHlsZXNDb25maWc8YW55LCBmYWxzZT4gPT4ge1xuICAgIHJldHVybiBnZXRNZW1vaXplZFN0eWxlcyh0aGVtZSwgekluZGV4KTtcbiAgfSwgW3RoZW1lLCB6SW5kZXhdKTtcblxuICByZXR1cm4gKFxuICAgIDxTZWxlY3REcm9wZG93bkNvbnRleHQuUHJvdmlkZXJcbiAgICAgIHZhbHVlPXt7XG4gICAgICAgIGN1cnJlbnRGb2N1c2VkVmFsdWUsXG4gICAgICAgIHNldEN1cnJlbnRGb2N1c2VkVmFsdWUsXG4gICAgICAgIHJlbW92ZUFsbEJ1dHRvblJlZixcbiAgICAgICAgc2VsZWN0SW5wdXRSZWYsXG4gICAgICB9fVxuICAgID5cbiAgICAgIDxUeXBlZFJlYWN0U2VsZWN0XG4gICAgICAgIGFjdGl2YXRlZD17YWN0aXZhdGVkfVxuICAgICAgICBhcmlhLWxpdmU9XCJhc3NlcnRpdmVcIlxuICAgICAgICBhcmlhTGl2ZU1lc3NhZ2VzPXt7IG9uRm9jdXMgfX1cbiAgICAgICAgY29tcG9uZW50cz17Y29tcG9uZW50c31cbiAgICAgICAgZHJvcGRvd25XaWR0aD17ZHJvcGRvd25XaWR0aH1cbiAgICAgICAgZXJyb3I9e0Jvb2xlYW4oZXJyb3IpfVxuICAgICAgICBmb3JtYXRDcmVhdGVMYWJlbD17Zm9ybWF0Q3JlYXRlTGFiZWx9XG4gICAgICAgIGZvcm1hdEdyb3VwTGFiZWw9e2Zvcm1hdEdyb3VwTGFiZWx9XG4gICAgICAgIGZvcm1hdE9wdGlvbkxhYmVsPXtmb3JtYXRPcHRpb25MYWJlbH1cbiAgICAgICAgaWQ9e2lkIHx8IHJlc3QuaHRtbEZvciB8fCByYXdJbnB1dElkfVxuICAgICAgICBpbnB1dElkPXtpbnB1dElkfVxuICAgICAgICBpbnB1dFByb3BzPXt7IC4uLmlucHV0UHJvcHMgfX1cbiAgICAgICAgaW5wdXRXaWR0aD17aW5wdXRXaWR0aH1cbiAgICAgICAgaXNDcmVhdGFibGU9e2lzQ3JlYXRhYmxlfVxuICAgICAgICBpc0Rpc2FibGVkPXtkaXNhYmxlZH1cbiAgICAgICAgaXNNdWx0aT17bXVsdGlwbGV9XG4gICAgICAgIGlzT3B0aW9uRGlzYWJsZWQ9eyhvcHRpb24pID0+IG9wdGlvbi5kaXNhYmxlZH1cbiAgICAgICAgaXNTZWFyY2hhYmxlPXtpc1NlYXJjaGFibGV9XG4gICAgICAgIGlzVmFsaWROZXdPcHRpb249e2lzVmFsaWROZXdPcHRpb259XG4gICAgICAgIG1lbnVBbGlnbm1lbnQ9e21lbnVBbGlnbm1lbnR9XG4gICAgICAgIG5hbWU9e25hbWV9XG4gICAgICAgIG5vT3B0aW9uc01lc3NhZ2U9e25vT3B0aW9uc01lc3NhZ2V9XG4gICAgICAgIG9wdGlvbnM9e3NlbGVjdE9wdGlvbnN9XG4gICAgICAgIHBsYWNlaG9sZGVyPXtwbGFjZWhvbGRlcn1cbiAgICAgICAgc2VsZWN0UmVmPXtzZWxlY3RJbnB1dFJlZn1cbiAgICAgICAgc2hvd25PcHRpb25zTGltaXQ9e3Nob3duT3B0aW9uc0xpbWl0fVxuICAgICAgICBzaXplPXtzaXplfVxuICAgICAgICBzdHlsZXM9e21lbW9pemVkU3R5bGVzfVxuICAgICAgICB2YWx1ZT17bXVsdGlwbGUgPyBtdWx0aVZhbHVlcyA6IHBhcnNlZFZhbHVlfVxuICAgICAgICBvbkNoYW5nZT17Y2hhbmdlSGFuZGxlcn1cbiAgICAgICAgb25JbnB1dENoYW5nZT17b25JbnB1dENoYW5nZX1cbiAgICAgICAgb25LZXlEb3duPXttdWx0aXBsZSA/IGtleVByZXNzSGFuZGxlciA6IHVuZGVmaW5lZH1cbiAgICAgICAgey4uLnJlc3R9XG4gICAgICAvPlxuICAgICAgPE5vT3B0aW9uc0xpdmVSZWdpb25cbiAgICAgICAgYXJpYS1saXZlPVwicG9saXRlXCJcbiAgICAgICAga2V5PXthbm5vdW5jZW1lbnRLZXl9XG4gICAgICAgIHJvbGU9XCJzdGF0dXNcIlxuICAgICAgPlxuICAgICAgICB7YW5ub3VuY2VtZW50fVxuICAgICAgPC9Ob09wdGlvbnNMaXZlUmVnaW9uPlxuICAgIDwvU2VsZWN0RHJvcGRvd25Db250ZXh0LlByb3ZpZGVyPlxuICApO1xufTtcbiJdfQ== */");
 
 /**
  * A flexible dropdown select component built on top of react-select.
  *
@@ -72,146 +67,119 @@
 export const SelectDropdown = ({
   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,
   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 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,
+    announcementKey
+  } = useNoOptionsAnnouncement();
+  const components = useMemo(() => getDefaultComponents(noOptionsMessageComponent), [noOptionsMessageComponent]);
+  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,
       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,
       ...rest
-    })
+    }), /*#__PURE__*/_jsx(NoOptionsLiveRegion, {
+      "aria-live": "polite",
+      role: "status",
+      children: announcement
+    }, announcementKey)]
   });
 };
\ No newline at end of file