{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "trailing-block-docs",
  "title": "Trailing Block",
  "description": "Keep a required block at the end of an editor or nested level.",
  "files": [
    {
      "path": "../../content/docs/(plugins)/(functionality)/(utils)/trailing-block.mdx",
      "content": "---\ntitle: Trailing Block\ndescription: Keep a required block at the end of an editor or nested level.\ndocs:\n  - route: /docs/single-block\n    title: Single Block\n  - route: /docs/forced-layout\n    title: Forced Layout\n---\n\nTrailing Block inserts a required block when the last node at a target level is missing or has the wrong type. `EditorKit` includes `TrailingBlockPlugin` so full Plate editors always end with a paragraph. Single-block and single-line editors disable it because they intentionally keep one root block.\n\n<PackageInfo>\n\n## Features\n\n- Default trailing type from the editor paragraph plugin.\n- Empty-editor protection.\n- Root or nested target level.\n- `allow`, `exclude`, `filter`, and `maxLevel` query filters.\n- Custom insertion wrapper through `options.insert`.\n- Built into `EditorKit`.\n\n</PackageInfo>\n\n## Fast Path\n\nAdd `TrailingBlockPlugin` when users need a safe place to continue typing after blocks such as headings, tables, media, or columns.\n\n```tsx\nimport { TrailingBlockPlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [TrailingBlockPlugin],\n});\n```\n\n`TrailingBlockPlugin` defaults to the editor's paragraph type.\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `TrailingBlockPlugin` | `platejs` / `@platejs/utils` | Stores trailing block options and overrides normalization. |\n| `withTrailingBlock` | `@platejs/utils` | Checks the last node and inserts the trailing block when needed. |\n| `editor.api.last([], { level })` | Core editor API | Finds the last node at the configured depth. |\n| `queryNode(lastChild, query)` | `@platejs/slate` | Applies `allow`, `exclude`, `filter`, and `maxLevel`. |\n| `EditorKit` | Registry | Adds `TrailingBlockPlugin` after editing plugins. |\n| `SuggestionKit` | Registry | Wraps trailing block insertion in `suggestion.withoutSuggestions`. |\n\nThere is no dedicated trailing-block UI. The plugin is a normalizer.\n\n## Configure The Type\n\nUse `type` when the trailing block should be something other than the default paragraph.\n\n```tsx\nimport { KEYS, TrailingBlockPlugin } from 'platejs';\n\nexport const trailingBlockPlugin = TrailingBlockPlugin.configure({\n  options: {\n    type: KEYS.p,\n  },\n});\n```\n\nThe default is already `editor.getType(KEYS.p)`, so most editors can use the plugin directly.\n\n## Query Filters\n\nThe plugin inserts only when there is no last node, or when the last node type differs from `type` and passes the query filters.\n\n```tsx\nimport { KEYS, TrailingBlockPlugin } from 'platejs';\n\nexport const trailingBlockPlugin = TrailingBlockPlugin.configure({\n  options: {\n    exclude: [KEYS.h1],\n    type: KEYS.p,\n  },\n});\n```\n\nWith that configuration, a trailing paragraph is not inserted after an H1. Use `allow` for the inverse rule, `filter` for a custom node-entry predicate, and `maxLevel` to limit which paths pass the query.\n\n## Nested Level\n\n`level` changes where the plugin looks for the last node.\n\n| `level` | Target |\n|---------|--------|\n| `0` | Last root block. |\n| `1` | Last child inside the last root-level container. |\n\n```tsx\nTrailingBlockPlugin.configure({\n  options: {\n    level: 1,\n    type: 'p',\n  },\n});\n```\n\nUse nested levels when a constrained container must always end with a text block.\n\n## Custom Insert\n\n`options.insert` lets another plugin wrap the generated insertion. The registry suggestion kit uses it so normalization-generated paragraphs do not create suggestion marks.\n\n```tsx\nimport { SuggestionPlugin } from '@platejs/suggestion/react';\nimport { TrailingBlockPlugin } from 'platejs';\n\nTrailingBlockPlugin.configure({\n  options: {\n    insert: (editor, { insert }) => {\n      editor.getApi(SuggestionPlugin).suggestion.withoutSuggestions(insert);\n    },\n  },\n});\n```\n\nThe callback receives the editor, insertion path, target type, and an `insert()` function. Call `insert()` exactly once unless you are intentionally replacing the default insertion.\n\n## Behavior\n\n| Case | Result |\n|------|--------|\n| Empty editor | Inserts a block at `[0]`. |\n| Last node already matches `type` | Falls through to the base `normalizeNode`. |\n| Last node has another type and passes query filters | Inserts the trailing block at `PathApi.next(lastChildPath)`. |\n| Last node is excluded by query filters | Does not insert. |\n\nThe inserted node comes from `editor.api.create.block({ type: trailingType }, at)`.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `TrailingBlockPlugin` | `platejs` / `@platejs/utils` | Normalizer that ensures a trailing block exists. |\n| `TrailingBlockConfig.options.type` | `@platejs/utils` | Block type to insert. Defaults to the editor paragraph type. |\n| `TrailingBlockConfig.options.level` | `@platejs/utils` | Depth used by `editor.api.last`. Defaults to `0`. |\n| `TrailingBlockConfig.options.insert` | `@platejs/utils` | Custom wrapper around the generated insertion. |\n| `TrailingBlockConfig.options.allow` | `@platejs/slate` query | Only insert after matching types. |\n| `TrailingBlockConfig.options.exclude` | `@platejs/slate` query | Skip insertion after matching types. |\n| `TrailingBlockConfig.options.filter` | `@platejs/slate` query | Custom predicate for the last node entry. |\n| `TrailingBlockConfig.options.maxLevel` | `@platejs/slate` query | Skip entries deeper than this path length. |\n",
      "type": "registry:file",
      "target": "content/docs/plate/(plugins)/(functionality)/(utils)/trailing-block.mdx"
    }
  ],
  "type": "registry:file"
}