function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

import _pt from "prop-types";
var _excluded = ["ariaClearLabel", "clearInputText", "items", "label", "loading", "children", "className", "clearInputOnBlur", "clearSearchButton"];

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

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; }

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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

/**
 * https://www.levelaccess.com/differences-aria-1-0-1-1-changes-rolecombobox/
 * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
 * https://www.digitala11y.com/aria-autocomplete-properties/
 *
 * We have opted to retain the ARIA 1.0 markup pattern for comboboxes.
 * This was done because the ARIA 1.1 markup pattern triggers a different
 * behavior on containers with a role="combobox" attribute. WCAG refers to
 * this as a composite widget: https://www.w3.org/TR/wai-aria-1.1/#h-composite
 *
 * Our testing with screen readers, specifically JAWS, has been the deciding
 * factor in going back to the ARIA 1.0 markup pattern. There were a number
 * of conflicting interactions using the 1.1 markup pattern that felt like
 * an unacceptable regression of the user experience.
 */
import Downshift from 'downshift';
import Button from '../Button/Button';
import React from 'react';
import TextField from '../TextField/TextField';
import WrapperDiv from './WrapperDiv';
import classNames from 'classnames';
import { errorPlacementDefault } from '../flags';
import get from 'lodash/get';
import keepInputDownshiftStateReducer from './keepInputDownshiftStateReducer';
import uniqueId from 'lodash/uniqueId';
import { t } from '../i18n';

/**
 * Determine if a React component is a TextField
 * @param {React.Node} child - a React component
 * @return {Boolean} Is this a TextField component?
 */
function isTextField(child) {
  var componentName = get(child, 'type.displayName') || get(child, 'type.name'); // Check child.type first and as a fallback, check child.type.displayName follow by child.type.name

  return child && (child.type === TextField || componentName === 'TextField');
}

export var Autocomplete = /*#__PURE__*/function (_React$Component) {
  _inherits(Autocomplete, _React$Component);

  var _super = _createSuper(Autocomplete);

  function Autocomplete(props) {
    var _this;

    _classCallCheck(this, Autocomplete);

    _this = _super.call(this, props);

    _defineProperty(_assertThisInitialized(_this), "id", void 0);

    _defineProperty(_assertThisInitialized(_this), "labelId", void 0);

    _defineProperty(_assertThisInitialized(_this), "listboxId", void 0);

    _defineProperty(_assertThisInitialized(_this), "listboxContainerId", void 0);

    _defineProperty(_assertThisInitialized(_this), "listboxHeadingId", void 0);

    _this.id = _this.props.id || uniqueId('autocomplete_');
    _this.labelId = _this.props.labelId || uniqueId('autocomplete_label_');
    _this.listboxId = uniqueId('autocomplete_owned_listbox_');
    _this.listboxContainerId = uniqueId('autocomplete_owned_container_');
    _this.listboxHeadingId = uniqueId('autocomplete_header_');
    return _this;
  } // Autocomplete class properties


  _createClass(Autocomplete, [{
    key: "filterItems",
    value: function filterItems(items, inputValue, getInputProps, getItemProps, highlightedIndex) {
      var _this$props$noResults;

      // If we have results, create a mapped list
      if (items.length) {
        var itemToString = this.props.itemToString;
        return items.map(function (item, index) {
          var _item$children;

          return /*#__PURE__*/React.createElement("li", _extends({
            "aria-selected": highlightedIndex === index,
            className: classNames(item.className, 'ds-c-autocomplete__list-item', {
              'ds-c-autocomplete__list-item--active': highlightedIndex === index
            }),
            key: item.id,
            role: "option"
          }, getItemProps({
            item: item
          })), (_item$children = item.children) !== null && _item$children !== void 0 ? _item$children : itemToString(item));
        });
      } // If we're waiting for results to load, show the non-selected message


      if (this.props.loading) {
        var _this$props$loadingMe;

        return /*#__PURE__*/React.createElement("li", {
          "aria-selected": "false",
          className: "ds-c-autocomplete__list-item--message",
          role: "option"
        }, (_this$props$loadingMe = this.props.loadingMessage) !== null && _this$props$loadingMe !== void 0 ? _this$props$loadingMe : t('autocomplete.loadingMessage'));
      } // If we have no results, show the non-selected message


      return /*#__PURE__*/React.createElement("li", {
        "aria-selected": "false",
        className: "ds-c-autocomplete__list-item--message",
        role: "option"
      }, (_this$props$noResults = this.props.noResultsMessage) !== null && _this$props$noResults !== void 0 ? _this$props$noResults : t('autocomplete.noResultsMessage'));
    }
  }, {
    key: "renderChildren",
    value: function renderChildren(getInputProps, listboxOpen) {
      var _this2 = this;

      var isOpen = listboxOpen;
      var clearSearchButton = this.props.clearSearchButton; // Extend props on the TextField, by passing them
      // through Downshift's `getInputProps` method

      return React.Children.map(this.props.children, function (child) {
        if (isTextField(child)) {
          // The display of bottom placed errorMessages in TextField breaks the Autocomplete's UI design.
          // Add errorMessageClassName to fix the styles for bottom placed errors
          var bottomError = (child.props.errorPlacement === 'bottom' || errorPlacementDefault() === 'bottom') && child.props.errorMessage;
          var errorMessageClassName = bottomError ? classNames('ds-c-autocomplete__error-message', {
            'ds-c-autocomplete__error-message--clear-btn': clearSearchButton
          }, child.props.errorMessageClassName) : child.props.errorMessageClassName;
          var propOverrides = {
            'aria-autocomplete': 'list',
            'aria-controls': _this2.listboxId,
            'aria-expanded': isOpen,
            'aria-labelledby': null,
            'aria-owns': isOpen ? _this2.listboxId : null,
            autoComplete: _this2.props.autoCompleteLabel,
            errorMessageClassName: errorMessageClassName,
            focusTrigger: _this2.props.focusTrigger,
            id: _this2.id,
            inputRef: _this2.props.inputRef,
            labelId: _this2.labelId,
            onBlur: child.props.onBlur,
            onChange: child.props.onChange,
            onKeyDown: child.props.onKeyDown,
            role: 'combobox'
          };
          return /*#__PURE__*/React.cloneElement(child, getInputProps(propOverrides));
        }

        return child;
      });
    }
  }, {
    key: "render",
    value: function render() {
      var _this3 = this;

      var _this$props = this.props,
          ariaClearLabel = _this$props.ariaClearLabel,
          clearInputText = _this$props.clearInputText,
          items = _this$props.items,
          label = _this$props.label,
          loading = _this$props.loading,
          children = _this$props.children,
          className = _this$props.className,
          clearInputOnBlur = _this$props.clearInputOnBlur,
          clearSearchButton = _this$props.clearSearchButton,
          autocompleteProps = _objectWithoutProperties(_this$props, _excluded);

      var rootClassName = classNames('ds-u-clearfix', 'ds-c-autocomplete', className);

      if (clearInputOnBlur === false) {
        autocompleteProps.stateReducer = keepInputDownshiftStateReducer;
      }

      if (items) {
        // We allow items that aren't technically results to be rendered as items in the list, such as
        // a button for viewing all results, but these non-result items should not be counted in the
        // accessibility messages as results. It is not enough to set downshift's `itemCount` property
        // because it will actually make any remaining items past the `itemCount` unselectable with
        // the keyboard.
        var resultCount = items.filter(function (item) {
          return item.isResult !== false;
        }).length;

        if (items.length !== resultCount) {
          var _autocompleteProps$ge;

          var getA11yStatusMessage = (_autocompleteProps$ge = autocompleteProps.getA11yStatusMessage) !== null && _autocompleteProps$ge !== void 0 ? _autocompleteProps$ge : Downshift.defaultProps.getA11yStatusMessage;

          autocompleteProps.getA11yStatusMessage = function (args) {
            var newArgs = _objectSpread(_objectSpread({}, args), {}, {
              resultCount: resultCount
            });

            if (args.previousResultCount === args.resultCount) {
              // Since we are modifying the resultCount, we want to avoid a case where the resultCount
              // doesn't match the previousResultCount when it naturally would. If there's an artificial
              // mismatch between these two values, the result count will be announced each time the
              // currently focused list item changes.
              newArgs.previousResultCount = newArgs.resultCount;
            }

            return getA11yStatusMessage(newArgs);
          };
        }
      }

      return /*#__PURE__*/React.createElement(Downshift, autocompleteProps, function (_ref) {
        var clearSelection = _ref.clearSelection,
            getInputProps = _ref.getInputProps,
            getItemProps = _ref.getItemProps,
            getRootProps = _ref.getRootProps,
            highlightedIndex = _ref.highlightedIndex,
            inputValue = _ref.inputValue,
            isOpen = _ref.isOpen;
        return /*#__PURE__*/React.createElement(WrapperDiv, _extends({}, getRootProps({
          refKey: 'innerRef'
        }), {
          "aria-expanded": null,
          "aria-haspopup": null,
          "aria-labelledby": null,
          "aria-owns": null,
          className: rootClassName // role has to be null to overwrite Downshift
          ,
          role: null
        }), _this3.renderChildren(getInputProps, isOpen), isOpen && (loading || items) ? /*#__PURE__*/React.createElement("div", {
          className: "ds-c-autocomplete__list",
          id: _this3.listboxContainerId
        }, label && !loading && /*#__PURE__*/React.createElement("h5", {
          className: "ds-c-autocomplete__label",
          id: _this3.listboxHeadingId
        }, label), /*#__PURE__*/React.createElement("ul", {
          "aria-labelledby": label ? _this3.listboxHeadingId : null,
          className: "ds-c-list--bare",
          id: _this3.listboxId,
          role: "listbox"
        }, _this3.filterItems(items, inputValue, getInputProps, getItemProps, highlightedIndex))) : null, clearSearchButton && /*#__PURE__*/React.createElement(Button, {
          "aria-label": ariaClearLabel !== null && ariaClearLabel !== void 0 ? ariaClearLabel : t('autocomplete.ariaClearLabel'),
          className: "ds-c-autocomplete__clear-btn",
          onClick: clearSelection,
          size: "small",
          variation: "ghost"
        }, clearInputText !== null && clearInputText !== void 0 ? clearInputText : t('autocomplete.clearInputText')));
      });
    }
  }]);

  return Autocomplete;
}(React.Component);

_defineProperty(Autocomplete, "propTypes", {
  /**
     * Screenreader-specific label for the Clear search `<button>`. Intended to provide a longer, more descriptive explanation of the button's behavior.
     */
  ariaClearLabel: _pt.string,

  /**
     * Control the `TextField` autocomplete attribute. Defaults to "off" to support accessibility. [Read more.](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion)
     */
  autoCompleteLabel: _pt.string,

  /**
     * Must contain a `TextField` component
     */
  children: _pt.node.isRequired,

  /**
     * Additional classes to be added to the root element.
     * Useful for adding utility classes.
     */
  className: _pt.string,

  /**
     * When set to `false`, do not clear the input when the input element loses focus.
     */
  clearInputOnBlur: _pt.bool,

  /**
     * Text rendered on the page if `clearInput` prop is passed. Default is "Clear search".
     */
  clearInputText: _pt.node,

  /**
     * Removes the Clear search button when set to `false`
     */
  clearSearchButton: _pt.bool,

  /**
     * Used to focus child `TextField` on `componentDidMount()`
     */
  focusTrigger: _pt.bool,

  /**
     * A unique id to be passed to the child `TextField`. If no id is passed as a prop,
     * the `Autocomplete` component will auto-generate one. This prop was provided in cases
     * where an id might need to be passed to multiple components, such as the `htmlFor`
     * attribute on a label and the id of an input.
     */
  id: _pt.string,

  /**
     * Access a reference to the child `TextField`'s `input` element
     */
  inputRef: _pt.func,

  /**
     * Array of objects used to populate the suggestion list that appears below the input as users type. This array of objects is intended for an async data callback, and should conform to the prescribed shape to avoid errors.
     */
  items: _pt.arrayOf(_pt.shape({
    /**
       * Unique identifier for this item
       */
    id: _pt.string,

    /**
       * Displayed value of the item. May alternatively provide a `children` value
       */
    name: _pt.string,

    /**
       * Custom React node as an alternative to a string-only `name`
       */
    children: _pt.node,

    /**
       * Additional classes to be added to the root element.
       * Useful for adding utility classes.
       */
    className: _pt.string,

    /**
       * Whether this item should be counted as one of the results for the purpose of announcing the
       * result count to screen readers
       */
    isResult: _pt.bool
  })),

  /**
     * Adds a heading to the top of the autocomplete list. This can be used to convey to the user that they're required to select an option from the autocomplete list.
     */
  label: _pt.node,

  /**
     * A unique `id` to be used on the child `TextField` label tag
     */
  labelId: _pt.string,

  /**
     * Can be called when the `items` array is being fetched remotely, or will be delayed for more than 1-2 seconds.
     */
  loading: _pt.bool,

  /**
     * Message users will see when the `loading` prop is passed to `Autocomplete`.
     */
  loadingMessage: _pt.node,

  /**
     * Message users will see when the `items` array returns empty and the `loading` prop is passed to `<Autocomplete />`.
     */
  noResultsMessage: _pt.node,

  /**
     * Called when the user selects an item and the selected item has changed. Called with the item that was selected and the new state. [Read more on downshift docs.](https://github.com/paypal/downshift#onchange)
     */
  onChange: _pt.func
});

_defineProperty(Autocomplete, "defaultProps", {
  autoCompleteLabel: 'off',
  clearSearchButton: true,
  clearInputOnBlur: true,
  itemToString: function itemToString(item) {
    return item ? item.name : '';
  }
});

export default Autocomplete;