Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .yarn/versions/b0eed05a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
releases:
"@handlewithcare/react-codemirror": patch
11 changes: 10 additions & 1 deletion demo/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,16 @@ function DemoEditor() {
dispatchTransactions={dispatchTransactions}
extensions={extensions}
>
<ThemePicker />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ThemePicker />
<button
onClick={() => {
setState(editorState);
}}
>
Reset
</button>
</div>
<CodeMirrorEditor />
</CodeMirror>
</main>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"peerDependencies": {
"@codemirror/state": "*",
"@codemirror/view": "*",
"react": ">=17 <=19",
"react-dom": ">=17 <=19"
"react": ">=18 <=19",
"react-dom": ">=18 <=19"
}
}
43 changes: 24 additions & 19 deletions src/hooks/useEditor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { EditorState, type Transaction } from "@codemirror/state";
import { EditorView, type EditorViewConfig } from "@codemirror/view";
import { useLayoutEffect, useRef, useState } from "react";
import { useRef, useState } from "react";

import { tracking } from "../extensions/tracking.js";

import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
import { useEffectEvent } from "./useEffectEvent.js";

export type UseViewOptions = Omit<EditorViewConfig, "parent"> & {
defaultState?: EditorState;
};
Expand Down Expand Up @@ -68,35 +71,37 @@ export function useEditor(

const [view, setView] = useState<EditorView | null>(null);

useLayoutEffect(() => {
const createEditorView = useEffectEvent((parent: HTMLDivElement | null) => {
if (parent) {
return new EditorView({
parent,
...config,
});
}
return null;
});

useClientLayoutEffect(() => {
const view = createEditorView(parent);
setView(view);

return () => {
view?.destroy();
};
}, [view]);
}, [parent, createEditorView]);

// eslint-disable-next-line react-hooks/exhaustive-deps
useLayoutEffect(() => {
useClientLayoutEffect(() => {
dispatchTransactionsRef.current = dispatchTransactions;

if (!parent) {
setView(null);
return;
}
if (!view || view.dom.parentElement !== parent) {
const newView = new EditorView({
parent,
...config,
});
setView(newView);
return;
}

const trs = state.field(tracking);
const newTrs = trs.filter((tr) => !seen.current.has(tr));

if (newTrs.length) {
if (view && newTrs.length) {
view.update(newTrs);
newTrs.forEach((tr) => seen.current.add(tr));
} else if (view && view.state.doc.toString() !== state.doc.toString()) {
view.setState(state);
seen.current = new Set(trs);
}
});

Expand Down
16 changes: 16 additions & 0 deletions src/hooks/useEffectEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useCallback, useInsertionEffect, useRef } from "react";

export function useEffectEvent<P extends unknown[], R>(
fn: (...args: P) => R,
): (...funcArgs: P) => R {
const ref = useRef(fn);

useInsertionEffect(() => {
ref.current = fn;
}, [fn]);

return useCallback((...args: P): R => {
const f = ref.current;
return f(...args);
}, []);
}
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -647,8 +647,8 @@ __metadata:
peerDependencies:
"@codemirror/state": "*"
"@codemirror/view": "*"
react: ">=17 <=19"
react-dom: ">=17 <=19"
react: ">=18 <=19"
react-dom: ">=18 <=19"
languageName: unknown
linkType: soft

Expand Down