UNPKG

@grafana/ui

Version:
170 lines (167 loc) 5.96 kB
import { jsx } from 'react/jsx-runtime'; import { cx, css } from '@emotion/css'; import classNames from 'classnames'; import { debounce } from 'lodash'; import { PureComponent } from 'react'; import Plain from 'slate-plain-serializer'; import { Editor } from 'slate-react'; import { selectors } from '@grafana/e2e-selectors'; import { ClearPlugin } from '../../slate-plugins/clear.mjs'; import { ClipboardPlugin } from '../../slate-plugins/clipboard.mjs'; import { IndentationPlugin } from '../../slate-plugins/indentation.mjs'; import { NewlinePlugin } from '../../slate-plugins/newline.mjs'; import { RunnerPlugin } from '../../slate-plugins/runner.mjs'; import { SelectionShortcutsPlugin } from '../../slate-plugins/selection_shortcuts.mjs'; import { SuggestionsPlugin } from '../../slate-plugins/suggestions.mjs'; import { withTheme2 } from '../../themes/ThemeContext.mjs'; import { getFocusStyles } from '../../themes/mixins.mjs'; import { makeValue, SCHEMA } from '../../utils/slate.mjs'; class UnThemedQueryField extends PureComponent { constructor(props) { super(props); this.lastExecutedValue = null; this.mounted = false; this.editor = null; /** * Update local state, propagate change upstream and optionally run the query afterwards. */ this.onChange = (value, runQuery) => { const documentChanged = value.document !== this.state.value.document; const prevValue = this.state.value; if (this.props.onRichValueChange) { this.props.onRichValueChange(value); } this.setState({ value }, () => { if (documentChanged) { const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value); if (textChanged && runQuery) { this.runOnChangeAndRunQuery(); } if (textChanged && !runQuery) { this.runOnChangeDebounced(); } } }); }; this.runOnChange = () => { const { onChange } = this.props; const value = Plain.serialize(this.state.value); if (onChange) { onChange(this.cleanText(value)); } }; this.runOnRunQuery = () => { const { onRunQuery } = this.props; if (onRunQuery) { onRunQuery(); this.lastExecutedValue = this.state.value; } }; this.runOnChangeAndRunQuery = () => { this.runOnChange(); this.runOnRunQuery(); }; /** * We need to handle blur events here mainly because of dashboard panels which expect to have query executed on blur. */ this.handleBlur = (_, editor, next) => { const { onBlur } = this.props; if (onBlur) { onBlur(); } else { const previousValue = this.lastExecutedValue ? Plain.serialize(this.lastExecutedValue) : ""; const currentValue = Plain.serialize(editor.value); if (previousValue !== currentValue) { this.runOnChangeAndRunQuery(); } } return next(); }; this.runOnChangeDebounced = debounce(this.runOnChange, 500); const { onTypeahead, cleanText, portalOrigin, onWillApplySuggestion } = props; this.plugins = [ // SuggestionsPlugin and RunnerPlugin need to be before NewlinePlugin // because they override Enter behavior SuggestionsPlugin({ onTypeahead, cleanText, portalOrigin, onWillApplySuggestion }), RunnerPlugin({ handler: this.runOnChangeAndRunQuery }), NewlinePlugin(), ClearPlugin(), SelectionShortcutsPlugin(), IndentationPlugin(), ClipboardPlugin(), ...props.additionalPlugins || [] ].filter((p) => p); this.state = { suggestions: [], typeaheadContext: null, typeaheadPrefix: "", typeaheadText: "", value: makeValue(props.query || "", props.syntax) }; } componentDidMount() { this.mounted = true; } componentWillUnmount() { this.mounted = false; } componentDidUpdate(prevProps, prevState) { const { query, syntax, syntaxLoaded } = this.props; if (!prevProps.syntaxLoaded && syntaxLoaded && this.editor) { const editor = this.editor.insertText(" ").deleteBackward(1); this.onChange(editor.value, true); } const { value } = this.state; if (query !== prevProps.query) { if (query !== Plain.serialize(value)) { this.setState({ value: makeValue(query || "", syntax) }); } } } cleanText(text) { const newText = text.replace(/[\r]/g, ""); return newText; } render() { const { disabled, theme } = this.props; const wrapperClassName = classNames("slate-query-field__wrapper", { "slate-query-field__wrapper--disabled": disabled }); const styles = getStyles(theme); return /* @__PURE__ */ jsx("div", { className: cx(wrapperClassName, styles.wrapper), children: /* @__PURE__ */ jsx("div", { className: "slate-query-field", "data-testid": selectors.components.QueryField.container, children: /* @__PURE__ */ jsx( Editor, { ref: (editor) => this.editor = editor, schema: SCHEMA, autoCorrect: false, readOnly: this.props.disabled, onBlur: this.handleBlur, onClick: this.props.onClick, onChange: (change) => { this.onChange(change.value, false); }, placeholder: this.props.placeholder, plugins: this.plugins, spellCheck: false, value: this.state.value } ) }) }); } } // By default QueryField calls onChange if onBlur is not defined, this will trigger a rerender // And slate will claim the focus, making it impossible to leave the field. UnThemedQueryField.defaultProps = { onBlur: () => { } }; const QueryField = withTheme2(UnThemedQueryField); const getStyles = (theme) => { const focusStyles = getFocusStyles(theme); return { wrapper: css({ "&:focus-within": focusStyles }) }; }; export { QueryField, UnThemedQueryField }; //# sourceMappingURL=QueryField.mjs.map