Skip to content

Commit 8533c49

Browse files
Merge branch 'next' into fix/codeEditor-MT-53
2 parents 8ec3a90 + 2fbf113 commit 8533c49

4 files changed

Lines changed: 122 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ This is a major release, and it might be not compatible with your current usage
1616

1717
### Fixed
1818

19+
- <CodeMirror />:
20+
- Editor is re-created after certain property changes and is reset, i.e. loses it current state.
21+
- <CodeAutocompleteField />:
22+
- Read-only mode does not work correctly. It is still possible to change the value via pressing Enter (in multiline mode) or clicking the clear button.
23+
- First auto-completion item not marked as active when drop down first shown.
1924
- `<CodeEditor />`:
2025
- Enter key handling (adding new line) broken when `onKeyDown` is defined.
21-
- `<CodeAutocompleteField />`:
22-
- First auto-completion item not marked as active when drop down first shown.
2326

2427

2528
## [24.3.0] - 2025-06-05

src/components/AutoSuggestion/AutoSuggestion.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ const AutoSuggestion = ({
676676
showScrollBar,
677677
multiline,
678678
handleInputMouseDown,
679+
readOnly
679680
]);
680681

681682
const hasError = !!value.current && !pathIsValid && !pathValidationPending;
@@ -720,6 +721,7 @@ const AutoSuggestion = ({
720721
data-test-id={"value-path-clear-btn"}
721722
name="operation-clear"
722723
text={clearIconText}
724+
disabled={readOnly}
723725
onClick={handleInputEditorClear}
724726
/>
725727
</span>

src/extensions/codemirror/CodeMirror.tsx

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useMemo, useRef } from "react";
22
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
33
import { foldKeymap } from "@codemirror/language";
4-
import { EditorState, Extension } from "@codemirror/state";
4+
import { EditorState, Extension, Compartment } from "@codemirror/state";
55
import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } from "@codemirror/view";
66
import { minimalSetup } from "codemirror";
77

@@ -30,7 +30,7 @@ import {
3030
adaptedHighlightSpecialChars,
3131
adaptedLineNumbers,
3232
adaptedLintGutter,
33-
adaptedPlaceholder,
33+
adaptedPlaceholder, compartment,
3434
} from "./tests/codemirrorTestHelper";
3535
import { ExtensionCreator } from "./types";
3636

@@ -221,7 +221,24 @@ export const CodeEditor = ({
221221
}: CodeEditorProps) => {
222222
const parent = useRef<any>(undefined);
223223
const [view, setView] = React.useState<EditorView | undefined>();
224+
const currentView = React.useRef<EditorView>()
225+
currentView.current = view
226+
const currentReadOnly = React.useRef(readOnly)
227+
currentReadOnly.current = readOnly
224228
const [showPreview, setShowPreview] = React.useState<boolean>(false);
229+
// CodeMirror Compartments in order to allow for re-configuration after initialization
230+
const readOnlyCompartment = React.useRef<Compartment>(compartment())
231+
const wrapLinesCompartment = React.useRef<Compartment>(compartment())
232+
const preventLineNumbersCompartment = React.useRef<Compartment>(compartment())
233+
const shouldHaveMinimalSetupCompartment = React.useRef<Compartment>(compartment())
234+
const placeholderCompartment = React.useRef<Compartment>(compartment())
235+
const modeCompartment = React.useRef<Compartment>(compartment())
236+
const keyMapConfigsCompartment = React.useRef<Compartment>(compartment())
237+
const tabIntentSizeCompartment = React.useRef<Compartment>(compartment())
238+
const disabledCompartment = React.useRef<Compartment>(compartment())
239+
const supportCodeFoldingCompartment = React.useRef<Compartment>(compartment())
240+
const useLintingCompartment = React.useRef<Compartment>(compartment())
241+
const shouldHighlightActiveLineCompartment = React.useRef<Compartment>(compartment())
225242

226243
const linters = useMemo(() => {
227244
if (!mode) {
@@ -240,7 +257,7 @@ export const CodeEditor = ({
240257

241258
const onKeyDownHandler = (event: KeyboardEvent, view: EditorView) => {
242259
if (onKeyDown && !onKeyDown(event)) {
243-
if (event.key === "Enter") {
260+
if (event.key === "Enter" && !currentReadOnly.current) {
244261
const cursor = view.state.selection.main.head;
245262
view.dispatch({
246263
changes: {
@@ -263,14 +280,17 @@ export const CodeEditor = ({
263280
return false;
264281
};
265282

266-
React.useEffect(() => {
283+
const createKeyMapConfigs = () => {
267284
const tabIndent =
268285
!!(tabIntentStyle === "tab" && mode && !(tabForceSpaceForModes ?? []).includes(mode)) || enableTab;
269-
const keyMapConfigs = [
286+
return [
270287
defaultKeymap as KeyBinding,
271288
...addToKeyMapConfigFor(supportCodeFolding, ...foldKeymap),
272289
...addToKeyMapConfigFor(tabIndent, indentWithTab),
273290
];
291+
}
292+
293+
React.useEffect(() => {
274294
const domEventHandlers = {
275295
...addHandlersFor(!!onScroll, "scroll", onScroll),
276296
...addHandlersFor(
@@ -284,13 +304,13 @@ export const CodeEditor = ({
284304
} as DOMEventHandlers<any>;
285305
const extensions = [
286306
markField,
287-
adaptedPlaceholder(placeholder),
307+
placeholderCompartment.current.of(adaptedPlaceholder(placeholder)),
288308
adaptedHighlightSpecialChars(),
289-
useCodeMirrorModeExtension(mode),
290-
keymap?.of(keyMapConfigs),
291-
EditorState?.tabSize.of(tabIntentSize),
292-
EditorState?.readOnly.of(readOnly),
293-
EditorView?.editable.of(!disabled),
309+
modeCompartment.current.of(useCodeMirrorModeExtension(mode)),
310+
keyMapConfigsCompartment.current.of(keymap?.of(createKeyMapConfigs())),
311+
tabIntentSizeCompartment.current.of(EditorState?.tabSize.of(tabIntentSize)),
312+
readOnlyCompartment.current.of(EditorState?.readOnly.of(readOnly)),
313+
disabledCompartment.current.of(EditorView?.editable.of(!disabled)),
294314
AdaptedEditorViewDomEventHandlers(domEventHandlers) as Extension,
295315
EditorView?.updateListener.of((v: ViewUpdate) => {
296316
if (disabled) return;
@@ -326,12 +346,12 @@ export const CodeEditor = ({
326346
}
327347
}
328348
}),
329-
addExtensionsFor(shouldHaveMinimalSetup, minimalSetup),
330-
addExtensionsFor(!preventLineNumbers, adaptedLineNumbers()),
331-
addExtensionsFor(shouldHighlightActiveLine, adaptedHighlightActiveLine()),
332-
addExtensionsFor(wrapLines, EditorView?.lineWrapping),
333-
addExtensionsFor(supportCodeFolding, adaptedFoldGutter(), adaptedCodeFolding()),
334-
addExtensionsFor(useLinting, ...linters),
349+
shouldHaveMinimalSetupCompartment.current.of(addExtensionsFor(shouldHaveMinimalSetup, minimalSetup)),
350+
preventLineNumbersCompartment.current.of(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers())),
351+
shouldHighlightActiveLineCompartment.current.of(addExtensionsFor(shouldHighlightActiveLine, adaptedHighlightActiveLine())),
352+
wrapLinesCompartment.current.of(addExtensionsFor(wrapLines, EditorView?.lineWrapping)),
353+
supportCodeFoldingCompartment.current.of(addExtensionsFor(supportCodeFolding, adaptedFoldGutter(), adaptedCodeFolding())),
354+
useLintingCompartment.current.of(addExtensionsFor(useLinting, ...linters)),
335355
additionalExtensions,
336356
];
337357

@@ -373,7 +393,64 @@ export const CodeEditor = ({
373393
setView(undefined);
374394
}
375395
};
376-
}, [parent.current, mode, preventLineNumbers, wrapLines]);
396+
}, [parent.current]);
397+
398+
// Updates an extension for a specific parameter that has changed after the initialization
399+
const updateExtension = (extension: Extension | undefined, parameterCompartment: Compartment): void => {
400+
if(extension) {
401+
currentView.current?.dispatch({
402+
effects: parameterCompartment.reconfigure(extension)
403+
})
404+
}
405+
}
406+
407+
React.useEffect(() => {
408+
updateExtension(EditorState?.readOnly.of(readOnly!), readOnlyCompartment.current)
409+
}, [readOnly])
410+
411+
React.useEffect(() => {
412+
updateExtension(adaptedPlaceholder(placeholder), placeholderCompartment.current)
413+
}, [placeholder])
414+
415+
React.useEffect(() => {
416+
updateExtension(useCodeMirrorModeExtension(mode), modeCompartment.current)
417+
}, [mode])
418+
419+
React.useEffect(() => {
420+
updateExtension(keymap?.of(createKeyMapConfigs()), keyMapConfigsCompartment.current)
421+
}, [supportCodeFolding, mode, tabIntentStyle, (tabForceSpaceForModes ?? []).join(", "), enableTab])
422+
423+
React.useEffect(() => {
424+
updateExtension(EditorState?.tabSize.of(tabIntentSize ?? 2), tabIntentSizeCompartment.current)
425+
}, [tabIntentSize])
426+
427+
React.useEffect(() => {
428+
updateExtension(EditorView?.editable.of(!disabled), disabledCompartment.current)
429+
}, [disabled])
430+
431+
React.useEffect(() => {
432+
updateExtension(addExtensionsFor(shouldHaveMinimalSetup ?? true, minimalSetup), shouldHaveMinimalSetupCompartment.current)
433+
}, [shouldHaveMinimalSetup])
434+
435+
React.useEffect(() => {
436+
updateExtension(addExtensionsFor(!preventLineNumbers, adaptedLineNumbers()), preventLineNumbersCompartment.current)
437+
}, [preventLineNumbers])
438+
439+
React.useEffect(() => {
440+
updateExtension(addExtensionsFor(shouldHighlightActiveLine ?? false, adaptedHighlightActiveLine()), shouldHighlightActiveLineCompartment.current)
441+
}, [shouldHighlightActiveLine])
442+
443+
React.useEffect(() => {
444+
updateExtension(addExtensionsFor(wrapLines ?? false, EditorView?.lineWrapping), wrapLinesCompartment.current)
445+
}, [wrapLines])
446+
447+
React.useEffect(() => {
448+
updateExtension(addExtensionsFor(supportCodeFolding ?? false, adaptedFoldGutter(), adaptedCodeFolding()), supportCodeFoldingCompartment.current)
449+
}, [supportCodeFolding])
450+
451+
React.useEffect(() => {
452+
updateExtension(addExtensionsFor(useLinting ?? false, ...linters), useLintingCompartment.current)
453+
}, [mode, useLinting])
377454

378455
const hasToolbarSupport = mode && ModeToolbarSupport.indexOf(mode) > -1 && useToolbar;
379456

src/extensions/codemirror/tests/codemirrorTestHelper.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { EditorView, placeholder, highlightSpecialChars, lineNumbers, highlightActiveLine } from "@codemirror/view";
1111
import { syntaxHighlighting, foldGutter, codeFolding } from "@codemirror/language";
12-
import { Extension } from "@codemirror/state";
12+
import {Extension, Compartment, StateEffect, EditorState} from "@codemirror/state";
1313
import { lintGutter } from "@codemirror/lint";
1414

1515
/** placeholder extension, current error '_view.placeholder is not a function' */
@@ -34,6 +34,25 @@ export const AdaptedEditorView = isConstructor(EditorView)
3434
destroy() {}
3535
} as any);
3636

37+
/** Creates a new compartment or a mock of a compartment. */
38+
export const compartment = () => {
39+
if(isConstructor(Compartment)) {
40+
return new Compartment()
41+
} else {
42+
let extension: Extension | undefined = undefined
43+
return {
44+
of: (ext: Extension): Extension => {
45+
extension = ext
46+
return ext
47+
},
48+
reconfigure: (_content: Extension): StateEffect<unknown> => {
49+
return {} as StateEffect<any>
50+
},
51+
get: (_state: EditorState): Extension | undefined => extension
52+
}
53+
}
54+
}
55+
3756
const emptyExtension = (() => {}) as any;
3857
/** extension adding event handlers, current error '(view, domEventHandlers) is not a function' */
3958
export const AdaptedEditorViewDomEventHandlers =

0 commit comments

Comments
 (0)