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',