'use strict';

Liferay.Loader.define("frontend-taglib-clay$clay-autocomplete@2.24.1/lib/ClayAutocomplete", ['module', 'exports', 'require', 'frontend-taglib-clay$clay-data-provider', 'frontend-taglib-clay$clay-loading-indicator', 'frontend-taglib-clay$clay-portal', 'frontend-js-metal-web$metal-position', 'frontend-js-metal-web$metal-state', 'frontend-js-metal-web$metal-dom', 'frontend-js-metal-web$metal', 'frontend-taglib-clay$clay-component', 'frontend-js-metal-web$metal-web-component', 'frontend-js-metal-web$metal-soy', './ClayAutocomplete.soy'], function (module, exports, require) {
	var define = undefined;
	var global = window;
	{
		Object.defineProperty(exports, "__esModule", {
			value: true
		});
		exports.ClayAutocomplete = undefined;

		var _createClass = 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);
				}
			}return function (Constructor, protoProps, staticProps) {
				if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
			};
		}();

		require("frontend-taglib-clay$clay-data-provider");

		require("frontend-taglib-clay$clay-loading-indicator");

		require("frontend-taglib-clay$clay-portal");

		var _metalPosition = require("frontend-js-metal-web$metal-position");

		var _metalState = require("frontend-js-metal-web$metal-state");

		var _metalDom = require("frontend-js-metal-web$metal-dom");

		var _metal = require("frontend-js-metal-web$metal");

		var _clayComponent = require("frontend-taglib-clay$clay-component");

		var _clayComponent2 = _interopRequireDefault(_clayComponent);

		var _metalWebComponent = require("frontend-js-metal-web$metal-web-component");

		var _metalWebComponent2 = _interopRequireDefault(_metalWebComponent);

		var _metalSoy = require("frontend-js-metal-web$metal-soy");

		var _metalSoy2 = _interopRequireDefault(_metalSoy);

		var _ClayAutocompleteSoy = require("./ClayAutocomplete.soy");

		var _ClayAutocompleteSoy2 = _interopRequireDefault(_ClayAutocompleteSoy);

		function _interopRequireDefault(obj) {
			return obj && obj.__esModule ? obj : { default: obj };
		}

		function _classCallCheck(instance, Constructor) {
			if (!(instance instanceof Constructor)) {
				throw new TypeError("Cannot call a class as a function");
			}
		}

		function _possibleConstructorReturn(self, call) {
			if (!self) {
				throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
			}return call && (typeof call === "object" || typeof call === "function") ? call : self;
		}

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

		/**
   * Metal ClayAutocomplete component.
   * @extends ClayComponent
   */
		var ClayAutocomplete = function (_ClayComponent) {
			_inherits(ClayAutocomplete, _ClayComponent);

			function ClayAutocomplete() {
				_classCallCheck(this, ClayAutocomplete);

				return _possibleConstructorReturn(this, (ClayAutocomplete.__proto__ || Object.getPrototypeOf(ClayAutocomplete)).apply(this, arguments));
			}

			_createClass(ClayAutocomplete, [{
				key: 'attached',

				/**
     * @inheritDoc
     */
				value: function attached() {
					this._dropdownItemFocused = null;

					this._documentClickHandler = _metalDom.dom.on(document, 'click', this._handleDocumentClick.bind(this));
					this.addListener('dataChange', this._defaultDataChange, true);
					this.addListener('dataLoading', this._defaultDataLoading, true);
					this.addListener('inputChange', this._defaultInputChange, true);
					this.addListener('itemSelected', this._defaultItemSelected, true);
					this.refs.dataProvider.refs.portal.on('rendered', this._handleRenderedPortal.bind(this));
				}

				/**
     * @inheritDoc
     */

			}, {
				key: 'rendered',
				value: function rendered() {
					if (this.inputFocused === true) {
						this.refs.input.focus();
					}
				}

				/**
     * @inheritDoc
     */

			}, {
				key: 'disposed',
				value: function disposed() {
					this._documentClickHandler.removeListener();
					this._dropdownItemFocused = null;
				}

				/**
     * @inheritDoc
     */

			}, {
				key: 'syncFilteredItems',
				value: function syncFilteredItems() {
					if (!this.filteredItems.length) {
						this._dropdownItemFocused = null;
					}
				}

				/**
     * @inheritDoc
     */

			}, {
				key: 'syncInputFocused',
				value: function syncInputFocused(newVal) {
					if (newVal === true) {
						this.refs.input.focus();
					}
				}

				/**
     * Sets filtered items with received data
     * @private
     */

			}, {
				key: '_defaultDataChange',
				value: function _defaultDataChange() {
					this._isFetching = false;

					if (this._query) {
						this.filteredItems = this._getUpdatedFilteredItems();
					} else {
						this.filteredItems = [];
					}
				}

				/**
     * @private
     */

			}, {
				key: '_defaultDataLoading',
				value: function _defaultDataLoading() {
					this._isFetching = true;
				}

				/**
     * Filters the items according to received input
     * @param {!Event} event
     * @private
     */

			}, {
				key: '_defaultInputChange',
				value: function _defaultInputChange(event) {
					this._query = event.data.value;

					this._expanded = true;

					if (this._query) {
						if ((0, _metal.isFunction)(this.dataSource)) {
							this.refs.dataProvider.updateData(this._query);
						} else {
							this.filteredItems = this._getUpdatedFilteredItems();
						}
					} else {
						this.filteredItems = [];
					}
				}

				/**
     * @private
     */

			}, {
				key: '_defaultItemSelected',
				value: function _defaultItemSelected() {
					this._expanded = false;
				}

				/**
     * Gets the accepted characters of the input
     * element values
     * @param {!string} value
     * @protected
     * @return {string}
     */

			}, {
				key: '_getCharactersAllowed',
				value: function _getCharactersAllowed(value) {
					var regexp = new RegExp(this.allowedCharacters);
					var match = value.match(regexp);

					return Array.isArray(match) ? match.join('') : '';
				}

				/**
     * Gets the the udpated filtered items
     * @protected
     * @return {Array}
     */

			}, {
				key: '_getUpdatedFilteredItems',
				value: function _getUpdatedFilteredItems() {
					var _this2 = this;

					var filteredItems = this.refs.dataProvider.filter(this._query, this.extractData);

					filteredItems.map(function (filteredItem) {
						var newFilteredItemData = {};

						if (_this2.labelLocator) {
							newFilteredItemData.label = _this2._performCall(_this2.labelLocator, filteredItem.data);
						}

						if (_this2.valueLocator) {
							newFilteredItemData.value = _this2._performCall(_this2.valueLocator, filteredItem.data);
						}

						if (typeof filteredItem.data === 'string') {
							filteredItem.data = newFilteredItemData;
						} else {
							filteredItem.data.label = newFilteredItemData.label;
							filteredItem.data.value = newFilteredItemData.value;
						}
					});

					return filteredItems;
				}

				/**
     * Continues the propagation of the data change event
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleDataChange',
				value: function _handleDataChange(event) {
					return !this.emit({
						data: event.data,
						name: 'dataChange',
						originalEvent: event
					});
				}

				/**
     * Continues the propagation of the data error event
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleDataError',
				value: function _handleDataError(event) {
					return !this.emit({
						data: event.data,
						name: 'dataError',
						originalEvent: event
					});
				}

				/**
     * Continues the propagation of the data loading event
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleDataLoading',
				value: function _handleDataLoading(event) {
					return !this.emit({
						data: event.data,
						name: 'dataLoading',
						originalEvent: event
					});
				}

				/**
     * Handles the dropdown hiding when clicking outside the autocomplete
     * component
     * @param {!Event} event
     */

			}, {
				key: '_handleDocumentClick',
				value: function _handleDocumentClick(event) {
					if (event.target === this.element) {
						return;
					}
					this._expanded = false;
				}

				/**
     * Continues the propagation of the item clicked event
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleDropdownItemClick',
				value: function _handleDropdownItemClick(event) {
					return this._handleItemSelected(event, event.delegateTarget.getAttribute('data-dropdown-item-index'));
				}

				/**
     * Handle the selected item in the dropdown and trigger the itemSelected event.
     * @param {!Event} event
     * @param {!string} index
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleItemSelected',
				value: function _handleItemSelected(event, index) {
					var item = this.filteredItems[Number(index)];

					return !this.emit({
						data: {
							item: item.data
						},
						name: 'itemSelected',
						originalEvent: event
					});
				}

				/**
     * Handles the input blur event and continues to propagate.
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleOnBlur',
				value: function _handleOnBlur(event) {
					return !this.emit({
						name: 'inputOnBlur',
						originalEvent: event
					});
				}

				/**
     * Handles the input focus event and continues to propagate.
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleOnFocus',
				value: function _handleOnFocus(event) {
					return !this.emit({
						name: 'inputOnFocus',
						originalEvent: event
					});
				}

				/**
     * Handles the input event and continues to propagate.
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleOnInput',
				value: function _handleOnInput(event) {
					var value = event.target.value;

					var newValue = this.allowedCharacters ? this._getCharactersAllowed(value) : value;
					var char = newValue.substr(-1);

					// Updates the value of the input with the value
					// entered by the user in case the validation is false
					// the above components can update the state of the input value.
					this.inputValue = value;

					return !this.emit({
						data: {
							value: newValue,
							char: char
						},
						name: 'inputChange',
						originalEvent: event
					});
				}

				/**
     * Handles the onKeydown events and continues to propagate.
     * @param {!Event} event
     * @protected
     * @return {Boolean} If the event has been prevented or not.
     */

			}, {
				key: '_handleOnKeydown',
				value: function _handleOnKeydown(event) {
					switch (event.key) {
						case 'Enter':
							event.stopPropagation();
							if (this.filteredItems.length && event.delegateTarget.tagName !== 'INPUT') {
								this._handleItemSelected(event, event.target.getAttribute('data-dropdown-item-index'));
							}
							break;

						case 'ArrowUp':
							event.preventDefault();
							this._setFocusItem(true);
							break;

						case 'ArrowDown':
							event.preventDefault();
							event.stopPropagation();
							this._setFocusItem(false);
							break;

						case 'Tab':
							if (this.inputFocused && this.filteredItems && this.filteredItems.length > 0) {
								event.preventDefault();
								event.stopPropagation();
								this._setFocusItem(event.shiftKey);
							}
							break;
					}

					return !this.emit({
						data: {
							element: event.target,
							value: this.refs.input.value,
							key: event.key,
							eventFromInput: event.delegateTarget.tagName === 'INPUT'
						},
						name: 'inputOnKeydown',
						originalEvent: event
					});
				}

				/**
     * Handle when the lifecycle `rendered` is called in ClayPortal.
     * @protected
     */

			}, {
				key: '_handleRenderedPortal',
				value: function _handleRenderedPortal() {
					var alignElement = this.element;

					if (alignElement) {
						var widthElement = alignElement.clientWidth;
						var bodyElement = this.refs.dataProvider.refs.portal.refs.dropdown;

						this._dropdownWidth = widthElement;
						this._alignedPosition = _metalPosition.Align.align(bodyElement, alignElement, _metalPosition.Align.BottomCenter, false);
					}
				}

				/**
     * Handles data mapping.
     * @param {!(function|string)} param
     * @param {!Array} data
     * @protected
     * @return {!(string|number)}
     */

			}, {
				key: '_performCall',
				value: function _performCall(param, data) {
					if (typeof param === 'function') {
						return param(data);
					}

					if (typeof data === 'string') {
						return data;
					}

					return data[param];
				}

				/**
     * Handle the interactions in the dropdown and add focus on the items.
     * @param {!Boolean} direction
     * @protected
     */

			}, {
				key: '_setFocusItem',
				value: function _setFocusItem(direction) {
					if (this.filteredItems.length) {
						var dropdown = this.refs.dataProvider.refs.portal.refs.dropdown;

						var elements = dropdown.querySelectorAll('a[data-dropdown-item-index]');

						if (direction && this._dropdownItemFocused === 0) {
							this.refs.input.focus();
							this._dropdownItemFocused = null;
						} else if (!(direction === true && this._dropdownItemFocused === null)) {
							this._dropdownItemFocused = this._dropdownItemFocused === null || elements.length - 1 === this._dropdownItemFocused ? direction ? this._dropdownItemFocused - 1 : 0 : direction ? this._dropdownItemFocused - 1 : this._dropdownItemFocused + 1;

							elements[this._dropdownItemFocused].focus();
						}
					}
				}
			}]);

			return ClayAutocomplete;
		}(_clayComponent2.default);

		/**
   * State definition.
   * @static
   * @type {!Object}
   */

		ClayAutocomplete.STATE = {
			/**
    * The current position of the tooltip after being aligned via `Align.align`.
    * @default Align.isValidPosition
    * @instance
    * @memberof ClayAutocomplete
    * @type {!number}
    */
			_alignedPosition: _metalState.Config.validator(_metalPosition.Align.isValidPosition).internal(),

			/**
    * Flag to indicate the dropdown width referring to the width of the input.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {!number}
    */
			_dropdownWidth: _metalState.Config.number().internal(),

			/**
    * Flag to indicate if dropdown is expanded.
    * @default false
    * @instance
    * @memberof ClayAutocomplete
    * @private
    * @type {?bool}
    */
			_expanded: _metalState.Config.bool().value(false).internal(),

			/**
    * @default false
    * @instance
    * @memberof ClayAutocomplete
    * @private
    * @type {?bool}
    */
			_isFetching: _metalState.Config.bool().value(false).internal(),

			/**
    * Flag to indicate the characters allowed in the
    * input element (e.g /[a-zA-Z0-9_]/g).
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?RegExp}
    */
			allowedCharacters: _metalState.Config.instanceOf(RegExp),

			/**
    * Variation name to render different deltemplates.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			contentRenderer: _metalState.Config.string(),

			/**
    * Data to add to the element.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?object}
    */
			data: _metalState.Config.object(),

			/**
    * The array of data items that the data source contains,
    * the URL for the data provider to request, or a function
    * that receives the query and returns a promise with the
    * elements.
    *
    * @instance
    * @default undefined
    * @memberof ClayAutocomplete
    * @type {!(string|object|array|function)}
    */
			dataSource: _metalState.Config.oneOfType([_metalState.Config.array(), _metalState.Config.func(), _metalState.Config.object(), _metalState.Config.string()]).required(),

			/**
    * Set the request debounce time
    * @instance
    * @default 200
    * @memberof ClayAutocomplete
    * @type {?(number)}
    */
			debounceTime: _metalState.Config.number().value(200),

			/**
    * Object that wires events with default listeners
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @review
    * @type {?(object|undefined)}
    */
			defaultEventHandler: _metalState.Config.object(),

			/**
    * CSS classes to be applied to the element.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			elementClasses: _metalState.Config.string(),

			/**
    * Flag to enable the filtered items with autocomplete default.
    * @default true
    * @instance
    * @memberof ClayAutocomplete
    * @type {?bool}
    */
			enableAutocomplete: _metalState.Config.validator(function (value) {
				if (value) {
					console.warn('🚨 `enableAutocomplete` has been deprecated and will be removed in the next major version.');
				}
			}),

			/**
    * Extracts from the data the item to be compared in autocomplete.
    * @instance
    * @default (elem) => elem
    * @memberof ClayAutocomplete
    * @type {?(function|string)}
    */
			extractData: _metalState.Config.oneOfType([_metalState.Config.func(), _metalState.Config.string()]).value(function (elem) {
				return elem;
			}),

			/**
    * List of filtered items for suggestion or autocomplete.
    * @default []
    * @instance
    * @memberof ClayAutocomplete
    * @type {?Array<Object>}
    */
			filteredItems: _metalState.Config.array(_metalState.Config.object()).value([]),

			/**
    * Id to be applied to the element.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			id: _metalState.Config.string(),

			/**
    * Set some initial data while the first request is being made
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(object|array)}
    */
			dataProviderInitialData: _metalState.Config.oneOfType([_metalState.Config.object(), _metalState.Config.array()]),

			dropdownPortalElement: _metalState.Config.string().value('#clay_dropdown_portal'),

			/**
    * CSS classes to be applied to the input element.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			inputElementClasses: _metalState.Config.string(),

			/**
    * Flag to indicate that if the input is focused.
    * @instance
    * @default false
    * @memberof ClayAutocomplete
    * @type {?bool}
    */
			inputFocused: _metalState.Config.bool().value(false),

			/**
    * Name of the selected items input.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			inputName: _metalState.Config.string(),

			/**
    * Value of the input.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			inputValue: _metalState.Config.string(),

			/**
    * Sets the name of the field to map the label of the item.
    * @default label
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(function|string)}
    */
			labelLocator: _metalState.Config.oneOfType([_metalState.Config.func(), _metalState.Config.string()]).value('label'),

			/**
    * Input placeholder.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(string|undefined)}
    */
			placeholder: _metalState.Config.string(),

			/**
    * Flag to define how often to refetch data (ms)
    * @instance
    * @default 0
    * @memberof ClayAutocomplete
    * @type {?(number|undefined)}
    */
			pollingInterval: _metalState.Config.number().value(0),

			/**
    * Specifies explicitly if request needs to be made with debounce
    * (userInput) or with polling (polling)
    * @instance
    * @default undefined
    * @memberof ClayAutocomplete
    * @type {?(object|array)}
    */
			requestInputMode: _metalState.Config.oneOf(['polling', 'userInput']).value('userInput'),

			/**
    * Set ups the request options
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(object|undefined)}
    */
			requestOptions: _metalState.Config.shapeOf({
				method: _metalState.Config.string(),
				mode: _metalState.Config.string(),
				cache: _metalState.Config.string(),
				credentials: _metalState.Config.string(),
				headers: _metalState.Config.object(),
				redirect: _metalState.Config.string(),
				referrer: _metalState.Config.string(),
				body: _metalState.Config.object()
			}),

			/**
    * Flag to define how often to refetch data (ms)
    * @instance
    * @default 0
    * @memberof ClayAutocomplete
    * @type {?(number|undefined)}
    */
			requestPolling: _metalState.Config.validator(function (value) {
				if (value) {
					console.warn('🚨 `requestPolling` has been renamed to `pollingInterval` and will be deprecated and removed in the next release.');
				}
			}),

			/**
    * Define how many attempts will be made when the request fails
    * @default 5
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(number|undefined)}
    */
			requestRetries: _metalState.Config.number().value(5),

			/**
    * Set timeout of the request
    * @default 30000
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(number|undefined)}
    */
			requestTimeout: _metalState.Config.number().value(30000),

			/**
    * The content renderer.
    * Warning: This API is unstable and can be changed at any time,
    * use at your own risk.
    * @default undefined
    * @instance
    * @memberof ClayAutocomplete
    * @type {?html}
    */
			unstable_content: _metalState.Config.any(),

			/**
    * Flag to indicate the autocomplete should use default css
    * classes or not.
    * @default true
    * @instance
    * @memberof ClayAutocomplete
    * @type {?bool}
    */
			useDefaultClasses: _metalState.Config.bool().value(true),

			/**
    * Sets the name of the field to map the value of the item.
    * @default label
    * @instance
    * @memberof ClayAutocomplete
    * @type {?(function|string)}
    */
			valueLocator: _metalState.Config.oneOfType([_metalState.Config.func(), _metalState.Config.string()]).value('value'),

			/**
    * Flag to indicate whether to wrap input with the
    * `.autofit` utility classes. See the MultiSelect use case.
    * @default false
    * @instance
    * @memberof ClayAutocomplete
    * @type {?bool}
    */
			wrapInput: _metalState.Config.bool().value(false)
		};

		(0, _metalWebComponent2.default)('clay-autocomplete', ClayAutocomplete);

		_metalSoy2.default.register(ClayAutocomplete, _ClayAutocompleteSoy2.default);

		exports.ClayAutocomplete = ClayAutocomplete;
		exports.default = ClayAutocomplete;
		//# sourceMappingURL=ClayAutocomplete.js.map
	}
});
//# sourceMappingURL=ClayAutocomplete.js.map