diff --git a/package.json b/package.json
index 3bcd5075747ea..2c71bd4dd679f 100644
--- a/package.json
+++ b/package.json
@@ -1064,7 +1064,6 @@
"react-popper-tooltip": "^3.1.1",
"react-redux": "^7.2.8",
"react-resizable": "^3.0.4",
- "react-resize-detector": "^7.1.1",
"react-reverse-portal": "^2.1.0",
"react-router": "^5.3.4",
"react-router-config": "^5.1.1",
diff --git a/packages/shared-ux/code_editor/impl/BUILD.bazel b/packages/shared-ux/code_editor/impl/BUILD.bazel
index ad571cb379afd..24f18820496a4 100644
--- a/packages/shared-ux/code_editor/impl/BUILD.bazel
+++ b/packages/shared-ux/code_editor/impl/BUILD.bazel
@@ -25,7 +25,6 @@ BUNDLER_DEPS = [
"@npm//react",
"@npm//tslib",
"@npm//react-monaco-editor",
- "@npm//react-resize-detector",
]
js_library(
diff --git a/packages/shared-ux/code_editor/impl/code_editor.stories.tsx b/packages/shared-ux/code_editor/impl/code_editor.stories.tsx
index 38c063e4ebe2b..e4a78b328cbe3 100644
--- a/packages/shared-ux/code_editor/impl/code_editor.stories.tsx
+++ b/packages/shared-ux/code_editor/impl/code_editor.stories.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import { monaco as monacoEditor } from '@kbn/monaco';
@@ -32,7 +32,13 @@ const argTypes = mock.getArgumentTypes();
export const Basic = (params: CodeEditorStorybookParams) => {
return (
-
+
);
};
@@ -199,3 +205,39 @@ export const HoverProvider = () => {
);
};
+
+export const AutomaticResize = (params: CodeEditorStorybookParams) => {
+ return (
+
+
+
+ );
+};
+
+AutomaticResize.argTypes = argTypes;
+
+export const FitToContent = (params: CodeEditorStorybookParams) => {
+ const [value, setValue] = useState('hello');
+ return (
+ {
+ setValue(newValue);
+ action('on change');
+ }}
+ value={value}
+ fitToContent={{ minLines: 3, maxLines: 5 }}
+ options={{ automaticLayout: true }}
+ />
+ );
+};
+
+FitToContent.argTypes = argTypes;
diff --git a/packages/shared-ux/code_editor/impl/code_editor.tsx b/packages/shared-ux/code_editor/impl/code_editor.tsx
index b41906d5ed456..54be81c2df4b5 100644
--- a/packages/shared-ux/code_editor/impl/code_editor.tsx
+++ b/packages/shared-ux/code_editor/impl/code_editor.tsx
@@ -7,7 +7,6 @@
*/
import React, { useState, useRef, useCallback, useMemo, useEffect, KeyboardEvent } from 'react';
-import { useResizeDetector } from 'react-resize-detector';
import ReactMonacoEditor, {
type MonacoEditorProps as ReactMonacoEditorProps,
} from 'react-monaco-editor';
@@ -140,6 +139,15 @@ export interface CodeEditorProps {
* Alternate text to display, when an attempt is made to edit read only content. (Defaults to "Cannot edit in read-only editor")
*/
readOnlyMessage?: string;
+
+ /**
+ * Enables the editor to grow vertically to fit its content.
+ * This option overrides the `height` option.
+ */
+ fitToContent?: {
+ minLines?: number;
+ maxLines?: number;
+ };
}
export const CodeEditor: React.FC = ({
@@ -168,6 +176,7 @@ export const CodeEditor: React.FC = ({
readOnlyMessage = i18n.translate('sharedUXPackages.codeEditor.readOnlyMessage', {
defaultMessage: 'Cannot edit in read-only editor',
}),
+ fitToContent,
}) => {
const { colorMode, euiTheme } = useEuiTheme();
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
@@ -189,7 +198,7 @@ export const CodeEditor: React.FC = ({
const isReadOnly = options?.readOnly ?? false;
- const _editor = useRef(null);
+ const [_editor, setEditor] = useState(null);
const _placeholderWidget = useRef(null);
const isSuggestionMenuOpen = useRef(false);
const editorHint = useRef(null);
@@ -197,21 +206,10 @@ export const CodeEditor: React.FC = ({
const [isHintActive, setIsHintActive] = useState(true);
- const _updateDimensions = useCallback(() => {
- _editor.current?.layout();
- }, []);
-
- useResizeDetector({
- handleWidth: true,
- handleHeight: true,
- onResize: _updateDimensions,
- refreshMode: 'debounce',
- });
-
const startEditing = useCallback(() => {
setIsHintActive(false);
- _editor.current?.focus();
- }, []);
+ _editor?.focus();
+ }, [_editor]);
const stopEditing = useCallback(() => {
setIsHintActive(true);
@@ -391,8 +389,6 @@ export const CodeEditor: React.FC = ({
remeasureFonts();
- _editor.current = editor;
-
const textbox = editor.getDomNode()?.getElementsByTagName('textarea')[0];
if (textbox) {
// Make sure the textarea is not directly accessible with TAB
@@ -435,6 +431,7 @@ export const CodeEditor: React.FC = ({
}
editorDidMount?.(editor);
+ setEditor(editor);
},
[editorDidMount, onBlurMonaco, onKeydownMonaco, readOnlyMessage]
);
@@ -454,16 +451,18 @@ export const CodeEditor: React.FC = ({
}, []);
useEffect(() => {
- if (placeholder && !value && _editor.current) {
+ if (placeholder && !value && _editor) {
// Mounts editor inside constructor
- _placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor.current);
+ _placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor);
}
return () => {
_placeholderWidget.current?.dispose();
_placeholderWidget.current = null;
};
- }, [placeholder, value, euiTheme]);
+ }, [placeholder, value, euiTheme, _editor]);
+
+ useFitToContent({ editor: _editor, fitToContent, isFullScreen });
const { CopyButton } = useCopy({ isCopyable, value });
@@ -512,7 +511,7 @@ export const CodeEditor: React.FC = ({
value={value}
onChange={onChange}
width={isFullScreen ? '100vw' : width}
- height={isFullScreen ? '100vh' : height}
+ height={isFullScreen ? '100vh' : fitToContent ? undefined : height}
editorWillMount={_editorWillMount}
editorDidMount={_editorDidMount}
editorWillUnmount={_editorWillUnmount}
@@ -640,3 +639,40 @@ const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string })
return { showCopyButton, CopyButton };
};
+
+const useFitToContent = ({
+ editor,
+ fitToContent,
+ isFullScreen,
+}: {
+ editor: monaco.editor.IStandaloneCodeEditor | null;
+ isFullScreen: boolean;
+ fitToContent?: { minLines?: number; maxLines?: number };
+}) => {
+ const isFitToContent = !!fitToContent;
+ const minLines = fitToContent?.minLines;
+ const maxLines = fitToContent?.maxLines;
+ useEffect(() => {
+ if (!editor) return;
+ if (isFullScreen) return;
+ if (!isFitToContent) return;
+
+ const updateHeight = () => {
+ const contentHeight = editor.getContentHeight();
+ const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
+ const minHeight = (minLines ?? 1) * lineHeight;
+ let maxHeight = maxLines ? maxLines * lineHeight : contentHeight;
+ maxHeight = Math.max(minHeight, maxHeight);
+ editor.layout({
+ height: Math.min(maxHeight, Math.max(minHeight, contentHeight)),
+ width: editor.getLayoutInfo().width,
+ });
+ };
+ updateHeight();
+ const disposable = editor.onDidContentSizeChange(updateHeight);
+ return () => {
+ disposable.dispose();
+ editor.layout(); // reset the layout that was controlled by the fitToContent
+ };
+ }, [editor, isFitToContent, minLines, maxLines, isFullScreen]);
+};
diff --git a/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx b/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx
index f5b2fc9ab4108..d9b2d4093e67f 100644
--- a/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx
+++ b/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx
@@ -106,7 +106,9 @@ export const MockedMonacoEditor = ({
className?: string;
['data-test-subj']?: string;
}) => {
- editorWillMount?.(monaco);
+ useComponentWillMount(() => {
+ editorWillMount?.(monaco);
+ });
useEffect(() => {
editorDidMount?.(
@@ -133,3 +135,11 @@ export const MockedMonacoEditor = ({
);
};
+
+const useComponentWillMount = (cb: Function) => {
+ const willMount = React.useRef(true);
+
+ if (willMount.current) cb();
+
+ willMount.current = false;
+};
diff --git a/src/plugins/kibana_react/public/url_template_editor/styles.scss b/src/plugins/kibana_react/public/url_template_editor/styles.scss
index 99379b21454ec..1bff881958076 100644
--- a/src/plugins/kibana_react/public/url_template_editor/styles.scss
+++ b/src/plugins/kibana_react/public/url_template_editor/styles.scss
@@ -1,5 +1,5 @@
.urlTemplateEditor__container {
.monaco-editor .lines-content.monaco-editor-background {
- margin: $euiSizeS;
+ margin: 0 $euiSizeS;
}
}
diff --git a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx
index 770753bc8c35c..13773396eba76 100644
--- a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx
+++ b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx
@@ -22,6 +22,7 @@ export interface UrlTemplateEditorVariable {
export interface UrlTemplateEditorProps {
value: string;
height?: CodeEditorProps['height'];
+ fitToContent?: CodeEditorProps['fitToContent'];
variables?: UrlTemplateEditorVariable[];
onChange: CodeEditorProps['onChange'];
onEditor?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
@@ -31,6 +32,7 @@ export interface UrlTemplateEditorProps {
export const UrlTemplateEditor: React.FC = ({
height = 105,
+ fitToContent,
value,
variables,
onChange,
@@ -127,6 +129,7 @@ export const UrlTemplateEditor: React.FC = ({
= ({
},
wordWrap: 'on',
wrappingIndent: 'none',
+ automaticLayout: true,
+ scrollBeyondLastLine: false,
+ overviewRulerLanes: 0,
+ padding: { top: 8, bottom: 8 },
}}
/>
diff --git a/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx b/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
index 0495f2d61063c..4db2510ba22e5 100644
--- a/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
+++ b/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
@@ -88,6 +88,7 @@ export const UrlDrilldownCollectConfig: React.FC
labelAppend={variablesDropdown}
>