diff --git a/apps/www/content/docs/api/core/plate-controller.mdx b/apps/www/content/docs/api/core/plate-controller.mdx
new file mode 100644
index 0000000000..62d6896222
--- /dev/null
+++ b/apps/www/content/docs/api/core/plate-controller.mdx
@@ -0,0 +1,173 @@
+---
+title: PlateController
+description: API reference for PlateController component.
+---
+
+**`PlateController`** is an optional provider component that facilitates accessing specific [Plate Stores](/docs/api/core/store) from outside their respective **`Plate`** components.
+
+## PlateController Store
+
+### State
+
+The PlateController Store contains the information required to fetch a Plate Store based on its **`id`**, and to determine which **`id`** is currently active.
+
+
+
+
+The **`id`** of the most recently focused Plate editor.
+
+- **Default:** `null`
+
+
+
+
+The **`id`**'s of all primary Plate editors. By default, an editor is considered primary unless **`primary={false}`** was passed to its **`Plate`** component.
+
+- **Default:** `[]`
+
+
+
+
+A map from the **`id`** of each mounted Plate editor to the **`JotaiStore`** corresponding to that editor's Plate Store.
+
+- **Default:** `{}`
+
+
+
+
+## Usage Patterns
+
+### Specific Editor by ID
+
+**`PlateController`** can be used to access the store of a specific editor using its **`id`**. Note that if a matching editor cannot be found, an immutable fallback editor will be returned instead.
+
+```tsx
+const App = withHoc(PlateController, () => {
+ const mainEditor = useEditorRef('main');
+
+ useEffect(() => {
+ if (!mainEditor.isFallback) {
+ console.log('Editor mounted', mainEditor);
+ }
+ }, [mainEditor]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+});
+```
+
+### Active Editor
+
+If hooks like **`useEditorRef`** are used inside a **`PlateController`** without an explicit **`id`**, they will resolve to the currently active editor.
+
+The active editor is determined as follows:
+
+1. If some editor has been focused, return the last such editor.
+2. If some editor is primary, return the first-mounted such editor.
+3. Otherwise, return an immutable fallback editor.
+
+```tsx
+const App = withHoc(PlateController, () => {
+ const activeEditorId = usePlateId();
+ const isFallback = !useEditorMounted();
+
+ const message = isFallback
+ ? 'Please focus an editor'
+ : `Active editor: ${activeEditorId}`;
+
+ return (
+
+
{message}
+
+
+
+
+
+
+
+
+
+ );
+});
+```
+
+## Dealing with Fallback Editors
+
+When a hook called inside a **`PlateController`** fails to locate a matching Plate Store, it will use Plate Store's default values. The default value for **`editor`** is **`createPlateFallbackEditor()`**. A fallback editor works like an empty Plate editor with no plugins, except it throws a runtime error if it receives a Slate operation (i.e. it is immutable and must not be used in transforms).
+
+The rationale behind this is to ensure that code that queries the editor (such as determining whether toolbar buttons are active) fails silently with a sensible default value, while code that transforms the editor (such as pressing a toolbar button) fails loudly with an error.
+
+There are two ways to determine if you're working with a fallback editor or a real editor:
+
+- **`useEditorMounted`** returns **`false`** if no mounted editor could be resolved
+- **`editor.isFallback`** is **`true`** for fallback editors
+
+When using hooks like **`useEditorRef`** inside a **`PlateController`**, you should code defensively to ensure that fallback editors are handled appropriately should they arise. For example, you can disable toolbar buttons if **`useEditorMounted`** returns **`false`**, or ignore events if **`editor.isFallback`** is **`true`**.
+
+```tsx
+const App = withHoc(PlateController, () => {
+ const activeEditor = useEditorRef();
+
+ const toggleBold = () => {
+ if (activeEditor.isFallback) return;
+ toggleMark(activeEditor, { key: MARK_BOLD });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+```
+
+```tsx
+const App = withHoc(PlateController, () => {
+ const activeEditor = useEditorRef();
+ const isFallback = !useEditorMounted();
+
+ const toggleBold = () => {
+ toggleMark(activeEditor, { key: MARK_BOLD });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+```
+
diff --git a/apps/www/content/docs/api/core/store.mdx b/apps/www/content/docs/api/core/store.mdx
index 1c5b146e1e..91db685755 100644
--- a/apps/www/content/docs/api/core/store.mdx
+++ b/apps/www/content/docs/api/core/store.mdx
@@ -30,7 +30,7 @@ A unique ID used as a provider scope. Use it if you have multiple `Plate` in the
Slate editor reference.
- See [`editor`](/docs/api/core/plate#editor).
-- **Default:** `pipe(createTEditor(), withPlate({ id, plugins, options, components }))`
+- **Default:** `createPlateFallbackEditor()`
diff --git a/apps/www/content/docs/components/changelog.mdx b/apps/www/content/docs/components/changelog.mdx
index 41cc8f812d..6a155a3556 100644
--- a/apps/www/content/docs/components/changelog.mdx
+++ b/apps/www/content/docs/components/changelog.mdx
@@ -27,7 +27,7 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver
```tsx
// Before
export const TableElement = withRef(({ className, children, ...props }, ref) => {
- // ...
+ // ...
});
// After
diff --git a/apps/www/src/config/docs.ts b/apps/www/src/config/docs.ts
index d64d494e51..85763eb9cc 100644
--- a/apps/www/src/config/docs.ts
+++ b/apps/www/src/config/docs.ts
@@ -291,6 +291,21 @@ export const docsConfig: DocsConfig = {
'value',
],
},
+ {
+ title: 'PlateController',
+ href: '/docs/api/core/plate-controller',
+ headings: [
+ 'platecontroller-store',
+ 'state',
+ 'activeId',
+ 'primaryEditorIds',
+ 'editorStores',
+ 'usage-patterns',
+ 'specific-editor-by-id',
+ 'active-editor',
+ 'dealing-with-fallback-editors',
+ ],
+ },
{
title: 'PlateEditor',
href: '/docs/api/core/plate-editor',