"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _clsx = _interopRequireDefault(require("clsx"));
var _i18n = require("@wordpress/i18n");
var _element = require("@wordpress/element");
var _compose = require("@wordpress/compose");
var _a11y = require("@wordpress/a11y");
var _icons = require("@wordpress/icons");
var _styles = require("./styles");
var _tokenInput = _interopRequireDefault(require("../form-token-field/token-input"));
var _suggestionsList = _interopRequireDefault(require("../form-token-field/suggestions-list"));
var _baseControl = _interopRequireDefault(require("../base-control"));
var _button = _interopRequireDefault(require("../button"));
var _flex = require("../flex");
var _withFocusOutside = _interopRequireDefault(require("../higher-order/with-focus-outside"));
var _hooks = require("../utils/hooks");
var _strings = require("../utils/strings");
var _useDeprecatedProps = require("../utils/use-deprecated-props");
var _withIgnoreImeEvents = require("../utils/with-ignore-ime-events");
var _jsxRuntime = require("react/jsx-runtime");
/**
 * External dependencies
 */

/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

const noop = () => {};
const DetectOutside = (0, _withFocusOutside.default)(class extends _element.Component {
  handleFocusOutside(event) {
    this.props.onFocusOutside(event);
  }
  render() {
    return this.props.children;
  }
});
const getIndexOfMatchingSuggestion = (selectedSuggestion, matchingSuggestions) => selectedSuggestion === null ? -1 : matchingSuggestions.indexOf(selectedSuggestion);

/**
 * `ComboboxControl` is an enhanced version of a [`SelectControl`](../select-control/README.md) with the addition of
 * being able to search for options using a search input.
 *
 * ```jsx
 * import { ComboboxControl } from '@wordpress/components';
 * import { useState } from '@wordpress/element';
 *
 * const options = [
 * 	{
 * 		value: 'small',
 * 		label: 'Small',
 * 	},
 * 	{
 * 		value: 'normal',
 * 		label: 'Normal',
 * 		disabled: true,
 * 	},
 * 	{
 * 		value: 'large',
 * 		label: 'Large',
 * 		disabled: false,
 * 	},
 * ];
 *
 * function MyComboboxControl() {
 * 	const [ fontSize, setFontSize ] = useState();
 * 	const [ filteredOptions, setFilteredOptions ] = useState( options );
 * 	return (
 * 		<ComboboxControl
 * 			label="Font Size"
 * 			value={ fontSize }
 * 			onChange={ setFontSize }
 * 			options={ filteredOptions }
 * 			onFilterValueChange={ ( inputValue ) =>
 * 				setFilteredOptions(
 * 					options.filter( ( option ) =>
 * 						option.label
 * 							.toLowerCase()
 * 							.startsWith( inputValue.toLowerCase() )
 * 					)
 * 				)
 * 			}
 * 		/>
 * 	);
 * }
 * ```
 */
function ComboboxControl(props) {
  var _currentOption$label;
  const {
    __nextHasNoMarginBottom = false,
    __next40pxDefaultSize = false,
    value: valueProp,
    label,
    options,
    onChange: onChangeProp,
    onFilterValueChange = noop,
    hideLabelFromVision,
    help,
    allowReset = true,
    className,
    messages = {
      selected: (0, _i18n.__)('Item selected.')
    },
    __experimentalRenderItem,
    expandOnFocus = true
  } = (0, _useDeprecatedProps.useDeprecated36pxDefaultSizeProp)(props);
  const [value, setValue] = (0, _hooks.useControlledValue)({
    value: valueProp,
    onChange: onChangeProp
  });
  const currentOption = options.find(option => option.value === value);
  const currentLabel = (_currentOption$label = currentOption?.label) !== null && _currentOption$label !== void 0 ? _currentOption$label : '';
  // Use a custom prefix when generating the `instanceId` to avoid having
  // duplicate input IDs when rendering this component and `FormTokenField`
  // in the same page (see https://github.com/WordPress/gutenberg/issues/42112).
  const instanceId = (0, _compose.useInstanceId)(ComboboxControl, 'combobox-control');
  const [selectedSuggestion, setSelectedSuggestion] = (0, _element.useState)(currentOption || null);
  const [isExpanded, setIsExpanded] = (0, _element.useState)(false);
  const [inputHasFocus, setInputHasFocus] = (0, _element.useState)(false);
  const [inputValue, setInputValue] = (0, _element.useState)('');
  const inputContainer = (0, _element.useRef)(null);
  const matchingSuggestions = (0, _element.useMemo)(() => {
    const startsWithMatch = [];
    const containsMatch = [];
    const match = (0, _strings.normalizeTextString)(inputValue);
    options.forEach(option => {
      const index = (0, _strings.normalizeTextString)(option.label).indexOf(match);
      if (index === 0) {
        startsWithMatch.push(option);
      } else if (index > 0) {
        containsMatch.push(option);
      }
    });
    return startsWithMatch.concat(containsMatch);
  }, [inputValue, options]);
  const onSuggestionSelected = newSelectedSuggestion => {
    if (newSelectedSuggestion.disabled) {
      return;
    }
    setValue(newSelectedSuggestion.value);
    (0, _a11y.speak)(messages.selected, 'assertive');
    setSelectedSuggestion(newSelectedSuggestion);
    setInputValue('');
    setIsExpanded(false);
  };
  const handleArrowNavigation = (offset = 1) => {
    const index = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions);
    let nextIndex = index + offset;
    if (nextIndex < 0) {
      nextIndex = matchingSuggestions.length - 1;
    } else if (nextIndex >= matchingSuggestions.length) {
      nextIndex = 0;
    }
    setSelectedSuggestion(matchingSuggestions[nextIndex]);
    setIsExpanded(true);
  };
  const onKeyDown = (0, _withIgnoreImeEvents.withIgnoreIMEEvents)(event => {
    let preventDefault = false;
    if (event.defaultPrevented) {
      return;
    }
    switch (event.code) {
      case 'Enter':
        if (selectedSuggestion) {
          onSuggestionSelected(selectedSuggestion);
          preventDefault = true;
        }
        break;
      case 'ArrowUp':
        handleArrowNavigation(-1);
        preventDefault = true;
        break;
      case 'ArrowDown':
        handleArrowNavigation(1);
        preventDefault = true;
        break;
      case 'Escape':
        setIsExpanded(false);
        setSelectedSuggestion(null);
        preventDefault = true;
        break;
      default:
        break;
    }
    if (preventDefault) {
      event.preventDefault();
    }
  });
  const onBlur = () => {
    setInputHasFocus(false);
  };
  const onFocus = () => {
    setInputHasFocus(true);
    if (expandOnFocus) {
      setIsExpanded(true);
    }
    onFilterValueChange('');
    setInputValue('');
  };
  const onClick = () => {
    setIsExpanded(true);
  };
  const onFocusOutside = () => {
    setIsExpanded(false);
  };
  const onInputChange = event => {
    const text = event.value;
    setInputValue(text);
    onFilterValueChange(text);
    if (inputHasFocus) {
      setIsExpanded(true);
    }
  };
  const handleOnReset = () => {
    setValue(null);
    inputContainer.current?.focus();
  };

  // Update current selections when the filter input changes.
  (0, _element.useEffect)(() => {
    const hasMatchingSuggestions = matchingSuggestions.length > 0;
    const hasSelectedMatchingSuggestions = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions) > 0;
    if (hasMatchingSuggestions && !hasSelectedMatchingSuggestions) {
      // If the current selection isn't present in the list of suggestions, then automatically select the first item from the list of suggestions.
      setSelectedSuggestion(matchingSuggestions[0]);
    }
  }, [matchingSuggestions, selectedSuggestion]);

  // Announcements.
  (0, _element.useEffect)(() => {
    const hasMatchingSuggestions = matchingSuggestions.length > 0;
    if (isExpanded) {
      const message = hasMatchingSuggestions ? (0, _i18n.sprintf)( /* translators: %d: number of results. */
      (0, _i18n._n)('%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', matchingSuggestions.length), matchingSuggestions.length) : (0, _i18n.__)('No results.');
      (0, _a11y.speak)(message, 'polite');
    }
  }, [matchingSuggestions, isExpanded]);

  // Disable reason: There is no appropriate role which describes the
  // input container intended accessible usability.
  // TODO: Refactor click detection to use blur to stop propagation.
  /* eslint-disable jsx-a11y/no-static-element-interactions */
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(DetectOutside, {
    onFocusOutside: onFocusOutside,
    children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_baseControl.default, {
      __nextHasNoMarginBottom: __nextHasNoMarginBottom,
      className: (0, _clsx.default)(className, 'components-combobox-control'),
      label: label,
      id: `components-form-token-input-${instanceId}`,
      hideLabelFromVision: hideLabelFromVision,
      help: help,
      children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
        className: "components-combobox-control__suggestions-container",
        tabIndex: -1,
        onKeyDown: onKeyDown,
        children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.InputWrapperFlex, {
          __next40pxDefaultSize: __next40pxDefaultSize,
          children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_flex.FlexBlock, {
            children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_tokenInput.default, {
              className: "components-combobox-control__input",
              instanceId: instanceId,
              ref: inputContainer,
              value: isExpanded ? inputValue : currentLabel,
              onFocus: onFocus,
              onBlur: onBlur,
              onClick: onClick,
              isExpanded: isExpanded,
              selectedSuggestionIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),
              onChange: onInputChange
            })
          }), allowReset && /*#__PURE__*/(0, _jsxRuntime.jsx)(_flex.FlexItem, {
            children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
              className: "components-combobox-control__reset",
              icon: _icons.closeSmall
              // Disable reason: Focus returns to input field when reset is clicked.
              // eslint-disable-next-line no-restricted-syntax
              ,
              disabled: !value,
              onClick: handleOnReset,
              label: (0, _i18n.__)('Reset')
            })
          })]
        }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_suggestionsList.default, {
          instanceId: instanceId
          // The empty string for `value` here is not actually used, but is
          // just a quick way to satisfy the TypeScript requirements of SuggestionsList.
          // See: https://github.com/WordPress/gutenberg/pull/47581/files#r1091089330
          ,
          match: {
            label: inputValue,
            value: ''
          },
          displayTransform: suggestion => suggestion.label,
          suggestions: matchingSuggestions,
          selectedIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),
          onHover: setSelectedSuggestion,
          onSelect: onSuggestionSelected,
          scrollIntoView: true,
          __experimentalRenderItem: __experimentalRenderItem
        })]
      })
    })
  });
  /* eslint-enable jsx-a11y/no-static-element-interactions */
}
var _default = exports.default = ComboboxControl;
//# sourceMappingURL=index.js.map