"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.strings = exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _analytics = require("@kbn/analytics"); var _eui = require("@elastic/eui"); var _i18n = require("@kbn/i18n"); var _i18nReact = require("@kbn/i18n-react"); var _lodash = require("lodash"); var _public = require("@kbn/data-plugin/public"); var _common = require("@kbn/data-plugin/common"); var _public2 = require("@kbn/kibana-react-plugin/public"); var _esQuery = require("@kbn/es-query"); var _match_pairs = require("./match_pairs"); var _to_user = require("./to_user"); var _from_user = require("./from_user"); var _fetch_index_patterns = require("./fetch_index_patterns"); var _language_switcher = require("./language_switcher"); var _typeahead = require("../typeahead"); var _utils = require("../utils"); var _filter_button_group = require("../filter_bar/filter_button_group/filter_button_group"); var _autocomplete = require("../autocomplete"); var _services = require("../services"); require("./query_string_input.scss"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ const strings = { getSearchInputPlaceholderForText: () => _i18n.i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholderForText', { defaultMessage: 'Filter your data' }), getSearchInputPlaceholder: language => _i18n.i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholder', { defaultMessage: 'Filter your data using {language} syntax', values: { language } }), getQueryBarComboboxAriaLabel: pageType => _i18n.i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { defaultMessage: 'Search and filter the {pageType} page', values: { pageType } }), getQueryBarSearchInputAriaLabel: pageType => _i18n.i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { defaultMessage: 'Start typing to search and filter the {pageType} page', values: { pageType } }), getQueryBarClearInputLabel: () => _i18n.i18n.translate('unifiedSearch.query.queryBar.clearInputLabel', { defaultMessage: 'Clear input' }), getKQLNestedQuerySyntaxInfoTitle: () => _i18n.i18n.translate('unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle', { defaultMessage: 'KQL nested query syntax' }) }; exports.strings = strings; const KEY_CODES = { LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, ENTER: 13, ESC: 27, TAB: 9, HOME: 36, END: 35 }; // Needed for React.lazy // eslint-disable-next-line import/no-default-export class QueryStringInputUI extends _react.PureComponent { constructor(...args) { var _this$props$deps$usag; super(...args); (0, _defineProperty2.default)(this, "state", { isSuggestionsVisible: false, index: null, suggestions: [], suggestionLimit: 50, selectionStart: null, selectionEnd: null, indexPatterns: [], queryBarInputDiv: null }); (0, _defineProperty2.default)(this, "inputRef", null); (0, _defineProperty2.default)(this, "persistedLog", void 0); (0, _defineProperty2.default)(this, "abortController", void 0); (0, _defineProperty2.default)(this, "fetchIndexPatternsAbortController", void 0); (0, _defineProperty2.default)(this, "reportUiCounter", (_this$props$deps$usag = this.props.deps.usageCollection) === null || _this$props$deps$usag === void 0 ? void 0 : _this$props$deps$usag.reportUiCounter.bind(this.props.deps.usageCollection, this.props.appName)); (0, _defineProperty2.default)(this, "componentIsUnmounting", false); /** * If any element within the container is currently focused * @private */ (0, _defineProperty2.default)(this, "isFocusWithin", false); (0, _defineProperty2.default)(this, "getQueryString", () => { return (0, _to_user.toUser)(this.props.query.query); }); (0, _defineProperty2.default)(this, "fetchIndexPatterns", (0, _lodash.debounce)(async () => { const [objectPatterns = [], stringPatterns = []] = (0, _lodash.partition)(this.props.indexPatterns || [], indexPattern => { return indexPattern.hasOwnProperty('fields') && indexPattern.hasOwnProperty('title'); }); const idOrTitlePatterns = stringPatterns.map(sp => typeof sp === 'string' ? { type: 'title', value: sp } : sp); // abort the previous fetch to avoid overriding with outdated data // issue https://github.com/elastic/kibana/issues/80831 if (this.fetchIndexPatternsAbortController) this.fetchIndexPatternsAbortController.abort(); this.fetchIndexPatternsAbortController = new AbortController(); const currentAbortController = this.fetchIndexPatternsAbortController; const objectPatternsFromStrings = await (0, _fetch_index_patterns.fetchIndexPatterns)(this.props.deps.data.dataViews, idOrTitlePatterns); if (!currentAbortController.signal.aborted) { this.setState({ indexPatterns: [...objectPatterns, ...objectPatternsFromStrings] }); this.updateSuggestions(); } }, 200)); (0, _defineProperty2.default)(this, "getSuggestions", async () => { if (!this.inputRef) { return; } const language = this.props.query.language; const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); const hasQuerySuggestions = this.props.deps.unifiedSearch.autocomplete.hasQuerySuggestions(language); if (!hasQuerySuggestions || !Array.isArray(this.state.indexPatterns) || (0, _lodash.compact)(this.state.indexPatterns).length === 0) { return recentSearchSuggestions; } const indexPatterns = this.state.indexPatterns; const { selectionStart, selectionEnd } = this.inputRef; if (selectionStart === null || selectionEnd === null) { return; } try { var _this$props$filtersFo; if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); const suggestions = (await this.props.deps.unifiedSearch.autocomplete.getQuerySuggestions({ language, indexPatterns, query: queryString, selectionStart, selectionEnd, signal: this.abortController.signal, useTimeRange: this.props.timeRangeForSuggestionsOverride, boolFilter: (0, _esQuery.buildQueryFromFilters)(this.props.filtersForSuggestions, undefined).filter, method: (_this$props$filtersFo = this.props.filtersForSuggestions) !== null && _this$props$filtersFo !== void 0 && _this$props$filtersFo.length ? 'terms_agg' : undefined, suggestionsAbstraction: this.props.suggestionsAbstraction })) || []; return [...suggestions, ...recentSearchSuggestions]; } catch (e) { var _this$reportUiCounter; // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error // Ignore aborted requests if (e.message === 'The user aborted a request.') return; (_this$reportUiCounter = this.reportUiCounter) === null || _this$reportUiCounter === void 0 ? void 0 : _this$reportUiCounter.call(this, _analytics.METRIC_TYPE.LOADED, `query_string:suggestions_error`); throw e; } }); (0, _defineProperty2.default)(this, "getRecentSearchSuggestions", query => { if (!this.persistedLog) { return []; } const recentSearches = this.persistedLog.get(); const matchingRecentSearches = recentSearches.filter(recentQuery => { const recentQueryString = typeof recentQuery === 'object' ? (0, _to_user.toUser)(recentQuery) : recentQuery; return recentQueryString !== '' && recentQueryString.includes(query); }); return matchingRecentSearches.map(recentSearch => { const text = (0, _to_user.toUser)(recentSearch); const start = 0; const end = query.length; return { type: _autocomplete.QuerySuggestionTypes.RecentSearch, text, start, end }; }); }); (0, _defineProperty2.default)(this, "updateSuggestions", (0, _lodash.debounce)(async () => { const suggestions = (await this.getSuggestions()) || []; if (!this.componentIsUnmounting) { this.setState({ suggestions }); } }, 100)); (0, _defineProperty2.default)(this, "onSubmit", query => { if (this.props.onSubmit) { if (this.persistedLog) { this.persistedLog.add(query.query); } this.props.onSubmit({ query: (0, _from_user.fromUser)(query.query), language: query.language }); } }); (0, _defineProperty2.default)(this, "onChange", query => { this.updateSuggestions(); if (this.props.onChange) { this.props.onChange({ query: (0, _from_user.fromUser)(query.query), language: query.language }); } }); (0, _defineProperty2.default)(this, "onQueryStringChange", value => { this.setState({ isSuggestionsVisible: true, index: null, suggestionLimit: 50 }); if (this.props.query.query !== value) { this.onChange({ query: value, language: this.props.query.language }); } }); (0, _defineProperty2.default)(this, "onInputChange", event => { const value = this.formatTextAreaValue(event.target.value); this.onQueryStringChange(value); if (event.target.value === '') { this.handleRemoveHeight(); } else { this.handleAutoHeight(); } }); (0, _defineProperty2.default)(this, "onClickInput", event => { if (event.target instanceof HTMLTextAreaElement) { const value = this.formatTextAreaValue(event.target.value); this.onQueryStringChange(value); } }); (0, _defineProperty2.default)(this, "onKeyUp", event => { if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { this.setState({ isSuggestionsVisible: true }); if (event.target instanceof HTMLTextAreaElement) { const value = this.formatTextAreaValue(event.target.value); this.onQueryStringChange(value); } } }); (0, _defineProperty2.default)(this, "onKeyDown", event => { if (event.target instanceof HTMLTextAreaElement) { const { isSuggestionsVisible, index } = this.state; const preventDefault = event.preventDefault.bind(event); const { target, key, metaKey } = event; const { value, selectionStart, selectionEnd } = target; const updateQuery = (query, newSelectionStart, newSelectionEnd) => { var _this$inputRef, _this$inputRef2; this.onQueryStringChange(query); if (((_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.selectionStart) !== newSelectionStart || ((_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.selectionEnd) !== newSelectionEnd) { this.setState({ selectionStart: newSelectionStart, selectionEnd: newSelectionEnd }); } }; switch (event.keyCode) { case KEY_CODES.DOWN: if (isSuggestionsVisible && index !== null) { event.preventDefault(); this.incrementIndex(index); // Note to engineers. `isSuggestionVisible` does not mean the suggestions are visible. // This should likely be fixed, it's more that suggestions can be shown. } else if (isSuggestionsVisible && index == null || this.getQueryString() === '') { event.preventDefault(); this.setState({ isSuggestionsVisible: true, index: 0 }); } break; case KEY_CODES.UP: if (isSuggestionsVisible && index !== null) { event.preventDefault(); this.decrementIndex(index); } break; case KEY_CODES.ENTER: if (!this.props.bubbleSubmitEvent) { event.preventDefault(); } if (isSuggestionsVisible && index !== null && this.state.suggestions[index]) { event.preventDefault(); this.selectSuggestion(this.state.suggestions[index], index); } else { this.onSubmit(this.props.query); this.setState({ isSuggestionsVisible: false }); } break; case KEY_CODES.ESC: if (isSuggestionsVisible) { event.preventDefault(); } this.setState({ isSuggestionsVisible: false, index: null }); break; case KEY_CODES.TAB: this.setState({ isSuggestionsVisible: false, index: null }); break; default: if (selectionStart !== null && selectionEnd !== null) { (0, _match_pairs.matchPairs)({ value, selectionStart, selectionEnd, key, metaKey, updateQuery, preventDefault }); } break; } } }); (0, _defineProperty2.default)(this, "selectSuggestion", (suggestion, listIndex) => { var _this$reportUiCounter2, _this$reportUiCounter3; if (!this.inputRef) { return; } const { type, text, start, end, cursorIndex } = suggestion; this.handleNestedFieldSyntaxNotification(suggestion); const query = this.getQueryString(); const { selectionStart, selectionEnd } = this.inputRef; if (selectionStart === null || selectionEnd === null) { return; } const value = query.substr(0, selectionStart) + query.substr(selectionEnd); const newQueryString = value.substr(0, start) + text + value.substr(end); (_this$reportUiCounter2 = this.reportUiCounter) === null || _this$reportUiCounter2 === void 0 ? void 0 : _this$reportUiCounter2.call(this, _analytics.METRIC_TYPE.CLICK, `query_string:${type}:suggestions_select_position_${listIndex}`); (_this$reportUiCounter3 = this.reportUiCounter) === null || _this$reportUiCounter3 === void 0 ? void 0 : _this$reportUiCounter3.call(this, _analytics.METRIC_TYPE.CLICK, `query_string:${type}:suggestions_select_q_length_${end - start}`); this.onQueryStringChange(newQueryString); this.setState({ selectionStart: start + (cursorIndex ? cursorIndex : text.length), selectionEnd: start + (cursorIndex ? cursorIndex : text.length) }); const isTypeRecentSearch = type === _autocomplete.QuerySuggestionTypes.RecentSearch; const isAutoSubmitAndValid = this.props.autoSubmit && (type === _autocomplete.QuerySuggestionTypes.Value || [':*', ': *'].includes(value.trim())); if (isTypeRecentSearch || isAutoSubmitAndValid) { this.setState({ isSuggestionsVisible: false, index: null }); this.onSubmit({ query: newQueryString, language: this.props.query.language }); } }); (0, _defineProperty2.default)(this, "handleNestedFieldSyntaxNotification", suggestion => { const subTypeNested = 'field' in suggestion && (0, _common.getFieldSubtypeNested)(suggestion.field); if (subTypeNested && subTypeNested.nested && !this.props.deps.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut')) { const { notifications, docLinks } = this.props.deps; const onKQLNestedQuerySyntaxInfoOptOut = toast => { if (!this.props.deps.storage) return; this.props.deps.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); notifications.toasts.remove(toast); }; if (notifications && docLinks) { const toast = notifications.toasts.add({ title: strings.getKQLNestedQuerySyntaxInfoTitle(), text: (0, _public2.toMountPoint)( /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", null, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoText", defaultMessage: "It looks like you're querying on a nested field. You can construct KQL syntax for nested queries in different ways, depending on the results you want. Learn more in our {link}.", values: { link: /*#__PURE__*/_react.default.createElement(_eui.EuiLink, { href: docLinks.links.query.kueryQuerySyntax, target: "_blank" }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText", defaultMessage: "docs" })) } })), /*#__PURE__*/_react.default.createElement(_eui.EuiFlexGroup, { justifyContent: "flexEnd", gutterSize: "s" }, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { grow: false }, /*#__PURE__*/_react.default.createElement(_eui.EuiButton, { size: "s", onClick: () => onKQLNestedQuerySyntaxInfoOptOut(toast) }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText", defaultMessage: "Don't show again" }))))), { theme$: (0, _services.getTheme)().theme$ }) }); } } }); (0, _defineProperty2.default)(this, "increaseLimit", () => { this.setState({ suggestionLimit: this.state.suggestionLimit + 50 }); }); (0, _defineProperty2.default)(this, "incrementIndex", currentIndex => { let nextIndex = currentIndex + 1; if (currentIndex === null || nextIndex >= this.state.suggestions.length) { nextIndex = 0; } this.setState({ index: nextIndex }); }); (0, _defineProperty2.default)(this, "decrementIndex", currentIndex => { const previousIndex = currentIndex - 1; if (previousIndex < 0) { this.setState({ index: this.state.suggestions.length - 1 }); } else { this.setState({ index: previousIndex }); } }); (0, _defineProperty2.default)(this, "onSelectLanguage", language => { var _this$reportUiCounter4; // Send telemetry info every time the user opts in or out of kuery // As a result it is important this function only ever gets called in the // UI component's change handler. this.props.deps.http.post('/internal/kql_opt_in_stats', { version: _common.KQL_TELEMETRY_ROUTE_LATEST_VERSION, body: JSON.stringify({ opt_in: language === 'kuery' }) }); const storageKey = this.props.storageKey; this.props.deps.storage.set(storageKey, language); const newQuery = { query: '', language }; this.onChange(newQuery); this.onSubmit(newQuery); (_this$reportUiCounter4 = this.reportUiCounter) === null || _this$reportUiCounter4 === void 0 ? void 0 : _this$reportUiCounter4.call(this, _analytics.METRIC_TYPE.LOADED, storageKey ? `${storageKey}:language:${language}` : `query_string:language:${language}`); }); (0, _defineProperty2.default)(this, "onOutsideClick", () => { if (this.state.isSuggestionsVisible) { this.setState({ isSuggestionsVisible: false, index: null }); this.scheduleOnInputBlur(); } }); (0, _defineProperty2.default)(this, "blurTimeoutHandle", void 0); /** * Notify parent about input's blur after a delay only * if the focus didn't get back inside the input container * and if suggestions were closed * https://github.com/elastic/kibana/issues/92040 */ (0, _defineProperty2.default)(this, "scheduleOnInputBlur", () => { clearTimeout(this.blurTimeoutHandle); this.blurTimeoutHandle = window.setTimeout(() => { if (!this.isFocusWithin && !this.state.isSuggestionsVisible && !this.componentIsUnmounting) { this.handleBlurHeight(); if (this.props.onChangeQueryInputFocus) { this.props.onChangeQueryInputFocus(false); } if (this.props.submitOnBlur) { this.onSubmit(this.props.query); } } }, 50); }); (0, _defineProperty2.default)(this, "onInputBlur", () => { if ((0, _lodash.isFunction)(this.props.onBlur)) { this.props.onBlur(); } }); (0, _defineProperty2.default)(this, "onClickSuggestion", (suggestion, index) => { if (!this.inputRef) { return; } this.selectSuggestion(suggestion, index); this.inputRef.focus(); }); (0, _defineProperty2.default)(this, "initPersistedLog", () => { const { uiSettings } = this.props.deps; const { appName } = this.props; this.persistedLog = this.props.persistedLog ? this.props.persistedLog : (0, _public.getQueryLog)(uiSettings, this.props.deps.storage, appName, this.props.query.language); }); (0, _defineProperty2.default)(this, "onMouseEnterSuggestion", (suggestion, index) => { this.setState({ index }); }); (0, _defineProperty2.default)(this, "textareaId", (0, _eui.htmlIdGenerator)()()); (0, _defineProperty2.default)(this, "handleAutoHeight", (0, _utils.onRaf)(() => { if (this.inputRef !== null && document.activeElement === this.inputRef) { this.inputRef.classList.add('kbnQueryBar__textarea--autoHeight'); this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); } })); (0, _defineProperty2.default)(this, "handleRemoveHeight", (0, _utils.onRaf)(() => { if (this.inputRef !== null) { this.inputRef.style.removeProperty('height'); this.inputRef.classList.remove('kbnQueryBar__textarea--autoHeight'); } })); (0, _defineProperty2.default)(this, "handleBlurHeight", (0, _utils.onRaf)(() => { if (this.inputRef !== null) { this.handleRemoveHeight(); this.inputRef.scrollTop = 0; } })); (0, _defineProperty2.default)(this, "handleOnFocus", () => { if (this.props.onChangeQueryInputFocus) { this.props.onChangeQueryInputFocus(true); } this.handleAutoHeight(); }); (0, _defineProperty2.default)(this, "getSearchInputPlaceholder", () => { if (!this.props.query.language || this.props.query.language === 'text') { return strings.getSearchInputPlaceholderForText(); } const language = this.props.query.language === 'kuery' ? 'KQL' : (0, _eui.toSentenceCase)(this.props.query.language); return strings.getSearchInputPlaceholder(language); }); (0, _defineProperty2.default)(this, "assignInputRef", node => { this.inputRef = node; }); (0, _defineProperty2.default)(this, "assignQueryInputDivRef", node => { this.setState({ queryBarInputDiv: node }); }); (0, _defineProperty2.default)(this, "onFocusWithin", () => { this.isFocusWithin = true; }); (0, _defineProperty2.default)(this, "onBlurWithin", () => { this.isFocusWithin = false; this.scheduleOnInputBlur(); }); } componentDidMount() { const parsedQuery = (0, _from_user.fromUser)((0, _to_user.toUser)(this.props.query.query)); if (!(0, _lodash.isEqual)(this.props.query.query, parsedQuery)) { this.onChange({ ...this.props.query, query: parsedQuery }); } this.initPersistedLog(); this.fetchIndexPatterns(); this.handleAutoHeight(); window.addEventListener('resize', this.handleAutoHeight); } componentDidUpdate(prevProps) { const parsedQuery = (0, _from_user.fromUser)((0, _to_user.toUser)(this.props.query.query)); if (!(0, _lodash.isEqual)(this.props.query.query, parsedQuery)) { this.onChange({ ...this.props.query, query: parsedQuery }); } this.initPersistedLog(); if (!(0, _lodash.isEqual)(prevProps.indexPatterns, this.props.indexPatterns)) { this.fetchIndexPatterns(); } else if (!(0, _lodash.isEqual)(prevProps.query, this.props.query)) { this.updateSuggestions(); } if (this.state.selectionStart !== null && this.state.selectionEnd !== null) { if (this.inputRef != null) { this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd); } this.setState({ selectionStart: null, selectionEnd: null }); } if (document.activeElement !== null && document.activeElement.id === this.textareaId) { this.handleAutoHeight(); } else { this.handleRemoveHeight(); } } componentWillUnmount() { if (this.abortController) this.abortController.abort(); if (this.updateSuggestions.cancel) this.updateSuggestions.cancel(); this.componentIsUnmounting = true; window.removeEventListener('resize', this.handleAutoHeight); } render() { const isSuggestionsVisible = this.state.isSuggestionsVisible && { 'aria-controls': 'kbnTypeahead__items', 'aria-owns': 'kbnTypeahead__items' }; const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' }; const simpleLanguageSwitcher = this.props.disableLanguageSwitcher ? null : /*#__PURE__*/_react.default.createElement(_language_switcher.QueryLanguageSwitcher, { language: this.props.query.language, anchorPosition: this.props.languageSwitcherPopoverAnchorPosition, onSelectLanguage: this.onSelectLanguage, nonKqlMode: this.props.nonKqlMode, deps: { docLinks: this.props.deps.docLinks } }); const prependElement = this.props.prepend || simpleLanguageSwitcher ? /*#__PURE__*/_react.default.createElement(_filter_button_group.FilterButtonGroup, { attached: true, items: [this.props.prepend, simpleLanguageSwitcher] }) : undefined; const containerClassName = (0, _classnames.default)('kbnQueryBar__wrap', this.props.className); const inputClassName = (0, _classnames.default)('kbnQueryBar__textarea', { 'kbnQueryBar__textarea--withIcon': this.props.iconType, 'kbnQueryBar__textarea--isClearable': this.props.isClearable, 'kbnQueryBar__textarea--withPrepend': prependElement, 'kbnQueryBar__textarea--isSuggestionsVisible': isSuggestionsVisible && !(0, _lodash.isEmpty)(this.state.suggestions) }); const inputWrapClassName = (0, _classnames.default)('kbnQueryBar__textareaWrap'); return /*#__PURE__*/_react.default.createElement("div", { className: containerClassName, onFocus: this.onFocusWithin, onBlur: this.onBlurWithin }, prependElement, /*#__PURE__*/_react.default.createElement(_eui.EuiOutsideClickDetector, { onOutsideClick: this.onOutsideClick }, /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({}, ariaCombobox, { style: { position: 'relative', width: '100%' }, "aria-label": strings.getQueryBarComboboxAriaLabel(this.props.appName), "aria-haspopup": "true", "aria-expanded": this.state.isSuggestionsVisible, "data-skip-axe": "aria-required-children" }), /*#__PURE__*/_react.default.createElement("div", { role: "search", className: inputWrapClassName, ref: this.assignQueryInputDivRef }, /*#__PURE__*/_react.default.createElement(_eui.EuiTextArea, { placeholder: this.props.placeholder || this.getSearchInputPlaceholder(), value: this.forwardNewValueIfNeeded(this.getQueryString()), onKeyDown: this.onKeyDown, onKeyUp: this.onKeyUp, onChange: this.onInputChange, onClick: this.onClickInput, onBlur: this.onInputBlur, onFocus: this.handleOnFocus, disabled: this.props.isDisabled, className: inputClassName, fullWidth: true, rows: 1, id: this.textareaId, autoFocus: this.props.onChangeQueryInputFocus ? false : !this.props.disableAutoFocus, inputRef: this.assignInputRef, autoComplete: "off", spellCheck: false, "aria-label": strings.getQueryBarSearchInputAriaLabel(this.props.appName), "aria-autocomplete": "list", "aria-controls": this.state.isSuggestionsVisible ? 'kbnTypeahead__items' : undefined, "aria-activedescendant": this.state.isSuggestionsVisible && typeof this.state.index === 'number' ? `suggestion-${this.state.index}` : undefined, role: "textbox", "data-test-subj": this.props.dataTestSubj || 'queryInput', isInvalid: this.props.isInvalid }, this.forwardNewValueIfNeeded(this.getQueryString())), this.props.iconType ? /*#__PURE__*/_react.default.createElement("div", { className: "euiFormControlLayoutIcons euiFormControlLayoutIcons--absolute euiFormControlLayoutIcons--left" }, /*#__PURE__*/_react.default.createElement(_eui.EuiIcon, { className: "euiFormControlLayoutCustomIcon__icon", "aria-hidden": "true", type: this.props.iconType })) : null, this.props.isClearable && !this.props.isDisabled && this.props.query.query ? /*#__PURE__*/_react.default.createElement("div", { className: "euiFormControlLayoutIcons euiFormControlLayoutIcons--absolute euiFormControlLayoutIcons--right" }, /*#__PURE__*/_react.default.createElement("button", { type: "button", className: "euiFormControlLayoutClearButton", title: strings.getQueryBarClearInputLabel(), onClick: () => { this.onQueryStringChange(''); if (this.props.autoSubmit) { this.onSubmit({ query: '', language: this.props.query.language }); } } }, /*#__PURE__*/_react.default.createElement(_eui.EuiIcon, { className: "euiFormControlLayoutClearButton__icon", type: "cross" }))) : null), /*#__PURE__*/_react.default.createElement(_eui.EuiPortal, null, /*#__PURE__*/_react.default.createElement(_typeahead.SuggestionsComponent, { show: this.state.isSuggestionsVisible, suggestions: this.state.suggestions.slice(0, this.state.suggestionLimit), index: this.state.index, onClick: this.onClickSuggestion, onMouseEnter: this.onMouseEnterSuggestion, loadMore: this.increaseLimit, size: this.props.size, inputContainer: this.state.queryBarInputDiv }))))); } /** * Used to apply any string formatting to textarea value before converting it to {@link Query} and emitting it to the parent. * This is a bit lower level then {@link fromUser} and needed to address any cross-browser inconsistencies where * {@link forwardNewValueIfNeeded} should be kept in mind */ formatTextAreaValue(newValue) { // Safari has a bug that it sometimes uses a non-breaking space instead of a regular space // this breaks the search query: https://github.com/elastic/kibana/issues/87176 return newValue.replace(/\u00A0/g, ' '); } /** * When passing a "value" prop into a textarea, * check first if value has changed because of {@link formatTextAreaValue}, * if this is just a formatting change, then skip this update by re-using current textarea value. * This is needed to avoid re-rendering to preserve focus and selection * @private */ forwardNewValueIfNeeded(newQueryString) { var _this$inputRef$value, _this$inputRef3; const oldQueryString = (_this$inputRef$value = (_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.value) !== null && _this$inputRef$value !== void 0 ? _this$inputRef$value : ''; const formattedNewQueryString = this.formatTextAreaValue(newQueryString); // if old & new values are equal with formatting applied, then return an old query without formatting applied if (formattedNewQueryString === this.formatTextAreaValue(oldQueryString)) { return oldQueryString; } else { return formattedNewQueryString; } } } exports.default = QueryStringInputUI; (0, _defineProperty2.default)(QueryStringInputUI, "defaultProps", { storageKey: _common.KIBANA_USER_QUERY_LANGUAGE_KEY, iconType: 'search', isClearable: true });