'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var React = require('react');
var debounce = require('../../utilities/debounce.js');
var useToggle = require('../../utilities/use-toggle.js');
var types = require('../../types.js');
var shared = require('../shared.js');
var Listbox$1 = require('./Listbox.scss.js');
var hooks$1 = require('../../utilities/combobox/hooks.js');
var utilities = require('../../utilities/listbox/utilities.js');
var context = require('../../utilities/listbox/context.js');
var TextOption = require('./components/TextOption/TextOption.js');
var Loading = require('./components/Loading/Loading.js');
var Section = require('./components/Section/Section.js');
var Header = require('./components/Header/Header.js');
var Action = require('./components/Action/Action.js');
var hooks = require('../../utilities/unique-id/hooks.js');
var KeypressListener = require('../KeypressListener/KeypressListener.js');
var Text = require('../Text/Text.js');
var Option = require('./components/Option/Option.js');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var React__default = /*#__PURE__*/_interopDefaultLegacy(React);

exports.AutoSelection = void 0;

(function (AutoSelection) {
  AutoSelection["FirstSelected"] = "FIRST_SELECTED";
  AutoSelection["First"] = "FIRST";
  AutoSelection["None"] = "NONE";
})(exports.AutoSelection || (exports.AutoSelection = {}));

const OPTION_SELECTOR = '[data-listbox-option]';
const OPTION_VALUE_ATTRIBUTE = 'data-listbox-option-value';
const OPTION_ACTION_ATTRIBUTE = 'data-listbox-option-action';
const OPTION_FOCUS_ATTRIBUTE = 'data-focused';
function Listbox({
  children,
  autoSelection = exports.AutoSelection.FirstSelected,
  enableKeyboardControl,
  accessibilityLabel,
  customListId,
  onSelect,
  onActiveOptionChange
}) {
  const [loading, setLoading] = React.useState();
  const [activeOption, setActiveOption] = React.useState();
  const [lazyLoading, setLazyLoading] = React.useState(false);
  const [currentOptions, setCurrentOptions] = React.useState([]);
  const {
    value: keyboardEventsEnabled,
    setTrue: enableKeyboardEvents,
    setFalse: disableKeyboardEvents
  } = useToggle.useToggle(Boolean(enableKeyboardControl));
  const uniqueId = hooks.useUniqueId('Listbox');
  const listId = customListId || uniqueId;
  const scrollableRef = React.useRef(null);
  const listboxRef = React.useRef(null);
  const {
    listboxId,
    textFieldLabelId,
    textFieldFocused,
    willLoadMoreOptions,
    setActiveOptionId,
    setListboxId,
    onOptionSelected,
    onKeyToBottom
  } = hooks$1.useComboboxListbox();
  const inCombobox = Boolean(setActiveOptionId);
  React.useEffect(() => {
    if (setListboxId && !listboxId) {
      setListboxId(listId);
    }
  }, [setListboxId, listboxId, listId]);
  const getNavigableOptions = React.useCallback(() => {
    if (!listboxRef.current) {
      return [];
    }

    return [...new Set(listboxRef.current.querySelectorAll(OPTION_SELECTOR))];
  }, []);
  const getFirstNavigableOption = React.useCallback(currentOptions => {
    const hasSelectedOptions = currentOptions.some(option => option.getAttribute('aria-selected') === 'true');
    let elementIndex = 0;
    const element = currentOptions.find((option, index) => {
      const isInteractable = option.getAttribute('aria-disabled') !== 'true';
      let isFirstNavigableOption;

      if (hasSelectedOptions && autoSelection === exports.AutoSelection.FirstSelected) {
        const isSelected = option.getAttribute('aria-selected') === 'true';
        isFirstNavigableOption = isSelected && isInteractable;
      } else {
        isFirstNavigableOption = isInteractable;
      }

      if (isFirstNavigableOption) elementIndex = index;
      return isFirstNavigableOption;
    });
    if (!element) return;
    return {
      element,
      index: elementIndex
    };
  }, [autoSelection]);
  const handleScrollIntoView = React.useCallback(option => {
    const {
      current: scrollable
    } = scrollableRef;

    if (scrollable) {
      utilities.scrollOptionIntoView(option.element, scrollable);
    }
  }, []);
  const handleScrollIntoViewDebounced = debounce.debounce(handleScrollIntoView, 50);
  const handleKeyToBottom = React.useCallback(() => {
    if (onKeyToBottom) {
      setLazyLoading(true);
      return Promise.resolve(onKeyToBottom());
    }
  }, [onKeyToBottom]);
  const handleChangeActiveOption = React.useCallback(nextOption => {
    if (!nextOption) return setActiveOption(undefined);
    activeOption === null || activeOption === void 0 ? void 0 : activeOption.element.removeAttribute(OPTION_FOCUS_ATTRIBUTE);
    nextOption.element.setAttribute(OPTION_FOCUS_ATTRIBUTE, 'true');
    handleScrollIntoViewDebounced(nextOption);
    setActiveOption(nextOption);
    setActiveOptionId === null || setActiveOptionId === void 0 ? void 0 : setActiveOptionId(nextOption.domId);
    onActiveOptionChange === null || onActiveOptionChange === void 0 ? void 0 : onActiveOptionChange(nextOption.value, nextOption.domId);
  }, [activeOption, setActiveOptionId, onActiveOptionChange, handleScrollIntoViewDebounced]);
  const getFormattedOption = React.useCallback((element, index) => {
    return {
      element,
      index,
      domId: element.id,
      value: element.getAttribute(OPTION_VALUE_ATTRIBUTE) || '',
      disabled: element.getAttribute('aria-disabled') === 'true',
      isAction: element.getAttribute(OPTION_ACTION_ATTRIBUTE) === 'true'
    };
  }, []);
  const resetActiveOption = React.useCallback(() => {
    var _nextOption, _nextOption2, _nextOption3;

    let nextOption;
    const nextOptions = getNavigableOptions();
    const nextActiveOption = getFirstNavigableOption(nextOptions);

    if (nextOptions.length === 0 && currentOptions.length > 0) {
      setCurrentOptions(nextOptions);
      handleChangeActiveOption();
      return;
    }

    if (nextActiveOption) {
      const {
        element,
        index
      } = nextActiveOption;
      nextOption = getFormattedOption(element, index);
    }

    const optionIsAlreadyActive = activeOption !== undefined && ((_nextOption = nextOption) === null || _nextOption === void 0 ? void 0 : _nextOption.domId) === (activeOption === null || activeOption === void 0 ? void 0 : activeOption.domId);
    const actionContentHasUpdated = (activeOption === null || activeOption === void 0 ? void 0 : activeOption.isAction) && ((_nextOption2 = nextOption) === null || _nextOption2 === void 0 ? void 0 : _nextOption2.isAction) && ((_nextOption3 = nextOption) === null || _nextOption3 === void 0 ? void 0 : _nextOption3.value) !== (activeOption === null || activeOption === void 0 ? void 0 : activeOption.value);
    const currentValues = currentOptions.map(option => option.getAttribute(OPTION_VALUE_ATTRIBUTE));
    const nextValues = nextOptions.map(option => option.getAttribute(OPTION_VALUE_ATTRIBUTE));
    const listIsUnchanged = nextValues.length === currentValues.length && nextValues.every((value, index) => {
      return currentValues[index] === value;
    });
    const listIsAppended = currentValues.length !== 0 && nextValues.length > currentValues.length && currentValues.every((value, index) => {
      return nextValues[index] === value;
    });

    if (listIsUnchanged) {
      if (optionIsAlreadyActive && actionContentHasUpdated) {
        setCurrentOptions(nextOptions);
        handleChangeActiveOption(nextOption);
      }

      return;
    }

    if (listIsAppended) {
      setCurrentOptions(nextOptions);
      return;
    }

    setCurrentOptions(nextOptions);

    if (lazyLoading) {
      setLazyLoading(false);
      return;
    }

    handleChangeActiveOption(nextOption);
  }, [lazyLoading, currentOptions, activeOption, getFirstNavigableOption, getNavigableOptions, getFormattedOption, handleChangeActiveOption]);
  React.useEffect(() => {
    if (autoSelection !== exports.AutoSelection.None && !loading && children && React.Children.count(children) > 0) {
      resetActiveOption();
    }
  }, [children, autoSelection, activeOption, loading, resetActiveOption]);
  React.useEffect(() => {
    if (listboxRef.current) {
      scrollableRef.current = listboxRef.current.closest(shared.scrollable.selector);
    }
  }, []);
  React.useEffect(() => {
    if (enableKeyboardControl && !keyboardEventsEnabled) {
      enableKeyboardEvents();
    }
  }, [enableKeyboardControl, keyboardEventsEnabled, enableKeyboardEvents]);
  const onOptionSelect = React.useCallback(option => {
    handleChangeActiveOption(option);
    if (onOptionSelected) onOptionSelected();
    if (onSelect) onSelect(option.value);
  }, [handleChangeActiveOption, onSelect, onOptionSelected]);
  const getNextIndex = React.useCallback((currentIndex, lastIndex, direction) => {
    let nextIndex;

    if (direction === 'down') {
      if (currentIndex === lastIndex) {
        nextIndex = willLoadMoreOptions ? currentIndex + 1 : 0;
      } else {
        nextIndex = currentIndex + 1;
      }
    } else {
      nextIndex = currentIndex === 0 ? lastIndex : currentIndex - 1;
    }

    return nextIndex;
  }, [willLoadMoreOptions]);
  const getNextValidOption = React.useCallback(async key => {
    const lastIndex = currentOptions.length - 1;
    let currentIndex = (activeOption === null || activeOption === void 0 ? void 0 : activeOption.index) || 0;
    let nextIndex = 0;
    let element = activeOption === null || activeOption === void 0 ? void 0 : activeOption.element;
    let totalOptions = -1;

    if (!activeOption && autoSelection === exports.AutoSelection.None) {
      const nextOptions = getNavigableOptions();
      const nextActiveOption = getFirstNavigableOption(nextOptions);
      setCurrentOptions(nextOptions);
      return {
        element: nextActiveOption === null || nextActiveOption === void 0 ? void 0 : nextActiveOption.element,
        nextIndex: (nextActiveOption === null || nextActiveOption === void 0 ? void 0 : nextActiveOption.index) || 0
      };
    }

    while (totalOptions++ < lastIndex) {
      var _element;

      nextIndex = getNextIndex(currentIndex, lastIndex, key);
      element = currentOptions[nextIndex];
      const triggerLazyLoad = nextIndex >= lastIndex;
      const isDisabled = ((_element = element) === null || _element === void 0 ? void 0 : _element.getAttribute('aria-disabled')) === 'true';

      if (triggerLazyLoad && willLoadMoreOptions) {
        await handleKeyToBottom();
      }

      if (isDisabled) {
        currentIndex = nextIndex;
        element = undefined;
        continue;
      }

      break;
    }

    return {
      element,
      nextIndex
    };
  }, [autoSelection, currentOptions, activeOption, willLoadMoreOptions, getNextIndex, handleKeyToBottom, getFirstNavigableOption, getNavigableOptions]);
  const handleArrow = React.useCallback(async (type, event) => {
    event.preventDefault();
    const {
      element,
      nextIndex
    } = await getNextValidOption(type);
    if (!element) return;
    const nextOption = getFormattedOption(element, nextIndex);
    handleChangeActiveOption(nextOption);
  }, [getFormattedOption, getNextValidOption, handleChangeActiveOption]);
  const handleDownArrow = React.useCallback(event => {
    handleArrow('down', event);
  }, [handleArrow]);
  const handleUpArrow = React.useCallback(event => {
    handleArrow('up', event);
  }, [handleArrow]);
  const handleEnter = React.useCallback(event => {
    event.preventDefault();
    event.stopPropagation();

    if (activeOption) {
      onOptionSelect(activeOption);
    }
  }, [activeOption, onOptionSelect]);
  const handleFocus = React.useCallback(() => {
    if (enableKeyboardControl) return;
    enableKeyboardEvents();
  }, [enableKeyboardControl, enableKeyboardEvents]);
  const handleBlur = React.useCallback(event => {
    event.stopPropagation();

    if (keyboardEventsEnabled) {
      const nextActiveOption = getFirstNavigableOption(currentOptions);

      if (nextActiveOption) {
        const {
          element,
          index
        } = nextActiveOption;
        const nextOption = getFormattedOption(element, index);
        handleChangeActiveOption(nextOption);
      }
    }

    if (enableKeyboardControl) return;
    disableKeyboardEvents();
  }, [enableKeyboardControl, currentOptions, keyboardEventsEnabled, disableKeyboardEvents, getFirstNavigableOption, getFormattedOption, handleChangeActiveOption]);
  const listeners = keyboardEventsEnabled || textFieldFocused ? /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(KeypressListener.KeypressListener, {
    keyEvent: "keydown",
    keyCode: types.Key.DownArrow,
    handler: handleDownArrow
  }), /*#__PURE__*/React__default["default"].createElement(KeypressListener.KeypressListener, {
    keyEvent: "keydown",
    keyCode: types.Key.UpArrow,
    handler: handleUpArrow
  }), /*#__PURE__*/React__default["default"].createElement(KeypressListener.KeypressListener, {
    keyEvent: "keydown",
    keyCode: types.Key.Enter,
    handler: handleEnter
  })) : null;
  const listboxContext = React.useMemo(() => ({
    onOptionSelect,
    setLoading
  }), [onOptionSelect]);
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, listeners, /*#__PURE__*/React__default["default"].createElement(Text.Text, {
    as: "span",
    visuallyHidden: true
  }, /*#__PURE__*/React__default["default"].createElement("div", {
    "aria-live": "polite"
  }, loading ? loading : null)), /*#__PURE__*/React__default["default"].createElement(context.ListboxContext.Provider, {
    value: listboxContext
  }, /*#__PURE__*/React__default["default"].createElement(context.WithinListboxContext.Provider, {
    value: true
  }, children ? /*#__PURE__*/React__default["default"].createElement("ul", {
    tabIndex: 0,
    role: "listbox",
    className: Listbox$1["default"].Listbox,
    "aria-label": inCombobox ? undefined : accessibilityLabel,
    "aria-labelledby": textFieldLabelId,
    "aria-busy": Boolean(loading),
    "aria-activedescendant": activeOption && activeOption.domId,
    id: listId,
    onFocus: inCombobox ? undefined : handleFocus,
    onBlur: inCombobox ? undefined : handleBlur,
    ref: listboxRef
  }, children) : null)));
}
Listbox.Option = Option.Option;
Listbox.TextOption = TextOption.TextOption;
Listbox.Loading = Loading.Loading;
Listbox.Section = Section.Section;
Listbox.Header = Header.Header;
Listbox.Action = Action.Action;

exports.Listbox = Listbox;
