import React__default, { useMemo, useRef, useEffect, useContext, useCallback, forwardRef, useState, useReducer } from 'react';
import _pt from 'prop-types';
import { forwardRefWithAs, makeId, checkStyles, isFunction, useUpdateEffect, useForkedRef, getOwnerDocument, useIsomorphicLayoutEffect, wrapEvent, createNamedContext, noop } from '../../utils/dist/utils.esm.esm.js';
import { useDescendantsInit, DescendantProvider, useDescendant, useDescendants, createDescendantContext } from '../../descendants/dist/descendants.esm.esm.js';
import { findAll as dist_1 } from '../../../highlight-words-core/dist/index.esm.js';
import { useId } from '../../auto-id/dist/auto-id.esm.esm.js';
import Popover, { positionMatchWidth } from '../../popover/dist/popover.esm.esm.js';

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

var _on, _on2, _on3, _on4, _states;
// States
// Nothing going on, waiting for the user to type or use the arrow keys

var IDLE = "IDLE"; // The component is suggesting options as the user types

var SUGGESTING = "SUGGESTING"; // The user is using the keyboard to navigate the list, not typing

var NAVIGATING = "NAVIGATING"; // The user is interacting with arbitrary elements inside the popup that
// are not ComboboxInputs

var INTERACTING = "INTERACTING"; ////////////////////////////////////////////////////////////////////////////////
// Events
// User cleared the value w/ backspace, but input still has focus

var CLEAR = "CLEAR"; // User is typing

var CHANGE = "CHANGE"; // Initial input value change handler for syncing user state with state machine
// Prevents initial change from sending the user to the NAVIGATING state
// https://github.com/reach/reach-ui/issues/464

var INITIAL_CHANGE = "INITIAL_CHANGE"; // User is navigating w/ the keyboard

var NAVIGATE = "NAVIGATE"; // User can be navigating with keyboard and then click instead, we want the
// value from the click, not the current nav item

var SELECT_WITH_KEYBOARD = "SELECT_WITH_KEYBOARD";
var SELECT_WITH_CLICK = "SELECT_WITH_CLICK"; // Pretty self-explanatory, user can hit escape or blur to close the popover

var ESCAPE = "ESCAPE";
var BLUR = "BLUR"; // The user left the input to interact with arbitrary elements inside the popup

var INTERACT = "INTERACT";
var FOCUS = "FOCUS";
var OPEN_WITH_BUTTON = "OPEN_WITH_BUTTON";
var CLOSE_WITH_BUTTON = "CLOSE_WITH_BUTTON"; ////////////////////////////////////////////////////////////////////////////////

var stateChart = {
  initial: IDLE,
  states: (_states = {}, _states[IDLE] = {
    on: (_on = {}, _on[BLUR] = IDLE, _on[CLEAR] = IDLE, _on[CHANGE] = SUGGESTING, _on[INITIAL_CHANGE] = IDLE, _on[FOCUS] = SUGGESTING, _on[NAVIGATE] = NAVIGATING, _on[OPEN_WITH_BUTTON] = SUGGESTING, _on)
  }, _states[SUGGESTING] = {
    on: (_on2 = {}, _on2[CHANGE] = SUGGESTING, _on2[FOCUS] = SUGGESTING, _on2[NAVIGATE] = NAVIGATING, _on2[CLEAR] = IDLE, _on2[ESCAPE] = IDLE, _on2[BLUR] = IDLE, _on2[SELECT_WITH_CLICK] = IDLE, _on2[INTERACT] = INTERACTING, _on2[CLOSE_WITH_BUTTON] = IDLE, _on2)
  }, _states[NAVIGATING] = {
    on: (_on3 = {}, _on3[CHANGE] = SUGGESTING, _on3[FOCUS] = SUGGESTING, _on3[CLEAR] = IDLE, _on3[BLUR] = IDLE, _on3[ESCAPE] = IDLE, _on3[NAVIGATE] = NAVIGATING, _on3[SELECT_WITH_CLICK] = IDLE, _on3[SELECT_WITH_KEYBOARD] = IDLE, _on3[CLOSE_WITH_BUTTON] = IDLE, _on3[INTERACT] = INTERACTING, _on3)
  }, _states[INTERACTING] = {
    on: (_on4 = {}, _on4[CLEAR] = IDLE, _on4[CHANGE] = SUGGESTING, _on4[FOCUS] = SUGGESTING, _on4[BLUR] = IDLE, _on4[ESCAPE] = IDLE, _on4[NAVIGATE] = NAVIGATING, _on4[CLOSE_WITH_BUTTON] = IDLE, _on4[SELECT_WITH_CLICK] = IDLE, _on4)
  }, _states)
};

var reducer = function reducer(data, event) {
  var nextState = _extends(_extends({}, data), {}, {
    lastEventType: event.type
  });

  switch (event.type) {
    case CHANGE:
    case INITIAL_CHANGE:
      return _extends(_extends({}, nextState), {}, {
        navigationValue: null,
        value: event.value
      });

    case NAVIGATE:
    case OPEN_WITH_BUTTON:
      return _extends(_extends({}, nextState), {}, {
        navigationValue: findNavigationValue(nextState, event)
      });

    case CLEAR:
      return _extends(_extends({}, nextState), {}, {
        value: "",
        navigationValue: null
      });

    case BLUR:
    case ESCAPE:
      return _extends(_extends({}, nextState), {}, {
        navigationValue: null
      });

    case SELECT_WITH_CLICK:
      return _extends(_extends({}, nextState), {}, {
        value: event.value,
        navigationValue: null
      });

    case SELECT_WITH_KEYBOARD:
      return _extends(_extends({}, nextState), {}, {
        value: data.navigationValue,
        navigationValue: null
      });

    case CLOSE_WITH_BUTTON:
      return _extends(_extends({}, nextState), {}, {
        navigationValue: null
      });

    case INTERACT:
      return nextState;

    case FOCUS:
      return _extends(_extends({}, nextState), {}, {
        navigationValue: findNavigationValue(nextState, event)
      });

    default:
      return nextState;
  }
};

function popoverIsExpanded(state) {
  return [SUGGESTING, NAVIGATING, INTERACTING].includes(state);
}
/**
 * When we open a list, set the navigation value to the value in the input, if
 * it's in the list, then it'll automatically be highlighted.
 *
 * @param stateData
 * @param event
 */


function findNavigationValue(stateData, event) {
  // @ts-ignore
  if (event.value) {
    // @ts-ignore
    return event.value; // @ts-ignore
  } else if (event.persistSelection) {
    return stateData.value;
  } else {
    return null;
  }
}

var ComboboxDescendantContext = /*#__PURE__*/createDescendantContext("ComboboxDescendantContext");
var ComboboxContext = /*#__PURE__*/createNamedContext("ComboboxContext", {}); // Allows us to put the option's value on context so that ComboboxOptionText
// can work it's highlight text magic no matter what else is rendered around
// it.

var OptionContext = /*#__PURE__*/createNamedContext("OptionContext", {}); ////////////////////////////////////////////////////////////////////////////////

/**
 * Combobox
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#combobox
 */

var Combobox = /*#__PURE__*/forwardRefWithAs(function Combobox(_ref, forwardedRef) {
  var onSelect = _ref.onSelect,
      _ref$openOnFocus = _ref.openOnFocus,
      openOnFocus = _ref$openOnFocus === void 0 ? false : _ref$openOnFocus,
      children = _ref.children,
      _ref$as = _ref.as,
      Comp = _ref$as === void 0 ? "div" : _ref$as,
      ariaLabel = _ref["aria-label"],
      ariaLabelledby = _ref["aria-labelledby"],
      props = _objectWithoutPropertiesLoose(_ref, ["onSelect", "openOnFocus", "children", "as", "aria-label", "aria-labelledby"]);

  var _useDescendantsInit = useDescendantsInit(),
      options = _useDescendantsInit[0],
      setOptions = _useDescendantsInit[1]; // Need this to focus it


  var inputRef = useRef();
  var popoverRef = useRef();
  var buttonRef = useRef(); // When <ComboboxInput autocomplete={false} /> we don't want cycle back to
  // the user's value while navigating (because it's always the user's value),
  // but we need to know this in useKeyDown which is far away from the prop
  // here, so we do something sneaky and write it to this ref on context so we
  // can use it anywhere else 😛. Another new trick for me and I'm excited
  // about this one too!

  var autocompletePropRef = useRef();
  var persistSelectionRef = useRef();
  var defaultData = {
    // The value the user has typed. We derive this also when the developer is
    // controlling the value of ComboboxInput.
    value: "",
    // the value the user has navigated to with the keyboard
    navigationValue: null
  };

  var _useReducerMachine = useReducerMachine(stateChart, reducer, defaultData),
      state = _useReducerMachine[0],
      data = _useReducerMachine[1],
      transition = _useReducerMachine[2];

  useFocusManagement(data.lastEventType, inputRef);
  var id = useId(props.id);
  var listboxId = id ? makeId("listbox", id) : "listbox";
  var context = {
    ariaLabel: ariaLabel,
    ariaLabelledby: ariaLabelledby,
    autocompletePropRef: autocompletePropRef,
    buttonRef: buttonRef,
    comboboxId: id,
    data: data,
    inputRef: inputRef,
    isExpanded: popoverIsExpanded(state),
    listboxId: listboxId,
    onSelect: onSelect || noop,
    openOnFocus: openOnFocus,
    persistSelectionRef: persistSelectionRef,
    popoverRef: popoverRef,
    state: state,
    transition: transition
  };
  useEffect(function () {
    return checkStyles("combobox");
  }, []);
  return React__default.createElement(DescendantProvider, {
    context: ComboboxDescendantContext,
    items: options,
    set: setOptions
  }, React__default.createElement(ComboboxContext.Provider, {
    value: context
  }, React__default.createElement(Comp, Object.assign({}, props, {
    "data-reach-combobox": "",
    ref: forwardedRef
  }), isFunction(children) ? children({
    id: id,
    isExpanded: popoverIsExpanded(state)
  }) : children)));
});

if (process.env.NODE_ENV !== "production") {
  Combobox.displayName = "Combobox";
  Combobox.propTypes = {
    as: _pt.any,
    onSelect: _pt.func,
    openOnFocus: _pt.bool
  };
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxInput
 *
 * Wraps an `<input/>` with a couple extra props that work with the combobox.
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#comboboxinput
 */


var ComboboxInput = /*#__PURE__*/forwardRefWithAs(function ComboboxInput(_ref2, forwardedRef) {
  var _ref2$as = _ref2.as,
      Comp = _ref2$as === void 0 ? "input" : _ref2$as,
      _ref2$selectOnClick = _ref2.selectOnClick,
      selectOnClick = _ref2$selectOnClick === void 0 ? false : _ref2$selectOnClick,
      _ref2$autocomplete = _ref2.autocomplete,
      autocomplete = _ref2$autocomplete === void 0 ? true : _ref2$autocomplete,
      onClick = _ref2.onClick,
      onChange = _ref2.onChange,
      onKeyDown = _ref2.onKeyDown,
      onBlur = _ref2.onBlur,
      onFocus = _ref2.onFocus,
      controlledValue = _ref2.value,
      props = _objectWithoutPropertiesLoose(_ref2, ["as", "selectOnClick", "autocomplete", "onClick", "onChange", "onKeyDown", "onBlur", "onFocus", "value"]);

  // https://github.com/reach/reach-ui/issues/464
  var _useRef = useRef(controlledValue),
      initialControlledValue = _useRef.current;

  var controlledValueChangedRef = useRef(false);
  useUpdateEffect(function () {
    controlledValueChangedRef.current = true;
  }, [controlledValue]);

  var _useContext = useContext(ComboboxContext),
      _useContext$data = _useContext.data,
      navigationValue = _useContext$data.navigationValue,
      value = _useContext$data.value,
      lastEventType = _useContext$data.lastEventType,
      inputRef = _useContext.inputRef,
      state = _useContext.state,
      transition = _useContext.transition,
      listboxId = _useContext.listboxId,
      autocompletePropRef = _useContext.autocompletePropRef,
      openOnFocus = _useContext.openOnFocus,
      isExpanded = _useContext.isExpanded,
      ariaLabel = _useContext.ariaLabel,
      ariaLabelledby = _useContext.ariaLabelledby;

  var ref = useForkedRef(inputRef, forwardedRef); // Because we close the List on blur, we need to track if the blur is
  // caused by clicking inside the list, and if so, don't close the List.

  var selectOnClickRef = useRef(false);
  var handleKeyDown = useKeyDown();
  var handleBlur = useBlur();
  var isControlled = controlledValue != null; // Layout effect should be SSR-safe here because we don't actually do
  // anything with this ref that involves rendering until after we've
  // let the client hydrate in nested components.

  useIsomorphicLayoutEffect(function () {
    autocompletePropRef.current = autocomplete;
  }, [autocomplete, autocompletePropRef]);
  var handleValueChange = useCallback(function (value) {
    if (value.trim() === "") {
      transition(CLEAR);
    } else if (value === initialControlledValue && !controlledValueChangedRef.current) {
      transition(INITIAL_CHANGE, {
        value: value
      });
    } else {
      transition(CHANGE, {
        value: value
      });
    }
  }, [initialControlledValue, transition]);
  useEffect(function () {
    // If they are controlling the value we still need to do our transitions,
    // so  we have this derived state to emulate onChange of the input as we
    // receive new `value`s ...[*]
    if (isControlled && controlledValue !== value && ( // https://github.com/reach/reach-ui/issues/481
    controlledValue.trim() === "" ? (value || "").trim() !== "" : true)) {
      handleValueChange(controlledValue);
    }
  }, [controlledValue, handleValueChange, isControlled, value]); // [*]... and when controlled, we don't trigger handleValueChange as the
  // user types, instead the developer controls it with the normal input
  // onChange prop

  function handleChange(event) {
    var value = event.target.value;

    if (!isControlled) {
      handleValueChange(value);
    }
  }

  function handleFocus() {
    if (selectOnClick) {
      selectOnClickRef.current = true;
    } // If we select an option with click, useFocusManagement will focus the
    // input, in those cases we don't want to cause the menu to open back up,
    // so we guard behind these states.


    if (openOnFocus && lastEventType !== SELECT_WITH_CLICK) {
      transition(FOCUS);
    }
  }

  function handleClick() {
    if (selectOnClickRef.current) {
      selectOnClickRef.current = false;
      inputRef.current.select();
    }
  }

  var inputValue = autocomplete && (state === NAVIGATING || state === INTERACTING) ? // When idle, we don't have a navigationValue on ArrowUp/Down
  navigationValue || controlledValue || value : controlledValue || value;
  return React__default.createElement(Comp, Object.assign({
    "aria-activedescendant": navigationValue ? String(makeHash(navigationValue)) : undefined,
    "aria-autocomplete": "both",
    "aria-controls": listboxId,
    "aria-expanded": isExpanded,
    "aria-haspopup": "listbox",
    "aria-label": ariaLabel,
    "aria-labelledby": ariaLabel ? undefined : ariaLabelledby,
    role: "combobox"
  }, props, {
    "data-reach-combobox-input": "",
    ref: ref,
    onBlur: wrapEvent(onBlur, handleBlur),
    onChange: wrapEvent(onChange, handleChange),
    onClick: wrapEvent(onClick, handleClick),
    onFocus: wrapEvent(onFocus, handleFocus),
    onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
    value: inputValue || ""
  }));
});

if (process.env.NODE_ENV !== "production") {
  ComboboxInput.displayName = "ComboboxInput";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxPopover
 *
 * Contains the popup that renders the list. Because some UI needs to render
 * more than the list in the popup, you need to render one of these around the
 * list. For example, maybe you want to render the number of results suggested.
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#comboboxpopover
 */


var ComboboxPopover = /*#__PURE__*/forwardRef(function ComboboxPopover(_ref3, forwardedRef) {
  var children = _ref3.children,
      _ref3$portal = _ref3.portal,
      portal = _ref3$portal === void 0 ? true : _ref3$portal,
      onKeyDown = _ref3.onKeyDown,
      onBlur = _ref3.onBlur,
      props = _objectWithoutPropertiesLoose(_ref3, ["children", "portal", "onKeyDown", "onBlur"]);

  var _useContext2 = useContext(ComboboxContext),
      popoverRef = _useContext2.popoverRef,
      inputRef = _useContext2.inputRef,
      isExpanded = _useContext2.isExpanded;

  var ref = useForkedRef(popoverRef, forwardedRef);
  var handleKeyDown = useKeyDown();
  var handleBlur = useBlur();
  var sharedProps = {
    "data-reach-combobox-popover": "",
    onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
    onBlur: wrapEvent(onBlur, handleBlur),
    // Instead of conditionally rendering the popover we use the `hidden` prop
    // because we don't want to unmount on close (from escape or onSelect).
    // However, the developer can conditionally render the ComboboxPopover if
    // they do want to cause mount/unmount based on the app's own data (like
    // results.length or whatever).
    hidden: !isExpanded,
    tabIndex: -1,
    children: children
  };
  return portal ? React__default.createElement(Popover, Object.assign({}, props, {
    // @ts-ignore
    ref: ref,
    position: positionMatchWidth,
    targetRef: inputRef
  }, sharedProps)) : React__default.createElement("div", Object.assign({
    ref: ref
  }, props, sharedProps));
});

if (process.env.NODE_ENV !== "production") {
  ComboboxPopover.displayName = "ComboboxPopover";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxList
 *
 * Contains the `ComboboxOption` elements and sets up the proper aria attributes
 * for the list.
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#comboboxlist
 */


var ComboboxList = /*#__PURE__*/forwardRefWithAs(function ComboboxList(_ref4, forwardedRef) {
  var _ref4$persistSelectio = _ref4.persistSelection,
      persistSelection = _ref4$persistSelectio === void 0 ? false : _ref4$persistSelectio,
      _ref4$as = _ref4.as,
      Comp = _ref4$as === void 0 ? "ul" : _ref4$as,
      props = _objectWithoutPropertiesLoose(_ref4, ["persistSelection", "as"]);

  var _useContext3 = useContext(ComboboxContext),
      persistSelectionRef = _useContext3.persistSelectionRef,
      listboxId = _useContext3.listboxId;

  if (persistSelection) {
    persistSelectionRef.current = true;
  }

  return React__default.createElement(Comp, Object.assign({
    role: "listbox"
  }, props, {
    ref: forwardedRef,
    "data-reach-combobox-list": "",
    id: listboxId
  }));
});

if (process.env.NODE_ENV !== "production") {
  ComboboxList.displayName = "ComboboxList";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxOption
 *
 * An option that is suggested to the user as they interact with the combobox.
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#comboboxoption
 */


var ComboboxOption = /*#__PURE__*/forwardRefWithAs(function ComboboxOption(_ref5, forwardedRef) {
  var _ref5$as = _ref5.as,
      Comp = _ref5$as === void 0 ? "li" : _ref5$as,
      children = _ref5.children,
      value = _ref5.value,
      onClick = _ref5.onClick,
      props = _objectWithoutPropertiesLoose(_ref5, ["as", "children", "value", "onClick"]);

  var _useContext4 = useContext(ComboboxContext),
      onSelect = _useContext4.onSelect,
      navigationValue = _useContext4.data.navigationValue,
      transition = _useContext4.transition;

  var ownRef = useRef(null);
  var ref = useForkedRef(forwardedRef, ownRef);
  var index = useDescendant({
    element: ownRef.current,
    value: value
  }, ComboboxDescendantContext);
  var isActive = navigationValue === value;

  var handleClick = function handleClick() {
    onSelect && onSelect(value);
    transition(SELECT_WITH_CLICK, {
      value: value
    });
  };

  return React__default.createElement(OptionContext.Provider, {
    value: {
      value: value,
      index: index
    }
  }, React__default.createElement(Comp, Object.assign({
    "aria-selected": isActive,
    role: "option"
  }, props, {
    "data-reach-combobox-option": "",
    ref: ref,
    id: String(makeHash(value)),
    "data-highlighted": isActive ? "" : undefined,
    // Without this the menu will close from `onBlur`, but with it the
    // element can be `document.activeElement` and then our focus checks in
    // onBlur will work as intended
    tabIndex: -1,
    onClick: wrapEvent(onClick, handleClick),
    children: children || React__default.createElement(ComboboxOptionText, null)
  })));
});

if (process.env.NODE_ENV !== "production") {
  ComboboxOption.displayName = "ComboboxOption";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxOptionText
 *
 * Renders the value of a `ComboboxOption` as text but with spans wrapping the
 * matching and non-matching segments of text.
 *
 * We don't forwardRef or spread props because we render multiple spans or null,
 * should be fine 🤙
 *
 * @example
 *   <ComboboxOption value="Seattle">
 *     🌧 <ComboboxOptionText />
 *   </ComboboxOption>
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#comboboxoptiontext
 */


function ComboboxOptionText() {
  var _useContext5 = useContext(OptionContext),
      value = _useContext5.value;

  var _useContext6 = useContext(ComboboxContext),
      contextValue = _useContext6.data.value;

  var results = useMemo(function () {
    return dist_1({
      searchWords: escapeRegexp(contextValue || "").split(/\s+/),
      textToHighlight: value
    });
  }, [contextValue, value]);
  return React__default.createElement(React__default.Fragment, null, results.length ? results.map(function (result, index) {
    var str = value.slice(result.start, result.end);
    return React__default.createElement("span", {
      key: index,
      "data-user-value": result.highlight ? true : undefined,
      "data-suggested-value": result.highlight ? undefined : true
    }, str);
  }) : value);
}

if (process.env.NODE_ENV !== "production") {
  ComboboxOptionText.displayName = "ComboboxOptionText";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * ComboboxButton
 */


var ComboboxButton = /*#__PURE__*/forwardRefWithAs(function ComboboxButton(_ref6, forwardedRef) {
  var _ref6$as = _ref6.as,
      Comp = _ref6$as === void 0 ? "button" : _ref6$as,
      onClick = _ref6.onClick,
      onKeyDown = _ref6.onKeyDown,
      props = _objectWithoutPropertiesLoose(_ref6, ["as", "onClick", "onKeyDown"]);

  var _useContext7 = useContext(ComboboxContext),
      transition = _useContext7.transition,
      state = _useContext7.state,
      buttonRef = _useContext7.buttonRef,
      listboxId = _useContext7.listboxId,
      isExpanded = _useContext7.isExpanded;

  var ref = useForkedRef(buttonRef, forwardedRef);
  var handleKeyDown = useKeyDown();

  var handleClick = function handleClick() {
    if (state === IDLE) {
      transition(OPEN_WITH_BUTTON);
    } else {
      transition(CLOSE_WITH_BUTTON);
    }
  };

  return React__default.createElement(Comp, Object.assign({
    "aria-controls": listboxId,
    "aria-haspopup": "listbox",
    "aria-expanded": isExpanded
  }, props, {
    "data-reach-combobox-button": "",
    ref: ref,
    onClick: wrapEvent(onClick, handleClick),
    onKeyDown: wrapEvent(onKeyDown, handleKeyDown)
  }));
});

if (process.env.NODE_ENV !== "production") {
  ComboboxButton.displayName = "ComboboxButton";
} ////////////////////////////////////////////////////////////////////////////////

/**
 * Move focus back to the input if we start navigating w/ the
 * keyboard after focus has moved to any focusable content in
 * the popup.
 *
 * @param lastEventType
 * @param inputRef
 */


function useFocusManagement(lastEventType, inputRef) {
  // useLayoutEffect so that the cursor goes to the end of the input instead
  // of awkwardly at the beginning, unclear to me why 🤷‍♂️
  //
  // Should be safe to use here since we're just focusing an input.
  useIsomorphicLayoutEffect(function () {
    if (lastEventType === NAVIGATE || lastEventType === ESCAPE || lastEventType === SELECT_WITH_CLICK || lastEventType === OPEN_WITH_BUTTON) {
      inputRef.current.focus();
    }
  }, [inputRef, lastEventType]);
}
/**
 * We want the same events when the input or the popup have focus (HOW COOL ARE
 * HOOKS BTW?) This is probably the hairiest piece but it's not bad.
 */


function useKeyDown() {
  var _useContext8 = useContext(ComboboxContext),
      navigationValue = _useContext8.data.navigationValue,
      onSelect = _useContext8.onSelect,
      state = _useContext8.state,
      transition = _useContext8.transition,
      autocompletePropRef = _useContext8.autocompletePropRef,
      persistSelectionRef = _useContext8.persistSelectionRef;

  var options = useDescendants(ComboboxDescendantContext);
  return function handleKeyDown(event) {
    var index = options.findIndex(function (_ref7) {
      var value = _ref7.value;
      return value === navigationValue;
    });

    function getNextOption() {
      var atBottom = index === options.length - 1;

      if (atBottom) {
        if (autocompletePropRef.current) {
          // Go back to the value the user has typed because we are
          // autocompleting and they need to be able to get back to what
          // they had typed w/o having to backspace out.
          return null;
        } else {
          // cycle through
          return getFirstOption();
        }
      } else {
        // Go to the next item in the list
        return options[(index + 1) % options.length];
      }
    }

    function getPreviousOption() {
      var atTop = index === 0;

      if (atTop) {
        if (autocompletePropRef.current) {
          // Go back to the value the user has typed because we are
          // autocompleting and they need to be able to get back to what
          // they had typed w/o having to backspace out.
          return null;
        } else {
          // cycle through
          return getLastOption();
        }
      } else if (index === -1) {
        // displaying the user's value, so go select the last one
        return getLastOption();
      } else {
        // normal case, select previous
        return options[(index - 1 + options.length) % options.length];
      }
    }

    function getFirstOption() {
      return options[0];
    }

    function getLastOption() {
      return options[options.length - 1];
    }

    switch (event.key) {
      case "ArrowDown":
        // Don't scroll the page
        event.preventDefault();

        if (!options || !options.length) {
          return;
        }

        if (state === IDLE) {
          // Opening a closed list
          transition(NAVIGATE, {
            persistSelection: persistSelectionRef.current
          });
        } else {
          var next = getNextOption();
          transition(NAVIGATE, {
            value: next ? next.value : null
          });
        }

        break;
      // A lot of duplicate code with ArrowDown up next, I'm already over it.

      case "ArrowUp":
        // Don't scroll the page
        event.preventDefault();

        if (!options || options.length === 0) {
          return;
        }

        if (state === IDLE) {
          transition(NAVIGATE);
        } else {
          var prev = getPreviousOption();
          transition(NAVIGATE, {
            value: prev ? prev.value : null
          });
        }

        break;

      case "Home":
      case "PageUp":
        // Don't scroll the page
        event.preventDefault();

        if (!options || options.length === 0) {
          return;
        }

        if (state === IDLE) {
          transition(NAVIGATE);
        } else {
          transition(NAVIGATE, {
            value: getFirstOption().value
          });
        }

        break;

      case "End":
      case "PageDown":
        // Don't scroll the page
        event.preventDefault();

        if (!options || options.length === 0) {
          return;
        }

        if (state === IDLE) {
          transition(NAVIGATE);
        } else {
          transition(NAVIGATE, {
            value: getLastOption().value
          });
        }

        break;

      case "Escape":
        if (state !== IDLE) {
          transition(ESCAPE);
        }

        break;

      case "Enter":
        if (state === NAVIGATING && navigationValue !== null) {
          // don't want to submit forms
          event.preventDefault();
          onSelect && onSelect(navigationValue);
          transition(SELECT_WITH_KEYBOARD);
        }

        break;
    }
  };
}

function useBlur() {
  var _useContext9 = useContext(ComboboxContext),
      state = _useContext9.state,
      transition = _useContext9.transition,
      popoverRef = _useContext9.popoverRef,
      inputRef = _useContext9.inputRef,
      buttonRef = _useContext9.buttonRef;

  return function handleBlur() {
    var ownerDocument = getOwnerDocument(inputRef.current) || document;
    requestAnimationFrame(function () {
      // we on want to close only if focus propss outside the combobox
      if (ownerDocument.activeElement !== inputRef.current && ownerDocument.activeElement !== buttonRef.current && popoverRef.current) {
        if (popoverRef.current.contains(ownerDocument.activeElement)) {
          // focus landed inside the combobox, keep it open
          if (state !== INTERACTING) {
            transition(INTERACT);
          }
        } else {
          // focus landed outside the combobox, close it.
          transition(BLUR);
        }
      }
    });
  };
}
/**
 * This manages transitions between states with a built in reducer to manage
 * the data that goes with those transitions.
 *
 * @param chart
 * @param reducer
 * @param initialData
 */


function useReducerMachine(chart, reducer, initialData) {
  var _useState = useState(chart.initial),
      state = _useState[0],
      setState = _useState[1];

  var _useReducer = useReducer(reducer, initialData),
      data = _useReducer[0],
      dispatch = _useReducer[1];

  var transition = function transition(event, payload) {
    if (payload === void 0) {
      payload = {};
    }

    var currentState = chart.states[state];
    var nextState = currentState && currentState.on[event];

    if (nextState) {
      dispatch(_extends({
        type: event,
        state: state,
        nextState: state
      }, payload));
      setState(nextState);
      return;
    }
  };

  return [state, data, transition];
}
/**
 * We don't want to track the active descendant with indexes because nothing is
 * more annoying in a combobox than having it change values RIGHT AS YOU HIT
 * ENTER. That only happens if you use the index as your data, rather than
 * *your data as your data*. We use this to generate a unique ID based on the
 * value of each item.  This function is short, sweet, and good enough™ (I also
 * don't know how it works, tbqh)
 *
 * @see https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript
 * @param str
 */


var makeHash = function makeHash(str) {
  var hash = 0;

  if (str.length === 0) {
    return hash;
  }

  for (var i = 0; i < str.length; i++) {
    var _char = str.charCodeAt(i);

    hash = (hash << 5) - hash + _char;
    hash = hash & hash;
  }

  return hash;
};
/**
 * Escape regexp special characters in `str`
 *
 * @see https://github.com/component/escape-regexp/blob/5ce923c1510c9802b3da972c90b6861dd2829b6b/index.js
 * @param str
 */


function escapeRegexp(str) {
  return String(str).replace(/([.*+?=^!:${}()|[\]/\\])/g, "\\$1");
} ////////////////////////////////////////////////////////////////////////////////

/**
 * A hook that exposes data for a given `Combobox` component to its descendants.
 *
 * @see Docs https://reacttraining.com/reach-ui/combobox#usecomboboxcontext
 */

function useComboboxContext() {
  var _useContext10 = useContext(ComboboxContext),
      isExpanded = _useContext10.isExpanded,
      comboboxId = _useContext10.comboboxId;

  return useMemo(function () {
    return {
      id: comboboxId,
      isExpanded: isExpanded
    };
  }, [comboboxId, isExpanded]);
}

export { Combobox, ComboboxButton, ComboboxInput, ComboboxList, ComboboxOption, ComboboxOptionText, ComboboxPopover, escapeRegexp, useComboboxContext };
//# sourceMappingURL=combobox.esm.esm.js.map
