"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.CodeEditor = void 0; var _react = _interopRequireWildcard(require("react")); var _reactResizeDetector = require("react-resize-detector"); var _reactMonacoEditor = _interopRequireDefault(require("react-monaco-editor")); var _eui = require("@elastic/eui"); var _monaco = require("@kbn/monaco"); var _i18n = require("@kbn/i18n"); var _i18nReact = require("@kbn/i18n-react"); var _classnames = _interopRequireDefault(require("classnames")); require("./register_languages"); var _remeasure_fonts = require("./remeasure_fonts"); var _editor_theme = require("./editor_theme"); var _placeholder_widget = require("./placeholder_widget"); require("./editor.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 CodeEditor = ({ languageId, value, onChange, width, height, options, overrideEditorWillMount, editorDidMount, editorWillMount, useDarkTheme: useDarkThemeProp, transparentBackground, suggestionProvider, signatureProvider, hoverProvider, placeholder, languageConfiguration, 'aria-label': ariaLabel = _i18n.i18n.translate('kibana-react.kibanaCodeEditor.ariaLabel', { defaultMessage: 'Code Editor' }), isCopyable = false, allowFullScreen = false }) => { var _options$readOnly, _options$theme; const { colorMode } = (0, _eui.useEuiTheme)(); const useDarkTheme = useDarkThemeProp !== null && useDarkThemeProp !== void 0 ? useDarkThemeProp : colorMode === 'DARK'; // We need to be able to mock the MonacoEditor in our test in order to not test implementation // detail and not have to call methods on the component instance. const MonacoEditor = (0, _react.useMemo)(() => { const isMockedComponent = typeof _reactMonacoEditor.default === 'function' && _reactMonacoEditor.default.name === 'JestMockEditor'; return isMockedComponent ? (0, _reactMonacoEditor.default)() : _reactMonacoEditor.default; }, []); const { FullScreenDisplay, FullScreenButton, isFullScreen, setIsFullScreen, onKeyDown } = useFullScreen({ allowFullScreen }); const isReadOnly = (_options$readOnly = options === null || options === void 0 ? void 0 : options.readOnly) !== null && _options$readOnly !== void 0 ? _options$readOnly : false; const _editor = (0, _react.useRef)(null); const _placeholderWidget = (0, _react.useRef)(null); const isSuggestionMenuOpen = (0, _react.useRef)(false); const editorHint = (0, _react.useRef)(null); const textboxMutationObserver = (0, _react.useRef)(null); const [isHintActive, setIsHintActive] = (0, _react.useState)(true); const promptClasses = (0, _classnames.default)('kibanaCodeEditor__keyboardHint', { 'kibanaCodeEditor__keyboardHint--isInactive': !isHintActive }); const _updateDimensions = (0, _react.useCallback)(() => { var _editor$current; (_editor$current = _editor.current) === null || _editor$current === void 0 ? void 0 : _editor$current.layout(); }, []); (0, _reactResizeDetector.useResizeDetector)({ handleWidth: true, handleHeight: true, onResize: _updateDimensions, refreshMode: 'debounce' }); const startEditing = (0, _react.useCallback)(() => { var _editor$current2; setIsHintActive(false); (_editor$current2 = _editor.current) === null || _editor$current2 === void 0 ? void 0 : _editor$current2.focus(); }, []); const stopEditing = (0, _react.useCallback)(() => { setIsHintActive(true); }, []); const onKeyDownHint = (0, _react.useCallback)(ev => { if (ev.key === _eui.keys.ENTER) { ev.preventDefault(); startEditing(); } }, [startEditing]); const onKeydownMonaco = (0, _react.useCallback)(ev => { if (ev.keyCode === _monaco.monaco.KeyCode.Escape) { // If the autocompletion context menu is open then we want to let ESCAPE close it but // **not** exit out of editing mode. if (!isSuggestionMenuOpen.current) { var _editorHint$current; ev.preventDefault(); ev.stopPropagation(); stopEditing(); (_editorHint$current = editorHint.current) === null || _editorHint$current === void 0 ? void 0 : _editorHint$current.focus(); } setIsFullScreen(false); } }, [stopEditing]); const onBlurMonaco = (0, _react.useCallback)(() => { stopEditing(); }, [stopEditing]); const renderPrompt = (0, _react.useCallback)(() => { const enterKey = /*#__PURE__*/_react.default.createElement("strong", null, _i18n.i18n.translate('kibana-react.kibanaCodeEditor.enterKeyLabel', { defaultMessage: 'Enter', description: 'The name used for the Enter key on keyword. Will be {key} in kibana-react.kibanaCodeEditor.startEditing(ReadOnly).' })); const escapeKey = /*#__PURE__*/_react.default.createElement("strong", null, _i18n.i18n.translate('kibana-react.kibanaCodeEditor.escapeKeyLabel', { defaultMessage: 'Esc', description: 'The label of the Escape key as printed on the keyboard. Will be {key} inside kibana-react.kibanaCodeEditor.stopEditing(ReadOnly).' })); return /*#__PURE__*/_react.default.createElement(_eui.EuiToolTip, { display: "block", content: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("p", null, isReadOnly ? /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "kibana-react.kibanaCodeEditor.startEditingReadOnly", defaultMessage: "Press {key} to start interacting with the code.", values: { key: enterKey } }) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "kibana-react.kibanaCodeEditor.startEditing", defaultMessage: "Press {key} to start editing.", values: { key: enterKey } })), /*#__PURE__*/_react.default.createElement("p", null, isReadOnly ? /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "kibana-react.kibanaCodeEditor.stopEditingReadOnly", defaultMessage: "Press {key} to stop interacting with the code.", values: { key: escapeKey } }) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "kibana-react.kibanaCodeEditor.stopEditing", defaultMessage: "Press {key} to stop editing.", values: { key: escapeKey } }))) }, /*#__PURE__*/_react.default.createElement("div", { className: promptClasses, id: (0, _eui.htmlIdGenerator)('codeEditor')(), ref: editorHint, tabIndex: 0, role: "button", onClick: startEditing, onKeyDown: onKeyDownHint, "aria-label": ariaLabel, "data-test-subj": "codeEditorHint" })); }, [onKeyDownHint, promptClasses, startEditing]); const _editorWillMount = (0, _react.useCallback)(__monaco => { if (__monaco !== _monaco.monaco) { throw new Error('react-monaco-editor is using a different version of monaco'); } if (overrideEditorWillMount) { overrideEditorWillMount(); return; } editorWillMount === null || editorWillMount === void 0 ? void 0 : editorWillMount(); _monaco.monaco.languages.onLanguage(languageId, () => { if (suggestionProvider) { _monaco.monaco.languages.registerCompletionItemProvider(languageId, suggestionProvider); } if (signatureProvider) { _monaco.monaco.languages.registerSignatureHelpProvider(languageId, signatureProvider); } if (hoverProvider) { _monaco.monaco.languages.registerHoverProvider(languageId, hoverProvider); } if (languageConfiguration) { _monaco.monaco.languages.setLanguageConfiguration(languageId, languageConfiguration); } }); // Register themes _monaco.monaco.editor.defineTheme('euiColors', useDarkTheme ? _editor_theme.DARK_THEME : _editor_theme.LIGHT_THEME); _monaco.monaco.editor.defineTheme('euiColorsTransparent', useDarkTheme ? _editor_theme.DARK_THEME_TRANSPARENT : _editor_theme.LIGHT_THEME_TRANSPARENT); }, [overrideEditorWillMount, editorWillMount, languageId, useDarkTheme, suggestionProvider, signatureProvider, hoverProvider, languageConfiguration]); (0, _react.useEffect)(() => { // Register themes when 'useDarkThem' changes _monaco.monaco.editor.defineTheme('euiColors', useDarkTheme ? _editor_theme.DARK_THEME : _editor_theme.LIGHT_THEME); _monaco.monaco.editor.defineTheme('euiColorsTransparent', useDarkTheme ? _editor_theme.DARK_THEME_TRANSPARENT : _editor_theme.LIGHT_THEME_TRANSPARENT); }, [useDarkTheme]); const _editorDidMount = (0, _react.useCallback)((editor, __monaco) => { var _editor$getDomNode, _editor$getContributi, _editor$getContributi2; if (__monaco !== _monaco.monaco) { throw new Error('react-monaco-editor is using a different version of monaco'); } (0, _remeasure_fonts.remeasureFonts)(); _editor.current = editor; const textbox = (_editor$getDomNode = editor.getDomNode()) === null || _editor$getDomNode === void 0 ? void 0 : _editor$getDomNode.getElementsByTagName('textarea')[0]; if (textbox) { // Make sure the textarea is not directly accesible with TAB textbox.tabIndex = -1; // The Monaco editor seems to override the tabindex and set it back to "0" // so we make sure that whenever the attributes change the tabindex stays at -1 textboxMutationObserver.current = new MutationObserver(function onTextboxAttributeChange() { if (textbox.tabIndex >= 0) { textbox.tabIndex = -1; } }); textboxMutationObserver.current.observe(textbox, { attributes: true }); } editor.onKeyDown(onKeydownMonaco); editor.onDidBlurEditorText(onBlurMonaco); // "widget" is not part of the TS interface but does exist // @ts-expect-errors const suggestionWidget = (_editor$getContributi = editor.getContribution('editor.contrib.suggestController')) === null || _editor$getContributi === void 0 ? void 0 : (_editor$getContributi2 = _editor$getContributi.widget) === null || _editor$getContributi2 === void 0 ? void 0 : _editor$getContributi2.value; // As I haven't found official documentation for "onDidShow" and "onDidHide" // we guard from possible changes in the underlying lib if (suggestionWidget && suggestionWidget.onDidShow && suggestionWidget.onDidHide) { suggestionWidget.onDidShow(() => { isSuggestionMenuOpen.current = true; }); suggestionWidget.onDidHide(() => { isSuggestionMenuOpen.current = false; }); } editorDidMount === null || editorDidMount === void 0 ? void 0 : editorDidMount(editor); }, [editorDidMount]); (0, _react.useEffect)(() => { return () => { var _textboxMutationObser; (_textboxMutationObser = textboxMutationObserver.current) === null || _textboxMutationObser === void 0 ? void 0 : _textboxMutationObser.disconnect(); }; }, []); (0, _react.useEffect)(() => { if (placeholder && !value && _editor.current) { // Mounts editor inside constructor _placeholderWidget.current = new _placeholder_widget.PlaceholderWidget(placeholder, _editor.current); } return () => { var _placeholderWidget$cu; (_placeholderWidget$cu = _placeholderWidget.current) === null || _placeholderWidget$cu === void 0 ? void 0 : _placeholderWidget$cu.dispose(); _placeholderWidget.current = null; }; }, [placeholder, value]); const { CopyButton } = useCopy({ isCopyable, value }); return /*#__PURE__*/_react.default.createElement("div", { className: "kibanaCodeEditor", onKeyDown: onKeyDown }, renderPrompt(), /*#__PURE__*/_react.default.createElement(FullScreenDisplay, null, allowFullScreen || isCopyable ? /*#__PURE__*/_react.default.createElement("div", { className: "kibanaCodeEditor__controls" }, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexGroup, { gutterSize: "xs" }, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, null, /*#__PURE__*/_react.default.createElement(CopyButton, null)), /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, null, /*#__PURE__*/_react.default.createElement(FullScreenButton, null)))) : null, /*#__PURE__*/_react.default.createElement(MonacoEditor, { theme: (_options$theme = options === null || options === void 0 ? void 0 : options.theme) !== null && _options$theme !== void 0 ? _options$theme : transparentBackground ? 'euiColorsTransparent' : 'euiColors', language: languageId, value: value, onChange: onChange, width: isFullScreen ? '100vw' : width, height: isFullScreen ? '100vh' : height, editorWillMount: _editorWillMount, editorDidMount: _editorDidMount, options: { padding: allowFullScreen || isCopyable ? { top: 24 } : {}, renderLineHighlight: 'none', scrollBeyondLastLine: false, minimap: { enabled: false }, scrollbar: { useShadows: false, // Scroll events are handled only when there is scrollable content. When there is scrollable content, the // editor should scroll to the bottom then break out of that scroll context and continue scrolling on any // outer scrollbars. alwaysConsumeMouseWheel: false }, wordBasedSuggestions: false, wordWrap: 'on', wrappingIndent: 'indent', matchBrackets: 'never', fontFamily: 'Roboto Mono', fontSize: isFullScreen ? 16 : 12, lineHeight: isFullScreen ? 24 : 21, ...options } }))); }; /** * Fullscreen logic */ exports.CodeEditor = CodeEditor; const useFullScreen = ({ allowFullScreen }) => { const [isFullScreen, setIsFullScreen] = (0, _react.useState)(false); const toggleFullScreen = () => { setIsFullScreen(!isFullScreen); }; const onKeyDown = (0, _react.useCallback)(event => { if (event.key === _eui.keys.ESCAPE) { event.preventDefault(); event.stopPropagation(); setIsFullScreen(false); } }, []); const FullScreenButton = () => { if (!allowFullScreen) return null; return /*#__PURE__*/_react.default.createElement(_eui.EuiI18n, { tokens: ['euiCodeBlock.fullscreenCollapse', 'euiCodeBlock.fullscreenExpand'], defaults: ['Collapse', 'Expand'] }, ([fullscreenCollapse, fullscreenExpand]) => /*#__PURE__*/_react.default.createElement(_eui.EuiButtonIcon, { className: "euiCodeBlock__fullScreenButton", onClick: toggleFullScreen, iconType: isFullScreen ? 'fullScreenExit' : 'fullScreen', color: "text", "aria-label": isFullScreen ? fullscreenCollapse : fullscreenExpand, size: "xs" })); }; const FullScreenDisplay = (0, _react.useMemo)(() => ({ children }) => { if (!isFullScreen) return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children); return /*#__PURE__*/_react.default.createElement(_eui.EuiOverlayMask, null, /*#__PURE__*/_react.default.createElement(_eui.EuiFocusTrap, { clickOutsideDisables: true }, /*#__PURE__*/_react.default.createElement("div", { className: 'kibanaCodeEditor__isFullScreen' }, children))); }, [isFullScreen]); return { FullScreenButton, FullScreenDisplay, onKeyDown, isFullScreen, setIsFullScreen }; }; const useCopy = ({ isCopyable, value }) => { const showCopyButton = isCopyable && value; const CopyButton = () => { if (!showCopyButton) return null; return /*#__PURE__*/_react.default.createElement("div", { className: "euiCodeBlock__copyButton" }, /*#__PURE__*/_react.default.createElement(_eui.EuiI18n, { token: "euiCodeBlock.copyButton", default: "Copy" }, copyButton => /*#__PURE__*/_react.default.createElement(_eui.EuiCopy, { textToCopy: value }, copy => /*#__PURE__*/_react.default.createElement(_eui.EuiButtonIcon, { onClick: copy, iconType: "copyClipboard", color: "text", "aria-label": copyButton, size: "xs" })))); }; return { showCopyButton, CopyButton }; }; // React.lazy requires default export // eslint-disable-next-line import/no-default-export var _default = CodeEditor; exports.default = _default;