diff --git a/.yarn/versions/9c02ca2d.yml b/.yarn/versions/9c02ca2d.yml
new file mode 100644
index 0000000..766dd55
--- /dev/null
+++ b/.yarn/versions/9c02ca2d.yml
@@ -0,0 +1,2 @@
+releases:
+ "@handlewithcare/react-codemirror": patch
diff --git a/README.md b/README.md
index d6fbd49..f0b11a1 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,362 @@
# @handlewithcare/react-codemirror
-- Basic hooks DONE
-- CMEditor.Before / CMEditor.After slots NOT WORKING
-- Compartments?
-- React-based widgets/replace decos? This may require a whole React EditorView
- to avoid state tearing TODO
+A simple library for safely integrating [React](https://react.dev) and
+[CodeMirror](https://codemirror.net).
+
+## Installation
+
+npm:
+
+```sh
+npm install @handlewithcare/react-codemirror
+```
+
+yarn:
+
+```sh
+yarn add @handlewithcare/react-codemirror
+```
+
+
+
+- [Overview](#overview)
+- [Usage](#usage)
+- [API](#api)
+ - [`CodeMirror`](#codemirror)
+ - [`CodeMirrorEditor`](#codemirroreditor)
+ - [`useEditorState`](#useeditorstate)
+ - [`useCompartment`](#usecompartment)
+ - [`useEditorEventCallback`](#useeditoreventcallback)
+ - [`useEditorEffect`](#useeditoreffect)
+- [Looking for someone to collaborate with?](#looking-for-someone-to-collaborate-with)
+
+
+
+## Overview
+
+This library provides an API similar to that of
+[`@handlewithcare/react-prosemirror`](https://github.com/handlewithcarecollective/react-prosemirror)
+for integrating React with CodeMirror. The surface area is considerably smaller,
+because CodeMirror has no notion of NodeViews. A future version of this library
+may support React-based widgets and tooltips.
+
+## Usage
+
+To get started, render the `CodeMirror` and `CodeMirrorEditor` components:
+
+```tsx
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+import React, { StrictMode, useCallback, useState } from "react";
+
+const editorState = EditorState.create({ doc: "", basicSetup });
+
+function CodeEditor() {
+ const [state, setState] = useState(editorState);
+
+ const dispatchTransactions = useCallback((trs: readonly Transaction[]) => {
+ setState(trs[trs.length - 1]!.state);
+ }, []);
+
+ return (
+
+
+
+ );
+}
+```
+
+The `CodeMirrorEditor` is where the actual CodeMirror editor will be
+instantiated. It can be nested anywhere as a descendant of the `CodeMirror`
+component.
+
+The `useCompartment` hook can be used to configure dynamic CodeMirror
+extensions. Here’s an example, using a simple language picker:
+
+```tsx
+// LanguagePicker.tsx
+import { htmlLanguage, html } from "@codemirror/lang-html";
+import { language, type Language } from "@codemirror/language";
+import { javascript, javascriptLanguage } from "@codemirror/lang-javascript";
+
+interface Props {
+ value: Language;
+ onChange: (lang: Language) => void;
+}
+
+function LanguagePicker({ value, onChange }: Props) {
+ return (
+
+ );
+}
+
+// CodeEditor.tsx
+import { javascript } from "@codemirror/lang-javascript";
+import { language } from "@codemirror/language";
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+import React, { StrictMode, useCallback, useState } from "react";
+
+import { LanguagePicker } from "./LanguagePicker.tsx";
+
+function CodeEditor() {
+ const [languageExt, reconfigureLanguage] = useCompartment(javascript());
+
+ const extensions = [basicSetup, languageExt];
+
+ const [state, setState] = useState(() =>
+ EditorState.create({ doc: "", extensions }),
+ );
+
+ const dispatchTransactions = useCallback((trs: readonly Transaction[]) => {
+ setState(trs[trs.length - 1]!.state);
+ }, []);
+
+ return (
+
+ reconfigureLanguage(lang)}
+ />
+
+
+ );
+}
+```
+
+The `useEditorEventCallback` hook is a more general purpose hook that allows
+components that are descendants of the `CodeMirror` component to dispatch a
+transaction or otherwise interact with the CodeMirror EditorView in an event
+callback:
+
+```tsx
+// AutocompleteButton.tsx
+import { useEditorEventCallback } from "@handlewithcare/react-codemirror";
+
+export function AutocompleteButton() {
+ const onClick = useEditorEventCallback(async (view) => {
+ const result = await fetchMagicAutoComplete(view.state.doc.toString());
+ view.dispatch({
+ changes: {
+ from: 0,
+ to: view.state.doc.size,
+ insert: result,
+ },
+ });
+ });
+
+ return ;
+}
+
+// CodeEditor.tsx
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+import React, { StrictMode, useCallback, useState } from "react";
+
+import { AutocompleteButton } from "./AutocompleteButton.tsx";
+
+const editorState = EditorState.create({ doc: "", basicSetup });
+
+function CodeEditor() {
+ const [state, setState] = useState(editorState);
+
+ const dispatchTransactions = useCallback((trs: readonly Transaction[]) => {
+ setState(trs[trs.length - 1]!.state);
+ }, []);
+
+ return (
+
+
+
+
+ );
+}
+```
+
+## API
+
+### `CodeMirror`
+
+```ts
+function CodeMirror(
+ props: Omit & {
+ defaultState?: EditorState;
+ children: ReactNode;
+ },
+): JSX.Element;
+```
+
+Provides the CodeMirror context.
+
+Example usage:
+
+```tsx
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+
+const editorState = EditorState.create({ doc: "", basicSetup });
+
+function CodeEditor() {
+ return (
+
+
+
+ );
+}
+```
+
+### `CodeMirrorEditor`
+
+```ts
+function CodeMirrorEditor(): JSX.Element;
+```
+
+Renders the actual editable CodeMirror editor.
+
+This **must** be a descendant of the `CodeMirror` component. It may be wrapped
+in other components, and other children may be passed before or after.
+
+Example usage:
+
+```tsx
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+
+const editorState = EditorState.create({ doc: "", basicSetup });
+
+function CodeEditor() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+```
+
+### `useEditorState`
+
+```ts
+function useEditorState(): EditorState;
+```
+
+Provides access to the current EditorState value.
+
+### `useCompartment`
+
+```ts
+function useCompartment(
+ initialExtension: Extension,
+): readonly [Extension, (extension: Extension) => void];
+```
+
+Returns a compartment of the provided extension, and a method to reconfigure it.
+
+Example usage:
+
+```tsx
+import { javascript } from "@codemirror/lang-javascript";
+import { language } from "@codemirror/language";
+import { EditorState, type Transaction } from "@codemirror/state";
+import { CodeMirror, CodeMirrorEditor } from "@handlewithcare/react-codemirror";
+import { basicSetup } from "codemirror";
+import React, { StrictMode, useCallback, useState } from "react";
+
+import { LanguagePicker } from "./LanguagePicker.tsx";
+
+function CodeEditor() {
+ const [languageExt, reconfigureLanguage] = useCompartment(javascript());
+
+ const extensions = [basicSetup, languageExt];
+
+ const [state, setState] = useState(() =>
+ EditorState.create({ doc: "", extensions }),
+ );
+
+ const dispatchTransactions = useCallback((trs: readonly Transaction[]) => {
+ setState(trs[trs.length - 1]!.state);
+ }, []);
+
+ return (
+
+ reconfigureLanguage(lang)}
+ />
+
+
+ );
+}
+```
+
+### `useEditorEventCallback`
+
+```ts
+function useEditorEventcallback(
+ callback: (view: EditorView, ...args: T) => void,
+): void;
+```
+
+Returns a stable function reference to be used as an event handler callback.
+
+The callback will be called with the EditorView instance as its first argument.
+
+### `useEditorEffect`
+
+```ts
+function useEditorEffect(
+ effect: (view: EditorView) => void | (() => void),
+ dependencies?: React.DependencyList,
+): void;
+```
+
+Registers a layout effect to run after the EditorView has been updated with the
+latest EditorState.
+
+Effects can take an EditorView instance as an argument. This hook should be used
+to execute layout effects that depend on the EditorView, such as for positioning
+DOM nodes based on CodeMirror positions.
+
+Layout effects registered with this hook still fire synchronously after all DOM
+mutations, but they do so _after_ the EditorView has been updated, even when the
+EditorView lives in an ancestor component.
+
+## Looking for someone to collaborate with?
+
+Reach out to [Handle with Care](https://handlewithcare.dev/#get-in-touch)! We're
+a product development collective with years of experience bringing excellent
+ideas to life. We love React and ProseMirror, and we're always looking for new
+folks to work with!
diff --git a/demo/main.tsx b/demo/main.tsx
index 2abc5af..f51749e 100644
--- a/demo/main.tsx
+++ b/demo/main.tsx
@@ -1,17 +1,12 @@
import { javascript } from "@codemirror/lang-javascript";
import { EditorState, type Transaction } from "@codemirror/state";
-import { oneDark } from "@codemirror/theme-one-dark";
import { basicSetup } from "codemirror";
import React, { StrictMode, useCallback, useState } from "react";
import { createRoot } from "react-dom/client";
import { CodeMirror, CodeMirrorEditor } from "../src/index.js";
-const extensions = [
- basicSetup,
- javascript({ jsx: true, typescript: true }),
- oneDark,
-];
+const extensions = [basicSetup, javascript({ jsx: true, typescript: true })];
const editorState = EditorState.create({ doc: `const a = "a"`, extensions });
diff --git a/src/hooks/useCompartment.ts b/src/hooks/useCompartment.ts
new file mode 100644
index 0000000..91bb1f0
--- /dev/null
+++ b/src/hooks/useCompartment.ts
@@ -0,0 +1,42 @@
+import { Compartment, type Extension } from "@codemirror/state";
+import { useRef } from "react";
+
+import { useEditorEventCallback } from "./useEditorEventCallback.js";
+
+/**
+ * Returns a compartment of the provided extension, and a method
+ * to reconfigure it.
+ *
+ * @example
+ *
+ * ```
+ * const [languageConf, reconfigureLanguage] = useCompartment(javascript())
+ * const extensions = [basicSetup, languageConf]
+ *
+ * return (
+ *
+ * reconfigureLanguage(lang)}
+ * />
+ *
+ *
+ * )
+ * ```
+ */
+export function useCompartment(initialExtension: Extension) {
+ const compartmentRef = useRef(new Compartment());
+ const extensionRef = useRef(compartmentRef.current.of(initialExtension));
+
+ const reconfigure = useEditorEventCallback((view, extension: Extension) => {
+ view.dispatch({
+ effects: compartmentRef.current.reconfigure(extension),
+ });
+ });
+
+ return [extensionRef.current, reconfigure] as const;
+}
diff --git a/src/index.ts b/src/index.ts
index 94a0fa3..b6313ef 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,3 +4,5 @@ export { CodeMirror } from "./components/CodeMirror.js";
export { CodeMirrorEditor } from "./components/CodeMirrorEditor.js";
export { useEditorEffect } from "./hooks/useEditorEffect.js";
export { useEditorEventCallback } from "./hooks/useEditorEventCallback.js";
+export { useEditorState } from "./hooks/useEditorState.js";
+export { useCompartment } from "./hooks/useCompartment.js";