{
  "name": "plate",
  "homepage": "https://platejs.org",
  "items": [
    {
      "name": "plate",
      "description": "Install Plate package",
      "dependencies": [
        "platejs"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [],
      "type": "registry:lib"
    },
    {
      "name": "plate-ui",
      "description": "Install Plate package and styles",
      "devDependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/plate.json"
      ],
      "files": [],
      "cssVars": {
        "light": {
          "brand": "oklch(0.623 0.214 259.815)"
        },
        "dark": {
          "brand": "oklch(0.707 0.165 254.624)"
        }
      },
      "type": "registry:style"
    },
    {
      "name": "ai-menu",
      "title": "AI Menu",
      "description": "A menu for AI-powered content generation and insertion.",
      "dependencies": [
        "@platejs/ai",
        "@platejs/selection",
        "ai@7",
        "cmdk",
        "@faker-js/faker"
      ],
      "registryDependencies": [
        "command",
        "popover",
        "https://platejs.org/r/use-chat.json",
        "https://platejs.org/r/editor-base-kit.json",
        "https://platejs.org/r/ai-node.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/ai-menu.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/ai-chat-editor.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/ai",
            "title": "AI"
          },
          {
            "route": "https://pro.platejs.org/docs/components/ai-menu",
            "title": "AI Menu"
          }
        ],
        "examples": [
          "ai-demo",
          "ai-pro"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "ai-toolbar-button",
      "title": "AI Toolbar Button",
      "description": "A toolbar button for accessing AI features.",
      "dependencies": [
        "@platejs/ai"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/ai-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/ai",
            "title": "AI"
          },
          {
            "route": "https://pro.platejs.org/docs/components/ai-toolbar-button"
          }
        ],
        "examples": [
          "ai-demo",
          "floating-toolbar-demo",
          "ai-pro"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "align-toolbar-button",
      "title": "Align Toolbar Button",
      "description": "A dropdown menu for text alignment controls.",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/align-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/text-align"
          }
        ],
        "examples": [
          "align-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "block-context-menu",
      "title": "Block Context Menu",
      "description": "A context menu for block-level operations.",
      "dependencies": [
        "@platejs/ai",
        "@platejs/selection"
      ],
      "registryDependencies": [
        "calendar",
        "context-menu",
        "https://platejs.org/r/use-is-touch-device.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/block-context-menu.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/block-menu"
          },
          {
            "route": "https://pro.platejs.org/docs/components/block-context-menu"
          }
        ],
        "examples": [
          "block-menu-demo",
          "block-menu-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "block-selection",
      "title": "Block Selection",
      "description": "A visual overlay for selected blocks.",
      "dependencies": [
        "@platejs/selection"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/block-selection.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/block-selection"
          },
          {
            "route": "https://pro.platejs.org/docs/components/block-selection"
          }
        ],
        "examples": [
          "block-selection-demo",
          "block-selection-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "import-toolbar-button",
      "title": "Import Toolbar Button",
      "description": "A toolbar button to import editor content from a file.",
      "dependencies": [
        "@platejs/docx-io",
        "use-file-picker@2.1.2"
      ],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/import-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/import",
            "title": "Import"
          }
        ],
        "examples": [
          "basic-nodes-demo"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "export-toolbar-button",
      "title": "Export Toolbar Button",
      "description": "A toolbar button for exporting editor content in various formats (HTML, PDF, Image, Markdown).",
      "dependencies": [
        "@platejs/docx-io",
        "@platejs/markdown",
        "html2canvas-pro",
        "pdf-lib",
        "lucide-react"
      ],
      "registryDependencies": [
        "https://platejs.org/r/docx-export-kit.json",
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/editor-base-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/export-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/export",
            "title": "Export"
          }
        ],
        "examples": [
          "basic-nodes-demo"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "caption",
      "title": "Caption",
      "description": "A text field for adding captions to media elements.",
      "dependencies": [
        "@udecode/cn",
        "@platejs/caption"
      ],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "src/registry/ui/caption.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/caption"
          },
          {
            "route": "https://pro.platejs.org/docs/components/caption"
          }
        ],
        "examples": [
          "media-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "font-color-toolbar-button",
      "title": "Font Color Toolbar Button",
      "description": "A color picker toolbar button with text and background color controls.",
      "dependencies": [
        "@udecode/cn",
        "@platejs/basic-styles",
        "lodash"
      ],
      "registryDependencies": [
        "dropdown-menu",
        "separator",
        "button",
        "tooltip",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/font-color-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/font"
          },
          {
            "route": "https://pro.platejs.org/docs/components/font-color-toolbar-button"
          }
        ],
        "examples": [
          "font-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "comment-toolbar-button",
      "title": "Comment Toolbar Button",
      "description": "A toolbar button for adding inline comments.",
      "dependencies": [
        "@platejs/comment"
      ],
      "registryDependencies": [
        "https://platejs.org/r/comment-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/comment-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/comment"
          },
          {
            "route": "https://pro.platejs.org/docs/components/comment-toolbar-button"
          }
        ],
        "examples": [
          "discussion-demo",
          "floating-toolbar-demo",
          "discussion-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "block-suggestion",
      "title": "Block Suggestion",
      "dependencies": [
        "@platejs/suggestion"
      ],
      "type": "registry:ui"
    },
    {
      "name": "block-discussion",
      "title": "Block Discussion",
      "description": "A popover interface for managing discussions: comments, replies, suggestions.",
      "dependencies": [
        "@platejs/comment",
        "date-fns",
        "@platejs/suggestion"
      ],
      "registryDependencies": [
        "button",
        "popover",
        "avatar",
        "dropdown-menu",
        "https://platejs.org/r/editor.json",
        "https://platejs.org/r/ai-node.json",
        "https://platejs.org/r/date-node.json",
        "https://platejs.org/r/emoji-node.json",
        "https://platejs.org/r/link-node.json",
        "https://platejs.org/r/mention-node.json",
        "https://platejs.org/r/highlight-style.json",
        "https://platejs.org/r/suggestion-kit.json",
        "https://platejs.org/r/discussion-kit.json",
        "https://platejs.org/r/basic-marks-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/block-discussion.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/lib/block-discussion-index.ts",
          "type": "registry:lib"
        },
        {
          "path": "src/registry/ui/block-suggestion.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/comment.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/comment"
          },
          {
            "route": "https://pro.platejs.org/docs/components/block-discussion"
          }
        ],
        "examples": [
          "discussion-demo",
          "discussion-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "cursor-overlay",
      "title": "Cursor Overlay",
      "description": "A visual overlay for cursors and selections.",
      "dependencies": [
        "@platejs/selection"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/cursor-overlay.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/cursor-overlay"
          },
          {
            "route": "https://pro.platejs.org/docs/components/cursor-overlay"
          }
        ],
        "examples": [
          "ai-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "block-draggable",
      "title": "Block Draggable",
      "description": "A block wrapper with a drag handle for moving editor blocks.",
      "dependencies": [
        "@platejs/dnd",
        "@platejs/selection"
      ],
      "registryDependencies": [
        "tooltip",
        "https://platejs.org/r/use-mounted.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/block-draggable.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/dnd",
            "title": "Drag & Drop"
          },
          {
            "route": "https://pro.platejs.org/docs/components/block-draggable"
          }
        ],
        "examples": [
          "dnd-demo",
          "dnd-pro"
        ],
        "usage": [
          "DndPlugin.configure({\n  render: {\n    aboveNodes: BlockDraggable,\n  },\n})"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "editor",
      "title": "Editor",
      "description": "A container for the editor content and styling.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/editor.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/editor-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "https://pro.platejs.org/docs/components/editor"
          }
        ],
        "examples": [
          "editor-default",
          "editor-disabled",
          "editor-full-width"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "select-editor",
      "title": "Select Editor",
      "description": "An editor to select tags.",
      "dependencies": [
        "fzf@0.5.2",
        "@platejs/tag",
        "@udecode/cmdk"
      ],
      "registryDependencies": [
        "https://platejs.org/r/editor.json",
        "command",
        "popover",
        "https://platejs.org/r/tag-node.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/select-editor.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/multi-select"
          }
        ],
        "examples": [
          "select-editor-demo"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "emoji-toolbar-button",
      "title": "Emoji Toolbar Button",
      "description": "An emoji picker toolbar button.",
      "dependencies": [
        "@platejs/emoji",
        "@emoji-mart/data@1.2.1",
        "@radix-ui/react-popover"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/emoji-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/emoji"
          },
          {
            "route": "https://pro.platejs.org/docs/components/emoji-picker"
          }
        ],
        "examples": [
          "emoji-demo",
          "emoji-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "fixed-toolbar-buttons",
      "title": "Fixed Toolbar Buttons",
      "description": "A set of commonly used formatting buttons.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/ai-toolbar-button.json",
        "https://platejs.org/r/align-toolbar-button.json",
        "https://platejs.org/r/comment-toolbar-button.json",
        "https://platejs.org/r/emoji-toolbar-button.json",
        "https://platejs.org/r/font-color-toolbar-button.json",
        "https://platejs.org/r/font-size-toolbar-button.json",
        "https://platejs.org/r/history-toolbar-button.json",
        "https://platejs.org/r/list-toolbar-button.json",
        "https://platejs.org/r/indent-toolbar-button.json",
        "https://platejs.org/r/import-toolbar-button.json",
        "https://platejs.org/r/insert-toolbar-button.json",
        "https://platejs.org/r/line-height-toolbar-button.json",
        "https://platejs.org/r/link-toolbar-button.json",
        "https://platejs.org/r/mark-toolbar-button.json",
        "https://platejs.org/r/media-toolbar-button.json",
        "https://platejs.org/r/mode-toolbar-button.json",
        "https://platejs.org/r/more-toolbar-button.json",
        "https://platejs.org/r/table-toolbar-button.json",
        "https://platejs.org/r/toggle-toolbar-button.json",
        "https://platejs.org/r/turn-into-toolbar-button.json",
        "https://platejs.org/r/export-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/fixed-toolbar-buttons.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "basic-nodes-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "fixed-toolbar-classic-buttons",
      "title": "Fixed Toolbar List Buttons",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/ai-toolbar-button.json",
        "https://platejs.org/r/align-toolbar-button.json",
        "https://platejs.org/r/font-color-toolbar-button.json",
        "https://platejs.org/r/comment-toolbar-button.json",
        "https://platejs.org/r/emoji-toolbar-button.json",
        "https://platejs.org/r/insert-toolbar-classic-button.json",
        "https://platejs.org/r/line-height-toolbar-button.json",
        "https://platejs.org/r/list-classic-toolbar-button.json",
        "https://platejs.org/r/link-toolbar-button.json",
        "https://platejs.org/r/mark-toolbar-button.json",
        "https://platejs.org/r/media-toolbar-button.json",
        "https://platejs.org/r/mode-toolbar-button.json",
        "https://platejs.org/r/more-toolbar-button.json",
        "https://platejs.org/r/table-toolbar-button.json",
        "https://platejs.org/r/toggle-toolbar-button.json",
        "https://platejs.org/r/turn-into-toolbar-classic-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/fixed-toolbar-classic-buttons.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {},
      "type": "registry:ui"
    },
    {
      "name": "fixed-toolbar",
      "title": "Fixed Toolbar",
      "description": "A fixed toolbar that stays at the top of the editor.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/tailwind-scrollbar-hide.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/fixed-toolbar.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "basic-nodes-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "floating-toolbar-buttons",
      "title": "Floating Toolbar Buttons",
      "description": "A set of formatting buttons for the floating toolbar.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/ai-toolbar-button.json",
        "https://platejs.org/r/comment-toolbar-button.json",
        "https://platejs.org/r/equation-toolbar-button.json",
        "https://platejs.org/r/link-toolbar-button.json",
        "https://platejs.org/r/mark-toolbar-button.json",
        "https://platejs.org/r/more-toolbar-button.json",
        "https://platejs.org/r/suggestion-toolbar-button.json",
        "https://platejs.org/r/turn-into-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/floating-toolbar-buttons.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/api/floating"
          },
          {
            "route": "https://pro.platejs.org/docs/components/floating-toolbar-buttons"
          }
        ],
        "examples": [
          "floating-toolbar-demo",
          "floating-toolbar-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "floating-toolbar-classic-buttons",
      "title": "Floating Toolbar Classic Buttons",
      "description": "A set of commonly used formatting buttons for the floating toolbar with classic list support.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/ai-toolbar-button.json",
        "https://platejs.org/r/comment-toolbar-button.json",
        "https://platejs.org/r/equation-toolbar-button.json",
        "https://platejs.org/r/link-toolbar-button.json",
        "https://platejs.org/r/mark-toolbar-button.json",
        "https://platejs.org/r/more-toolbar-button.json",
        "https://platejs.org/r/suggestion-toolbar-button.json",
        "https://platejs.org/r/turn-into-toolbar-classic-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/floating-toolbar-classic-buttons.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "list-classic-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "floating-toolbar",
      "title": "Floating Toolbar",
      "description": "A contextual toolbar that appears over selected text.",
      "dependencies": [
        "@udecode/cn",
        "@platejs/floating"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/tailwind-scrollbar-hide.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/floating-toolbar.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/api/floating"
          },
          {
            "route": "https://pro.platejs.org/docs/components/floating-toolbar"
          }
        ],
        "examples": [
          "floating-toolbar-demo",
          "floating-toolbar-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "ghost-text",
      "title": "Ghost Text",
      "description": "A text suggestion system that displays AI-generated content after the cursor.",
      "dependencies": [
        "@platejs/ai"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/ghost-text.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/copilot"
          },
          {
            "route": "https://pro.platejs.org/docs/components/ghost-text"
          }
        ],
        "examples": [
          "copilot-demo",
          "copilot-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "history-toolbar-button",
      "title": "History Toolbar Button",
      "description": "Toolbar buttons for undo and redo operations.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/history-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "https://docs.slatejs.org/libraries/slate-history",
            "title": "Slate History"
          }
        ],
        "examples": [
          "basic-nodes-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "list-toolbar-button",
      "title": "List Toolbar Button",
      "description": "A toolbar control for adjusting list indentation.",
      "dependencies": [
        "@platejs/list"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/list-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/list"
          }
        ],
        "examples": [
          "list-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "indent-toolbar-button",
      "title": "Indent Toolbar Buttons",
      "description": "Toolbar controls for block indentation.",
      "dependencies": [
        "@platejs/indent"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/indent-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/indent"
          }
        ],
        "examples": [
          "indent-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "inline-combobox",
      "title": "Inline Combobox",
      "description": "A combobox for inline suggestions.",
      "dependencies": [
        "@platejs/combobox",
        "@ariakit/react"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/inline-combobox.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/combobox"
          },
          {
            "route": "https://pro.platejs.org/docs/components/inline-combobox"
          }
        ],
        "examples": [
          "mention-demo",
          "slash-command-demo",
          "emoji-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "insert-toolbar-button",
      "title": "Insert Toolbar Button",
      "description": "A menu for inserting different types of blocks.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/transforms.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/insert-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "basic-nodes-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "insert-toolbar-classic-button",
      "title": "Insert Toolbar Classic Button",
      "description": "A menu for inserting different types of blocks with classic list support.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/transforms-classic.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/insert-toolbar-classic-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "list-classic-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "line-height-toolbar-button",
      "title": "Line Height Toolbar Button",
      "description": "A menu for controlling text line spacing.",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "dropdown-menu"
      ],
      "files": [
        {
          "path": "src/registry/ui/line-height-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/line-height"
          }
        ],
        "examples": [
          "line-height-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "link-toolbar",
      "title": "Link Floating Toolbar",
      "description": "A floating interface for link editing.",
      "dependencies": [
        "@platejs/link",
        "@platejs/floating"
      ],
      "registryDependencies": [
        "button",
        "input",
        "popover",
        "separator"
      ],
      "files": [
        {
          "path": "src/registry/ui/link-toolbar.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/link"
          },
          {
            "route": "/docs/api/floating"
          },
          {
            "route": "https://pro.platejs.org/docs/components/link-toolbar"
          }
        ],
        "examples": [
          "link-demo",
          "link-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "link-toolbar-button",
      "title": "Link Toolbar Button",
      "description": "A toolbar control for link management.",
      "dependencies": [
        "@platejs/link"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/link-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/link"
          },
          {
            "route": "https://pro.platejs.org/docs/components/link-toolbar-button"
          }
        ],
        "examples": [
          "link-demo",
          "link-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "list-classic-toolbar-button",
      "title": "List Toolbar Buttons",
      "description": "Toolbar controls for list creation and management.",
      "dependencies": [
        "@platejs/list-classic"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/list-classic-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/list-classic"
          }
        ],
        "examples": [
          "list-classic-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "mark-toolbar-button",
      "title": "Mark Toolbar Button",
      "description": "A toolbar control for basic text formatting.",
      "dependencies": [
        "@platejs/basic-nodes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/mark-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/basic-marks"
          }
        ],
        "examples": [
          "basic-marks-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-toolbar",
      "title": "Media Toolbar",
      "description": "A toolbar interface for media settings.",
      "dependencies": [
        "@platejs/media"
      ],
      "registryDependencies": [
        "button",
        "input",
        "popover",
        "separator"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-toolbar.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-toolbar-button",
      "title": "Media Toolbar Button",
      "description": "Toolbar button for inserting and managing media.",
      "dependencies": [
        "@platejs/media",
        "use-file-picker@2.1.2",
        "sonner"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json",
        "input",
        "dropdown-menu",
        "alert-dialog"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-upload-toast",
      "title": "Media Upload Toast",
      "description": "Show toast notifications for media uploads.",
      "dependencies": [
        "@platejs/media",
        "sonner"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/media-upload-toast.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "mode-toolbar-button",
      "title": "Mode Toolbar Button",
      "description": "A menu for switching between editor modes.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/mode-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "basic-nodes-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "more-toolbar-button",
      "title": "More Toolbar Button",
      "description": "A menu for additional text formatting options.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/more-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "https://pro.platejs.org/docs/components/more-toolbar-button"
          }
        ],
        "examples": [
          "basic-marks-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "resize-handle",
      "title": "Resize Handle",
      "description": "A resizable wrapper with resize handles.",
      "dependencies": [
        "@platejs/resizable"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/resize-handle.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/api/resizable"
          },
          {
            "route": "https://pro.platejs.org/docs/components/resizable"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "table-toolbar-button",
      "title": "Table Toolbar Button",
      "description": "A menu for table manipulation and formatting.",
      "dependencies": [
        "@platejs/table"
      ],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/table-toolbar-button.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/table-icons.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/table"
          }
        ],
        "examples": [
          "table-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "toggle-toolbar-button",
      "title": "Toggle Toolbar Button",
      "description": "A toolbar button for expanding and collapsing blocks.",
      "dependencies": [
        "@platejs/toggle"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/toggle-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/toggle"
          }
        ],
        "examples": [
          "toggle-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "turn-into-toolbar-button",
      "title": "Turn Into Toolbar Button",
      "description": "A menu for converting between different block types.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/transforms.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/turn-into-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "https://pro.platejs.org/docs/components/turn-into-toolbar-button"
          }
        ],
        "examples": [
          "basic-nodes-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "turn-into-toolbar-classic-button",
      "title": "Turn Into Toolbar Classic Button",
      "description": "A dropdown to convert block types with classic list support.",
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/transforms-classic.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/turn-into-toolbar-classic-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "examples": [
          "list-classic-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "remote-cursor-overlay",
      "title": "Remote Cursor Overlay",
      "description": "A cursor overlay to display multiplayer cursors in the yjs plugin.",
      "dependencies": [
        "@slate-yjs/react"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/remote-cursor-overlay.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/yjs"
          }
        ],
        "examples": []
      },
      "type": "registry:ui"
    },
    {
      "name": "toolbar",
      "title": "Toolbar",
      "description": "A customizable toolbar component with various button styles and group",
      "dependencies": [
        "@radix-ui/react-toolbar"
      ],
      "registryDependencies": [
        "tooltip",
        "separator",
        "dropdown-menu"
      ],
      "files": [
        {
          "path": "src/registry/ui/toolbar.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {},
      "type": "registry:ui"
    },
    {
      "name": "suggestion-toolbar-button",
      "title": "Suggestion Toolbar Button",
      "description": "A toolbar button for toggling suggestion mode in the editor.",
      "dependencies": [
        "@platejs/suggestion"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/suggestion-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/suggestion"
          }
        ],
        "examples": [
          "discussion-demo",
          "discussion-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "ai-node",
      "title": "AI Leaf",
      "description": "A text highlighter for AI-generated content.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/ai-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/ai",
            "title": "AI"
          },
          {
            "route": "https://pro.platejs.org/docs/components/ai-node",
            "title": "AI Leaf"
          }
        ],
        "examples": [
          "ai-demo",
          "ai-pro"
        ],
        "label": "New"
      },
      "type": "registry:ui"
    },
    {
      "name": "block-list",
      "title": "List",
      "description": "List components.",
      "dependencies": [
        "@platejs/list"
      ],
      "registryDependencies": [
        "checkbox"
      ],
      "files": [
        {
          "path": "src/registry/ui/block-list.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/block-list-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/list"
          }
        ],
        "examples": [
          "list-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "blockquote-node",
      "title": "Blockquote Element",
      "description": "A quote component for block quotes.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/blockquote-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/blockquote-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/blockquote"
          },
          {
            "route": "https://pro.platejs.org/docs/components/blockquote-node"
          }
        ],
        "examples": [
          "basic-blocks-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "callout-node",
      "description": "A callout component for highlighting important information with customizable icons and styles.",
      "dependencies": [
        "@platejs/callout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/emoji-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/callout-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/callout-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/callout"
          },
          {
            "route": "https://pro.platejs.org/docs/components/callout-node"
          }
        ],
        "examples": [
          "callout-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "code-block-node",
      "title": "Code Block Nodes",
      "description": "A code block with syntax highlighting and language selection.",
      "dependencies": [
        "@platejs/code-block",
        "lowlight"
      ],
      "registryDependencies": [
        "command",
        "popover",
        "button"
      ],
      "files": [
        {
          "path": "src/registry/ui/code-block-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/code-block-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/code-block"
          },
          {
            "route": "https://pro.platejs.org/docs/components/code-block-node"
          }
        ],
        "examples": [
          "code-block-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "code-drawing-node",
      "title": "Code Drawing Node",
      "description": "Create diagrams from code using PlantUML, Graphviz, Flowchart, or Mermaid.",
      "dependencies": [
        "@platejs/code-drawing"
      ],
      "registryDependencies": [
        "popover",
        "button",
        "select",
        "https://platejs.org/r/use-mobile.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/code-drawing-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/code-drawing-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/code-drawing"
          },
          {
            "route": "https://pro.platejs.org/docs/components/code-drawing-node"
          }
        ],
        "examples": [
          "code-drawing-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "code-node",
      "title": "Code Leaf",
      "description": "An inline component for code snippets.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/code-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/code-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/code"
          },
          {
            "route": "https://pro.platejs.org/docs/components/code-node"
          }
        ],
        "examples": [
          "basic-marks-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "column-node",
      "title": "Column Nodes",
      "description": "Resizable column components for layout.",
      "dependencies": [
        "@udecode/cn",
        "@platejs/layout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/resize-handle.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/column-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/column-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/column"
          },
          {
            "route": "https://pro.platejs.org/docs/components/column-node"
          }
        ],
        "examples": [
          "column-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "comment-node",
      "title": "Comment Leaf",
      "description": "A text component for displaying comments with visual indicators.",
      "dependencies": [
        "@platejs/comment"
      ],
      "registryDependencies": [
        "https://platejs.org/r/highlight-style.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/comment-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/comment-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/comment"
          },
          {
            "route": "https://pro.platejs.org/docs/components/comment-node"
          }
        ],
        "examples": [
          "discussion-demo",
          "discussion-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "suggestion-node",
      "title": "Suggestion Leaf",
      "description": "A text component for suggestion.",
      "dependencies": [
        "@platejs/suggestion"
      ],
      "registryDependencies": [
        "https://platejs.org/r/suggestion.json",
        "https://platejs.org/r/suggestion-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/suggestion-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/suggestion-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/suggestion"
          }
        ],
        "examples": [
          "discussion-demo",
          "discussion-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "date-node",
      "title": "Date Element",
      "description": "A date field component with calendar picker.",
      "dependencies": [
        "@platejs/date"
      ],
      "registryDependencies": [
        "calendar",
        "https://platejs.org/r/suggestion.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/date-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/date-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/date"
          },
          {
            "route": "https://pro.platejs.org/docs/components/date-node"
          }
        ],
        "examples": [
          "date-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "equation-node",
      "title": "Equation Element",
      "description": "Displays a LaTeX equation element with an editable popover for inputting and rendering mathematical expressions.",
      "dependencies": [
        "@platejs/math",
        "react-textarea-autosize"
      ],
      "registryDependencies": [
        "popover",
        "https://platejs.org/r/suggestion.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/equation-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/equation-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "http://localhost:3000/docs/equation",
            "title": "Equation"
          }
        ],
        "examples": [
          "equation-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "equation-toolbar-button",
      "title": "Equation Toolbar Button",
      "description": "A toolbar button for inserting and editing equations.",
      "dependencies": [
        "@platejs/math"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/equation-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "http://localhost:3000/docs/equation",
            "title": "Equation"
          }
        ],
        "examples": [
          "equation-demo",
          "floating-toolbar-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "emoji-node",
      "title": "Emoji Input Element",
      "description": "An input component for emoji search and insertion.",
      "dependencies": [
        "@platejs/emoji",
        "@emoji-mart/data@1.2.1"
      ],
      "registryDependencies": [
        "https://platejs.org/r/inline-combobox.json",
        "https://platejs.org/r/use-debounce.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/emoji-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/emoji"
          },
          {
            "route": "https://pro.platejs.org/docs/components/emoji-node"
          }
        ],
        "examples": [
          "emoji-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "excalidraw-node",
      "title": "Excalidraw Element",
      "description": "A drawing component powered by Excalidraw.",
      "dependencies": [
        "@platejs/excalidraw"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/excalidraw-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/excalidraw"
          }
        ],
        "examples": [
          "excalidraw-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "font-size-toolbar-button",
      "title": "Font Size Toolbar Button",
      "description": "A toolbar control for adjusting font size.",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "popover",
        "https://platejs.org/r/toolbar.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/font-size-toolbar-button.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/font"
          }
        ],
        "examples": [
          "font-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "footnote-node",
      "title": "Footnote Elements",
      "description": "Inline footnote references, definitions, and input UI.",
      "dependencies": [
        "@platejs/footnote"
      ],
      "registryDependencies": [
        "button",
        "command",
        "hover-card",
        "popover",
        "https://platejs.org/r/inline-combobox.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/footnote-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/footnote-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/footnote"
          }
        ],
        "examples": [
          "footnote-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "heading-node",
      "title": "Heading Element",
      "description": "A heading with multiple level support.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/highlight-style.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/heading-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/heading-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/heading"
          },
          {
            "route": "https://pro.platejs.org/docs/components/heading-node"
          }
        ],
        "examples": [
          "basic-blocks-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "highlight-node",
      "title": "Highlight Leaf",
      "description": "A text highlighter with customizable colors.",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/highlight-style.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/highlight-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/highlight-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/highlight"
          }
        ],
        "examples": [
          "basic-marks-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "hr-node",
      "title": "Horizontal Rule Element",
      "description": "A horizontal rule component with focus states.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/hr-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/hr-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/horizontal-rule"
          },
          {
            "route": "https://pro.platejs.org/docs/components/hr-node"
          }
        ],
        "examples": [
          "basic-blocks-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-image-node",
      "title": "Image Element",
      "description": "Image element with lazy loading, resizing capabilities, and optional caption.",
      "dependencies": [
        "@platejs/media",
        "@platejs/resizable"
      ],
      "registryDependencies": [
        "https://platejs.org/r/media-toolbar.json",
        "https://platejs.org/r/caption.json",
        "https://platejs.org/r/resize-handle.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-image-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/media-image-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "/docs/api/resizable"
          },
          {
            "route": "https://pro.platejs.org/docs/components/image-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-preview-dialog",
      "title": "Media Preview Dialog",
      "description": "A modal component for previewing and manipulating images.",
      "dependencies": [
        "@platejs/media"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/media-preview-dialog.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "https://pro.platejs.org/docs/components/image-preview"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "kbd-node",
      "title": "Keyboard Input Leaf",
      "description": "A component for styling keyboard shortcuts.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/kbd-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/kbd-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/kbd",
            "title": "Keyboard Input"
          }
        ],
        "examples": [
          "basic-marks-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "link-node",
      "title": "Link Element",
      "description": "A component for rendering hyperlinks with hover states.",
      "dependencies": [
        "@platejs/link"
      ],
      "registryDependencies": [
        "https://platejs.org/r/suggestion.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/link-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/link-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/link"
          },
          {
            "route": "https://pro.platejs.org/docs/components/link-node"
          }
        ],
        "examples": [
          "link-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "list-classic-node",
      "title": "List Nodes",
      "description": "List (classic) nodes for ordered and unordered items.",
      "dependencies": [
        "@platejs/list-classic"
      ],
      "registryDependencies": [
        "checkbox"
      ],
      "files": [
        {
          "path": "src/registry/ui/list-classic-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/list-classic"
          }
        ],
        "examples": [
          "list-classic-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-audio-node",
      "title": "Media Audio Element",
      "description": "An audio player component with caption support.",
      "dependencies": [
        "@platejs/media",
        "@platejs/resizable"
      ],
      "registryDependencies": [
        "https://platejs.org/r/caption.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-audio-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/media-audio-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "https://pro.platejs.org/docs/components/media-audio-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-embed-node",
      "title": "Media Embed Element",
      "description": "A component for embedded media content with resizing and caption support.",
      "dependencies": [
        "@platejs/media",
        "@platejs/resizable",
        "react-tweet",
        "react-lite-youtube-embed"
      ],
      "registryDependencies": [
        "https://platejs.org/r/media-toolbar.json",
        "https://platejs.org/r/caption.json",
        "https://platejs.org/r/resize-handle.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-embed-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "/docs/api/resizable"
          },
          {
            "route": "https://pro.platejs.org/docs/components/media-embed-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-file-node",
      "title": "Media File Element",
      "description": "A file attachment component with download capability and caption.",
      "dependencies": [
        "@platejs/media",
        "@platejs/resizable"
      ],
      "registryDependencies": [
        "https://platejs.org/r/caption.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-file-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/media-file-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "https://pro.platejs.org/docs/components/media-file-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-placeholder-node",
      "title": "Media Placeholder Element",
      "description": "A placeholder for media upload progress indication.",
      "dependencies": [
        "@platejs/media",
        "use-file-picker@2.1.2"
      ],
      "registryDependencies": [
        "https://platejs.org/r/uploadthing.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-placeholder-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "https://pro.platejs.org/docs/components/media-placeholder-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "media-video-node",
      "title": "Media Video Element",
      "description": "A video player component with YouTube and file upload support.",
      "dependencies": [
        "@platejs/media",
        "@platejs/resizable",
        "react-player@3.3.1",
        "react-lite-youtube-embed"
      ],
      "registryDependencies": [
        "https://platejs.org/r/media-toolbar.json",
        "https://platejs.org/r/caption.json",
        "https://platejs.org/r/resize-handle.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/media-video-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/media-video-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/media"
          },
          {
            "route": "/docs/api/resizable"
          },
          {
            "route": "https://pro.platejs.org/docs/components/media-video-node"
          }
        ],
        "examples": [
          "media-demo",
          "media-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "mention-node",
      "title": "Mention Nodes",
      "description": "A mention element with customizable prefix and label, powered by a combobox.",
      "dependencies": [
        "@platejs/mention"
      ],
      "registryDependencies": [
        "https://platejs.org/r/suggestion.json",
        "https://platejs.org/r/use-mounted.json",
        "https://platejs.org/r/inline-combobox.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/mention-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/mention-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/mention"
          },
          {
            "route": "https://pro.platejs.org/docs/components/mention-node"
          }
        ],
        "examples": [
          "mention-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "paragraph-node",
      "title": "Paragraph Element",
      "description": "A paragraph block with background color support.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/paragraph-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/paragraph-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/basic-blocks"
          },
          {
            "route": "https://pro.platejs.org/docs/components/paragraph-node"
          }
        ],
        "examples": [
          "basic-blocks-demo",
          "basic-nodes-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "search-highlight-node",
      "title": "Search Highlight Leaf",
      "description": "A component that highlights search results in text.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/search-highlight-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/highlight"
          }
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "slash-node",
      "title": "Slash Input Element",
      "description": "A command input component for inserting various elements.",
      "dependencies": [
        "@platejs/ai"
      ],
      "registryDependencies": [
        "https://platejs.org/r/inline-combobox.json",
        "https://platejs.org/r/transforms.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/slash-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/slash-command",
            "title": "Slash"
          },
          {
            "route": "https://pro.platejs.org/docs/components/slash-node"
          }
        ],
        "examples": [
          "slash-command-demo",
          "slash-command-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "table-node",
      "title": "Table Element",
      "description": "A table component with floating toolbar and border customization.",
      "dependencies": [
        "@platejs/table",
        "@radix-ui/react-popover"
      ],
      "registryDependencies": [
        "dropdown-menu",
        "popover",
        "https://platejs.org/r/resize-handle.json",
        "https://platejs.org/r/block-selection.json",
        "https://platejs.org/r/toolbar.json",
        "https://platejs.org/r/tailwind-scrollbar-hide.json",
        "https://platejs.org/r/font-color-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/ui/table-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/table-icons.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/table-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/table"
          },
          {
            "route": "https://pro.platejs.org/docs/components/table-node"
          }
        ],
        "examples": [
          "table-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "tag-node",
      "title": "Tag Element",
      "description": "A tag element component with selection states and styling.",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/tag-node.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/multi-select"
          }
        ],
        "examples": [
          "select-editor-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "toc-node",
      "title": "TOC Element",
      "description": "A table of contents component with links to document headings.",
      "dependencies": [
        "@platejs/toc"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/ui/toc-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/toc-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/toc"
          },
          {
            "route": "https://pro.platejs.org/docs/components/toc-node"
          }
        ],
        "examples": [
          "toc-demo",
          "toc-pro"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "toggle-node",
      "title": "Toggle Element",
      "description": "A collapsible component for toggling content visibility.",
      "dependencies": [
        "@platejs/toggle"
      ],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "src/registry/ui/toggle-node.tsx",
          "type": "registry:ui"
        },
        {
          "path": "src/registry/ui/toggle-node-static.tsx",
          "type": "registry:ui"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/toggle"
          }
        ],
        "examples": [
          "toggle-demo"
        ]
      },
      "type": "registry:ui"
    },
    {
      "name": "align-base-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/align-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/align-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "basic-blocks-base-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/blockquote-node.json",
        "https://platejs.org/r/heading-node.json",
        "https://platejs.org/r/hr-node.json",
        "https://platejs.org/r/paragraph-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/basic-blocks-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/basic-blocks-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "basic-marks-base-kit",
      "dependencies": [
        "@platejs/basic-nodes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/code-node.json",
        "https://platejs.org/r/highlight-node.json",
        "https://platejs.org/r/kbd-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/basic-marks-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/basic-marks-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "callout-base-kit",
      "dependencies": [
        "@platejs/callout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/callout-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/callout-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/callout-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "code-block-base-kit",
      "dependencies": [
        "@platejs/code-block",
        "lowlight"
      ],
      "registryDependencies": [
        "https://platejs.org/r/code-block-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/code-block-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/code-block-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "code-drawing-base-kit",
      "dependencies": [
        "@platejs/code-drawing"
      ],
      "registryDependencies": [
        "https://platejs.org/r/code-drawing-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/code-drawing-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/code-drawing-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "column-base-kit",
      "dependencies": [
        "@platejs/layout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/column-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/column-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/column-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "comment-base-kit",
      "dependencies": [
        "@platejs/comment"
      ],
      "registryDependencies": [
        "https://platejs.org/r/comment-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/comment-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/comment-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "date-base-kit",
      "dependencies": [
        "@platejs/date"
      ],
      "registryDependencies": [
        "https://platejs.org/r/date-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/date-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/date-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "footnote-base-kit",
      "dependencies": [
        "@platejs/footnote"
      ],
      "registryDependencies": [
        "https://platejs.org/r/footnote-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/footnote-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/footnote-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "excalidraw-kit",
      "dependencies": [
        "@platejs/excalidraw",
        "@excalidraw/excalidraw"
      ],
      "registryDependencies": [
        "https://platejs.org/r/excalidraw-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/excalidraw-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/excalidraw-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "font-base-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/font-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/font-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "indent-base-kit",
      "dependencies": [
        "@platejs/indent"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/indent-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/indent-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "line-height-base-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/line-height-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/line-height-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "link-base-kit",
      "dependencies": [
        "@platejs/link"
      ],
      "registryDependencies": [
        "https://platejs.org/r/link-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/link-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/link-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "list-base-kit",
      "dependencies": [
        "@platejs/list"
      ],
      "registryDependencies": [
        "https://platejs.org/r/block-list.json",
        "https://platejs.org/r/indent-base-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/list-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/list-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "math-base-kit",
      "dependencies": [
        "@platejs/math"
      ],
      "registryDependencies": [
        "https://platejs.org/r/equation-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/math-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/math-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "media-base-kit",
      "dependencies": [
        "@platejs/media"
      ],
      "registryDependencies": [
        "https://platejs.org/r/media-audio-node.json",
        "https://platejs.org/r/media-file-node.json",
        "https://platejs.org/r/media-image-node.json",
        "https://platejs.org/r/media-video-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/media-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/media-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "mention-base-kit",
      "dependencies": [
        "@platejs/mention"
      ],
      "registryDependencies": [
        "https://platejs.org/r/mention-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/mention-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/mention-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "suggestion-base-kit",
      "dependencies": [
        "@platejs/suggestion"
      ],
      "registryDependencies": [
        "https://platejs.org/r/suggestion-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/suggestion-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/suggestion-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "table-base-kit",
      "dependencies": [
        "@platejs/table"
      ],
      "registryDependencies": [
        "https://platejs.org/r/table-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/table-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/table-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "toc-base-kit",
      "dependencies": [
        "@platejs/toc"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toc-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/toc-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/toc-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "toggle-base-kit",
      "dependencies": [
        "@platejs/toggle"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toggle-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/toggle-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/toggle-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "editor-base-kit",
      "registryDependencies": [
        "https://platejs.org/r/editor.json",
        "https://platejs.org/r/align-base-kit.json",
        "https://platejs.org/r/basic-blocks-base-kit.json",
        "https://platejs.org/r/basic-marks-base-kit.json",
        "https://platejs.org/r/callout-base-kit.json",
        "https://platejs.org/r/code-block-base-kit.json",
        "https://platejs.org/r/code-drawing-base-kit.json",
        "https://platejs.org/r/column-base-kit.json",
        "https://platejs.org/r/comment-base-kit.json",
        "https://platejs.org/r/date-base-kit.json",
        "https://platejs.org/r/footnote-base-kit.json",
        "https://platejs.org/r/font-base-kit.json",
        "https://platejs.org/r/line-height-base-kit.json",
        "https://platejs.org/r/link-base-kit.json",
        "https://platejs.org/r/list-base-kit.json",
        "https://platejs.org/r/math-base-kit.json",
        "https://platejs.org/r/media-base-kit.json",
        "https://platejs.org/r/mention-base-kit.json",
        "https://platejs.org/r/suggestion-base-kit.json",
        "https://platejs.org/r/table-base-kit.json",
        "https://platejs.org/r/toc-base-kit.json",
        "https://platejs.org/r/toggle-base-kit.json",
        "https://platejs.org/r/markdown-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/editor-base-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/editor-base-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "ai-kit",
      "dependencies": [
        "@platejs/ai"
      ],
      "registryDependencies": [
        "https://platejs.org/r/markdown-kit.json",
        "https://platejs.org/r/cursor-overlay-kit.json",
        "https://platejs.org/r/ai-menu.json",
        "https://platejs.org/r/ai-node.json",
        "https://platejs.org/r/ai-toolbar-button.json",
        "https://platejs.org/r/ai-api.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/ai-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/ai-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "align-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "https://platejs.org/r/align-base-kit.json",
        "https://platejs.org/r/align-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/align-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/align-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "autoformat-classic-kit",
      "dependencies": [
        "@platejs/code-block",
        "@platejs/list-classic"
      ],
      "registryDependencies": [
        "https://platejs.org/r/list-classic-node.json",
        "https://platejs.org/r/list-classic-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/autoformat-classic-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/autoformat-classic-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "autoformat-kit",
      "dependencies": [
        "@platejs/code-block",
        "@platejs/list"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/autoformat-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/autoformat-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "basic-blocks-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/basic-blocks-base-kit.json",
        "https://platejs.org/r/blockquote-node.json",
        "https://platejs.org/r/heading-node.json",
        "https://platejs.org/r/hr-node.json",
        "https://platejs.org/r/paragraph-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/basic-blocks-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/basic-blocks-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "basic-marks-kit",
      "dependencies": [
        "@platejs/basic-nodes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/basic-marks-base-kit.json",
        "https://platejs.org/r/code-node.json",
        "https://platejs.org/r/highlight-node.json",
        "https://platejs.org/r/kbd-node.json",
        "https://platejs.org/r/mark-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/basic-marks-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/basic-marks-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "basic-nodes-kit",
      "registryDependencies": [
        "https://platejs.org/r/basic-blocks-kit.json",
        "https://platejs.org/r/basic-marks-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/basic-nodes-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/basic-nodes-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "block-menu-kit",
      "dependencies": [
        "@platejs/selection"
      ],
      "registryDependencies": [
        "https://platejs.org/r/block-context-menu.json",
        "https://platejs.org/r/block-selection-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/block-menu-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/block-menu-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "block-placeholder-kit",
      "dependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/block-placeholder-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/block-placeholder-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "block-selection-kit",
      "dependencies": [
        "@platejs/selection"
      ],
      "registryDependencies": [
        "https://platejs.org/r/block-selection.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/block-selection-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/block-selection-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "callout-kit",
      "dependencies": [
        "@platejs/callout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/callout-base-kit.json",
        "https://platejs.org/r/callout-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/callout-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/callout-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "code-block-kit",
      "dependencies": [
        "@platejs/code-block",
        "lowlight"
      ],
      "registryDependencies": [
        "https://platejs.org/r/code-block-base-kit.json",
        "https://platejs.org/r/code-block-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/code-block-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/code-block-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "code-drawing-kit",
      "dependencies": [
        "@platejs/code-drawing"
      ],
      "registryDependencies": [
        "https://platejs.org/r/code-drawing-base-kit.json",
        "https://platejs.org/r/code-drawing-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/code-drawing-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/code-drawing-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "column-kit",
      "dependencies": [
        "@platejs/layout"
      ],
      "registryDependencies": [
        "https://platejs.org/r/column-base-kit.json",
        "https://platejs.org/r/column-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/column-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/column-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "comment-kit",
      "dependencies": [
        "@platejs/comment"
      ],
      "registryDependencies": [
        "https://platejs.org/r/comment-base-kit.json",
        "https://platejs.org/r/comment-node.json",
        "https://platejs.org/r/comment-toolbar-button.json",
        "https://platejs.org/r/discussion-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/comment-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/comment-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "copilot-kit",
      "dependencies": [
        "@platejs/ai",
        "@platejs/markdown",
        "@faker-js/faker"
      ],
      "registryDependencies": [
        "https://platejs.org/r/ghost-text.json",
        "https://platejs.org/r/markdown-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/copilot-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/copilot-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "cursor-overlay-kit",
      "dependencies": [
        "@platejs/selection"
      ],
      "registryDependencies": [
        "https://platejs.org/r/cursor-overlay.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/cursor-overlay-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/cursor-overlay-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "date-kit",
      "dependencies": [
        "@platejs/date"
      ],
      "registryDependencies": [
        "https://platejs.org/r/date-base-kit.json",
        "https://platejs.org/r/date-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/date-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/date-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "discussion-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/block-discussion.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/discussion-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/discussion-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "dnd-kit",
      "dependencies": [
        "@platejs/dnd",
        "@platejs/media",
        "react-dnd",
        "react-dnd-html5-backend"
      ],
      "registryDependencies": [
        "https://platejs.org/r/block-draggable.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/dnd-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/dnd-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "docx-kit",
      "dependencies": [
        "@platejs/docx",
        "@platejs/juice"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/docx-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/docx-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "docx-export-kit",
      "dependencies": [
        "@platejs/docx-io"
      ],
      "registryDependencies": [
        "https://platejs.org/r/callout-node.json",
        "https://platejs.org/r/code-block-node.json",
        "https://platejs.org/r/column-node.json",
        "https://platejs.org/r/equation-node.json",
        "https://platejs.org/r/toc-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/docx-export-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/docx-export-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "editor-kit",
      "registryDependencies": [
        "https://platejs.org/r/editor-base-kit.json",
        "https://platejs.org/r/ai-kit.json",
        "https://platejs.org/r/align-kit.json",
        "https://platejs.org/r/autoformat-kit.json",
        "https://platejs.org/r/basic-nodes-kit.json",
        "https://platejs.org/r/block-menu-kit.json",
        "https://platejs.org/r/block-placeholder-kit.json",
        "https://platejs.org/r/callout-kit.json",
        "https://platejs.org/r/code-block-kit.json",
        "https://platejs.org/r/code-drawing-kit.json",
        "https://platejs.org/r/column-kit.json",
        "https://platejs.org/r/comment-kit.json",
        "https://platejs.org/r/cursor-overlay-kit.json",
        "https://platejs.org/r/date-kit.json",
        "https://platejs.org/r/discussion-kit.json",
        "https://platejs.org/r/dnd-kit.json",
        "https://platejs.org/r/docx-kit.json",
        "https://platejs.org/r/emoji-kit.json",
        "https://platejs.org/r/excalidraw-kit.json",
        "https://platejs.org/r/exit-break-kit.json",
        "https://platejs.org/r/fixed-toolbar-kit.json",
        "https://platejs.org/r/floating-toolbar-kit.json",
        "https://platejs.org/r/footnote-kit.json",
        "https://platejs.org/r/font-kit.json",
        "https://platejs.org/r/line-height-kit.json",
        "https://platejs.org/r/link-kit.json",
        "https://platejs.org/r/list-kit.json",
        "https://platejs.org/r/markdown-kit.json",
        "https://platejs.org/r/math-kit.json",
        "https://platejs.org/r/media-kit.json",
        "https://platejs.org/r/mention-kit.json",
        "https://platejs.org/r/slash-kit.json",
        "https://platejs.org/r/suggestion-kit.json",
        "https://platejs.org/r/table-kit.json",
        "https://platejs.org/r/toc-kit.json",
        "https://platejs.org/r/toggle-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/editor-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/editor-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "emoji-kit",
      "dependencies": [
        "@platejs/emoji",
        "@emoji-mart/data@1.2.1"
      ],
      "registryDependencies": [
        "https://platejs.org/r/emoji-node.json",
        "https://platejs.org/r/emoji-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/emoji-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/emoji-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "exit-break-kit",
      "dependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/exit-break-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/exit-break-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "fixed-toolbar-classic-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/fixed-toolbar.json",
        "https://platejs.org/r/fixed-toolbar-classic-buttons.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/fixed-toolbar-classic-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/fixed-toolbar-classic-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "fixed-toolbar-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/fixed-toolbar.json",
        "https://platejs.org/r/fixed-toolbar-buttons.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/fixed-toolbar-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/fixed-toolbar-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "floating-toolbar-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/floating-toolbar.json",
        "https://platejs.org/r/floating-toolbar-buttons.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/floating-toolbar-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/floating-toolbar-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "floating-toolbar-classic-kit",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/floating-toolbar.json",
        "https://platejs.org/r/floating-toolbar-classic-buttons.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/floating-toolbar-classic-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/floating-toolbar-classic-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "footnote-kit",
      "dependencies": [
        "@platejs/footnote"
      ],
      "registryDependencies": [
        "https://platejs.org/r/footnote-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/footnote-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/footnote-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "font-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "https://platejs.org/r/font-base-kit.json",
        "https://platejs.org/r/font-size-toolbar-button.json",
        "https://platejs.org/r/font-color-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/font-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/font-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "indent-kit",
      "dependencies": [
        "@platejs/indent"
      ],
      "registryDependencies": [
        "https://platejs.org/r/indent-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/indent-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/indent-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "line-height-kit",
      "dependencies": [
        "@platejs/basic-styles"
      ],
      "registryDependencies": [
        "https://platejs.org/r/line-height-base-kit.json",
        "https://platejs.org/r/line-height-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/line-height-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/line-height-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "link-kit",
      "dependencies": [
        "@platejs/link"
      ],
      "registryDependencies": [
        "https://platejs.org/r/link-base-kit.json",
        "https://platejs.org/r/link-node.json",
        "https://platejs.org/r/link-toolbar.json",
        "https://platejs.org/r/link-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/link-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/link-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "list-classic-kit",
      "dependencies": [
        "@platejs/list-classic"
      ],
      "registryDependencies": [
        "https://platejs.org/r/list-classic-node.json",
        "https://platejs.org/r/list-classic-toolbar-button.json",
        "https://platejs.org/r/autoformat-classic-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/list-classic-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/list-classic-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "list-kit",
      "dependencies": [
        "@platejs/list"
      ],
      "registryDependencies": [
        "https://platejs.org/r/list-base-kit.json",
        "https://platejs.org/r/block-list.json",
        "https://platejs.org/r/list-toolbar-button.json",
        "https://platejs.org/r/indent-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/list-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/list-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "markdown-kit",
      "dependencies": [
        "@platejs/footnote",
        "@platejs/markdown",
        "remark-emoji",
        "remark-gfm",
        "remark-math"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/markdown-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/markdown-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "math-kit",
      "dependencies": [
        "@platejs/math"
      ],
      "registryDependencies": [
        "https://platejs.org/r/math-base-kit.json",
        "https://platejs.org/r/equation-toolbar-button.json",
        "https://platejs.org/r/equation-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/math-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/math-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "media-kit",
      "description": "Media kit without API (see media-uploadthing-api for reference)",
      "dependencies": [
        "@platejs/caption",
        "@platejs/media"
      ],
      "registryDependencies": [
        "https://platejs.org/r/media-base-kit.json",
        "https://platejs.org/r/media-audio-node.json",
        "https://platejs.org/r/media-embed-node.json",
        "https://platejs.org/r/media-file-node.json",
        "https://platejs.org/r/media-image-node.json",
        "https://platejs.org/r/media-placeholder-node.json",
        "https://platejs.org/r/media-preview-dialog.json",
        "https://platejs.org/r/media-toolbar.json",
        "https://platejs.org/r/media-upload-toast.json",
        "https://platejs.org/r/media-video-node.json",
        "https://platejs.org/r/media-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/media-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/media-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "media-uploadthing-kit",
      "description": "media-kit + media-uploadthing-api",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/media-kit.json",
        "https://platejs.org/r/media-uploadthing-api.json"
      ],
      "files": [],
      "type": "registry:component"
    },
    {
      "name": "mention-kit",
      "dependencies": [
        "@platejs/mention"
      ],
      "registryDependencies": [
        "https://platejs.org/r/mention-base-kit.json",
        "https://platejs.org/r/mention-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/mention-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/mention-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "slash-kit",
      "dependencies": [
        "@platejs/slash-command"
      ],
      "registryDependencies": [
        "https://platejs.org/r/slash-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/slash-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/slash-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "suggestion-kit",
      "dependencies": [
        "@platejs/suggestion"
      ],
      "registryDependencies": [
        "https://platejs.org/r/suggestion-base-kit.json",
        "https://platejs.org/r/suggestion-node.json",
        "https://platejs.org/r/suggestion-toolbar-button.json",
        "https://platejs.org/r/discussion-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/suggestion-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/suggestion-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "tabbable-kit",
      "dependencies": [
        "@platejs/tabbable"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/tabbable-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/tabbable-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "table-kit",
      "dependencies": [
        "@platejs/table"
      ],
      "registryDependencies": [
        "https://platejs.org/r/table-base-kit.json",
        "https://platejs.org/r/table-node.json",
        "https://platejs.org/r/table-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/table-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/table-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "toc-kit",
      "dependencies": [
        "@platejs/toc"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toc-base-kit.json",
        "https://platejs.org/r/toc-node.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/toc-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/toc-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "toggle-kit",
      "dependencies": [
        "@platejs/toggle"
      ],
      "registryDependencies": [
        "https://platejs.org/r/toggle-base-kit.json",
        "https://platejs.org/r/indent-kit.json",
        "https://platejs.org/r/toggle-node.json",
        "https://platejs.org/r/toggle-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plugins/toggle-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/plugins/toggle-kit.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "ai-api",
      "dependencies": [
        "@ai-sdk/react@4",
        "ai@7",
        "dedent@1.0.0"
      ],
      "registryDependencies": [
        "https://platejs.org/r/copilot-api.json",
        "https://platejs.org/r/markdown-joiner-transform.json"
      ],
      "files": [
        {
          "path": "src/registry/app/api/ai/command/route.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/route.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/utils.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/utils.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/index.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/index.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/common.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/common.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/getChooseToolPrompt.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/getChooseToolPrompt.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/getCommentPrompt.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/getCommentPrompt.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/getEditPrompt.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/getEditPrompt.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/getEditTablePrompt.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/getEditTablePrompt.ts"
        },
        {
          "path": "src/registry/app/api/ai/command/prompt/getGeneratePrompt.ts",
          "type": "registry:file",
          "target": "app/api/ai/command/prompt/getGeneratePrompt.ts"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "copilot-api",
      "dependencies": [
        "ai@7"
      ],
      "registryDependencies": [],
      "files": [
        {
          "path": "src/registry/app/api/ai/copilot/route.ts",
          "type": "registry:file",
          "target": "app/api/ai/copilot/route.ts"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "media-uploadthing-api",
      "dependencies": [
        "uploadthing@7.7.4"
      ],
      "registryDependencies": [
        "https://platejs.org/r/uploadthing.json"
      ],
      "files": [
        {
          "path": "src/registry/app/api/uploadthing/route.ts",
          "type": "registry:file",
          "target": "app/api/uploadthing/route.ts"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "settings-dialog",
      "dependencies": [
        "@platejs/ai"
      ],
      "registryDependencies": [
        "https://platejs.org/r/ai-kit.json",
        "button",
        "command",
        "dialog",
        "input",
        "popover"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/settings-dialog.tsx",
          "type": "registry:component",
          "target": "@components/editor/settings-dialog.tsx"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "use-chat",
      "dependencies": [
        "@ai-sdk/react@4",
        "@faker-js/faker"
      ],
      "registryDependencies": [
        "https://platejs.org/r/ai-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/use-chat.ts",
          "type": "registry:component",
          "target": "@components/editor/use-chat.ts"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "transforms",
      "dependencies": [
        "@platejs/callout",
        "@platejs/code-block",
        "@platejs/date",
        "@platejs/toc",
        "@platejs/layout",
        "@platejs/link",
        "@platejs/math",
        "@platejs/media",
        "@platejs/table"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/transforms.ts",
          "type": "registry:component",
          "target": "@components/editor/transforms.ts"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "transforms-classic",
      "dependencies": [
        "@platejs/callout",
        "@platejs/code-block",
        "@platejs/date",
        "@platejs/layout",
        "@platejs/link",
        "@platejs/list-classic",
        "@platejs/math",
        "@platejs/media",
        "@platejs/table"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/transforms-classic.ts",
          "type": "registry:component",
          "target": "@components/editor/transforms-classic.ts"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "plate-types",
      "dependencies": [
        "@platejs/comment",
        "@platejs/excalidraw",
        "@platejs/link",
        "@platejs/media",
        "@platejs/mention",
        "@platejs/table",
        "@platejs/toggle"
      ],
      "files": [
        {
          "path": "src/registry/components/editor/plate-types.ts",
          "type": "registry:component",
          "target": "@components/editor/plate-types.ts"
        }
      ],
      "type": "registry:component"
    },
    {
      "name": "editor-ai",
      "description": "An AI editor",
      "dependencies": [
        "sonner"
      ],
      "registryDependencies": [
        "https://platejs.org/r/plate-ui.json",
        "https://platejs.org/r/copilot-kit.json",
        "https://platejs.org/r/media-uploadthing-api.json",
        "https://platejs.org/r/plate-types.json",
        "https://platejs.org/r/settings-dialog.json",
        "https://platejs.org/r/editor-base-kit.json",
        "https://platejs.org/r/ai-kit.json",
        "https://platejs.org/r/align-kit.json",
        "https://platejs.org/r/autoformat-kit.json",
        "https://platejs.org/r/basic-nodes-kit.json",
        "https://platejs.org/r/block-menu-kit.json",
        "https://platejs.org/r/block-placeholder-kit.json",
        "https://platejs.org/r/callout-kit.json",
        "https://platejs.org/r/code-block-kit.json",
        "https://platejs.org/r/column-kit.json",
        "https://platejs.org/r/comment-kit.json",
        "https://platejs.org/r/cursor-overlay-kit.json",
        "https://platejs.org/r/date-kit.json",
        "https://platejs.org/r/discussion-kit.json",
        "https://platejs.org/r/dnd-kit.json",
        "https://platejs.org/r/docx-kit.json",
        "https://platejs.org/r/emoji-kit.json",
        "https://platejs.org/r/exit-break-kit.json",
        "https://platejs.org/r/fixed-toolbar-kit.json",
        "https://platejs.org/r/floating-toolbar-kit.json",
        "https://platejs.org/r/font-kit.json",
        "https://platejs.org/r/line-height-kit.json",
        "https://platejs.org/r/link-kit.json",
        "https://platejs.org/r/list-kit.json",
        "https://platejs.org/r/markdown-kit.json",
        "https://platejs.org/r/math-kit.json",
        "https://platejs.org/r/media-kit.json",
        "https://platejs.org/r/mention-kit.json",
        "https://platejs.org/r/slash-kit.json",
        "https://platejs.org/r/suggestion-kit.json",
        "https://platejs.org/r/table-kit.json",
        "https://platejs.org/r/toc-kit.json",
        "https://platejs.org/r/toggle-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/blocks/editor-ai/page.tsx",
          "type": "registry:page",
          "target": "app/editor/page.tsx"
        },
        {
          "path": "src/registry/blocks/editor-ai/components/editor/plate-editor.tsx",
          "type": "registry:component",
          "target": "@components/editor/plate-editor.tsx"
        },
        {
          "path": "src/registry/blocks/editor-ai/components/editor/editor-kit.tsx",
          "type": "registry:component",
          "target": "@components/editor/editor-kit.tsx"
        }
      ],
      "categories": [
        "Editors"
      ],
      "type": "registry:block"
    },
    {
      "name": "editor-select",
      "description": "A multi-select editor",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/plate-ui.json",
        "https://platejs.org/r/select-editor-demo.json"
      ],
      "files": [
        {
          "path": "src/registry/blocks/editor-select/page.tsx",
          "type": "registry:page",
          "target": "app/editor/page.tsx"
        }
      ],
      "meta": {
        "descriptionSrc": "/docs/multi-select"
      },
      "categories": [
        "Editors"
      ],
      "type": "registry:block"
    },
    {
      "name": "editor-basic",
      "description": "A basic editor",
      "dependencies": [
        "@platejs/basic-nodes",
        "@platejs/basic-nodes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/plate-ui.json",
        "https://platejs.org/r/editor.json",
        "https://platejs.org/r/basic-nodes-kit.json",
        "https://platejs.org/r/basic-marks-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/blocks/editor-basic/page.tsx",
          "type": "registry:page",
          "target": "app/editor/page.tsx"
        },
        {
          "path": "src/registry/blocks/editor-basic/components/editor/plate-editor.tsx",
          "type": "registry:component",
          "target": "@components/editor/plate-editor.tsx"
        }
      ],
      "categories": [
        "Editors"
      ],
      "type": "registry:block"
    },
    {
      "name": "slate-to-html",
      "dependencies": [
        "next-themes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/plate-ui.json",
        "https://platejs.org/r/editor-base-kit.json",
        "button"
      ],
      "files": [
        {
          "path": "src/registry/blocks/slate-to-html/page.tsx",
          "type": "registry:page",
          "target": "app/html/page.tsx"
        },
        {
          "path": "src/registry/components/editor/slate-to-html.tsx",
          "type": "registry:component",
          "target": "@components/editor/slate-to-html.tsx"
        },
        {
          "path": "src/registry/lib/create-html-document.ts",
          "type": "registry:lib"
        },
        {
          "path": "src/registry/examples/values/align-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/basic-blocks-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/basic-marks-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/column-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/discussion-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/date-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/equation-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/font-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/indent-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/line-height-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/link-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/list-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/media-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/mention-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/table-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/toc-value.tsx",
          "type": "registry:example"
        }
      ],
      "meta": {
        "rsc": true
      },
      "categories": [
        "Serializers"
      ],
      "type": "registry:block"
    },
    {
      "name": "suggestion",
      "dependencies": [],
      "files": [
        {
          "path": "src/registry/lib/suggestion.ts",
          "type": "registry:lib"
        }
      ],
      "type": "registry:lib"
    },
    {
      "name": "uploadthing",
      "dependencies": [
        "uploadthing@7.7.4",
        "@uploadthing/react@7.3.3",
        "sonner",
        "zod"
      ],
      "files": [
        {
          "path": "src/registry/hooks/use-upload-file.ts",
          "type": "registry:hook"
        },
        {
          "path": "src/registry/lib/uploadthing.ts",
          "type": "registry:lib"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "markdown-joiner-transform",
      "dependencies": [
        "ai@7"
      ],
      "files": [
        {
          "path": "src/registry/lib/markdown-joiner-transform.ts",
          "type": "registry:lib"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "tailwind-scrollbar-hide",
      "dependencies": [
        "tailwind-scrollbar-hide"
      ],
      "files": [],
      "tailwind": {
        "config": {
          "plugins": [
            "tailwind-scrollbar-hide"
          ]
        }
      },
      "type": "registry:style"
    },
    {
      "name": "highlight-style",
      "files": [],
      "cssVars": {
        "light": {
          "highlight": "oklch(0.852 0.199 91.936)"
        },
        "dark": {
          "highlight": "oklch(0.852 0.199 91.936)"
        }
      },
      "type": "registry:style"
    },
    {
      "name": "use-debounce",
      "files": [
        {
          "path": "src/registry/hooks/use-debounce.ts",
          "type": "registry:hook"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "use-mobile",
      "files": [
        {
          "path": "src/registry/hooks/use-mobile.ts",
          "type": "registry:hook"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "use-mounted",
      "files": [
        {
          "path": "src/registry/hooks/use-mounted.ts",
          "type": "registry:hook"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "use-is-touch-device",
      "files": [
        {
          "path": "src/registry/hooks/use-is-touch-device.ts",
          "type": "registry:hook"
        }
      ],
      "type": "registry:hook"
    },
    {
      "name": "copilot-demo",
      "description": "Renders AI ghost text suggestions at the cursor position.",
      "dependencies": [
        "@platejs/ai",
        "@platejs/markdown"
      ],
      "registryDependencies": [
        "https://platejs.org/r/copilot-kit.json",
        "https://platejs.org/r/editor-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/copilot-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/copilot-value.tsx",
          "type": "registry:example"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/copilot",
            "title": "Copilot"
          }
        ]
      },
      "type": "registry:example"
    },
    {
      "name": "select-editor-demo",
      "title": "Select Editor Form",
      "description": "A form with a select editor component for managing labels.",
      "dependencies": [
        "@platejs/tag"
      ],
      "registryDependencies": [
        "form",
        "button",
        "https://platejs.org/r/select-editor.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/select-editor-demo.tsx",
          "type": "registry:example"
        }
      ],
      "meta": {
        "docs": [
          {
            "route": "/docs/multi-select"
          }
        ]
      },
      "type": "registry:example"
    },
    {
      "name": "controlled-demo",
      "registryDependencies": [
        "https://platejs.org/r/editor.json",
        "button"
      ],
      "files": [
        {
          "path": "src/registry/examples/controlled-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "huge-document-demo",
      "dependencies": [
        "@faker-js/faker"
      ],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "src/registry/examples/huge-document-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/huge-document-value.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "hundreds-editors-demo",
      "registryDependencies": [
        "https://platejs.org/r/editor.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/hundreds-editors-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/multi-editors-value.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "multiple-editors-demo",
      "dependencies": [
        "@platejs/basic-nodes",
        "@platejs/basic-nodes",
        "@platejs/media"
      ],
      "registryDependencies": [
        "separator",
        "https://platejs.org/r/basic-nodes-kit.json",
        "https://platejs.org/r/media-kit.json",
        "https://platejs.org/r/editor.json",
        "https://platejs.org/r/fixed-toolbar.json",
        "https://platejs.org/r/turn-into-toolbar-button.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/multiple-editors-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/basic-blocks-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/basic-marks-value.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/media-value.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "version-history-demo",
      "dependencies": [
        "@platejs/basic-nodes",
        "@platejs/diff",
        "lodash"
      ],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "src/registry/examples/version-history-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "preview-markdown-demo",
      "dependencies": [
        "prismjs"
      ],
      "registryDependencies": [
        "https://platejs.org/r/basic-nodes-kit.json",
        "https://platejs.org/r/editor.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/preview-markdown-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/preview-md-value.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "markdown-to-slate-demo",
      "dependencies": [
        "remark-emoji"
      ],
      "registryDependencies": [
        "https://platejs.org/r/editor-kit.json",
        "https://platejs.org/r/use-debounce.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/markdown-to-slate-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "markdown-streaming-demo",
      "dependencies": [],
      "registryDependencies": [
        "https://platejs.org/r/copilot-kit.json",
        "https://platejs.org/r/editor-kit.json",
        "https://platejs.org/r/markdown-joiner-transform.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/markdown-streaming-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "collaboration-demo",
      "description": "Real-time collaboration with cursors and selections.",
      "dependencies": [
        "@platejs/yjs",
        "nanoid"
      ],
      "registryDependencies": [
        "https://platejs.org/r/use-mounted.json",
        "https://platejs.org/r/remote-cursor-overlay.json",
        "button",
        "input"
      ],
      "files": [
        {
          "path": "src/registry/examples/collaboration-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "installation-next-demo",
      "dependencies": [
        "@platejs/basic-nodes",
        "@platejs/basic-nodes"
      ],
      "registryDependencies": [
        "https://platejs.org/r/editor.json",
        "https://platejs.org/r/fixed-toolbar.json",
        "https://platejs.org/r/mark-toolbar-button.json",
        "https://platejs.org/r/heading-node.json",
        "https://platejs.org/r/paragraph-node.json",
        "https://platejs.org/r/blockquote-node.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/installation-next-04-value-demo.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "tabbable-demo",
      "dependencies": [
        "@platejs/tabbable"
      ],
      "registryDependencies": [
        "https://platejs.org/r/editor-kit.json"
      ],
      "files": [
        {
          "path": "src/registry/examples/tabbable-demo.tsx",
          "type": "registry:example"
        },
        {
          "path": "src/registry/examples/values/tabbable-value.tsx",
          "type": "registry:example"
        }
      ],
      "type": "registry:example"
    },
    {
      "name": "docs-meta",
      "title": "Documentation metadata",
      "description": "Fumadocs metadata for plate documentation",
      "files": [
        {
          "path": "../../content/docs/meta.json",
          "content": "{\n  \"root\": true,\n  \"pages\": [\n    \"---Get Started---\",\n    \"[Introduction](/docs/plate)\",\n    \"[Installation](/docs/plate/installation)\",\n    \"[Releases](/docs/plate/releases)\",\n    \"---Installation---\",\n    \"[Plate UI](/docs/plate/installation/plate-ui)\",\n    \"[Next.js](/docs/plate/installation/next)\",\n    \"[React](/docs/plate/installation/react)\",\n    \"[Manual](/docs/plate/installation/manual)\",\n    \"[RSC](/docs/plate/installation/rsc)\",\n    \"[Node.js](/docs/plate/installation/node)\",\n    \"[Local Docs](/docs/plate/installation/docs)\",\n    \"[MCP](/docs/plate/installation/mcp)\",\n    \"---Guides---\",\n    \"[Feature Kits](/docs/plate/feature-kits)\",\n    \"[Plugin](/docs/plate/plugin)\",\n    \"[Plugin Methods](/docs/plate/plugin-methods)\",\n    \"[Plugin Shortcuts](/docs/plate/plugin-shortcuts)\",\n    \"[Plugin Context](/docs/plate/plugin-context)\",\n    \"[Plugin Components](/docs/plate/plugin-components)\",\n    \"[Plugin Rules](/docs/plate/plugin-rules)\",\n    \"[Editing Behavior](/docs/plate/editing-behavior)\",\n    \"[Plugin Input Rules](/docs/plate/plugin-input-rules)\",\n    \"[Editor](/docs/plate/editor)\",\n    \"[Editor Methods](/docs/plate/editor-methods)\",\n    \"[Controlled Value](/docs/plate/controlled)\",\n    \"[Performance](/docs/plate/performance)\",\n    \"[Static Rendering](/docs/plate/static)\",\n    \"[HTML](/docs/plate/html)\",\n    \"[Markdown](/docs/plate/markdown)\",\n    \"[Form](/docs/plate/form)\",\n    \"[TypeScript](/docs/plate/typescript)\",\n    \"[Debugging](/docs/plate/debugging)\",\n    \"[Unit Testing](/docs/plate/unit-testing)\",\n    \"[Playwright Testing](/docs/plate/playwright)\",\n    \"[Troubleshooting](/docs/plate/troubleshooting)\",\n    \"---Plugins---\",\n    \"[Overview](/docs/plate/plugins)\",\n    \"[Stream](/docs/plate/ai)\",\n    \"[Copilot](/docs/plate/copilot)\",\n    \"[Discussion](/docs/plate/discussion)\",\n    \"[Comments](/docs/plate/comment)\",\n    \"[Suggestion](/docs/plate/suggestion)\",\n    \"[Basic Blocks](/docs/plate/basic-blocks)\",\n    \"[Blockquote](/docs/plate/blockquote)\",\n    \"[Heading](/docs/plate/heading)\",\n    \"[Horizontal Rule](/docs/plate/horizontal-rule)\",\n    \"[Callout](/docs/plate/callout)\",\n    \"[Code Block](/docs/plate/code-block)\",\n    \"[Column](/docs/plate/column)\",\n    \"[Date](/docs/plate/date)\",\n    \"[Equation](/docs/plate/equation)\",\n    \"[Link](/docs/plate/link)\",\n    \"[List Classic](/docs/plate/list-classic)\",\n    \"[Media](/docs/plate/media)\",\n    \"[Mention](/docs/plate/mention)\",\n    \"[Table](/docs/plate/table)\",\n    \"[Table of Contents](/docs/plate/toc)\",\n    \"[Footnote](/docs/plate/footnote)\",\n    \"[Toggle](/docs/plate/toggle)\",\n    \"[Marks](/docs/plate/basic-marks)\",\n    \"[Bold](/docs/plate/bold)\",\n    \"[Italic](/docs/plate/italic)\",\n    \"[Underline](/docs/plate/underline)\",\n    \"[Code](/docs/plate/code)\",\n    \"[Highlight](/docs/plate/highlight)\",\n    \"[Keyboard Input](/docs/plate/kbd)\",\n    \"[Strikethrough](/docs/plate/strikethrough)\",\n    \"[Subscript](/docs/plate/subscript)\",\n    \"[Superscript](/docs/plate/superscript)\",\n    \"[Font](/docs/plate/font)\",\n    \"[Line Height](/docs/plate/line-height)\",\n    \"[Text Align](/docs/plate/text-align)\",\n    \"[Indent](/docs/plate/indent)\",\n    \"[List](/docs/plate/list)\",\n    \"[Exit Break](/docs/plate/exit-break)\",\n    \"[Single Block](/docs/plate/single-block)\",\n    \"[Trailing Block](/docs/plate/trailing-block)\",\n    \"[Autoformat](/docs/plate/autoformat)\",\n    \"[Block Menu](/docs/plate/block-menu)\",\n    \"[Block Placeholder](/docs/plate/block-placeholder)\",\n    \"[Block Selection](/docs/plate/block-selection)\",\n    \"[Caption](/docs/plate/caption)\",\n    \"[Combobox](/docs/plate/combobox)\",\n    \"[Emoji](/docs/plate/emoji)\",\n    \"[Slash Command](/docs/plate/slash-command)\",\n    \"[Cursor Overlay](/docs/plate/cursor-overlay)\",\n    \"[Drag & Drop](/docs/plate/dnd)\",\n    \"[Navigation Feedback](/docs/plate/navigation-feedback)\",\n    \"[Tabbable](/docs/plate/tabbable)\",\n    \"[Toolbar](/docs/plate/toolbar)\",\n    \"[Yjs](/docs/plate/yjs)\",\n    \"[Multi Select](/docs/plate/multi-select)\",\n    \"[CSV](/docs/plate/csv)\",\n    \"[DOCX](/docs/plate/docx)\",\n    \"[DOCX Import/Export](/docs/plate/docx-io)\",\n    \"---Components---\",\n    \"[Components](/docs/plate/components)\",\n    \"[AI Menu](/docs/plate/components/ai-menu)\",\n    \"[AI Toolbar Button](/docs/plate/components/ai-toolbar-button)\",\n    \"[Align Toolbar Button](/docs/plate/components/align-toolbar-button)\",\n    \"[Block Context Menu](/docs/plate/components/block-context-menu)\",\n    \"[Block Selection](/docs/plate/components/block-selection)\",\n    \"[Import Toolbar Button](/docs/plate/components/import-toolbar-button)\",\n    \"[Export Toolbar Button](/docs/plate/components/export-toolbar-button)\",\n    \"[Caption](/docs/plate/components/caption)\",\n    \"[Font Color Toolbar Button](/docs/plate/components/font-color-toolbar-button)\",\n    \"[Comment Toolbar Button](/docs/plate/components/comment-toolbar-button)\",\n    \"[Block Discussion](/docs/plate/components/block-discussion)\",\n    \"[Cursor Overlay](/docs/plate/components/cursor-overlay)\",\n    \"[Block Draggable](/docs/plate/components/block-draggable)\",\n    \"[Editor](/docs/plate/components/editor)\",\n    \"[Select Editor](/docs/plate/components/select-editor)\",\n    \"[Emoji Toolbar Button](/docs/plate/components/emoji-toolbar-button)\",\n    \"[Fixed Toolbar Buttons](/docs/plate/components/fixed-toolbar-buttons)\",\n    \"[Fixed Toolbar List Buttons](/docs/plate/components/fixed-toolbar-classic-buttons)\",\n    \"[Fixed Toolbar](/docs/plate/components/fixed-toolbar)\",\n    \"[Floating Toolbar Buttons](/docs/plate/components/floating-toolbar-buttons)\",\n    \"[Floating Toolbar Classic Buttons](/docs/plate/components/floating-toolbar-classic-buttons)\",\n    \"[Floating Toolbar](/docs/plate/components/floating-toolbar)\",\n    \"[Ghost Text](/docs/plate/components/ghost-text)\",\n    \"[History Toolbar Button](/docs/plate/components/history-toolbar-button)\",\n    \"[List Toolbar Button](/docs/plate/components/list-toolbar-button)\",\n    \"[Indent Toolbar Buttons](/docs/plate/components/indent-toolbar-button)\",\n    \"[Inline Combobox](/docs/plate/components/inline-combobox)\",\n    \"[Insert Toolbar Button](/docs/plate/components/insert-toolbar-button)\",\n    \"[Insert Toolbar Classic Button](/docs/plate/components/insert-toolbar-classic-button)\",\n    \"[Line Height Toolbar Button](/docs/plate/components/line-height-toolbar-button)\",\n    \"[Link Floating Toolbar](/docs/plate/components/link-toolbar)\",\n    \"[Link Toolbar Button](/docs/plate/components/link-toolbar-button)\",\n    \"[List Toolbar Buttons](/docs/plate/components/list-classic-toolbar-button)\",\n    \"[Mark Toolbar Button](/docs/plate/components/mark-toolbar-button)\",\n    \"[Media Toolbar](/docs/plate/components/media-toolbar)\",\n    \"[Media Toolbar Button](/docs/plate/components/media-toolbar-button)\",\n    \"[Media Upload Toast](/docs/plate/components/media-upload-toast)\",\n    \"[Mode Toolbar Button](/docs/plate/components/mode-toolbar-button)\",\n    \"[More Toolbar Button](/docs/plate/components/more-toolbar-button)\",\n    \"[Resize Handle](/docs/plate/components/resize-handle)\",\n    \"[Table Toolbar Button](/docs/plate/components/table-toolbar-button)\",\n    \"[Toggle Toolbar Button](/docs/plate/components/toggle-toolbar-button)\",\n    \"[Turn Into Toolbar Button](/docs/plate/components/turn-into-toolbar-button)\",\n    \"[Turn Into Toolbar Classic Button](/docs/plate/components/turn-into-toolbar-classic-button)\",\n    \"[Remote Cursor Overlay](/docs/plate/components/remote-cursor-overlay)\",\n    \"[Toolbar](/docs/plate/components/toolbar)\",\n    \"[Suggestion Toolbar Button](/docs/plate/components/suggestion-toolbar-button)\",\n    \"[Node Components](/docs/plate/components#node-components)\",\n    \"[AI Leaf](/docs/plate/components/ai-node)\",\n    \"[List](/docs/plate/components/block-list)\",\n    \"[Blockquote Element](/docs/plate/components/blockquote-node)\",\n    \"[Callout Node](/docs/plate/components/callout-node)\",\n    \"[Code Block Nodes](/docs/plate/components/code-block-node)\",\n    \"[Code Drawing Node](/docs/plate/components/code-drawing-node)\",\n    \"[Code Leaf](/docs/plate/components/code-node)\",\n    \"[Column Nodes](/docs/plate/components/column-node)\",\n    \"[Comment Leaf](/docs/plate/components/comment-node)\",\n    \"[Suggestion Leaf](/docs/plate/components/suggestion-node)\",\n    \"[Date Element](/docs/plate/components/date-node)\",\n    \"[Equation Element](/docs/plate/components/equation-node)\",\n    \"[Equation Toolbar Button](/docs/plate/components/equation-toolbar-button)\",\n    \"[Emoji Input Element](/docs/plate/components/emoji-node)\",\n    \"[Excalidraw Element](/docs/plate/components/excalidraw-node)\",\n    \"[Font Size Toolbar Button](/docs/plate/components/font-size-toolbar-button)\",\n    \"[Footnote Elements](/docs/plate/components/footnote-node)\",\n    \"[Heading Element](/docs/plate/components/heading-node)\",\n    \"[Highlight Leaf](/docs/plate/components/highlight-node)\",\n    \"[Horizontal Rule Element](/docs/plate/components/hr-node)\",\n    \"[Image Element](/docs/plate/components/media-image-node)\",\n    \"[Media Preview Dialog](/docs/plate/components/media-preview-dialog)\",\n    \"[Keyboard Input Leaf](/docs/plate/components/kbd-node)\",\n    \"[Link Element](/docs/plate/components/link-node)\",\n    \"[List Nodes](/docs/plate/components/list-classic-node)\",\n    \"[Media Audio Element](/docs/plate/components/media-audio-node)\",\n    \"[Media Embed Element](/docs/plate/components/media-embed-node)\",\n    \"[Media File Element](/docs/plate/components/media-file-node)\",\n    \"[Media Placeholder Element](/docs/plate/components/media-placeholder-node)\",\n    \"[Media Video Element](/docs/plate/components/media-video-node)\",\n    \"[Mention Nodes](/docs/plate/components/mention-node)\",\n    \"[Paragraph Element](/docs/plate/components/paragraph-node)\",\n    \"[Search Highlight Leaf](/docs/plate/components/search-highlight-node)\",\n    \"[Slash Input Element](/docs/plate/components/slash-node)\",\n    \"[Table Element](/docs/plate/components/table-node)\",\n    \"[Tag Element](/docs/plate/components/tag-node)\",\n    \"[TOC Element](/docs/plate/components/toc-node)\",\n    \"[Toggle Element](/docs/plate/components/toggle-node)\",\n    \"---Examples---\",\n    \"[Overview](/docs/plate/examples)\",\n    \"[Slate to HTML](/docs/plate/examples/slate-to-html)\",\n    \"[Export](/docs/plate/examples/export)\",\n    \"[Server-Side](/docs/plate/examples/server-side)\",\n    \"[Version History](/docs/plate/examples/version-history)\",\n    \"[Editable Voids](/docs/plate/examples/editable-voids)\",\n    \"[Huge Document](/docs/plate/examples/huge-document)\",\n    \"[Hundreds Editors](/docs/plate/examples/hundreds-editors)\",\n    \"[Markdown Streaming](/docs/plate/examples/markdown-streaming)\",\n    \"[Preview Markdown](/docs/plate/examples/preview-markdown)\",\n    \"[Collaboration Demo](/docs/plate/examples/collaboration)\",\n    \"[Table Nomerge Demo](/docs/plate/examples/table-nomerge)\",\n    \"[Excalidraw Demo](/docs/plate/examples/excalidraw)\",\n    \"[Code Drawing Demo](/docs/plate/examples/code-drawing)\",\n    \"[Single Block Demo](/docs/plate/examples/single-block)\",\n    \"[List Classic Demo](/docs/plate/examples/list-classic)\",\n    \"[Find Replace Demo](/docs/plate/examples/find-replace)\",\n    \"[AI Demo](/docs/plate/examples/ai)\",\n    \"[Align Demo](/docs/plate/examples/align)\",\n    \"[Autoformat Demo](/docs/plate/examples/autoformat)\",\n    \"[Basic Nodes Demo](/docs/plate/examples/basic-nodes)\",\n    \"[Block Menu Demo](/docs/plate/examples/block-menu)\",\n    \"[Block Selection Demo](/docs/plate/examples/block-selection)\",\n    \"[Column Demo](/docs/plate/examples/column)\",\n    \"[Code Block Demo](/docs/plate/examples/code-block)\",\n    \"[Callout Demo](/docs/plate/examples/callout)\",\n    \"[Discussion Demo](/docs/plate/examples/discussion)\",\n    \"[Cursor Overlay Demo](/docs/plate/examples/cursor-overlay)\",\n    \"[Date Demo](/docs/plate/examples/date)\",\n    \"[Footnote Demo](/docs/plate/examples/footnote)\",\n    \"[Drag & Drop Demo](/docs/plate/examples/dnd)\",\n    \"[Emoji Demo](/docs/plate/examples/emoji)\",\n    \"[Equation Demo](/docs/plate/examples/equation)\",\n    \"[Exit Break Demo](/docs/plate/examples/exit-break)\",\n    \"[Floating Toolbar Demo](/docs/plate/examples/floating-toolbar)\",\n    \"[Font Demo](/docs/plate/examples/font)\",\n    \"[Indent Demo](/docs/plate/examples/indent)\",\n    \"[List Demo](/docs/plate/examples/list)\",\n    \"[Line Height Demo](/docs/plate/examples/line-height)\",\n    \"[Link Demo](/docs/plate/examples/link)\",\n    \"[Media Demo](/docs/plate/examples/media)\",\n    \"[Mention Demo](/docs/plate/examples/mention)\",\n    \"[Block Placeholder Demo](/docs/plate/examples/block-placeholder)\",\n    \"[Serializing CSV Demo](/docs/plate/examples/csv)\",\n    \"[Serializing Docx Demo](/docs/plate/examples/docx)\",\n    \"[Serializing HTML Demo](/docs/plate/examples/html)\",\n    \"[Serializing Markdown Demo](/docs/plate/examples/markdown)\",\n    \"[Slash Command Demo](/docs/plate/examples/slash-command)\",\n    \"[Plugin Rules Demo](/docs/plate/examples/plugin-rules)\",\n    \"[Table Demo](/docs/plate/examples/table)\",\n    \"[Table of Contents Demo](/docs/plate/examples/toc)\",\n    \"[Toggle Demo](/docs/plate/examples/toggle)\",\n    \"---Migrations---\",\n    \"[From Slate to Plate](/docs/plate/migration/slate-to-plate)\",\n    \"---API---\",\n    \"[Overview](/docs/plate/api)\",\n    \"[Plate](/docs/plate/api/plate)\",\n    \"[Slate](/docs/plate/api/slate)\",\n    \"[Editor API](/docs/plate/api/slate/editor-api)\",\n    \"[Editor Transforms](/docs/plate/api/slate/editor-transforms)\",\n    \"[Node](/docs/plate/api/slate/node)\",\n    \"[Element](/docs/plate/api/slate/element)\",\n    \"[Text](/docs/plate/api/slate/text)\",\n    \"[Path](/docs/plate/api/slate/path)\",\n    \"[Point](/docs/plate/api/slate/point)\",\n    \"[Range](/docs/plate/api/slate/range)\",\n    \"[Location](/docs/plate/api/slate/location)\",\n    \"[Location Ref](/docs/plate/api/slate/location-ref)\",\n    \"[Operation](/docs/plate/api/slate/operation)\",\n    \"[Plate Core](/docs/plate/api/core)\",\n    \"[Plate Components](/docs/plate/api/core/plate-components)\",\n    \"[Plate Editor](/docs/plate/api/core/plate-editor)\",\n    \"[Plate Plugin](/docs/plate/api/core/plate-plugin)\",\n    \"[Plate Store](/docs/plate/api/core/plate-store)\",\n    \"[Plate Controller](/docs/plate/api/core/plate-controller)\",\n    \"[Plate Utils](/docs/plate/api/utils)\",\n    \"[React Utils](/docs/plate/api/react-utils)\",\n    \"[cn](/docs/plate/api/cn)\",\n    \"[Floating](/docs/plate/api/floating)\",\n    \"[Resizable](/docs/plate/api/resizable)\"\n  ],\n  \"_plate\": {\n    \"categoryGroups\": {\n      \"api\": [\n        {\n          \"items\": [\n            {\n              \"description\": \"Plate API.\",\n              \"headings\": [],\n              \"href\": \"/docs/plate/api/plate\",\n              \"title\": \"Plate\"\n            },\n            {\n              \"description\": \"Slate API.\",\n              \"headings\": [],\n              \"href\": \"/docs/plate/api/slate\",\n              \"items\": [\n                {\n                  \"href\": \"/docs/plate/api/slate/editor-api\",\n                  \"title\": \"Editor API\",\n                  \"titleCn\": \"编辑器 API\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/editor-transforms\",\n                  \"title\": \"Editor Transforms\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/node\",\n                  \"title\": \"Node\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/element\",\n                  \"title\": \"Element\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/text\",\n                  \"title\": \"Text\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/path\",\n                  \"title\": \"Path\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/point\",\n                  \"title\": \"Point\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/range\",\n                  \"title\": \"Range\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/location\",\n                  \"title\": \"Location\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/location-ref\",\n                  \"title\": \"Location Ref\"\n                },\n                {\n                  \"href\": \"/docs/plate/api/slate/operation\",\n                  \"title\": \"Operation\"\n                }\n              ],\n              \"label\": \"v42\",\n              \"title\": \"Slate\"\n            },\n            {\n              \"description\": \"Core utilities for Plate.\",\n              \"headings\": [\n                \"createAtomStore\",\n                \"createDeserializeAstPlugin\",\n                \"createHtmlPlugin\",\n                \"createEditorProtocolPlugin\",\n                \"createEventEditorPlugin\",\n                \"createHistoryPlugin\",\n                \"createInlineVoidPlugin\",\n                \"createInsertDataPlugin\",\n                \"createPlateEditor\",\n                \"createSlatePlugin\",\n                \"createPrevSelectionPlugin\",\n                \"createPlatePlugin\",\n                \"getPlugin\",\n                \"getPluginInjectProps\",\n                \"getPluginOptions\",\n                \"getPluginType\",\n                \"Hotkeys\",\n                \"toggleBlock\",\n                \"useEditorRef\",\n                \"useEditorState\",\n                \"useElement\",\n                \"useEditorReadOnly\",\n                \"useEditorSelection\",\n                \"useEditorVersion\",\n                \"useSelectionVersion\",\n                \"withPlate\",\n                \"withTReact\"\n              ],\n              \"href\": \"/docs/plate/api/core\",\n              \"items\": [\n                {\n                  \"headings\": [\n                    \"PlateProps\",\n                    \"PlateContent\",\n                    \"id\",\n                    \"children\",\n                    \"decorate\",\n                    \"disableCorePlugins\",\n                    \"editableProps\",\n                    \"editableRef\",\n                    \"editor\",\n                    \"firstChildren\",\n                    \"initialValue\",\n                    \"normalizeInitialValue\",\n                    \"onChange\",\n                    \"plugins\",\n                    \"renderEditable\",\n                    \"renderElement\",\n                    \"renderLeaf\",\n                    \"value\"\n                  ],\n                  \"href\": \"/docs/plate/api/core/plate-components\",\n                  \"title\": \"Plate Components\",\n                  \"titleCn\": \"Plate 组件\"\n                },\n                {\n                  \"headings\": [\n                    \"id\",\n                    \"dom\",\n                    \"plugins\",\n                    \"meta\",\n                    \"getApi\",\n                    \"getTransforms\",\n                    \"getPlugin\",\n                    \"getType\",\n                    \"init\",\n                    \"getOption\",\n                    \"getOptions\",\n                    \"setOption\",\n                    \"setOptions\",\n                    \"getOptionsStore\",\n                    \"DebugPlugin\",\n                    \"log\",\n                    \"info\",\n                    \"warn\",\n                    \"error\",\n                    \"HtmlPlugin\",\n                    \"deserialize\",\n                    \"ReactPlugin\",\n                    \"reset\",\n                    \"SlateReactExtensionPlugin\",\n                    \"redecorate\",\n                    \"setValue\"\n                  ],\n                  \"href\": \"/docs/plate/api/core/plate-editor\",\n                  \"title\": \"Plate Editor\",\n                  \"titleCn\": \"Plate 编辑器\"\n                },\n                {\n                  \"headings\": [\n                    \"key\",\n                    \"api\",\n                    \"transforms\",\n                    \"options\",\n                    \"handlers\",\n                    \"inject\",\n                    \"nodeProps\",\n                    \"excludePlugins\",\n                    \"excludeBelowPlugins\",\n                    \"isBlock\",\n                    \"isElement\",\n                    \"isLeaf\",\n                    \"maxLevel\",\n                    \"plugins\",\n                    \"targetPluginToInject\",\n                    \"targetPlugins\",\n                    \"node\",\n                    \"isDecoration\",\n                    \"rules\",\n                    \"affinity\",\n                    \"isInline\",\n                    \"isContainer\",\n                    \"clearOnEdge\",\n                    \"isMarkableVoid\",\n                    \"isSelectable\",\n                    \"isStrictSiblings\",\n                    \"rules.break\",\n                    \"isVoid\",\n                    \"type\",\n                    \"component\",\n                    \"leafProps\",\n                    \"props\",\n                    \"textProps\",\n                    \"override\",\n                    \"components\",\n                    \"enabled\",\n                    \"parser\",\n                    \"parsers\",\n                    \"html\",\n                    \"htmlReact\",\n                    \"render\",\n                    \"aboveEditable\",\n                    \"aboveNodes\",\n                    \"aboveSlate\",\n                    \"afterEditable\",\n                    \"beforeEditable\",\n                    \"belowNodes\",\n                    \"belowRootNodes\",\n                    \"leaf\",\n                    \"as\",\n                    \"shortcuts\",\n                    \"useOptionsStore\",\n                    \"dependencies\",\n                    \"priority\",\n                    \"decorate\",\n                    \"extendEditor\",\n                    \"useHooks\",\n                    \"editOnly\",\n                    \"configure\",\n                    \"extend\",\n                    \"extendPlugin\",\n                    \"withComponent\",\n                    \"overrideEditor\",\n                    \"extendApi\",\n                    \"extendEditorApi\",\n                    \"extendTransforms\",\n                    \"extendEditorTransforms\",\n                    \"extendSelectors\",\n                    \"editor\",\n                    \"plugin\",\n                    \"getOption\",\n                    \"getOptions\",\n                    \"setOption\",\n                    \"setOptions\"\n                  ],\n                  \"href\": \"/docs/plate/api/core/plate-plugin\",\n                  \"title\": \"Plate Plugin\",\n                  \"titleCn\": \"Plate 插件\"\n                },\n                {\n                  \"headings\": [\n                    \"useEventEditorSelectors\",\n                    \"useEventPlateId\"\n                  ],\n                  \"href\": \"/docs/plate/api/core/plate-store\",\n                  \"title\": \"Plate Store\",\n                  \"titleCn\": \"Plate 存储\"\n                },\n                {\n                  \"headings\": [\n                    \"platecontroller-store\",\n                    \"state\",\n                    \"activeId\",\n                    \"primaryEditorIds\",\n                    \"editorStores\",\n                    \"usage-patterns\",\n                    \"specific-editor-by-id\",\n                    \"active-editor\",\n                    \"dealing-with-fallback-editors\"\n                  ],\n                  \"href\": \"/docs/plate/api/core/plate-controller\",\n                  \"title\": \"Plate Controller\",\n                  \"titleCn\": \"Plate 控制器\"\n                }\n              ],\n              \"title\": \"Plate Core\",\n              \"titleCn\": \"Plate 核心\"\n            },\n            {\n              \"description\": \"Additional utilities for Plate.\",\n              \"headings\": [],\n              \"href\": \"/docs/plate/api/utils\",\n              \"title\": \"Plate Utils\",\n              \"titleCn\": \"Plate 工具\"\n            },\n            {\n              \"description\": \"React utilities.\",\n              \"headings\": [\n                \"PortalBody\",\n                \"Text\",\n                \"Box\",\n                \"createPrimitiveComponent\",\n                \"createSlotComponent\",\n                \"withProviders\"\n              ],\n              \"href\": \"/docs/plate/api/react-utils\",\n              \"title\": \"React Utils\",\n              \"titleCn\": \"React 工具\"\n            },\n            {\n              \"description\": \"Class utilities.\",\n              \"headings\": [\n                \"cn\",\n                \"withCn\",\n                \"withProps\",\n                \"withVariants\"\n              ],\n              \"href\": \"/docs/plate/api/cn\",\n              \"title\": \"cn\",\n              \"titleCn\": \"cn\"\n            },\n            {\n              \"description\": \"API reference for @platejs/floating\",\n              \"headings\": [],\n              \"href\": \"/docs/plate/api/floating\",\n              \"title\": \"Floating\",\n              \"titleCn\": \"Floating\"\n            },\n            {\n              \"description\": \"API reference for @platejs/resizable\",\n              \"headings\": [],\n              \"href\": \"/docs/plate/api/resizable\",\n              \"title\": \"Resizable\",\n              \"titleCn\": \"Resizable\"\n            }\n          ]\n        }\n      ],\n      \"component\": [\n        {\n          \"href\": \"/docs/plate/components\",\n          \"items\": [\n            {\n              \"description\": \"A menu for AI-powered content generation and insertion.\",\n              \"href\": \"/docs/plate/components/ai-menu\",\n              \"title\": \"AI Menu\",\n              \"titleCn\": \"AI Menu\"\n            },\n            {\n              \"description\": \"A toolbar button for accessing AI features.\",\n              \"href\": \"/docs/plate/components/ai-toolbar-button\",\n              \"title\": \"AI Toolbar Button\",\n              \"titleCn\": \"AI Toolbar Button\"\n            },\n            {\n              \"description\": \"A dropdown menu for text alignment controls.\",\n              \"href\": \"/docs/plate/components/align-toolbar-button\",\n              \"title\": \"Align Toolbar Button\",\n              \"titleCn\": \"Align Toolbar Button\"\n            },\n            {\n              \"description\": \"A context menu for block-level operations.\",\n              \"href\": \"/docs/plate/components/block-context-menu\",\n              \"title\": \"Block Context Menu\",\n              \"titleCn\": \"Block Context Menu\"\n            },\n            {\n              \"description\": \"A visual overlay for selected blocks.\",\n              \"href\": \"/docs/plate/components/block-selection\",\n              \"title\": \"Block Selection\",\n              \"titleCn\": \"Block Selection\"\n            },\n            {\n              \"description\": \"A toolbar button to import editor content from a file.\",\n              \"href\": \"/docs/plate/components/import-toolbar-button\",\n              \"title\": \"Import Toolbar Button\",\n              \"titleCn\": \"Import Toolbar Button\"\n            },\n            {\n              \"description\": \"A toolbar button for exporting editor content in various formats (HTML, PDF, Image, Markdown).\",\n              \"href\": \"/docs/plate/components/export-toolbar-button\",\n              \"title\": \"Export Toolbar Button\",\n              \"titleCn\": \"Export Toolbar Button\"\n            },\n            {\n              \"description\": \"A text field for adding captions to media elements.\",\n              \"href\": \"/docs/plate/components/caption\",\n              \"title\": \"Caption\",\n              \"titleCn\": \"Caption\"\n            },\n            {\n              \"description\": \"A color picker toolbar button with text and background color controls.\",\n              \"href\": \"/docs/plate/components/font-color-toolbar-button\",\n              \"title\": \"Font Color Toolbar Button\",\n              \"titleCn\": \"Font Color Toolbar Button\"\n            },\n            {\n              \"description\": \"A toolbar button for adding inline comments.\",\n              \"href\": \"/docs/plate/components/comment-toolbar-button\",\n              \"title\": \"Comment Toolbar Button\",\n              \"titleCn\": \"Comment Toolbar Button\"\n            },\n            {\n              \"description\": \"A popover interface for managing discussions: comments, replies, suggestions.\",\n              \"href\": \"/docs/plate/components/block-discussion\",\n              \"title\": \"Block Discussion\",\n              \"titleCn\": \"Block Discussion\"\n            },\n            {\n              \"description\": \"A visual overlay for cursors and selections.\",\n              \"href\": \"/docs/plate/components/cursor-overlay\",\n              \"title\": \"Cursor Overlay\",\n              \"titleCn\": \"Cursor Overlay\"\n            },\n            {\n              \"description\": \"A block wrapper with a drag handle for moving editor blocks.\",\n              \"href\": \"/docs/plate/components/block-draggable\",\n              \"title\": \"Block Draggable\",\n              \"titleCn\": \"Block Draggable\"\n            },\n            {\n              \"description\": \"A container for the editor content and styling.\",\n              \"href\": \"/docs/plate/components/editor\",\n              \"title\": \"Editor\",\n              \"titleCn\": \"Editor\"\n            },\n            {\n              \"description\": \"An editor to select tags.\",\n              \"href\": \"/docs/plate/components/select-editor\",\n              \"title\": \"Select Editor\",\n              \"titleCn\": \"Select Editor\"\n            },\n            {\n              \"description\": \"An emoji picker toolbar button.\",\n              \"href\": \"/docs/plate/components/emoji-toolbar-button\",\n              \"title\": \"Emoji Toolbar Button\",\n              \"titleCn\": \"Emoji Toolbar Button\"\n            },\n            {\n              \"description\": \"A set of commonly used formatting buttons.\",\n              \"href\": \"/docs/plate/components/fixed-toolbar-buttons\",\n              \"title\": \"Fixed Toolbar Buttons\",\n              \"titleCn\": \"Fixed Toolbar Buttons\"\n            },\n            {\n              \"href\": \"/docs/plate/components/fixed-toolbar-classic-buttons\",\n              \"title\": \"Fixed Toolbar List Buttons\",\n              \"titleCn\": \"Fixed Toolbar List Buttons\"\n            },\n            {\n              \"description\": \"A fixed toolbar that stays at the top of the editor.\",\n              \"href\": \"/docs/plate/components/fixed-toolbar\",\n              \"title\": \"Fixed Toolbar\",\n              \"titleCn\": \"Fixed Toolbar\"\n            },\n            {\n              \"description\": \"A set of formatting buttons for the floating toolbar.\",\n              \"href\": \"/docs/plate/components/floating-toolbar-buttons\",\n              \"title\": \"Floating Toolbar Buttons\",\n              \"titleCn\": \"Floating Toolbar Buttons\"\n            },\n            {\n              \"description\": \"A set of commonly used formatting buttons for the floating toolbar with classic list support.\",\n              \"href\": \"/docs/plate/components/floating-toolbar-classic-buttons\",\n              \"title\": \"Floating Toolbar Classic Buttons\",\n              \"titleCn\": \"Floating Toolbar Classic Buttons\"\n            },\n            {\n              \"description\": \"A contextual toolbar that appears over selected text.\",\n              \"href\": \"/docs/plate/components/floating-toolbar\",\n              \"title\": \"Floating Toolbar\",\n              \"titleCn\": \"Floating Toolbar\"\n            },\n            {\n              \"description\": \"A text suggestion system that displays AI-generated content after the cursor.\",\n              \"href\": \"/docs/plate/components/ghost-text\",\n              \"title\": \"Ghost Text\",\n              \"titleCn\": \"Ghost Text\"\n            },\n            {\n              \"description\": \"Toolbar buttons for undo and redo operations.\",\n              \"href\": \"/docs/plate/components/history-toolbar-button\",\n              \"title\": \"History Toolbar Button\",\n              \"titleCn\": \"History Toolbar Button\"\n            },\n            {\n              \"description\": \"A toolbar control for adjusting list indentation.\",\n              \"href\": \"/docs/plate/components/list-toolbar-button\",\n              \"title\": \"List Toolbar Button\",\n              \"titleCn\": \"List Toolbar Button\"\n            },\n            {\n              \"description\": \"Toolbar controls for block indentation.\",\n              \"href\": \"/docs/plate/components/indent-toolbar-button\",\n              \"title\": \"Indent Toolbar Buttons\",\n              \"titleCn\": \"Indent Toolbar Buttons\"\n            },\n            {\n              \"description\": \"A combobox for inline suggestions.\",\n              \"href\": \"/docs/plate/components/inline-combobox\",\n              \"title\": \"Inline Combobox\",\n              \"titleCn\": \"Inline Combobox\"\n            },\n            {\n              \"description\": \"A menu for inserting different types of blocks.\",\n              \"href\": \"/docs/plate/components/insert-toolbar-button\",\n              \"title\": \"Insert Toolbar Button\",\n              \"titleCn\": \"Insert Toolbar Button\"\n            },\n            {\n              \"description\": \"A menu for inserting different types of blocks with classic list support.\",\n              \"href\": \"/docs/plate/components/insert-toolbar-classic-button\",\n              \"title\": \"Insert Toolbar Classic Button\",\n              \"titleCn\": \"Insert Toolbar Classic Button\"\n            },\n            {\n              \"description\": \"A menu for controlling text line spacing.\",\n              \"href\": \"/docs/plate/components/line-height-toolbar-button\",\n              \"title\": \"Line Height Toolbar Button\",\n              \"titleCn\": \"Line Height Toolbar Button\"\n            },\n            {\n              \"description\": \"A floating interface for link editing.\",\n              \"href\": \"/docs/plate/components/link-toolbar\",\n              \"title\": \"Link Floating Toolbar\",\n              \"titleCn\": \"Link Floating Toolbar\"\n            },\n            {\n              \"description\": \"A toolbar control for link management.\",\n              \"href\": \"/docs/plate/components/link-toolbar-button\",\n              \"title\": \"Link Toolbar Button\",\n              \"titleCn\": \"Link Toolbar Button\"\n            },\n            {\n              \"description\": \"Toolbar controls for list creation and management.\",\n              \"href\": \"/docs/plate/components/list-classic-toolbar-button\",\n              \"title\": \"List Toolbar Buttons\",\n              \"titleCn\": \"List Toolbar Buttons\"\n            },\n            {\n              \"description\": \"A toolbar control for basic text formatting.\",\n              \"href\": \"/docs/plate/components/mark-toolbar-button\",\n              \"title\": \"Mark Toolbar Button\",\n              \"titleCn\": \"Mark Toolbar Button\"\n            },\n            {\n              \"description\": \"A toolbar interface for media settings.\",\n              \"href\": \"/docs/plate/components/media-toolbar\",\n              \"title\": \"Media Toolbar\",\n              \"titleCn\": \"Media Toolbar\"\n            },\n            {\n              \"description\": \"Toolbar button for inserting and managing media.\",\n              \"href\": \"/docs/plate/components/media-toolbar-button\",\n              \"title\": \"Media Toolbar Button\",\n              \"titleCn\": \"Media Toolbar Button\"\n            },\n            {\n              \"description\": \"Show toast notifications for media uploads.\",\n              \"href\": \"/docs/plate/components/media-upload-toast\",\n              \"title\": \"Media Upload Toast\",\n              \"titleCn\": \"Media Upload Toast\"\n            },\n            {\n              \"description\": \"A menu for switching between editor modes.\",\n              \"href\": \"/docs/plate/components/mode-toolbar-button\",\n              \"title\": \"Mode Toolbar Button\",\n              \"titleCn\": \"Mode Toolbar Button\"\n            },\n            {\n              \"description\": \"A menu for additional text formatting options.\",\n              \"href\": \"/docs/plate/components/more-toolbar-button\",\n              \"title\": \"More Toolbar Button\",\n              \"titleCn\": \"More Toolbar Button\"\n            },\n            {\n              \"description\": \"A resizable wrapper with resize handles.\",\n              \"href\": \"/docs/plate/components/resize-handle\",\n              \"title\": \"Resize Handle\",\n              \"titleCn\": \"Resize Handle\"\n            },\n            {\n              \"description\": \"A menu for table manipulation and formatting.\",\n              \"href\": \"/docs/plate/components/table-toolbar-button\",\n              \"title\": \"Table Toolbar Button\",\n              \"titleCn\": \"Table Toolbar Button\"\n            },\n            {\n              \"description\": \"A toolbar button for expanding and collapsing blocks.\",\n              \"href\": \"/docs/plate/components/toggle-toolbar-button\",\n              \"title\": \"Toggle Toolbar Button\",\n              \"titleCn\": \"Toggle Toolbar Button\"\n            },\n            {\n              \"description\": \"A menu for converting between different block types.\",\n              \"href\": \"/docs/plate/components/turn-into-toolbar-button\",\n              \"title\": \"Turn Into Toolbar Button\",\n              \"titleCn\": \"Turn Into Toolbar Button\"\n            },\n            {\n              \"description\": \"A dropdown to convert block types with classic list support.\",\n              \"href\": \"/docs/plate/components/turn-into-toolbar-classic-button\",\n              \"title\": \"Turn Into Toolbar Classic Button\",\n              \"titleCn\": \"Turn Into Toolbar Classic Button\"\n            },\n            {\n              \"description\": \"A cursor overlay to display multiplayer cursors in the yjs plugin.\",\n              \"href\": \"/docs/plate/components/remote-cursor-overlay\",\n              \"title\": \"Remote Cursor Overlay\",\n              \"titleCn\": \"Remote Cursor Overlay\"\n            },\n            {\n              \"description\": \"A customizable toolbar component with various button styles and group\",\n              \"href\": \"/docs/plate/components/toolbar\",\n              \"title\": \"Toolbar\",\n              \"titleCn\": \"Toolbar\"\n            },\n            {\n              \"description\": \"A toolbar button for toggling suggestion mode in the editor.\",\n              \"href\": \"/docs/plate/components/suggestion-toolbar-button\",\n              \"title\": \"Suggestion Toolbar Button\",\n              \"titleCn\": \"Suggestion Toolbar Button\"\n            }\n          ],\n          \"title\": \"Components\",\n          \"titleCn\": \"组件\"\n        },\n        {\n          \"href\": \"/docs/plate/components#node-components\",\n          \"items\": [\n            {\n              \"description\": \"A text highlighter for AI-generated content.\",\n              \"href\": \"/docs/plate/components/ai-node\",\n              \"title\": \"AI Leaf\",\n              \"titleCn\": \"AI Leaf\"\n            },\n            {\n              \"description\": \"List components.\",\n              \"href\": \"/docs/plate/components/block-list\",\n              \"title\": \"List\",\n              \"titleCn\": \"List\"\n            },\n            {\n              \"description\": \"A quote component for block quotes.\",\n              \"href\": \"/docs/plate/components/blockquote-node\",\n              \"title\": \"Blockquote Element\",\n              \"titleCn\": \"Blockquote Element\"\n            },\n            {\n              \"description\": \"A callout component for highlighting important information with customizable icons and styles.\",\n              \"href\": \"/docs/plate/components/callout-node\",\n              \"title\": \"Callout Node\",\n              \"titleCn\": \"Callout Node\"\n            },\n            {\n              \"description\": \"A code block with syntax highlighting and language selection.\",\n              \"href\": \"/docs/plate/components/code-block-node\",\n              \"title\": \"Code Block Nodes\",\n              \"titleCn\": \"Code Block Nodes\"\n            },\n            {\n              \"description\": \"Create diagrams from code using PlantUML, Graphviz, Flowchart, or Mermaid.\",\n              \"href\": \"/docs/plate/components/code-drawing-node\",\n              \"title\": \"Code Drawing Node\",\n              \"titleCn\": \"Code Drawing Node\"\n            },\n            {\n              \"description\": \"An inline component for code snippets.\",\n              \"href\": \"/docs/plate/components/code-node\",\n              \"title\": \"Code Leaf\",\n              \"titleCn\": \"Code Leaf\"\n            },\n            {\n              \"description\": \"Resizable column components for layout.\",\n              \"href\": \"/docs/plate/components/column-node\",\n              \"title\": \"Column Nodes\",\n              \"titleCn\": \"Column Nodes\"\n            },\n            {\n              \"description\": \"A text component for displaying comments with visual indicators.\",\n              \"href\": \"/docs/plate/components/comment-node\",\n              \"title\": \"Comment Leaf\",\n              \"titleCn\": \"Comment Leaf\"\n            },\n            {\n              \"description\": \"A text component for suggestion.\",\n              \"href\": \"/docs/plate/components/suggestion-node\",\n              \"title\": \"Suggestion Leaf\",\n              \"titleCn\": \"Suggestion Leaf\"\n            },\n            {\n              \"description\": \"A date field component with calendar picker.\",\n              \"href\": \"/docs/plate/components/date-node\",\n              \"title\": \"Date Element\",\n              \"titleCn\": \"Date Element\"\n            },\n            {\n              \"description\": \"Displays a LaTeX equation element with an editable popover for inputting and rendering mathematical expressions.\",\n              \"href\": \"/docs/plate/components/equation-node\",\n              \"title\": \"Equation Element\",\n              \"titleCn\": \"Equation Element\"\n            },\n            {\n              \"description\": \"A toolbar button for inserting and editing equations.\",\n              \"href\": \"/docs/plate/components/equation-toolbar-button\",\n              \"title\": \"Equation Toolbar Button\",\n              \"titleCn\": \"Equation Toolbar Button\"\n            },\n            {\n              \"description\": \"An input component for emoji search and insertion.\",\n              \"href\": \"/docs/plate/components/emoji-node\",\n              \"title\": \"Emoji Input Element\",\n              \"titleCn\": \"Emoji Input Element\"\n            },\n            {\n              \"description\": \"A drawing component powered by Excalidraw.\",\n              \"href\": \"/docs/plate/components/excalidraw-node\",\n              \"title\": \"Excalidraw Element\",\n              \"titleCn\": \"Excalidraw Element\"\n            },\n            {\n              \"description\": \"A toolbar control for adjusting font size.\",\n              \"href\": \"/docs/plate/components/font-size-toolbar-button\",\n              \"title\": \"Font Size Toolbar Button\",\n              \"titleCn\": \"Font Size Toolbar Button\"\n            },\n            {\n              \"description\": \"Inline footnote references, definitions, and input UI.\",\n              \"href\": \"/docs/plate/components/footnote-node\",\n              \"title\": \"Footnote Elements\",\n              \"titleCn\": \"Footnote Elements\"\n            },\n            {\n              \"description\": \"A heading with multiple level support.\",\n              \"href\": \"/docs/plate/components/heading-node\",\n              \"title\": \"Heading Element\",\n              \"titleCn\": \"Heading Element\"\n            },\n            {\n              \"description\": \"A text highlighter with customizable colors.\",\n              \"href\": \"/docs/plate/components/highlight-node\",\n              \"title\": \"Highlight Leaf\",\n              \"titleCn\": \"Highlight Leaf\"\n            },\n            {\n              \"description\": \"A horizontal rule component with focus states.\",\n              \"href\": \"/docs/plate/components/hr-node\",\n              \"title\": \"Horizontal Rule Element\",\n              \"titleCn\": \"Horizontal Rule Element\"\n            },\n            {\n              \"description\": \"Image element with lazy loading, resizing capabilities, and optional caption.\",\n              \"href\": \"/docs/plate/components/media-image-node\",\n              \"title\": \"Image Element\",\n              \"titleCn\": \"Image Element\"\n            },\n            {\n              \"description\": \"A modal component for previewing and manipulating images.\",\n              \"href\": \"/docs/plate/components/media-preview-dialog\",\n              \"title\": \"Media Preview Dialog\",\n              \"titleCn\": \"Media Preview Dialog\"\n            },\n            {\n              \"description\": \"A component for styling keyboard shortcuts.\",\n              \"href\": \"/docs/plate/components/kbd-node\",\n              \"title\": \"Keyboard Input Leaf\",\n              \"titleCn\": \"Keyboard Input Leaf\"\n            },\n            {\n              \"description\": \"A component for rendering hyperlinks with hover states.\",\n              \"href\": \"/docs/plate/components/link-node\",\n              \"title\": \"Link Element\",\n              \"titleCn\": \"Link Element\"\n            },\n            {\n              \"description\": \"List (classic) nodes for ordered and unordered items.\",\n              \"href\": \"/docs/plate/components/list-classic-node\",\n              \"title\": \"List Nodes\",\n              \"titleCn\": \"List Nodes\"\n            },\n            {\n              \"description\": \"An audio player component with caption support.\",\n              \"href\": \"/docs/plate/components/media-audio-node\",\n              \"title\": \"Media Audio Element\",\n              \"titleCn\": \"Media Audio Element\"\n            },\n            {\n              \"description\": \"A component for embedded media content with resizing and caption support.\",\n              \"href\": \"/docs/plate/components/media-embed-node\",\n              \"title\": \"Media Embed Element\",\n              \"titleCn\": \"Media Embed Element\"\n            },\n            {\n              \"description\": \"A file attachment component with download capability and caption.\",\n              \"href\": \"/docs/plate/components/media-file-node\",\n              \"title\": \"Media File Element\",\n              \"titleCn\": \"Media File Element\"\n            },\n            {\n              \"description\": \"A placeholder for media upload progress indication.\",\n              \"href\": \"/docs/plate/components/media-placeholder-node\",\n              \"title\": \"Media Placeholder Element\",\n              \"titleCn\": \"Media Placeholder Element\"\n            },\n            {\n              \"description\": \"A video player component with YouTube and file upload support.\",\n              \"href\": \"/docs/plate/components/media-video-node\",\n              \"title\": \"Media Video Element\",\n              \"titleCn\": \"Media Video Element\"\n            },\n            {\n              \"description\": \"A mention element with customizable prefix and label, powered by a combobox.\",\n              \"href\": \"/docs/plate/components/mention-node\",\n              \"title\": \"Mention Nodes\",\n              \"titleCn\": \"Mention Nodes\"\n            },\n            {\n              \"description\": \"A paragraph block with background color support.\",\n              \"href\": \"/docs/plate/components/paragraph-node\",\n              \"title\": \"Paragraph Element\",\n              \"titleCn\": \"Paragraph Element\"\n            },\n            {\n              \"description\": \"A component that highlights search results in text.\",\n              \"href\": \"/docs/plate/components/search-highlight-node\",\n              \"title\": \"Search Highlight Leaf\",\n              \"titleCn\": \"Search Highlight Leaf\"\n            },\n            {\n              \"description\": \"A command input component for inserting various elements.\",\n              \"href\": \"/docs/plate/components/slash-node\",\n              \"title\": \"Slash Input Element\",\n              \"titleCn\": \"Slash Input Element\"\n            },\n            {\n              \"description\": \"A table component with floating toolbar and border customization.\",\n              \"href\": \"/docs/plate/components/table-node\",\n              \"title\": \"Table Element\",\n              \"titleCn\": \"Table Element\"\n            },\n            {\n              \"description\": \"A tag element component with selection states and styling.\",\n              \"href\": \"/docs/plate/components/tag-node\",\n              \"title\": \"Tag Element\",\n              \"titleCn\": \"Tag Element\"\n            },\n            {\n              \"description\": \"A table of contents component with links to document headings.\",\n              \"href\": \"/docs/plate/components/toc-node\",\n              \"title\": \"TOC Element\",\n              \"titleCn\": \"TOC Element\"\n            },\n            {\n              \"description\": \"A collapsible component for toggling content visibility.\",\n              \"href\": \"/docs/plate/components/toggle-node\",\n              \"title\": \"Toggle Element\",\n              \"titleCn\": \"Toggle Element\"\n            }\n          ],\n          \"title\": \"Node Components\",\n          \"titleCn\": \"节点组件\"\n        }\n      ],\n      \"example\": [\n        {\n          \"items\": [\n            {\n              \"description\": \"Slate to HTML\",\n              \"href\": \"/docs/plate/examples/slate-to-html\",\n              \"title\": \"Slate to HTML\",\n              \"titleCn\": \"Slate 转 HTML\"\n            },\n            {\n              \"description\": \"Export a Plate document to a file.\",\n              \"href\": \"/docs/plate/examples/export\",\n              \"title\": \"Export\",\n              \"titleCn\": \"导出\"\n            },\n            {\n              \"description\": \"Server-side rendering.\",\n              \"href\": \"/docs/plate/examples/server-side\",\n              \"title\": \"Server-Side\",\n              \"titleCn\": \"服务端\"\n            },\n            {\n              \"description\": \"Show a diff of two different points in a Plate document's history.\",\n              \"href\": \"/docs/plate/examples/version-history\",\n              \"title\": \"Version History\",\n              \"titleCn\": \"版本历史\"\n            },\n            {\n              \"description\": \"Nest editors in void nodes.\",\n              \"href\": \"/docs/plate/examples/editable-voids\",\n              \"title\": \"Editable Voids\",\n              \"titleCn\": \"可编辑的空节点\"\n            },\n            {\n              \"description\": \"Compare Plate and Slate on a large document.\",\n              \"href\": \"/docs/plate/examples/huge-document\",\n              \"title\": \"Huge Document\",\n              \"titleCn\": \"大型文档\"\n            },\n            {\n              \"description\": \"Render hundreds of editors.\",\n              \"href\": \"/docs/plate/examples/hundreds-editors\",\n              \"title\": \"Hundreds Editors\",\n              \"titleCn\": \"数百个编辑器\"\n            },\n            {\n              \"description\": \"Streaming markdown to editor.\",\n              \"href\": \"/docs/plate/examples/markdown-streaming\",\n              \"title\": \"Markdown Streaming\",\n              \"titleCn\": \"Markdown 流式\"\n            },\n            {\n              \"description\": \"Decorate texts with markdown preview.\",\n              \"href\": \"/docs/plate/examples/preview-markdown\",\n              \"title\": \"Preview Markdown\",\n              \"titleCn\": \"Markdown 预览\"\n            },\n            {\n              \"description\": \"Collaborative editing.\",\n              \"href\": \"/docs/plate/examples/collaboration\",\n              \"title\": \"Collaboration Demo\",\n              \"titleCn\": \"协作演示\"\n            },\n            {\n              \"href\": \"/docs/plate/examples/table-nomerge\",\n              \"title\": \"Table Nomerge Demo\",\n              \"titleCn\": \"Table Nomerge\"\n            },\n            {\n              \"description\": \"A drawing component powered by Excalidraw.\",\n              \"href\": \"/docs/plate/examples/excalidraw\",\n              \"title\": \"Excalidraw Demo\",\n              \"titleCn\": \"Excalidraw\"\n            },\n            {\n              \"description\": \"Create diagrams from code using PlantUML, Graphviz, Flowchart, or Mermaid.\",\n              \"href\": \"/docs/plate/examples/code-drawing\",\n              \"title\": \"Code Drawing Demo\",\n              \"titleCn\": \"Code Drawing\"\n            },\n            {\n              \"description\": \"Restrict the editor to a single block.\",\n              \"href\": \"/docs/plate/examples/single-block\",\n              \"title\": \"Single Block Demo\",\n              \"titleCn\": \"Single Block\"\n            },\n            {\n              \"description\": \"List creation and formatting.\",\n              \"href\": \"/docs/plate/examples/list-classic\",\n              \"title\": \"List Classic Demo\",\n              \"titleCn\": \"List Classic\"\n            },\n            {\n              \"description\": \"Find and replace functionality in text.\",\n              \"href\": \"/docs/plate/examples/find-replace\",\n              \"title\": \"Find Replace Demo\",\n              \"titleCn\": \"Find Replace\"\n            },\n            {\n              \"description\": \"AI menu with commands, streaming responses in a preview or directly into the editor.\",\n              \"href\": \"/docs/plate/examples/ai\",\n              \"title\": \"AI Demo\",\n              \"titleCn\": \"AI\"\n            },\n            {\n              \"description\": \"Text alignment controls for blocks.\",\n              \"href\": \"/docs/plate/examples/align\",\n              \"title\": \"Align Demo\",\n              \"titleCn\": \"Align\"\n            },\n            {\n              \"description\": \"Apply formatting automatically using shortcodes.\",\n              \"href\": \"/docs/plate/examples/autoformat\",\n              \"title\": \"Autoformat Demo\",\n              \"titleCn\": \"Autoformat\"\n            },\n            {\n              \"description\": \"Basic block elements and text marks.\",\n              \"href\": \"/docs/plate/examples/basic-nodes\",\n              \"keywords\": [\n                \"element\",\n                \"leaf\"\n              ],\n              \"title\": \"Basic Nodes Demo\",\n              \"titleCn\": \"Basic Nodes\"\n            },\n            {\n              \"description\": \"Block-level context menu with formatting options.\",\n              \"href\": \"/docs/plate/examples/block-menu\",\n              \"title\": \"Block Menu Demo\",\n              \"titleCn\": \"Block Menu\"\n            },\n            {\n              \"description\": \"Visual block selection with keyboard support.\",\n              \"href\": \"/docs/plate/examples/block-selection\",\n              \"title\": \"Block Selection Demo\",\n              \"titleCn\": \"Block Selection\"\n            },\n            {\n              \"description\": \"Column layout.\",\n              \"href\": \"/docs/plate/examples/column\",\n              \"title\": \"Column Demo\",\n              \"titleCn\": \"Column\"\n            },\n            {\n              \"description\": \"Display code with syntax highlighting.\",\n              \"href\": \"/docs/plate/examples/code-block\",\n              \"title\": \"Code Block Demo\",\n              \"titleCn\": \"Code Block\"\n            },\n            {\n              \"description\": \"Display callouts with different variants and icons.\",\n              \"href\": \"/docs/plate/examples/callout\",\n              \"title\": \"Callout Demo\",\n              \"titleCn\": \"Callout\"\n            },\n            {\n              \"description\": \"Adding and displaying comments within content.\",\n              \"href\": \"/docs/plate/examples/discussion\",\n              \"title\": \"Discussion Demo\",\n              \"titleCn\": \"Discussion\"\n            },\n            {\n              \"description\": \"Visual indicator for cursor position within the editor.\",\n              \"href\": \"/docs/plate/examples/cursor-overlay\",\n              \"title\": \"Cursor Overlay Demo\",\n              \"titleCn\": \"Cursor Overlay\"\n            },\n            {\n              \"description\": \"Inline date elements with calendar selection interface.\",\n              \"href\": \"/docs/plate/examples/date\",\n              \"title\": \"Date Demo\",\n              \"titleCn\": \"Date\"\n            },\n            {\n              \"description\": \"GFM footnote references and definitions as dedicated editor nodes.\",\n              \"href\": \"/docs/plate/examples/footnote\",\n              \"title\": \"Footnote Demo\",\n              \"titleCn\": \"Footnote\"\n            },\n            {\n              \"description\": \"Implements draggable functionality for editor blocks, including drag handles and drop indicators.\",\n              \"href\": \"/docs/plate/examples/dnd\",\n              \"title\": \"Drag & Drop Demo\",\n              \"titleCn\": \"Drag & Drop\"\n            },\n            {\n              \"description\": \"Emoji insertion via toolbar or colon-triggered combobox.\",\n              \"href\": \"/docs/plate/examples/emoji\",\n              \"title\": \"Emoji Demo\",\n              \"titleCn\": \"Emoji\"\n            },\n            {\n              \"description\": \"LaTeX equations with inline and block formats.\",\n              \"href\": \"/docs/plate/examples/equation\",\n              \"title\": \"Equation Demo\",\n              \"titleCn\": \"Equation\"\n            },\n            {\n              \"description\": \"Exit a large block using a shortcut.\",\n              \"href\": \"/docs/plate/examples/exit-break\",\n              \"title\": \"Exit Break Demo\",\n              \"titleCn\": \"Exit Break\"\n            },\n            {\n              \"description\": \"Floating toolbar with text formatting and AI assistance options.\",\n              \"href\": \"/docs/plate/examples/floating-toolbar\",\n              \"title\": \"Floating Toolbar Demo\",\n              \"titleCn\": \"Floating Toolbar\"\n            },\n            {\n              \"description\": \"Color picker for text and background colors.\",\n              \"href\": \"/docs/plate/examples/font\",\n              \"title\": \"Font Demo\",\n              \"titleCn\": \"Font\"\n            },\n            {\n              \"href\": \"/docs/plate/examples/indent\",\n              \"title\": \"Indent Demo\",\n              \"titleCn\": \"Indent\"\n            },\n            {\n              \"description\": \"Turn any block into a list item.\",\n              \"href\": \"/docs/plate/examples/list\",\n              \"title\": \"List Demo\",\n              \"titleCn\": \"List\"\n            },\n            {\n              \"description\": \"Line height adjustment controls.\",\n              \"href\": \"/docs/plate/examples/line-height\",\n              \"title\": \"Line Height Demo\",\n              \"titleCn\": \"Line Height\"\n            },\n            {\n              \"description\": \"Hyperlinks with toolbar insertion and URL pasting support.\",\n              \"href\": \"/docs/plate/examples/link\",\n              \"title\": \"Link Demo\",\n              \"titleCn\": \"Link\"\n            },\n            {\n              \"description\": \"Media embedding and management.\",\n              \"href\": \"/docs/plate/examples/media\",\n              \"title\": \"Media Demo\",\n              \"titleCn\": \"Media\"\n            },\n            {\n              \"description\": \"Mention functionality for referencing users or entities.\",\n              \"href\": \"/docs/plate/examples/mention\",\n              \"title\": \"Mention Demo\",\n              \"titleCn\": \"Mention\"\n            },\n            {\n              \"href\": \"/docs/plate/examples/block-placeholder\",\n              \"title\": \"Block Placeholder Demo\",\n              \"titleCn\": \"Block Placeholder\"\n            },\n            {\n              \"description\": \"Copy paste from CSV to Slate.\",\n              \"href\": \"/docs/plate/examples/csv\",\n              \"title\": \"Serializing CSV Demo\",\n              \"titleCn\": \"Serializing CSV\"\n            },\n            {\n              \"description\": \"Copy paste from DOCX to Slate.\",\n              \"href\": \"/docs/plate/examples/docx\",\n              \"title\": \"Serializing Docx Demo\",\n              \"titleCn\": \"Serializing Docx\"\n            },\n            {\n              \"description\": \"Copy paste from HTML to Slate.\",\n              \"href\": \"/docs/plate/examples/html\",\n              \"title\": \"Serializing HTML Demo\",\n              \"titleCn\": \"Serializing HTML\"\n            },\n            {\n              \"description\": \"Copy paste from Markdown to Slate.\",\n              \"href\": \"/docs/plate/examples/markdown\",\n              \"title\": \"Serializing Markdown Demo\",\n              \"titleCn\": \"Serializing Markdown\"\n            },\n            {\n              \"description\": \"Slash command menu for quick insertion of various content types.\",\n              \"href\": \"/docs/plate/examples/slash-command\",\n              \"title\": \"Slash Command Demo\",\n              \"titleCn\": \"Slash Command\"\n            },\n            {\n              \"description\": \"Use plugin rules to customize the common editing behaviors.\",\n              \"href\": \"/docs/plate/examples/plugin-rules\",\n              \"title\": \"Plugin Rules Demo\",\n              \"titleCn\": \"Plugin Rules\"\n            },\n            {\n              \"description\": \"Customizable tables with resizable columns and row merging options.\",\n              \"href\": \"/docs/plate/examples/table\",\n              \"title\": \"Table Demo\",\n              \"titleCn\": \"Table\"\n            },\n            {\n              \"description\": \"Dynamic TOC with in-document element for easy navigation.\",\n              \"href\": \"/docs/plate/examples/toc\",\n              \"title\": \"Table of Contents Demo\",\n              \"titleCn\": \"Table of Contents\"\n            },\n            {\n              \"description\": \"Collapsible content blocks.\",\n              \"href\": \"/docs/plate/examples/toggle\",\n              \"title\": \"Toggle Demo\",\n              \"titleCn\": \"Toggle\"\n            }\n          ]\n        }\n      ],\n      \"guide\": [\n        {\n          \"items\": [\n            {\n              \"href\": \"/docs/plate\",\n              \"title\": \"Introduction\",\n              \"titleCn\": \"介绍\"\n            },\n            {\n              \"href\": \"/docs/plate/installation\",\n              \"title\": \"Installation\",\n              \"titleCn\": \"安装\"\n            },\n            {\n              \"href\": \"/docs/plate/releases\",\n              \"title\": \"Releases\",\n              \"titleCn\": \"版本发布\"\n            }\n          ],\n          \"title\": \"Overview\",\n          \"titleCn\": \"概览\"\n        },\n        {\n          \"items\": [\n            {\n              \"href\": \"/docs/plate/installation/plate-ui\",\n              \"items\": [\n                {\n                  \"href\": \"/docs/plate/installation/next\",\n                  \"title\": \"Next.js\",\n                  \"titleCn\": \"Next.js\"\n                },\n                {\n                  \"href\": \"/docs/plate/installation/react\",\n                  \"title\": \"React\",\n                  \"titleCn\": \"React\"\n                }\n              ],\n              \"title\": \"Plate UI\",\n              \"titleCn\": \"Plate UI\"\n            },\n            {\n              \"href\": \"/docs/plate/installation/manual\",\n              \"title\": \"Manual\",\n              \"titleCn\": \"手动安装\"\n            },\n            {\n              \"href\": \"/docs/plate/installation/rsc\",\n              \"title\": \"RSC\",\n              \"titleCn\": \"RSC\"\n            },\n            {\n              \"href\": \"/docs/plate/installation/node\",\n              \"title\": \"Node.js\",\n              \"titleCn\": \"Node.js\"\n            },\n            {\n              \"href\": \"/docs/plate/installation/docs\",\n              \"title\": \"Local Docs\",\n              \"titleCn\": \"本地文档\"\n            },\n            {\n              \"href\": \"/docs/plate/installation/mcp\",\n              \"title\": \"MCP\",\n              \"titleCn\": \"MCP\"\n            }\n          ],\n          \"title\": \"Installation\",\n          \"titleCn\": \"安装\"\n        },\n        {\n          \"items\": [\n            {\n              \"href\": \"/docs/plate/feature-kits\",\n              \"title\": \"Feature Kits\",\n              \"titleCn\": \"功能套件\"\n            },\n            {\n              \"href\": \"/docs/plate/plugin\",\n              \"items\": [\n                {\n                  \"href\": \"/docs/plate/plugin-methods\",\n                  \"title\": \"Plugin Methods\",\n                  \"titleCn\": \"插件方法\"\n                },\n                {\n                  \"href\": \"/docs/plate/plugin-shortcuts\",\n                  \"title\": \"Plugin Shortcuts\",\n                  \"titleCn\": \"插件快捷键\"\n                },\n                {\n                  \"href\": \"/docs/plate/plugin-context\",\n                  \"title\": \"Plugin Context\",\n                  \"titleCn\": \"插件上下文\"\n                },\n                {\n                  \"href\": \"/docs/plate/plugin-components\",\n                  \"title\": \"Plugin Components\",\n                  \"titleCn\": \"插件组件\"\n                },\n                {\n                  \"href\": \"/docs/plate/plugin-rules\",\n                  \"title\": \"Plugin Rules\",\n                  \"titleCn\": \"插件规则\"\n                },\n                {\n                  \"href\": \"/docs/plate/editing-behavior\",\n                  \"label\": \"New\",\n                  \"title\": \"Editing Behavior\",\n                  \"titleCn\": \"编辑行为\"\n                },\n                {\n                  \"href\": \"/docs/plate/plugin-input-rules\",\n                  \"title\": \"Plugin Input Rules\",\n                  \"label\": \"New\",\n                  \"titleCn\": \"插件输入规则\"\n                }\n              ],\n              \"title\": \"Plugin\",\n              \"titleCn\": \"插件\"\n            },\n            {\n              \"href\": \"/docs/plate/editor\",\n              \"items\": [\n                {\n                  \"href\": \"/docs/plate/editor-methods\",\n                  \"title\": \"Editor Methods\",\n                  \"titleCn\": \"编辑器方法\"\n                },\n                {\n                  \"href\": \"/docs/plate/controlled\",\n                  \"title\": \"Controlled Value\",\n                  \"titleCn\": \"受控值\"\n                }\n              ],\n              \"title\": \"Editor\",\n              \"titleCn\": \"编辑器\"\n            },\n            {\n              \"href\": \"/docs/plate/performance\",\n              \"label\": \"New\",\n              \"title\": \"Performance\",\n              \"titleCn\": \"性能\"\n            },\n            {\n              \"href\": \"/docs/plate/static\",\n              \"title\": \"Static Rendering\",\n              \"titleCn\": \"静态渲染\"\n            },\n            {\n              \"description\": \"HTML ↔ Plate\",\n              \"href\": \"/docs/plate/html\",\n              \"title\": \"HTML\",\n              \"titleCn\": \"HTML\"\n            },\n            {\n              \"description\": \"Markdown ↔ Plate\",\n              \"href\": \"/docs/plate/markdown\",\n              \"title\": \"Markdown\",\n              \"titleCn\": \"Markdown\"\n            },\n            {\n              \"href\": \"/docs/plate/form\",\n              \"title\": \"Form\",\n              \"titleCn\": \"表单\"\n            },\n            {\n              \"href\": \"/docs/plate/typescript\",\n              \"title\": \"TypeScript\",\n              \"titleCn\": \"TypeScript\"\n            },\n            {\n              \"href\": \"/docs/plate/debugging\",\n              \"title\": \"Debugging\",\n              \"titleCn\": \"调试\"\n            },\n            {\n              \"href\": \"/docs/plate/unit-testing\",\n              \"title\": \"Unit Testing\",\n              \"titleCn\": \"单元测试\"\n            },\n            {\n              \"href\": \"/docs/plate/playwright\",\n              \"title\": \"Playwright Testing\",\n              \"titleCn\": \"Playwright 测试\"\n            },\n            {\n              \"href\": \"/docs/plate/troubleshooting\",\n              \"keywords\": [\n                \"depset\"\n              ],\n              \"title\": \"Troubleshooting\",\n              \"titleCn\": \"故障排除\"\n            }\n          ],\n          \"title\": \"Guides\",\n          \"titleCn\": \"指南\"\n        },\n        {\n          \"items\": [\n            {\n              \"href\": \"/docs/plate/migration/slate-to-plate\",\n              \"title\": \"From Slate to Plate\",\n              \"titleCn\": \"从 Slate 到 Plate\"\n            }\n          ],\n          \"title\": \"Migration\",\n          \"titleCn\": \"迁移\"\n        }\n      ],\n      \"plugin\": [\n        {\n          \"items\": [\n            {\n              \"items\": [\n                {\n                  \"description\": \"AI menu with commands, streaming responses in a preview or directly into the editor.\",\n                  \"href\": \"/docs/plate/ai\",\n                  \"keywords\": [\n                    \"ai\"\n                  ],\n                  \"title\": \"Stream\",\n                  \"titleCn\": \"Stream\"\n                },\n                {\n                  \"description\": \"Render AI suggestions ghost text as you type.\",\n                  \"href\": \"/docs/plate/copilot\",\n                  \"title\": \"Copilot\",\n                  \"titleCn\": \"Copilot\",\n                  \"keywords\": [\n                    \"ai\"\n                  ]\n                }\n              ],\n              \"title\": \"AI\"\n            },\n            {\n              \"href\": \"/docs/plate/discussion\",\n              \"items\": [\n                {\n                  \"description\": \"Add comments to text as marks.\",\n                  \"href\": \"/docs/plate/comment\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ],\n                  \"title\": \"Comments\",\n                  \"titleCn\": \"Comments\"\n                },\n                {\n                  \"description\": \"Add suggestions to text as marks.\",\n                  \"href\": \"/docs/plate/suggestion\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ],\n                  \"title\": \"Suggestion\",\n                  \"titleCn\": \"Suggestion\"\n                }\n              ],\n              \"title\": \"Discussion\",\n              \"titleCn\": \"Discussion\"\n            },\n            {\n              \"items\": [\n                {\n                  \"description\": \"Enhance your editor with essential formatting elements.\",\n                  \"href\": \"/docs/plate/basic-blocks\",\n                  \"items\": [\n                    {\n                      \"description\": \"Create blockquotes to emphasize important information.\",\n                      \"href\": \"/docs/plate/blockquote\",\n                      \"title\": \"Blockquote\",\n                      \"keywords\": [\n                        \"element\"\n                      ]\n                    },\n                    {\n                      \"description\": \"Create headings of various levels to structure content.\",\n                      \"href\": \"/docs/plate/heading\",\n                      \"title\": \"Heading\",\n                      \"keywords\": [\n                        \"element\"\n                      ]\n                    },\n                    {\n                      \"description\": \"Visually divide and organize content sections with a horizontal line.\",\n                      \"href\": \"/docs/plate/horizontal-rule\",\n                      \"title\": \"Horizontal Rule\",\n                      \"keywords\": [\n                        \"element\"\n                      ]\n                    }\n                  ],\n                  \"title\": \"Basic Blocks\",\n                  \"titleCn\": \"Basic Blocks\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Highlight important information or add special notes.\",\n                  \"href\": \"/docs/plate/callout\",\n                  \"title\": \"Callout\",\n                  \"titleCn\": \"Callout\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Display code with syntax highlighting.\",\n                  \"href\": \"/docs/plate/code-block\",\n                  \"title\": \"Code Block\",\n                  \"titleCn\": \"Code Block\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Add columns to your document.\",\n                  \"href\": \"/docs/plate/column\",\n                  \"title\": \"Column\",\n                  \"titleCn\": \"Column\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Insert and format dates in your document.\",\n                  \"href\": \"/docs/plate/date\",\n                  \"title\": \"Date\",\n                  \"titleCn\": \"Date\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Enables the insertion and rendering of LaTeX equations in your editor.\",\n                  \"href\": \"/docs/plate/equation\",\n                  \"title\": \"Equation\",\n                  \"titleCn\": \"Equation\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Insert and manage hyperlinks.\",\n                  \"href\": \"/docs/plate/link\",\n                  \"title\": \"Link\",\n                  \"titleCn\": \"Link\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Organize nestable items in a bulleted or numbered list.\",\n                  \"href\": \"/docs/plate/list-classic\",\n                  \"title\": \"List Classic\",\n                  \"titleCn\": \"List Classic\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Embed medias like videos or tweets into your document.\",\n                  \"href\": \"/docs/plate/media\",\n                  \"title\": \"Media\",\n                  \"titleCn\": \"Media\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Enable autocompletion for user mentions.\",\n                  \"href\": \"/docs/plate/mention\",\n                  \"title\": \"Mention\",\n                  \"titleCn\": \"Mention\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Organize and display data in a structured and resizable tabular format.\",\n                  \"href\": \"/docs/plate/table\",\n                  \"title\": \"Table\",\n                  \"titleCn\": \"Table\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Renders a table of contents element with clickable links to headings in the document.\",\n                  \"href\": \"/docs/plate/toc\",\n                  \"title\": \"Table of Contents\",\n                  \"titleCn\": \"Table of Contents\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Parse and edit GFM footnote references and definitions as dedicated nodes.\",\n                  \"href\": \"/docs/plate/footnote\",\n                  \"title\": \"Footnote\",\n                  \"titleCn\": \"Footnote\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                },\n                {\n                  \"description\": \"Add toggles to your document.\",\n                  \"href\": \"/docs/plate/toggle\",\n                  \"title\": \"Toggle\",\n                  \"titleCn\": \"Toggle\",\n                  \"keywords\": [\n                    \"element\"\n                  ]\n                }\n              ],\n              \"title\": \"Elements\",\n              \"titleCn\": \"Elements\"\n            },\n            {\n              \"description\": \"Set of essential text formatting options.\",\n              \"href\": \"/docs/plate/basic-marks\",\n              \"items\": [\n                {\n                  \"description\": \"Apply bold formatting to emphasize important text.\",\n                  \"href\": \"/docs/plate/bold\",\n                  \"title\": \"Bold\",\n                  \"titleCn\": \"Bold\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Apply italic formatting for emphasis or stylistic purposes.\",\n                  \"href\": \"/docs/plate/italic\",\n                  \"title\": \"Italic\",\n                  \"titleCn\": \"Italic\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Apply underline formatting to text.\",\n                  \"href\": \"/docs/plate/underline\",\n                  \"title\": \"Underline\",\n                  \"titleCn\": \"Underline\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Format inline code snippets and technical terms.\",\n                  \"href\": \"/docs/plate/code\",\n                  \"title\": \"Code\",\n                  \"titleCn\": \"Code\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Highlight important text with background colors.\",\n                  \"href\": \"/docs/plate/highlight\",\n                  \"title\": \"Highlight\",\n                  \"titleCn\": \"Highlight\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Display keyboard shortcuts and key combinations.\",\n                  \"href\": \"/docs/plate/kbd\",\n                  \"title\": \"Keyboard Input\",\n                  \"titleCn\": \"Keyboard Input\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Apply strikethrough formatting to indicate deleted content.\",\n                  \"href\": \"/docs/plate/strikethrough\",\n                  \"title\": \"Strikethrough\",\n                  \"titleCn\": \"Strikethrough\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Format text as subscript for mathematical expressions.\",\n                  \"href\": \"/docs/plate/subscript\",\n                  \"title\": \"Subscript\",\n                  \"titleCn\": \"Subscript\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                },\n                {\n                  \"description\": \"Format text as superscript for mathematical expressions.\",\n                  \"href\": \"/docs/plate/superscript\",\n                  \"title\": \"Superscript\",\n                  \"titleCn\": \"Superscript\",\n                  \"keywords\": [\n                    \"leaf\"\n                  ]\n                }\n              ],\n              \"title\": \"Marks\",\n              \"titleCn\": \"Marks\"\n            },\n            {\n              \"items\": [\n                {\n                  \"items\": [\n                    {\n                      \"description\": \"Set font styles to your content.\",\n                      \"href\": \"/docs/plate/font\",\n                      \"title\": \"Font\"\n                    },\n                    {\n                      \"description\": \"Adjust the height between lines of text.\",\n                      \"href\": \"/docs/plate/line-height\",\n                      \"title\": \"Line Height\"\n                    },\n                    {\n                      \"description\": \"Align your content to different positions.\",\n                      \"href\": \"/docs/plate/text-align\",\n                      \"title\": \"Text Align\"\n                    }\n                  ],\n                  \"title\": \"Basic Styles\",\n                  \"titleCn\": \"Basic Styles\"\n                },\n                {\n                  \"description\": \"Customize text indentation.\",\n                  \"href\": \"/docs/plate/indent\",\n                  \"title\": \"Indent\",\n                  \"titleCn\": \"Indent\"\n                },\n                {\n                  \"description\": \"Turn any block into a list item.\",\n                  \"href\": \"/docs/plate/list\",\n                  \"title\": \"List\",\n                  \"titleCn\": \"List\"\n                }\n              ],\n              \"title\": \"Styles\"\n            },\n            {\n              \"items\": [\n                {\n                  \"items\": [\n                    {\n                      \"description\": \"Exit a large block using a shortcut.\",\n                      \"href\": \"/docs/plate/exit-break\",\n                      \"title\": \"Exit Break\"\n                    },\n                    {\n                      \"description\": \"Restrict the editor to a single block.\",\n                      \"href\": \"/docs/plate/single-block\",\n                      \"title\": \"Single Block\"\n                    },\n                    {\n                      \"description\": \"Ensure a trailing block is always present in the document.\",\n                      \"href\": \"/docs/plate/trailing-block\",\n                      \"title\": \"Trailing Block\"\n                    }\n                  ],\n                  \"title\": \"Utils\",\n                  \"titleCn\": \"Utils\"\n                },\n                {\n                  \"description\": \"Apply formatting automatically using shortcodes.\",\n                  \"href\": \"/docs/plate/autoformat\",\n                  \"title\": \"Autoformat\",\n                  \"titleCn\": \"Autoformat\"\n                },\n                {\n                  \"description\": \"Provides quick access to block-specific actions.\",\n                  \"href\": \"/docs/plate/block-menu\",\n                  \"title\": \"Block Menu\",\n                  \"titleCn\": \"Block Menu\"\n                },\n                {\n                  \"description\": \"Show placeholder when a block is empty.\",\n                  \"href\": \"/docs/plate/block-placeholder\",\n                  \"title\": \"Block Placeholder\",\n                  \"titleCn\": \"Block Placeholder\"\n                },\n                {\n                  \"description\": \"Select and manipulate entire text blocks.\",\n                  \"href\": \"/docs/plate/block-selection\",\n                  \"title\": \"Block Selection\",\n                  \"titleCn\": \"Block Selection\"\n                },\n                {\n                  \"description\": \"Add captions to your blocks.\",\n                  \"href\": \"/docs/plate/caption\",\n                  \"title\": \"Caption\",\n                  \"titleCn\": \"Caption\"\n                },\n                {\n                  \"description\": \"Utilities for adding combobox to your editor.\",\n                  \"href\": \"/docs/plate/combobox\",\n                  \"items\": [\n                    {\n                      \"description\": \"Insert emoji inline.\",\n                      \"href\": \"/docs/plate/emoji\",\n                      \"title\": \"Emoji\"\n                    },\n                    {\n                      \"description\": \"Enable autocompletion for user mentions.\",\n                      \"href\": \"/docs/plate/mention\",\n                      \"label\": \"Element\",\n                      \"title\": \"Mention\"\n                    },\n                    {\n                      \"description\": \"Slash command menu for quick insertion of various content types.\",\n                      \"href\": \"/docs/plate/slash-command\",\n                      \"title\": \"Slash Command\"\n                    }\n                  ],\n                  \"title\": \"Combobox\",\n                  \"titleCn\": \"Combobox\"\n                },\n                {\n                  \"description\": \"A visual overlay for cursors and selections.\",\n                  \"href\": \"/docs/plate/cursor-overlay\",\n                  \"title\": \"Cursor Overlay\",\n                  \"titleCn\": \"Cursor Overlay\"\n                },\n                {\n                  \"description\": \"Allows movement of blocks, such as paragraph or tables, within the editor.\",\n                  \"href\": \"/docs/plate/dnd\",\n                  \"title\": \"Drag & Drop\",\n                  \"titleCn\": \"Drag & Drop\"\n                },\n                {\n                  \"description\": \"Flash the landed target after TOC, footnote, search, or custom navigation jumps.\",\n                  \"href\": \"/docs/plate/navigation-feedback\",\n                  \"title\": \"Navigation Feedback\",\n                  \"titleCn\": \"Navigation Feedback\"\n                },\n                {\n                  \"description\": \"Maintain a consistent tab order for tabbable elements.\",\n                  \"href\": \"/docs/plate/tabbable\",\n                  \"title\": \"Tabbable\",\n                  \"titleCn\": \"Tabbable\"\n                },\n                {\n                  \"description\": \"Add a toolbar to your editor.\",\n                  \"href\": \"/docs/plate/toolbar\",\n                  \"title\": \"Toolbar\",\n                  \"titleCn\": \"Toolbar\"\n                },\n                {\n                  \"description\": \"Collaborate with others in a single document.\",\n                  \"href\": \"/docs/plate/yjs\",\n                  \"title\": \"Yjs\",\n                  \"titleCn\": \"Yjs\"\n                },\n                {\n                  \"description\": \"A rich multi-select editor.\",\n                  \"href\": \"/docs/plate/multi-select\",\n                  \"label\": \"Editor\",\n                  \"title\": \"Multi Select\",\n                  \"titleCn\": \"Multi Select\"\n                }\n              ],\n              \"title\": \"Functionality\"\n            },\n            {\n              \"items\": [\n                {\n                  \"description\": \"Copy paste from CSV to Slate.\",\n                  \"href\": \"/docs/plate/csv\",\n                  \"title\": \"CSV\",\n                  \"titleCn\": \"CSV\"\n                },\n                {\n                  \"description\": \"Copy paste from DOCX to Slate.\",\n                  \"href\": \"/docs/plate/docx\",\n                  \"title\": \"DOCX\",\n                  \"titleCn\": \"DOCX\"\n                },\n                {\n                  \"description\": \"Import DOCX files and export to Word.\",\n                  \"href\": \"/docs/plate/docx-io\",\n                  \"title\": \"DOCX Import/Export\",\n                  \"titleCn\": \"DOCX 导入/导出\"\n                },\n                {\n                  \"description\": \"Copy paste from HTML to Slate.\",\n                  \"href\": \"/docs/plate/html\",\n                  \"title\": \"HTML\",\n                  \"titleCn\": \"HTML\"\n                },\n                {\n                  \"description\": \"Copy paste from Markdown to Slate.\",\n                  \"href\": \"/docs/plate/markdown\",\n                  \"title\": \"Markdown\",\n                  \"titleCn\": \"Markdown\"\n                }\n              ],\n              \"title\": \"Serializing\",\n              \"titleCn\": \"Serializing\"\n            }\n          ]\n        }\n      ]\n    },\n    \"docSections\": [\n      {\n        \"items\": [\n          {\n            \"href\": \"/docs/plate\",\n            \"title\": \"Guides\",\n            \"titleCn\": \"指南\",\n            \"value\": \"guide\"\n          },\n          {\n            \"href\": \"/docs/plate/plugins\",\n            \"title\": \"Plugins\",\n            \"titleCn\": \"插件\",\n            \"value\": \"plugin\"\n          },\n          {\n            \"href\": \"/docs/plate/components\",\n            \"title\": \"Components\",\n            \"titleCn\": \"组件\",\n            \"value\": \"component\"\n          },\n          {\n            \"href\": \"/docs/plate/examples\",\n            \"title\": \"Examples\",\n            \"titleCn\": \"示例\",\n            \"value\": \"example\"\n          },\n          {\n            \"href\": \"/docs/plate/api\",\n            \"title\": \"API Reference\",\n            \"titleCn\": \"API 参考\",\n            \"value\": \"api\"\n          }\n        ]\n      }\n    ],\n    \"sections\": {\n      \"Get Started\": \"开始\",\n      \"Installation\": \"安装\",\n      \"Guides\": \"指南\",\n      \"Plugins\": \"插件\",\n      \"Components\": \"组件\",\n      \"Examples\": \"示例\",\n      \"Migrations\": \"迁移\",\n      \"API\": \"API\"\n    },\n    \"items\": {\n      \"/docs/plate\": {\n        \"title\": \"Introduction\",\n        \"titleCn\": \"介绍\"\n      },\n      \"/docs/plate/installation\": {\n        \"title\": \"Installation\",\n        \"titleCn\": \"安装\"\n      },\n      \"/docs/plate/releases\": {\n        \"title\": \"Releases\",\n        \"titleCn\": \"版本发布\"\n      },\n      \"/docs/plate/installation/plate-ui\": {\n        \"title\": \"Plate UI\",\n        \"titleCn\": \"Plate UI\"\n      },\n      \"/docs/plate/installation/next\": {\n        \"title\": \"Next.js\",\n        \"titleCn\": \"Next.js\"\n      },\n      \"/docs/plate/installation/react\": {\n        \"title\": \"React\",\n        \"titleCn\": \"React\"\n      },\n      \"/docs/plate/installation/manual\": {\n        \"title\": \"Manual\",\n        \"titleCn\": \"手动安装\"\n      },\n      \"/docs/plate/installation/rsc\": {\n        \"title\": \"RSC\",\n        \"titleCn\": \"RSC\"\n      },\n      \"/docs/plate/installation/node\": {\n        \"title\": \"Node.js\",\n        \"titleCn\": \"Node.js\"\n      },\n      \"/docs/plate/installation/docs\": {\n        \"title\": \"Local Docs\",\n        \"titleCn\": \"本地文档\"\n      },\n      \"/docs/plate/installation/mcp\": {\n        \"title\": \"MCP\",\n        \"titleCn\": \"MCP\"\n      },\n      \"/docs/plate/feature-kits\": {\n        \"title\": \"Feature Kits\",\n        \"titleCn\": \"功能套件\"\n      },\n      \"/docs/plate/plugin\": {\n        \"title\": \"Plugin\",\n        \"titleCn\": \"插件\"\n      },\n      \"/docs/plate/plugin-methods\": {\n        \"title\": \"Plugin Methods\",\n        \"titleCn\": \"插件方法\"\n      },\n      \"/docs/plate/plugin-shortcuts\": {\n        \"title\": \"Plugin Shortcuts\",\n        \"titleCn\": \"插件快捷键\"\n      },\n      \"/docs/plate/plugin-context\": {\n        \"title\": \"Plugin Context\",\n        \"titleCn\": \"插件上下文\"\n      },\n      \"/docs/plate/plugin-components\": {\n        \"title\": \"Plugin Components\",\n        \"titleCn\": \"插件组件\"\n      },\n      \"/docs/plate/plugin-rules\": {\n        \"title\": \"Plugin Rules\",\n        \"titleCn\": \"插件规则\"\n      },\n      \"/docs/plate/editing-behavior\": {\n        \"label\": \"New\",\n        \"title\": \"Editing Behavior\",\n        \"titleCn\": \"编辑行为\"\n      },\n      \"/docs/plate/plugin-input-rules\": {\n        \"label\": \"New\",\n        \"title\": \"Plugin Input Rules\",\n        \"titleCn\": \"插件输入规则\"\n      },\n      \"/docs/plate/editor\": {\n        \"title\": \"Editor\",\n        \"titleCn\": \"编辑器\"\n      },\n      \"/docs/plate/editor-methods\": {\n        \"title\": \"Editor Methods\",\n        \"titleCn\": \"编辑器方法\"\n      },\n      \"/docs/plate/controlled\": {\n        \"title\": \"Controlled Value\",\n        \"titleCn\": \"受控值\"\n      },\n      \"/docs/plate/static\": {\n        \"title\": \"Static Rendering\",\n        \"titleCn\": \"静态渲染\"\n      },\n      \"/docs/plate/performance\": {\n        \"label\": \"New\",\n        \"title\": \"Performance\",\n        \"titleCn\": \"性能\"\n      },\n      \"/docs/plate/html\": {\n        \"description\": \"Copy paste from HTML to Slate.\",\n        \"title\": \"HTML\",\n        \"titleCn\": \"HTML\"\n      },\n      \"/docs/plate/markdown\": {\n        \"description\": \"Copy paste from Markdown to Slate.\",\n        \"title\": \"Markdown\",\n        \"titleCn\": \"Markdown\"\n      },\n      \"/docs/plate/form\": {\n        \"title\": \"Form\",\n        \"titleCn\": \"表单\"\n      },\n      \"/docs/plate/typescript\": {\n        \"title\": \"TypeScript\",\n        \"titleCn\": \"TypeScript\"\n      },\n      \"/docs/plate/debugging\": {\n        \"title\": \"Debugging\",\n        \"titleCn\": \"调试\"\n      },\n      \"/docs/plate/unit-testing\": {\n        \"title\": \"Unit Testing\",\n        \"titleCn\": \"单元测试\"\n      },\n      \"/docs/plate/playwright\": {\n        \"title\": \"Playwright Testing\",\n        \"titleCn\": \"Playwright 测试\"\n      },\n      \"/docs/plate/troubleshooting\": {\n        \"keywords\": [\n          \"depset\"\n        ],\n        \"title\": \"Troubleshooting\",\n        \"titleCn\": \"故障排除\"\n      },\n      \"/docs/plate/plugins\": {\n        \"title\": \"Overview\",\n        \"titleCn\": \"概览\"\n      },\n      \"/docs/plate/ai\": {\n        \"description\": \"AI menu with commands, streaming responses in a preview or directly into the editor.\",\n        \"keywords\": [\n          \"ai\"\n        ],\n        \"title\": \"Stream\",\n        \"titleCn\": \"Stream\"\n      },\n      \"/docs/plate/copilot\": {\n        \"description\": \"Render AI suggestions ghost text as you type.\",\n        \"keywords\": [\n          \"ai\"\n        ],\n        \"title\": \"Copilot\",\n        \"titleCn\": \"Copilot\"\n      },\n      \"/docs/plate/discussion\": {\n        \"title\": \"Discussion\",\n        \"titleCn\": \"Discussion\"\n      },\n      \"/docs/plate/comment\": {\n        \"description\": \"Add comments to text as marks.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Comments\",\n        \"titleCn\": \"Comments\"\n      },\n      \"/docs/plate/suggestion\": {\n        \"description\": \"Add suggestions to text as marks.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Suggestion\",\n        \"titleCn\": \"Suggestion\"\n      },\n      \"/docs/plate/basic-blocks\": {\n        \"description\": \"Enhance your editor with essential formatting elements.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Basic Blocks\",\n        \"titleCn\": \"Basic Blocks\"\n      },\n      \"/docs/plate/blockquote\": {\n        \"description\": \"Create blockquotes to emphasize important information.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Blockquote\"\n      },\n      \"/docs/plate/heading\": {\n        \"description\": \"Create headings of various levels to structure content.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Heading\"\n      },\n      \"/docs/plate/horizontal-rule\": {\n        \"description\": \"Visually divide and organize content sections with a horizontal line.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Horizontal Rule\"\n      },\n      \"/docs/plate/callout\": {\n        \"description\": \"Highlight important information or add special notes.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Callout\",\n        \"titleCn\": \"Callout\"\n      },\n      \"/docs/plate/code-block\": {\n        \"description\": \"Display code with syntax highlighting.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Code Block\",\n        \"titleCn\": \"Code Block\"\n      },\n      \"/docs/plate/column\": {\n        \"description\": \"Add columns to your document.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Column\",\n        \"titleCn\": \"Column\"\n      },\n      \"/docs/plate/date\": {\n        \"description\": \"Insert and format dates in your document.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Date\",\n        \"titleCn\": \"Date\"\n      },\n      \"/docs/plate/equation\": {\n        \"description\": \"Enables the insertion and rendering of LaTeX equations in your editor.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Equation\",\n        \"titleCn\": \"Equation\"\n      },\n      \"/docs/plate/link\": {\n        \"description\": \"Insert and manage hyperlinks.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Link\",\n        \"titleCn\": \"Link\"\n      },\n      \"/docs/plate/list-classic\": {\n        \"description\": \"Organize nestable items in a bulleted or numbered list.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"List Classic\",\n        \"titleCn\": \"List Classic\"\n      },\n      \"/docs/plate/media\": {\n        \"description\": \"Embed medias like videos or tweets into your document.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Media\",\n        \"titleCn\": \"Media\"\n      },\n      \"/docs/plate/mention\": {\n        \"description\": \"Enable autocompletion for user mentions.\",\n        \"label\": \"Element\",\n        \"title\": \"Mention\"\n      },\n      \"/docs/plate/table\": {\n        \"description\": \"Organize and display data in a structured and resizable tabular format.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Table\",\n        \"titleCn\": \"Table\"\n      },\n      \"/docs/plate/toc\": {\n        \"description\": \"Renders a table of contents element with clickable links to headings in the document.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Table of Contents\",\n        \"titleCn\": \"Table of Contents\"\n      },\n      \"/docs/plate/footnote\": {\n        \"description\": \"Parse and edit GFM footnote references and definitions as dedicated nodes.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Footnote\",\n        \"titleCn\": \"Footnote\"\n      },\n      \"/docs/plate/toggle\": {\n        \"description\": \"Add toggles to your document.\",\n        \"keywords\": [\n          \"element\"\n        ],\n        \"title\": \"Toggle\",\n        \"titleCn\": \"Toggle\"\n      },\n      \"/docs/plate/basic-marks\": {\n        \"description\": \"Set of essential text formatting options.\",\n        \"title\": \"Marks\",\n        \"titleCn\": \"Marks\"\n      },\n      \"/docs/plate/bold\": {\n        \"description\": \"Apply bold formatting to emphasize important text.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Bold\",\n        \"titleCn\": \"Bold\"\n      },\n      \"/docs/plate/italic\": {\n        \"description\": \"Apply italic formatting for emphasis or stylistic purposes.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Italic\",\n        \"titleCn\": \"Italic\"\n      },\n      \"/docs/plate/underline\": {\n        \"description\": \"Apply underline formatting to text.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Underline\",\n        \"titleCn\": \"Underline\"\n      },\n      \"/docs/plate/code\": {\n        \"description\": \"Format inline code snippets and technical terms.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Code\",\n        \"titleCn\": \"Code\"\n      },\n      \"/docs/plate/highlight\": {\n        \"description\": \"Highlight important text with background colors.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Highlight\",\n        \"titleCn\": \"Highlight\"\n      },\n      \"/docs/plate/kbd\": {\n        \"description\": \"Display keyboard shortcuts and key combinations.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Keyboard Input\",\n        \"titleCn\": \"Keyboard Input\"\n      },\n      \"/docs/plate/strikethrough\": {\n        \"description\": \"Apply strikethrough formatting to indicate deleted content.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Strikethrough\",\n        \"titleCn\": \"Strikethrough\"\n      },\n      \"/docs/plate/subscript\": {\n        \"description\": \"Format text as subscript for mathematical expressions.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Subscript\",\n        \"titleCn\": \"Subscript\"\n      },\n      \"/docs/plate/superscript\": {\n        \"description\": \"Format text as superscript for mathematical expressions.\",\n        \"keywords\": [\n          \"leaf\"\n        ],\n        \"title\": \"Superscript\",\n        \"titleCn\": \"Superscript\"\n      },\n      \"/docs/plate/font\": {\n        \"description\": \"Set font styles to your content.\",\n        \"title\": \"Font\"\n      },\n      \"/docs/plate/line-height\": {\n        \"description\": \"Adjust the height between lines of text.\",\n        \"title\": \"Line Height\"\n      },\n      \"/docs/plate/text-align\": {\n        \"description\": \"Align your content to different positions.\",\n        \"title\": \"Text Align\"\n      },\n      \"/docs/plate/indent\": {\n        \"description\": \"Customize text indentation.\",\n        \"title\": \"Indent\",\n        \"titleCn\": \"Indent\"\n      },\n      \"/docs/plate/list\": {\n        \"description\": \"Turn any block into a list item.\",\n        \"title\": \"List\",\n        \"titleCn\": \"List\"\n      },\n      \"/docs/plate/exit-break\": {\n        \"description\": \"Exit a large block using a shortcut.\",\n        \"title\": \"Exit Break\"\n      },\n      \"/docs/plate/single-block\": {\n        \"description\": \"Restrict the editor to a single block.\",\n        \"title\": \"Single Block\"\n      },\n      \"/docs/plate/trailing-block\": {\n        \"description\": \"Ensure a trailing block is always present in the document.\",\n        \"title\": \"Trailing Block\"\n      },\n      \"/docs/plate/autoformat\": {\n        \"description\": \"Apply formatting automatically using shortcodes.\",\n        \"title\": \"Autoformat\",\n        \"titleCn\": \"Autoformat\"\n      },\n      \"/docs/plate/block-menu\": {\n        \"description\": \"Provides quick access to block-specific actions.\",\n        \"title\": \"Block Menu\",\n        \"titleCn\": \"Block Menu\"\n      },\n      \"/docs/plate/block-placeholder\": {\n        \"description\": \"Show placeholder when a block is empty.\",\n        \"title\": \"Block Placeholder\",\n        \"titleCn\": \"Block Placeholder\"\n      },\n      \"/docs/plate/block-selection\": {\n        \"description\": \"Select and manipulate entire text blocks.\",\n        \"title\": \"Block Selection\",\n        \"titleCn\": \"Block Selection\"\n      },\n      \"/docs/plate/caption\": {\n        \"description\": \"Add captions to your blocks.\",\n        \"title\": \"Caption\",\n        \"titleCn\": \"Caption\"\n      },\n      \"/docs/plate/combobox\": {\n        \"description\": \"Utilities for adding combobox to your editor.\",\n        \"title\": \"Combobox\",\n        \"titleCn\": \"Combobox\"\n      },\n      \"/docs/plate/emoji\": {\n        \"description\": \"Insert emoji inline.\",\n        \"title\": \"Emoji\"\n      },\n      \"/docs/plate/slash-command\": {\n        \"description\": \"Slash command menu for quick insertion of various content types.\",\n        \"title\": \"Slash Command\"\n      },\n      \"/docs/plate/cursor-overlay\": {\n        \"description\": \"A visual overlay for cursors and selections.\",\n        \"title\": \"Cursor Overlay\",\n        \"titleCn\": \"Cursor Overlay\"\n      },\n      \"/docs/plate/dnd\": {\n        \"description\": \"Allows movement of blocks, such as paragraph or tables, within the editor.\",\n        \"title\": \"Drag & Drop\",\n        \"titleCn\": \"Drag & Drop\"\n      },\n      \"/docs/plate/navigation-feedback\": {\n        \"description\": \"Flash the landed target after TOC, footnote, search, or custom navigation jumps.\",\n        \"title\": \"Navigation Feedback\",\n        \"titleCn\": \"Navigation Feedback\"\n      },\n      \"/docs/plate/tabbable\": {\n        \"description\": \"Maintain a consistent tab order for tabbable elements.\",\n        \"title\": \"Tabbable\",\n        \"titleCn\": \"Tabbable\"\n      },\n      \"/docs/plate/toolbar\": {\n        \"description\": \"Add a toolbar to your editor.\",\n        \"title\": \"Toolbar\",\n        \"titleCn\": \"Toolbar\"\n      },\n      \"/docs/plate/yjs\": {\n        \"description\": \"Collaborate with others in a single document.\",\n        \"title\": \"Yjs\",\n        \"titleCn\": \"Yjs\"\n      },\n      \"/docs/plate/multi-select\": {\n        \"description\": \"A rich multi-select editor.\",\n        \"label\": \"Editor\",\n        \"title\": \"Multi Select\",\n        \"titleCn\": \"Multi Select\"\n      },\n      \"/docs/plate/csv\": {\n        \"description\": \"Copy paste from CSV to Slate.\",\n        \"title\": \"CSV\",\n        \"titleCn\": \"CSV\"\n      },\n      \"/docs/plate/docx\": {\n        \"description\": \"Copy paste from DOCX to Slate.\",\n        \"title\": \"DOCX\",\n        \"titleCn\": \"DOCX\"\n      },\n      \"/docs/plate/docx-io\": {\n        \"description\": \"Import DOCX files and export to Word.\",\n        \"title\": \"DOCX Import/Export\",\n        \"titleCn\": \"DOCX 导入/导出\"\n      },\n      \"/docs/plate/components\": {\n        \"title\": \"Components\",\n        \"titleCn\": \"组件\"\n      },\n      \"/docs/plate/components/ai-menu\": {\n        \"description\": \"A menu for AI-powered content generation and insertion.\",\n        \"title\": \"AI Menu\",\n        \"titleCn\": \"AI Menu\"\n      },\n      \"/docs/plate/components/ai-toolbar-button\": {\n        \"description\": \"A toolbar button for accessing AI features.\",\n        \"title\": \"AI Toolbar Button\",\n        \"titleCn\": \"AI Toolbar Button\"\n      },\n      \"/docs/plate/components/align-toolbar-button\": {\n        \"description\": \"A dropdown menu for text alignment controls.\",\n        \"title\": \"Align Toolbar Button\",\n        \"titleCn\": \"Align Toolbar Button\"\n      },\n      \"/docs/plate/components/block-context-menu\": {\n        \"description\": \"A context menu for block-level operations.\",\n        \"title\": \"Block Context Menu\",\n        \"titleCn\": \"Block Context Menu\"\n      },\n      \"/docs/plate/components/block-selection\": {\n        \"description\": \"A visual overlay for selected blocks.\",\n        \"title\": \"Block Selection\",\n        \"titleCn\": \"Block Selection\"\n      },\n      \"/docs/plate/components/import-toolbar-button\": {\n        \"description\": \"A toolbar button to import editor content from a file.\",\n        \"title\": \"Import Toolbar Button\",\n        \"titleCn\": \"Import Toolbar Button\"\n      },\n      \"/docs/plate/components/export-toolbar-button\": {\n        \"description\": \"A toolbar button for exporting editor content in various formats (HTML, PDF, Image, Markdown).\",\n        \"title\": \"Export Toolbar Button\",\n        \"titleCn\": \"Export Toolbar Button\"\n      },\n      \"/docs/plate/components/caption\": {\n        \"description\": \"A text field for adding captions to media elements.\",\n        \"title\": \"Caption\",\n        \"titleCn\": \"Caption\"\n      },\n      \"/docs/plate/components/font-color-toolbar-button\": {\n        \"description\": \"A color picker toolbar button with text and background color controls.\",\n        \"title\": \"Font Color Toolbar Button\",\n        \"titleCn\": \"Font Color Toolbar Button\"\n      },\n      \"/docs/plate/components/comment-toolbar-button\": {\n        \"description\": \"A toolbar button for adding inline comments.\",\n        \"title\": \"Comment Toolbar Button\",\n        \"titleCn\": \"Comment Toolbar Button\"\n      },\n      \"/docs/plate/components/block-discussion\": {\n        \"description\": \"A popover interface for managing discussions: comments, replies, suggestions.\",\n        \"title\": \"Block Discussion\",\n        \"titleCn\": \"Block Discussion\"\n      },\n      \"/docs/plate/components/cursor-overlay\": {\n        \"description\": \"A visual overlay for cursors and selections.\",\n        \"title\": \"Cursor Overlay\",\n        \"titleCn\": \"Cursor Overlay\"\n      },\n      \"/docs/plate/components/block-draggable\": {\n        \"description\": \"A block wrapper with a drag handle for moving editor blocks.\",\n        \"title\": \"Block Draggable\",\n        \"titleCn\": \"Block Draggable\"\n      },\n      \"/docs/plate/components/editor\": {\n        \"description\": \"A container for the editor content and styling.\",\n        \"title\": \"Editor\",\n        \"titleCn\": \"Editor\"\n      },\n      \"/docs/plate/components/select-editor\": {\n        \"description\": \"An editor to select tags.\",\n        \"title\": \"Select Editor\",\n        \"titleCn\": \"Select Editor\"\n      },\n      \"/docs/plate/components/emoji-toolbar-button\": {\n        \"description\": \"An emoji picker toolbar button.\",\n        \"title\": \"Emoji Toolbar Button\",\n        \"titleCn\": \"Emoji Toolbar Button\"\n      },\n      \"/docs/plate/components/fixed-toolbar-buttons\": {\n        \"description\": \"A set of commonly used formatting buttons.\",\n        \"title\": \"Fixed Toolbar Buttons\",\n        \"titleCn\": \"Fixed Toolbar Buttons\"\n      },\n      \"/docs/plate/components/fixed-toolbar-classic-buttons\": {\n        \"title\": \"Fixed Toolbar List Buttons\",\n        \"titleCn\": \"Fixed Toolbar List Buttons\"\n      },\n      \"/docs/plate/components/fixed-toolbar\": {\n        \"description\": \"A fixed toolbar that stays at the top of the editor.\",\n        \"title\": \"Fixed Toolbar\",\n        \"titleCn\": \"Fixed Toolbar\"\n      },\n      \"/docs/plate/components/floating-toolbar-buttons\": {\n        \"description\": \"A set of formatting buttons for the floating toolbar.\",\n        \"title\": \"Floating Toolbar Buttons\",\n        \"titleCn\": \"Floating Toolbar Buttons\"\n      },\n      \"/docs/plate/components/floating-toolbar-classic-buttons\": {\n        \"description\": \"A set of commonly used formatting buttons for the floating toolbar with classic list support.\",\n        \"title\": \"Floating Toolbar Classic Buttons\",\n        \"titleCn\": \"Floating Toolbar Classic Buttons\"\n      },\n      \"/docs/plate/components/floating-toolbar\": {\n        \"description\": \"A contextual toolbar that appears over selected text.\",\n        \"title\": \"Floating Toolbar\",\n        \"titleCn\": \"Floating Toolbar\"\n      },\n      \"/docs/plate/components/ghost-text\": {\n        \"description\": \"A text suggestion system that displays AI-generated content after the cursor.\",\n        \"title\": \"Ghost Text\",\n        \"titleCn\": \"Ghost Text\"\n      },\n      \"/docs/plate/components/history-toolbar-button\": {\n        \"description\": \"Toolbar buttons for undo and redo operations.\",\n        \"title\": \"History Toolbar Button\",\n        \"titleCn\": \"History Toolbar Button\"\n      },\n      \"/docs/plate/components/list-toolbar-button\": {\n        \"description\": \"A toolbar control for adjusting list indentation.\",\n        \"title\": \"List Toolbar Button\",\n        \"titleCn\": \"List Toolbar Button\"\n      },\n      \"/docs/plate/components/indent-toolbar-button\": {\n        \"description\": \"Toolbar controls for block indentation.\",\n        \"title\": \"Indent Toolbar Buttons\",\n        \"titleCn\": \"Indent Toolbar Buttons\"\n      },\n      \"/docs/plate/components/inline-combobox\": {\n        \"description\": \"A combobox for inline suggestions.\",\n        \"title\": \"Inline Combobox\",\n        \"titleCn\": \"Inline Combobox\"\n      },\n      \"/docs/plate/components/insert-toolbar-button\": {\n        \"description\": \"A menu for inserting different types of blocks.\",\n        \"title\": \"Insert Toolbar Button\",\n        \"titleCn\": \"Insert Toolbar Button\"\n      },\n      \"/docs/plate/components/insert-toolbar-classic-button\": {\n        \"description\": \"A menu for inserting different types of blocks with classic list support.\",\n        \"title\": \"Insert Toolbar Classic Button\",\n        \"titleCn\": \"Insert Toolbar Classic Button\"\n      },\n      \"/docs/plate/components/line-height-toolbar-button\": {\n        \"description\": \"A menu for controlling text line spacing.\",\n        \"title\": \"Line Height Toolbar Button\",\n        \"titleCn\": \"Line Height Toolbar Button\"\n      },\n      \"/docs/plate/components/link-toolbar\": {\n        \"description\": \"A floating interface for link editing.\",\n        \"title\": \"Link Floating Toolbar\",\n        \"titleCn\": \"Link Floating Toolbar\"\n      },\n      \"/docs/plate/components/link-toolbar-button\": {\n        \"description\": \"A toolbar control for link management.\",\n        \"title\": \"Link Toolbar Button\",\n        \"titleCn\": \"Link Toolbar Button\"\n      },\n      \"/docs/plate/components/list-classic-toolbar-button\": {\n        \"description\": \"Toolbar controls for list creation and management.\",\n        \"title\": \"List Toolbar Buttons\",\n        \"titleCn\": \"List Toolbar Buttons\"\n      },\n      \"/docs/plate/components/mark-toolbar-button\": {\n        \"description\": \"A toolbar control for basic text formatting.\",\n        \"title\": \"Mark Toolbar Button\",\n        \"titleCn\": \"Mark Toolbar Button\"\n      },\n      \"/docs/plate/components/media-toolbar\": {\n        \"description\": \"A toolbar interface for media settings.\",\n        \"title\": \"Media Toolbar\",\n        \"titleCn\": \"Media Toolbar\"\n      },\n      \"/docs/plate/components/media-toolbar-button\": {\n        \"description\": \"Toolbar button for inserting and managing media.\",\n        \"title\": \"Media Toolbar Button\",\n        \"titleCn\": \"Media Toolbar Button\"\n      },\n      \"/docs/plate/components/media-upload-toast\": {\n        \"description\": \"Show toast notifications for media uploads.\",\n        \"title\": \"Media Upload Toast\",\n        \"titleCn\": \"Media Upload Toast\"\n      },\n      \"/docs/plate/components/mode-toolbar-button\": {\n        \"description\": \"A menu for switching between editor modes.\",\n        \"title\": \"Mode Toolbar Button\",\n        \"titleCn\": \"Mode Toolbar Button\"\n      },\n      \"/docs/plate/components/more-toolbar-button\": {\n        \"description\": \"A menu for additional text formatting options.\",\n        \"title\": \"More Toolbar Button\",\n        \"titleCn\": \"More Toolbar Button\"\n      },\n      \"/docs/plate/components/resize-handle\": {\n        \"description\": \"A resizable wrapper with resize handles.\",\n        \"title\": \"Resize Handle\",\n        \"titleCn\": \"Resize Handle\"\n      },\n      \"/docs/plate/components/table-toolbar-button\": {\n        \"description\": \"A menu for table manipulation and formatting.\",\n        \"title\": \"Table Toolbar Button\",\n        \"titleCn\": \"Table Toolbar Button\"\n      },\n      \"/docs/plate/components/toggle-toolbar-button\": {\n        \"description\": \"A toolbar button for expanding and collapsing blocks.\",\n        \"title\": \"Toggle Toolbar Button\",\n        \"titleCn\": \"Toggle Toolbar Button\"\n      },\n      \"/docs/plate/components/turn-into-toolbar-button\": {\n        \"description\": \"A menu for converting between different block types.\",\n        \"title\": \"Turn Into Toolbar Button\",\n        \"titleCn\": \"Turn Into Toolbar Button\"\n      },\n      \"/docs/plate/components/turn-into-toolbar-classic-button\": {\n        \"description\": \"A dropdown to convert block types with classic list support.\",\n        \"title\": \"Turn Into Toolbar Classic Button\",\n        \"titleCn\": \"Turn Into Toolbar Classic Button\"\n      },\n      \"/docs/plate/components/remote-cursor-overlay\": {\n        \"description\": \"A cursor overlay to display multiplayer cursors in the yjs plugin.\",\n        \"title\": \"Remote Cursor Overlay\",\n        \"titleCn\": \"Remote Cursor Overlay\"\n      },\n      \"/docs/plate/components/toolbar\": {\n        \"description\": \"A customizable toolbar component with various button styles and group\",\n        \"title\": \"Toolbar\",\n        \"titleCn\": \"Toolbar\"\n      },\n      \"/docs/plate/components/suggestion-toolbar-button\": {\n        \"description\": \"A toolbar button for toggling suggestion mode in the editor.\",\n        \"title\": \"Suggestion Toolbar Button\",\n        \"titleCn\": \"Suggestion Toolbar Button\"\n      },\n      \"/docs/plate/components#node-components\": {\n        \"title\": \"Node Components\",\n        \"titleCn\": \"节点组件\"\n      },\n      \"/docs/plate/components/ai-node\": {\n        \"description\": \"A text highlighter for AI-generated content.\",\n        \"title\": \"AI Leaf\",\n        \"titleCn\": \"AI Leaf\"\n      },\n      \"/docs/plate/components/block-list\": {\n        \"description\": \"List components.\",\n        \"title\": \"List\",\n        \"titleCn\": \"List\"\n      },\n      \"/docs/plate/components/blockquote-node\": {\n        \"description\": \"A quote component for block quotes.\",\n        \"title\": \"Blockquote Element\",\n        \"titleCn\": \"Blockquote Element\"\n      },\n      \"/docs/plate/components/callout-node\": {\n        \"description\": \"A callout component for highlighting important information with customizable icons and styles.\",\n        \"title\": \"Callout Node\",\n        \"titleCn\": \"Callout Node\"\n      },\n      \"/docs/plate/components/code-block-node\": {\n        \"description\": \"A code block with syntax highlighting and language selection.\",\n        \"title\": \"Code Block Nodes\",\n        \"titleCn\": \"Code Block Nodes\"\n      },\n      \"/docs/plate/components/code-drawing-node\": {\n        \"description\": \"Create diagrams from code using PlantUML, Graphviz, Flowchart, or Mermaid.\",\n        \"title\": \"Code Drawing Node\",\n        \"titleCn\": \"Code Drawing Node\"\n      },\n      \"/docs/plate/components/code-node\": {\n        \"description\": \"An inline component for code snippets.\",\n        \"title\": \"Code Leaf\",\n        \"titleCn\": \"Code Leaf\"\n      },\n      \"/docs/plate/components/column-node\": {\n        \"description\": \"Resizable column components for layout.\",\n        \"title\": \"Column Nodes\",\n        \"titleCn\": \"Column Nodes\"\n      },\n      \"/docs/plate/components/comment-node\": {\n        \"description\": \"A text component for displaying comments with visual indicators.\",\n        \"title\": \"Comment Leaf\",\n        \"titleCn\": \"Comment Leaf\"\n      },\n      \"/docs/plate/components/suggestion-node\": {\n        \"description\": \"A text component for suggestion.\",\n        \"title\": \"Suggestion Leaf\",\n        \"titleCn\": \"Suggestion Leaf\"\n      },\n      \"/docs/plate/components/date-node\": {\n        \"description\": \"A date field component with calendar picker.\",\n        \"title\": \"Date Element\",\n        \"titleCn\": \"Date Element\"\n      },\n      \"/docs/plate/components/equation-node\": {\n        \"description\": \"Displays a LaTeX equation element with an editable popover for inputting and rendering mathematical expressions.\",\n        \"title\": \"Equation Element\",\n        \"titleCn\": \"Equation Element\"\n      },\n      \"/docs/plate/components/equation-toolbar-button\": {\n        \"description\": \"A toolbar button for inserting and editing equations.\",\n        \"title\": \"Equation Toolbar Button\",\n        \"titleCn\": \"Equation Toolbar Button\"\n      },\n      \"/docs/plate/components/emoji-node\": {\n        \"description\": \"An input component for emoji search and insertion.\",\n        \"title\": \"Emoji Input Element\",\n        \"titleCn\": \"Emoji Input Element\"\n      },\n      \"/docs/plate/components/excalidraw-node\": {\n        \"description\": \"A drawing component powered by Excalidraw.\",\n        \"title\": \"Excalidraw Element\",\n        \"titleCn\": \"Excalidraw Element\"\n      },\n      \"/docs/plate/components/font-size-toolbar-button\": {\n        \"description\": \"A toolbar control for adjusting font size.\",\n        \"title\": \"Font Size Toolbar Button\",\n        \"titleCn\": \"Font Size Toolbar Button\"\n      },\n      \"/docs/plate/components/footnote-node\": {\n        \"description\": \"Inline footnote references, definitions, and input UI.\",\n        \"title\": \"Footnote Elements\",\n        \"titleCn\": \"Footnote Elements\"\n      },\n      \"/docs/plate/components/heading-node\": {\n        \"description\": \"A heading with multiple level support.\",\n        \"title\": \"Heading Element\",\n        \"titleCn\": \"Heading Element\"\n      },\n      \"/docs/plate/components/highlight-node\": {\n        \"description\": \"A text highlighter with customizable colors.\",\n        \"title\": \"Highlight Leaf\",\n        \"titleCn\": \"Highlight Leaf\"\n      },\n      \"/docs/plate/components/hr-node\": {\n        \"description\": \"A horizontal rule component with focus states.\",\n        \"title\": \"Horizontal Rule Element\",\n        \"titleCn\": \"Horizontal Rule Element\"\n      },\n      \"/docs/plate/components/media-image-node\": {\n        \"description\": \"Image element with lazy loading, resizing capabilities, and optional caption.\",\n        \"title\": \"Image Element\",\n        \"titleCn\": \"Image Element\"\n      },\n      \"/docs/plate/components/media-preview-dialog\": {\n        \"description\": \"A modal component for previewing and manipulating images.\",\n        \"title\": \"Media Preview Dialog\",\n        \"titleCn\": \"Media Preview Dialog\"\n      },\n      \"/docs/plate/components/kbd-node\": {\n        \"description\": \"A component for styling keyboard shortcuts.\",\n        \"title\": \"Keyboard Input Leaf\",\n        \"titleCn\": \"Keyboard Input Leaf\"\n      },\n      \"/docs/plate/components/link-node\": {\n        \"description\": \"A component for rendering hyperlinks with hover states.\",\n        \"title\": \"Link Element\",\n        \"titleCn\": \"Link Element\"\n      },\n      \"/docs/plate/components/list-classic-node\": {\n        \"description\": \"List (classic) nodes for ordered and unordered items.\",\n        \"title\": \"List Nodes\",\n        \"titleCn\": \"List Nodes\"\n      },\n      \"/docs/plate/components/media-audio-node\": {\n        \"description\": \"An audio player component with caption support.\",\n        \"title\": \"Media Audio Element\",\n        \"titleCn\": \"Media Audio Element\"\n      },\n      \"/docs/plate/components/media-embed-node\": {\n        \"description\": \"A component for embedded media content with resizing and caption support.\",\n        \"title\": \"Media Embed Element\",\n        \"titleCn\": \"Media Embed Element\"\n      },\n      \"/docs/plate/components/media-file-node\": {\n        \"description\": \"A file attachment component with download capability and caption.\",\n        \"title\": \"Media File Element\",\n        \"titleCn\": \"Media File Element\"\n      },\n      \"/docs/plate/components/media-placeholder-node\": {\n        \"description\": \"A placeholder for media upload progress indication.\",\n        \"title\": \"Media Placeholder Element\",\n        \"titleCn\": \"Media Placeholder Element\"\n      },\n      \"/docs/plate/components/media-video-node\": {\n        \"description\": \"A video player component with YouTube and file upload support.\",\n        \"title\": \"Media Video Element\",\n        \"titleCn\": \"Media Video Element\"\n      },\n      \"/docs/plate/components/mention-node\": {\n        \"description\": \"A mention element with customizable prefix and label, powered by a combobox.\",\n        \"title\": \"Mention Nodes\",\n        \"titleCn\": \"Mention Nodes\"\n      },\n      \"/docs/plate/components/paragraph-node\": {\n        \"description\": \"A paragraph block with background color support.\",\n        \"title\": \"Paragraph Element\",\n        \"titleCn\": \"Paragraph Element\"\n      },\n      \"/docs/plate/components/search-highlight-node\": {\n        \"description\": \"A component that highlights search results in text.\",\n        \"title\": \"Search Highlight Leaf\",\n        \"titleCn\": \"Search Highlight Leaf\"\n      },\n      \"/docs/plate/components/slash-node\": {\n        \"description\": \"A command input component for inserting various elements.\",\n        \"title\": \"Slash Input Element\",\n        \"titleCn\": \"Slash Input Element\"\n      },\n      \"/docs/plate/components/table-node\": {\n        \"description\": \"A table component with floating toolbar and border customization.\",\n        \"title\": \"Table Element\",\n        \"titleCn\": \"Table Element\"\n      },\n      \"/docs/plate/components/tag-node\": {\n        \"description\": \"A tag element component with selection states and styling.\",\n        \"title\": \"Tag Element\",\n        \"titleCn\": \"Tag Element\"\n      },\n      \"/docs/plate/components/toc-node\": {\n        \"description\": \"A table of contents component with links to document headings.\",\n        \"title\": \"TOC Element\",\n        \"titleCn\": \"TOC Element\"\n      },\n      \"/docs/plate/components/toggle-node\": {\n        \"description\": \"A collapsible component for toggling content visibility.\",\n        \"title\": \"Toggle Element\",\n        \"titleCn\": \"Toggle Element\"\n      },\n      \"/docs/plate/examples\": {\n        \"title\": \"Overview\",\n        \"titleCn\": \"概览\"\n      },\n      \"/docs/plate/examples/slate-to-html\": {\n        \"description\": \"Slate to HTML\",\n        \"title\": \"Slate to HTML\",\n        \"titleCn\": \"Slate 转 HTML\"\n      },\n      \"/docs/plate/examples/export\": {\n        \"description\": \"Export a Plate document to a file.\",\n        \"title\": \"Export\",\n        \"titleCn\": \"导出\"\n      },\n      \"/docs/plate/examples/server-side\": {\n        \"description\": \"Server-side rendering.\",\n        \"title\": \"Server-Side\",\n        \"titleCn\": \"服务端\"\n      },\n      \"/docs/plate/examples/version-history\": {\n        \"description\": \"Show a diff of two different points in a Plate document's history.\",\n        \"title\": \"Version History\",\n        \"titleCn\": \"版本历史\"\n      },\n      \"/docs/plate/examples/editable-voids\": {\n        \"title\": \"Editable Voids Demo\",\n        \"titleCn\": \"Editable Voids\"\n      },\n      \"/docs/plate/examples/huge-document\": {\n        \"description\": \"Compare Plate and Slate on a large document.\",\n        \"title\": \"Huge Document\",\n        \"titleCn\": \"大型文档\"\n      },\n      \"/docs/plate/examples/hundreds-editors\": {\n        \"description\": \"Render hundreds of editors.\",\n        \"title\": \"Hundreds Editors\",\n        \"titleCn\": \"数百个编辑器\"\n      },\n      \"/docs/plate/examples/markdown-streaming\": {\n        \"description\": \"Streaming markdown to editor.\",\n        \"title\": \"Markdown Streaming\",\n        \"titleCn\": \"Markdown 流式\"\n      },\n      \"/docs/plate/examples/preview-markdown\": {\n        \"description\": \"Decorate texts with markdown preview.\",\n        \"title\": \"Preview Markdown\",\n        \"titleCn\": \"Markdown 预览\"\n      },\n      \"/docs/plate/examples/collaboration\": {\n        \"description\": \"Collaborative editing.\",\n        \"title\": \"Collaboration Demo\",\n        \"titleCn\": \"协作演示\"\n      },\n      \"/docs/plate/examples/table-nomerge\": {\n        \"title\": \"Table Nomerge Demo\",\n        \"titleCn\": \"Table Nomerge\"\n      },\n      \"/docs/plate/examples/excalidraw\": {\n        \"description\": \"A drawing component powered by Excalidraw.\",\n        \"title\": \"Excalidraw Demo\",\n        \"titleCn\": \"Excalidraw\"\n      },\n      \"/docs/plate/examples/code-drawing\": {\n        \"description\": \"Create diagrams from code using PlantUML, Graphviz, Flowchart, or Mermaid.\",\n        \"title\": \"Code Drawing Demo\",\n        \"titleCn\": \"Code Drawing\"\n      },\n      \"/docs/plate/examples/single-block\": {\n        \"description\": \"Restrict the editor to a single block.\",\n        \"title\": \"Single Block Demo\",\n        \"titleCn\": \"Single Block\"\n      },\n      \"/docs/plate/examples/list-classic\": {\n        \"description\": \"List creation and formatting.\",\n        \"title\": \"List Classic Demo\",\n        \"titleCn\": \"List Classic\"\n      },\n      \"/docs/plate/examples/find-replace\": {\n        \"description\": \"Find and replace functionality in text.\",\n        \"title\": \"Find Replace Demo\",\n        \"titleCn\": \"Find Replace\"\n      },\n      \"/docs/plate/examples/ai\": {\n        \"description\": \"AI menu with commands, streaming responses in a preview or directly into the editor.\",\n        \"title\": \"AI Demo\",\n        \"titleCn\": \"AI\"\n      },\n      \"/docs/plate/examples/align\": {\n        \"description\": \"Text alignment controls for blocks.\",\n        \"title\": \"Align Demo\",\n        \"titleCn\": \"Align\"\n      },\n      \"/docs/plate/examples/autoformat\": {\n        \"description\": \"Apply formatting automatically using shortcodes.\",\n        \"title\": \"Autoformat Demo\",\n        \"titleCn\": \"Autoformat\"\n      },\n      \"/docs/plate/examples/basic-nodes\": {\n        \"description\": \"Basic block elements and text marks.\",\n        \"keywords\": [\n          \"element\",\n          \"leaf\"\n        ],\n        \"title\": \"Basic Nodes Demo\",\n        \"titleCn\": \"Basic Nodes\"\n      },\n      \"/docs/plate/examples/block-menu\": {\n        \"description\": \"Block-level context menu with formatting options.\",\n        \"title\": \"Block Menu Demo\",\n        \"titleCn\": \"Block Menu\"\n      },\n      \"/docs/plate/examples/block-selection\": {\n        \"description\": \"Visual block selection with keyboard support.\",\n        \"title\": \"Block Selection Demo\",\n        \"titleCn\": \"Block Selection\"\n      },\n      \"/docs/plate/examples/column\": {\n        \"description\": \"Column layout.\",\n        \"title\": \"Column Demo\",\n        \"titleCn\": \"Column\"\n      },\n      \"/docs/plate/examples/code-block\": {\n        \"description\": \"Display code with syntax highlighting.\",\n        \"title\": \"Code Block Demo\",\n        \"titleCn\": \"Code Block\"\n      },\n      \"/docs/plate/examples/callout\": {\n        \"description\": \"Display callouts with different variants and icons.\",\n        \"title\": \"Callout Demo\",\n        \"titleCn\": \"Callout\"\n      },\n      \"/docs/plate/examples/discussion\": {\n        \"description\": \"Adding and displaying comments within content.\",\n        \"title\": \"Discussion Demo\",\n        \"titleCn\": \"Discussion\"\n      },\n      \"/docs/plate/examples/cursor-overlay\": {\n        \"description\": \"Visual indicator for cursor position within the editor.\",\n        \"title\": \"Cursor Overlay Demo\",\n        \"titleCn\": \"Cursor Overlay\"\n      },\n      \"/docs/plate/examples/date\": {\n        \"description\": \"Inline date elements with calendar selection interface.\",\n        \"title\": \"Date Demo\",\n        \"titleCn\": \"Date\"\n      },\n      \"/docs/plate/examples/footnote\": {\n        \"description\": \"GFM footnote references and definitions as dedicated editor nodes.\",\n        \"title\": \"Footnote Demo\",\n        \"titleCn\": \"Footnote\"\n      },\n      \"/docs/plate/examples/dnd\": {\n        \"description\": \"Implements draggable functionality for editor blocks, including drag handles and drop indicators.\",\n        \"title\": \"Drag & Drop Demo\",\n        \"titleCn\": \"Drag & Drop\"\n      },\n      \"/docs/plate/examples/emoji\": {\n        \"description\": \"Emoji insertion via toolbar or colon-triggered combobox.\",\n        \"title\": \"Emoji Demo\",\n        \"titleCn\": \"Emoji\"\n      },\n      \"/docs/plate/examples/equation\": {\n        \"description\": \"LaTeX equations with inline and block formats.\",\n        \"title\": \"Equation Demo\",\n        \"titleCn\": \"Equation\"\n      },\n      \"/docs/plate/examples/exit-break\": {\n        \"description\": \"Exit a large block using a shortcut.\",\n        \"title\": \"Exit Break Demo\",\n        \"titleCn\": \"Exit Break\"\n      },\n      \"/docs/plate/examples/floating-toolbar\": {\n        \"description\": \"Floating toolbar with text formatting and AI assistance options.\",\n        \"title\": \"Floating Toolbar Demo\",\n        \"titleCn\": \"Floating Toolbar\"\n      },\n      \"/docs/plate/examples/font\": {\n        \"description\": \"Color picker for text and background colors.\",\n        \"title\": \"Font Demo\",\n        \"titleCn\": \"Font\"\n      },\n      \"/docs/plate/examples/indent\": {\n        \"title\": \"Indent Demo\",\n        \"titleCn\": \"Indent\"\n      },\n      \"/docs/plate/examples/list\": {\n        \"description\": \"Turn any block into a list item.\",\n        \"title\": \"List Demo\",\n        \"titleCn\": \"List\"\n      },\n      \"/docs/plate/examples/line-height\": {\n        \"description\": \"Line height adjustment controls.\",\n        \"title\": \"Line Height Demo\",\n        \"titleCn\": \"Line Height\"\n      },\n      \"/docs/plate/examples/link\": {\n        \"description\": \"Hyperlinks with toolbar insertion and URL pasting support.\",\n        \"title\": \"Link Demo\",\n        \"titleCn\": \"Link\"\n      },\n      \"/docs/plate/examples/media\": {\n        \"description\": \"Media embedding and management.\",\n        \"title\": \"Media Demo\",\n        \"titleCn\": \"Media\"\n      },\n      \"/docs/plate/examples/mention\": {\n        \"description\": \"Mention functionality for referencing users or entities.\",\n        \"title\": \"Mention Demo\",\n        \"titleCn\": \"Mention\"\n      },\n      \"/docs/plate/examples/block-placeholder\": {\n        \"title\": \"Block Placeholder Demo\",\n        \"titleCn\": \"Block Placeholder\"\n      },\n      \"/docs/plate/examples/csv\": {\n        \"description\": \"Copy paste from CSV to Slate.\",\n        \"title\": \"Serializing CSV Demo\",\n        \"titleCn\": \"Serializing CSV\"\n      },\n      \"/docs/plate/examples/docx\": {\n        \"description\": \"Copy paste from DOCX to Slate.\",\n        \"title\": \"Serializing Docx Demo\",\n        \"titleCn\": \"Serializing Docx\"\n      },\n      \"/docs/plate/examples/html\": {\n        \"description\": \"Copy paste from HTML to Slate.\",\n        \"title\": \"Serializing HTML Demo\",\n        \"titleCn\": \"Serializing HTML\"\n      },\n      \"/docs/plate/examples/markdown\": {\n        \"description\": \"Copy paste from Markdown to Slate.\",\n        \"title\": \"Serializing Markdown Demo\",\n        \"titleCn\": \"Serializing Markdown\"\n      },\n      \"/docs/plate/examples/slash-command\": {\n        \"description\": \"Slash command menu for quick insertion of various content types.\",\n        \"title\": \"Slash Command Demo\",\n        \"titleCn\": \"Slash Command\"\n      },\n      \"/docs/plate/examples/plugin-rules\": {\n        \"description\": \"Use plugin rules to customize the common editing behaviors.\",\n        \"title\": \"Plugin Rules Demo\",\n        \"titleCn\": \"Plugin Rules\"\n      },\n      \"/docs/plate/examples/table\": {\n        \"description\": \"Customizable tables with resizable columns and row merging options.\",\n        \"title\": \"Table Demo\",\n        \"titleCn\": \"Table\"\n      },\n      \"/docs/plate/examples/toc\": {\n        \"description\": \"Dynamic TOC with in-document element for easy navigation.\",\n        \"title\": \"Table of Contents Demo\",\n        \"titleCn\": \"Table of Contents\"\n      },\n      \"/docs/plate/examples/toggle\": {\n        \"description\": \"Collapsible content blocks.\",\n        \"title\": \"Toggle Demo\",\n        \"titleCn\": \"Toggle\"\n      },\n      \"/docs/plate/migration/v48\": {\n        \"title\": \"v48\",\n        \"titleCn\": \"v48\"\n      },\n      \"/docs/plate/migration/slate-to-plate\": {\n        \"title\": \"From Slate to Plate\",\n        \"titleCn\": \"从 Slate 到 Plate\"\n      },\n      \"/docs/plate/api\": {\n        \"title\": \"Overview\",\n        \"titleCn\": \"概览\"\n      },\n      \"/docs/plate/api/plate\": {\n        \"description\": \"Plate API.\",\n        \"title\": \"Plate\"\n      },\n      \"/docs/plate/api/slate\": {\n        \"description\": \"Slate API.\",\n        \"label\": \"v42\",\n        \"title\": \"Slate\"\n      },\n      \"/docs/plate/api/slate/editor-api\": {\n        \"title\": \"Editor API\",\n        \"titleCn\": \"编辑器 API\"\n      },\n      \"/docs/plate/api/slate/editor-transforms\": {\n        \"title\": \"Editor Transforms\"\n      },\n      \"/docs/plate/api/slate/node\": {\n        \"title\": \"Node\"\n      },\n      \"/docs/plate/api/slate/element\": {\n        \"title\": \"Element\"\n      },\n      \"/docs/plate/api/slate/text\": {\n        \"title\": \"Text\"\n      },\n      \"/docs/plate/api/slate/path\": {\n        \"title\": \"Path\"\n      },\n      \"/docs/plate/api/slate/point\": {\n        \"title\": \"Point\"\n      },\n      \"/docs/plate/api/slate/range\": {\n        \"title\": \"Range\"\n      },\n      \"/docs/plate/api/slate/location\": {\n        \"title\": \"Location\"\n      },\n      \"/docs/plate/api/slate/location-ref\": {\n        \"title\": \"Location Ref\"\n      },\n      \"/docs/plate/api/slate/operation\": {\n        \"title\": \"Operation\"\n      },\n      \"/docs/plate/api/core\": {\n        \"description\": \"Core utilities for Plate.\",\n        \"title\": \"Plate Core\",\n        \"titleCn\": \"Plate 核心\"\n      },\n      \"/docs/plate/api/core/plate-components\": {\n        \"title\": \"Plate Components\",\n        \"titleCn\": \"Plate 组件\"\n      },\n      \"/docs/plate/api/core/plate-editor\": {\n        \"title\": \"Plate Editor\",\n        \"titleCn\": \"Plate 编辑器\"\n      },\n      \"/docs/plate/api/core/plate-plugin\": {\n        \"title\": \"Plate Plugin\",\n        \"titleCn\": \"Plate 插件\"\n      },\n      \"/docs/plate/api/core/plate-store\": {\n        \"title\": \"Plate Store\",\n        \"titleCn\": \"Plate 存储\"\n      },\n      \"/docs/plate/api/core/plate-controller\": {\n        \"title\": \"Plate Controller\",\n        \"titleCn\": \"Plate 控制器\"\n      },\n      \"/docs/plate/api/utils\": {\n        \"description\": \"Additional utilities for Plate.\",\n        \"title\": \"Plate Utils\",\n        \"titleCn\": \"Plate 工具\"\n      },\n      \"/docs/plate/api/react-utils\": {\n        \"description\": \"React utilities.\",\n        \"title\": \"React Utils\",\n        \"titleCn\": \"React 工具\"\n      },\n      \"/docs/plate/api/cn\": {\n        \"description\": \"Class utilities.\",\n        \"title\": \"cn\",\n        \"titleCn\": \"cn\"\n      },\n      \"/docs/plate/api/floating\": {\n        \"description\": \"API reference for @platejs/floating\",\n        \"title\": \"Floating\",\n        \"titleCn\": \"Floating\"\n      },\n      \"/docs/plate/api/resizable\": {\n        \"description\": \"API reference for @platejs/resizable\",\n        \"title\": \"Resizable\",\n        \"titleCn\": \"Resizable\"\n      }\n    }\n  }\n}\n",
          "type": "registry:file",
          "target": "content/docs/plate/meta.json"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "docs",
      "title": "Documentation",
      "description": "All documentation files for plate",
      "registryDependencies": [
        "https://platejs.org/r/docs-meta.json",
        "https://platejs.org/r/controlled-docs.json",
        "https://platejs.org/r/debugging-docs.json",
        "https://platejs.org/r/editing-behavior-docs.json",
        "https://platejs.org/r/editor-methods-docs.json",
        "https://platejs.org/r/editor-docs.json",
        "https://platejs.org/r/feature-kits-docs.json",
        "https://platejs.org/r/form-docs.json",
        "https://platejs.org/r/performance-docs.json",
        "https://platejs.org/r/playwright-docs.json",
        "https://platejs.org/r/plugin-components-docs.json",
        "https://platejs.org/r/plugin-context-docs.json",
        "https://platejs.org/r/plugin-input-rules-docs.json",
        "https://platejs.org/r/plugin-methods-docs.json",
        "https://platejs.org/r/plugin-rules-docs.json",
        "https://platejs.org/r/plugin-shortcuts-docs.json",
        "https://platejs.org/r/plugin-docs.json",
        "https://platejs.org/r/static-docs.json",
        "https://platejs.org/r/troubleshooting-docs.json",
        "https://platejs.org/r/typescript-docs.json",
        "https://platejs.org/r/unit-testing-docs.json",
        "https://platejs.org/r/ai-docs.json",
        "https://platejs.org/r/copilot-docs.json",
        "https://platejs.org/r/comment-docs.json",
        "https://platejs.org/r/discussion-docs.json",
        "https://platejs.org/r/suggestion-docs.json",
        "https://platejs.org/r/yjs-docs.json",
        "https://platejs.org/r/basic-blocks-docs.json",
        "https://platejs.org/r/blockquote-docs.json",
        "https://platejs.org/r/callout-docs.json",
        "https://platejs.org/r/code-block-docs.json",
        "https://platejs.org/r/code-drawing-docs.json",
        "https://platejs.org/r/column-docs.json",
        "https://platejs.org/r/date-docs.json",
        "https://platejs.org/r/equation-docs.json",
        "https://platejs.org/r/excalidraw-docs.json",
        "https://platejs.org/r/footnote-docs.json",
        "https://platejs.org/r/heading-docs.json",
        "https://platejs.org/r/horizontal-rule-docs.json",
        "https://platejs.org/r/link-docs.json",
        "https://platejs.org/r/list-classic-docs.json",
        "https://platejs.org/r/media-docs.json",
        "https://platejs.org/r/mention-docs.json",
        "https://platejs.org/r/table-docs.json",
        "https://platejs.org/r/toc-docs.json",
        "https://platejs.org/r/toggle-docs.json",
        "https://platejs.org/r/combobox-docs.json",
        "https://platejs.org/r/emoji-docs.json",
        "https://platejs.org/r/slash-command-docs.json",
        "https://platejs.org/r/exit-break-docs.json",
        "https://platejs.org/r/forced-layout-docs.json",
        "https://platejs.org/r/single-block-docs.json",
        "https://platejs.org/r/trailing-block-docs.json",
        "https://platejs.org/r/autoformat-docs.json",
        "https://platejs.org/r/block-menu-docs.json",
        "https://platejs.org/r/block-placeholder-docs.json",
        "https://platejs.org/r/block-selection-docs.json",
        "https://platejs.org/r/caption-docs.json",
        "https://platejs.org/r/cursor-overlay-docs.json",
        "https://platejs.org/r/dnd-docs.json",
        "https://platejs.org/r/find-replace-docs.json",
        "https://platejs.org/r/multi-select-docs.json",
        "https://platejs.org/r/navigation-feedback-docs.json",
        "https://platejs.org/r/tabbable-docs.json",
        "https://platejs.org/r/toolbar-docs.json",
        "https://platejs.org/r/basic-marks-docs.json",
        "https://platejs.org/r/bold-docs.json",
        "https://platejs.org/r/code-docs.json",
        "https://platejs.org/r/highlight-docs.json",
        "https://platejs.org/r/italic-docs.json",
        "https://platejs.org/r/kbd-docs.json",
        "https://platejs.org/r/strikethrough-docs.json",
        "https://platejs.org/r/subscript-docs.json",
        "https://platejs.org/r/superscript-docs.json",
        "https://platejs.org/r/underline-docs.json",
        "https://platejs.org/r/csv-docs.json",
        "https://platejs.org/r/docx-io-docs.json",
        "https://platejs.org/r/docx-docs.json",
        "https://platejs.org/r/html-docs.json",
        "https://platejs.org/r/markdown-docs.json",
        "https://platejs.org/r/font-docs.json",
        "https://platejs.org/r/indent-docs.json",
        "https://platejs.org/r/line-height-docs.json",
        "https://platejs.org/r/list-docs.json",
        "https://platejs.org/r/text-align-docs.json",
        "https://platejs.org/r/api-cn-docs.json",
        "https://platejs.org/r/api-core-plate-components-docs.json",
        "https://platejs.org/r/api-core-plate-controller-docs.json",
        "https://platejs.org/r/api-core-plate-editor-docs.json",
        "https://platejs.org/r/api-core-plate-plugin-docs.json",
        "https://platejs.org/r/api-core-plate-store-docs.json",
        "https://platejs.org/r/api-core-docs.json",
        "https://platejs.org/r/api-floating-docs.json",
        "https://platejs.org/r/api-plate-docs.json",
        "https://platejs.org/r/api-react-utils-docs.json",
        "https://platejs.org/r/api-resizable-docs.json",
        "https://platejs.org/r/api-slate-editor-api-docs.json",
        "https://platejs.org/r/api-slate-editor-transforms-docs.json",
        "https://platejs.org/r/api-slate-element-docs.json",
        "https://platejs.org/r/api-slate-location-ref-docs.json",
        "https://platejs.org/r/api-slate-location-docs.json",
        "https://platejs.org/r/api-slate-node-docs.json",
        "https://platejs.org/r/api-slate-operation-docs.json",
        "https://platejs.org/r/api-slate-path-docs.json",
        "https://platejs.org/r/api-slate-point-docs.json",
        "https://platejs.org/r/api-slate-range-docs.json",
        "https://platejs.org/r/api-slate-text-docs.json",
        "https://platejs.org/r/api-slate-docs.json",
        "https://platejs.org/r/api-utils-docs.json",
        "https://platejs.org/r/examples-collaboration-example-docs.json",
        "https://platejs.org/r/examples-editable-voids-docs.json",
        "https://platejs.org/r/examples-export-docs.json",
        "https://platejs.org/r/examples-huge-document-docs.json",
        "https://platejs.org/r/examples-hundreds-editors-docs.json",
        "https://platejs.org/r/examples-preview-markdown-docs.json",
        "https://platejs.org/r/examples-version-history-docs.json",
        "https://platejs.org/r/index-docs.json",
        "https://platejs.org/r/installation-docs-docs.json",
        "https://platejs.org/r/installation-manual-docs.json",
        "https://platejs.org/r/installation-mcp-docs.json",
        "https://platejs.org/r/installation-next-docs.json",
        "https://platejs.org/r/installation-node-docs.json",
        "https://platejs.org/r/installation-plate-ui-docs.json",
        "https://platejs.org/r/installation-react-docs.json",
        "https://platejs.org/r/installation-rsc-docs.json",
        "https://platejs.org/r/installation-docs.json",
        "https://platejs.org/r/migration-slate-to-plate-docs.json",
        "https://platejs.org/r/migration-v48-docs.json",
        "https://platejs.org/r/releases-index-docs.json"
      ],
      "files": [],
      "type": "registry:file"
    },
    {
      "name": "fumadocs",
      "title": "Fumadocs app",
      "description": "Fumadocs app for plate",
      "dependencies": [
        "@radix-ui/react-separator",
        "@radix-ui/react-accordion",
        "lucide-react",
        "class-variance-authority",
        "tailwind-merge",
        "clsx"
      ],
      "registryDependencies": [
        "https://platejs.org/r/docs.json"
      ],
      "files": [
        {
          "path": "src/registry/blocks/fumadocs/content/docs/index.mdx",
          "type": "registry:file",
          "target": "content/docs/index.mdx"
        },
        {
          "path": "src/registry/blocks/fumadocs/fumadocs-mdx-components.tsx",
          "type": "registry:file",
          "target": "mdx-components.tsx"
        },
        {
          "path": "src/registry/blocks/fumadocs/mdx-plate-components.tsx",
          "type": "registry:file",
          "target": "mdx-plate-components.tsx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "controlled-docs",
      "title": "Controlled Editor Value",
      "description": "Control initial values, persistence, replacement, and async initialization.",
      "files": [
        {
          "path": "../../content/docs/(guides)/controlled.mdx",
          "content": "---\ntitle: Controlled Editor Value\ndescription: Control initial values, persistence, replacement, and async initialization.\n---\n\nPlate is not a normal controlled text input. The editor owns content, selection, history, plugin state, and normalization. This guide shows the safe control points: initial values, change persistence, explicit replacement, reset, and delayed initialization.\n\n## Value Ownership\n\n<Callout type=\"warning\" title=\"Do not control every keystroke\">\n  Do not mirror `editor.children` into React state and pass it back on every\n  change. That fights Slate selection/history and turns normal typing into a\n  full-document replacement loop.\n</Callout>\n\n| Goal | API |\n| --- | --- |\n| Set initial content. | `value` in `usePlateEditor` or `createPlateEditor`. |\n| Persist edits. | `<Plate onValueChange>` or `<Plate onChange>`. |\n| Replace content from outside the editor. | `editor.tf.setValue(value)`. |\n| Reset editor state. | `editor.tf.reset()`. |\n| Delay initialization. | `skipInitialization: true` plus `editor.tf.init(...)`. |\n\n<Steps>\n\n### Set the Initial Value\n\nPass a `Value`, an HTML string, a function, or an async function to `value`.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Initial value' }],\n    type: 'p',\n  },\n];\n\nexport function MyEditor() {\n  const editor = usePlateEditor({\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n### Persist Changes\n\nUse `onValueChange` when you only need the document value.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers {15-19,25}\nimport type { Value } from 'platejs';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nconst STORAGE_KEY = 'plate-value';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Autosaved value' }],\n    type: 'p',\n  },\n];\n\nfunction saveValue(value: Value) {\n  localStorage.setItem(STORAGE_KEY, JSON.stringify(value));\n}\n\nexport function MyEditor() {\n  const editor = usePlateEditor({\n    value: () => {\n      const saved = localStorage.getItem(STORAGE_KEY);\n\n      return saved ? JSON.parse(saved) : initialValue;\n    },\n  });\n\n  return (\n    <Plate editor={editor} onValueChange={({ value }) => saveValue(value)}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\nUse `onChange` when the callback needs the editor instance too.\n\n```tsx title=\"components/editor.tsx\"\n<Plate\n  editor={editor}\n  onChange={({ editor, value }) => {\n    console.info(editor.id, value);\n  }}\n/>\n```\n\n### Replace or Reset Content\n\nUse transforms for external changes. `setValue` replaces the document and\n`reset` returns the editor to its initialized state.\n\n```tsx title=\"components/replace-controls.tsx\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\n\nconst replacementValue: Value = [\n  {\n    children: [{ text: 'Replaced value' }],\n    type: 'p',\n  },\n];\n\nexport function ReplaceControls() {\n  const editor = useEditorRef();\n\n  return (\n    <div className=\"flex gap-2\">\n      <Button onClick={() => editor.tf.setValue(replacementValue)}>\n        Replace Value\n      </Button>\n      <Button onClick={() => editor.tf.reset()}>Reset Editor</Button>\n    </div>\n  );\n}\n```\n\n<Callout type=\"info\">\n  `editor.tf.setValue` replaces nodes at the document root. Use it for explicit\n  outside-editor changes, not for every `onValueChange`.\n</Callout>\n\n<ComponentPreview name=\"controlled-demo\" padding=\"md\" />\n\n### Load Async Initial Content\n\nUse an async `value` function when the editor can initialize as soon as the data\nresolves.\n\n```tsx title=\"components/async-editor.tsx\" showLineNumbers\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function AsyncEditor() {\n  const editor = usePlateEditor({\n    autoSelect: 'end',\n    value: async () => {\n      const response = await fetch('/api/document');\n      const data = await response.json();\n\n      return data.content;\n    },\n    onReady: ({ isAsync, value }) => {\n      if (isAsync) console.info('Loaded value:', value);\n    },\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n### Initialize Manually\n\nUse `skipInitialization` when another system owns the startup moment, such as\ncollaboration or a multi-step loader.\n\n```tsx title=\"components/manual-init-editor.tsx\" showLineNumbers {8,13-18}\nimport * as React from 'react';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function ManualInitEditor() {\n  const editor = usePlateEditor({\n    skipInitialization: true,\n  });\n\n  React.useEffect(() => {\n    void fetch('/api/document')\n      .then((response) => response.json())\n      .then((data) => {\n        editor.tf.init({\n          autoSelect: 'end',\n          value: data.content,\n        });\n      });\n  }, [editor]);\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n</Steps>\n\nDone. Plate owns live editor state; your app controls the entry points around it.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/controlled.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "debugging-docs",
      "title": "Debugging",
      "description": "Debugging in Plate.",
      "files": [
        {
          "path": "../../content/docs/(guides)/debugging.mdx",
          "content": "---\ntitle: Debugging\ndescription: Debugging in Plate.\n---\n\n## Using the DebugPlugin\n\nThe `DebugPlugin` is automatically included when you create a Plate editor. You can access its methods through the editor's API:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [/* your plugins */],\n});\n\neditor.api.debug.log('This is a log message');\neditor.api.debug.info('This is an info message');\neditor.api.debug.warn('This is a warning');\neditor.api.debug.error('This is an error');\n```\n\n### Log Levels\n\nThe `DebugPlugin` supports four log levels:\n\n1. `log`: For general logging\n2. `info`: For informational messages\n3. `warn`: For warnings\n4. `error`: For errors\n\nYou can set the minimum log level to control which messages are displayed:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [\n    DebugPlugin.configure({\n      options: {\n        logLevel: 'warn', // Only show warnings and errors\n      },\n    }),\n  ],\n});\n```\n\n### Configuration Options\n\nThe `DebugPlugin` can be configured with the following options:\n\n- `isProduction`: Set to `true` to disable logging in production environments.\n- `logLevel`: Set the minimum log level (`'error'`, `'warn'`, `'info'`, or `'log'`).\n- `logger`: Provide custom logging functions for each log level.\n- `throwErrors`: Set to `true` to throw errors instead of logging them (default: `true`).\n\nExample configuration:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [\n    DebugPlugin.configure({\n      options: {\n        isProduction: process.env.NODE_ENV === 'production',\n        logLevel: 'info',\n        logger: {\n          error: (message, type, details) => {\n            // Custom error logging\n            console.error(`Custom Error: ${message}`, type, details);\n          },\n          // ... custom loggers for other levels\n        },\n        throwErrors: false,\n      },\n    }),\n  ],\n});\n```\n\n### Error Handling\n\nBy default, the `DebugPlugin` throws errors when `error` is called. You can catch these errors and handle them as needed:\n\n```ts\ntry {\n  editor.api.debug.error('An error occurred', 'CUSTOM_ERROR', { details: 'Additional information' });\n} catch (error) {\n  if (error instanceof PlateError) {\n    console.debug(error.type); // 'CUSTOM_ERROR'\n    console.debug(error.message); // '[CUSTOM_ERROR] An error occurred'\n  }\n}\n```\n\nTo log errors instead of throwing them, set `throwErrors` to `false` in the configuration.\n\n### Best Practices\n\n1. Use appropriate log levels for different types of messages.\n2. In production, set `isProduction` to `true` to disable non-essential logging.\n3. Use custom loggers to integrate with your preferred logging service.\n4. Include relevant details when logging to make debugging easier.\n5. Use error types to categorize and handle different error scenarios.\n\n## Additional Debugging Strategies\n\nBesides using the DebugPlugin, there are other effective ways to debug your Plate editor:\n\n### 1. Override Editor Methods with Logging\n\nYou can use the `extendEditor` option to override editor methods and add logging:\n\n```ts\nconst LoggingPlugin = createPlatePlugin({\n  key: 'logging',\n}).overrideEditor(({ editor, tf: { apply } }) => ({\n  transforms: {\n    apply(operation) {\n      console.debug('Operation:', operation);\n      apply(operation);\n    },\n  },\n}));\n\nconst editor = createPlateEditor({\n  plugins: [LoggingPlugin],\n});\n```\n\nThis approach allows you to log operations, selections, or any other editor behavior you want to inspect.\n\n### 2. Remove Suspected Plugins\n\nIf you're experiencing issues, try removing plugins one by one to isolate the problem:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [\n    // Comment out or remove suspected plugins\n    // HeadingPlugin,\n    // BoldPlugin,\n    // ...other plugins\n  ],\n});\n```\n\nGradually add plugins back until you identify the one causing the issue.\n\n### 3. Use React DevTools\n\nReact DevTools can be invaluable for debugging Plate components:\n\n1. Install the React DevTools browser extension.\n2. Open your app and the DevTools.\n3. Navigate to the Components tab.\n4. Inspect Plate components, their props, and state.\n\n### 4. Use Browser DevTools Breakpoints\n\nSet breakpoints in your code using browser DevTools:\n\n1. Open your app in the browser and open DevTools.\n2. Navigate to the Sources tab.\n3. Find your source file and click on the line number where you want to set a breakpoint.\n4. Interact with your editor to trigger the breakpoint.\n5. Inspect variables and step through the code.\n\n### 5. Create Minimal Reproducible Examples\n\nIf you're facing a complex issue:\n\n1. Pick a [template](/docs/plate/installation).\n2. Add only the essential plugins and components to reproduce the issue.\n3. If the issue persists, [open an issue on GitHub](https://github.com/udecode/plate/issues/new?assignees=&labels=bug&projects=&template=bug.yml) or share your example on [Discord](https://discord.gg/mAZRuBzGM3).\n\n### 6. Use Redux DevTools for zustand stores\n\nZustand and thus zustand-x works with the Redux DevTools browser extension. It can be very useful to help track state changes in zustand stores.\n\nFollow the [zustand documentation](https://zustand.docs.pmnd.rs/middlewares/devtools) to get going with Redux DevTools and zustand.\n\n\n## Debug Error Types\n\nPlate uses several predefined error types to help identify specific issues during development. Here's a list of these error types and their descriptions:\n\n### DEFAULT\n\nA general error that doesn't fit into other specific categories. Used when no other error type is applicable to the situation.\n\n### OPTION_UNDEFINED\n\nThrown when an attempt is made to access an undefined plugin option. This occurs when trying to use a plugin option that hasn't been set or is undefined.\n\n### OVERRIDE_MISSING\n\nIndicates that an expected override is missing in a plugin configuration. This happens when a plugin expects certain overrides to be provided, but they are not present in the configuration.\n\n### PLUGIN_DEPENDENCY_MISSING\n\nOccurs when a required plugin dependency is not found. This error is thrown when a plugin depends on another plugin that hasn't been registered or included in the editor configuration.\n\n### PLUGIN_MISSING\n\nIndicates an attempt to use a plugin that hasn't been registered. This happens when trying to access or use a plugin that is not part of the current editor configuration.\n\n### USE_CREATE_PLUGIN\n\nThrown when a plugin wasn't created using `createSlatePlugin` or `createPlatePlugin` function. This error occurs when a plugin is added to the editor without being properly created using the designated function.\n\n### USE_ELEMENT_CONTEXT\n\nIndicates that the `useElement` hook is being used outside of the appropriate element context. This occurs when trying to access element-specific data or functionality outside of the correct component context.\n\n### PLUGIN_NODE_TYPE\n\nThrown when a plugin is incorrectly configured as both an element and a leaf. This error occurs when a plugin's configuration contradicts itself by setting both `isElement` and `isLeaf` to true.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/debugging.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "editing-behavior-docs",
      "title": "Editing Behavior",
      "description": "How Plate handles Enter, Backspace, merge, normalize, and selection behavior.",
      "files": [
        {
          "path": "../../content/docs/(guides)/editing-behavior.mdx",
          "content": "---\ntitle: Editing Behavior\ndescription: How Plate handles Enter, Backspace, merge, normalize, and selection behavior.\n---\n\nEditing behavior is the path from a key press or transform to the final document shape. Use [Plugin Rules](/docs/plate/plugin-rules) for declarative node policy, and use [Editor Methods](/docs/plate/editor-methods) when you need an imperative transform. This guide shows how break, delete, merge, normalize, and selection behavior fit together.\n\n## On This Page\n\n- [Choose the Right Surface](#choose-the-right-surface)\n- [Runtime Pipeline](#runtime-pipeline)\n- [Break Behavior](#break-behavior)\n- [Delete Behavior](#delete-behavior)\n- [Merge Behavior](#merge-behavior)\n- [Normalize Behavior](#normalize-behavior)\n- [Selection Behavior](#selection-behavior)\n- [Recipes](#recipes)\n- [API Reference](#api-reference)\n\n## Choose the Right Surface\n\nMost editing behavior belongs in `plugin.rules`. Reach for custom transforms only when the rule table cannot express the behavior.\n\n| Need | Use |\n| --- | --- |\n| Change how `Enter` works in a node. | `rules.break` |\n| Change how `Backspace` works at the start of a block. | `rules.delete` |\n| Decide whether an empty sibling disappears during a merge. | `rules.merge` |\n| Remove empty nodes during normalization. | `rules.normalize` |\n| Control mark or inline boundaries while typing and moving. | `rules.selection` |\n| Apply one plugin's rules to another node type. | `rules.match` |\n| Run product-specific behavior that rules cannot express. | `.overrideEditor()` or an explicit `editor.tf.*` command |\n\nRules keep common editor policy close to the plugin that owns the node. Transforms are still the right tool for commands, toolbar actions, and behavior that depends on app state.\n\n## Runtime Pipeline\n\nPlate resolves plugins first, then core plugins wrap Slate APIs and transforms.\n\n| Layer | Owner | Handles |\n| --- | --- | --- |\n| `OverridePlugin` | Core runtime | Node flags, break rules, delete rules, merge rules, normalize rules. |\n| `AffinityPlugin` | Core runtime | `rules.selection` for mark and inline boundaries. |\n| Feature plugins | Feature packages | Default rules for headings, callouts, lists, links, tables, marks, and other nodes. |\n| App plugins | Your app | Local overrides, custom node policy, and custom transforms. |\n\nThe normal flow is:\n\n```txt\nkey press or command\n  -> optional input rule for typed patterns\n  -> plugin rule lookup for the current node\n  -> editor.tf transform\n  -> merge guard when nodes are joined\n  -> normalization\n  -> selection affinity cleanup\n```\n\nInput rules are for text patterns such as markdown shortcuts and autolinks. Plugin rules are for node behavior such as \"a heading resets to paragraph on Backspace\" or \"a callout inserts soft breaks on Enter.\"\n\n## Break Behavior\n\n`rules.break` controls `editor.tf.insertBreak()`, which is what `Enter` calls.\n\nPlate checks the current block and handles these cases in order:\n\n| Case | Rule | What happens |\n| --- | --- | --- |\n| Empty collapsed block | `break.empty` | Runs `reset`, `exit`, `lift`, `deleteExit`, or falls through. |\n| Cursor after a trailing newline | `break.emptyLineEnd` | Runs `exit`, `deleteExit`, or falls through. |\n| Normal Enter | `break.default` | Runs `lineBreak`, `exit`, `deleteExit`, or falls through. |\n| Split created a new block | `break.splitReset` | Resets the new block to the default type. |\n\nUse `splitReset` for blocks that should not keep their type after a normal split.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { H1Plugin } from '@platejs/basic-nodes/react';\n\nexport const AppH1Plugin = H1Plugin.configure({\n  rules: {\n    break: {\n      splitReset: true,\n    },\n  },\n});\n```\n\nUse `lineBreak` and `deleteExit` for container-like blocks that need soft lines before they leave the block.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { CalloutPlugin } from '@platejs/callout/react';\n\nexport const AppCalloutPlugin = CalloutPlugin.configure({\n  rules: {\n    break: {\n      default: 'lineBreak',\n      empty: 'reset',\n      emptyLineEnd: 'deleteExit',\n    },\n  },\n});\n```\n\nThat callout keeps normal Enter inside the callout, resets empty callouts to paragraphs, and exits after a trailing empty line.\n\n## Delete Behavior\n\n`rules.delete` controls collapsed `Backspace` behavior. Expanded selections still delete through the normal fragment path unless the whole editor is selected.\n\nPlate checks collapsed `deleteBackward` in this order:\n\n| Case | Rule | What happens |\n| --- | --- | --- |\n| Cursor at the start of the current block | `delete.start` | Runs `reset`, `lift`, or falls through. |\n| Current block is empty | `delete.empty` | Runs `reset` or falls through. |\n| Cursor is at the start of the document | Core default | Resets the first block. |\n| Nothing handled the case | Slate transform | Runs the original delete transform. |\n\nUse `start: 'reset'` for formatted text blocks that should become paragraphs before they merge into the previous block.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { H1Plugin } from '@platejs/basic-nodes/react';\n\nexport const AppH1Plugin = H1Plugin.configure({\n  rules: {\n    delete: {\n      start: 'reset',\n    },\n  },\n});\n```\n\nUse `start: 'lift'` for nested blocks that should move out one ancestor level.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const QuoteItemPlugin = createPlatePlugin({\n  key: 'quote_item',\n  node: {\n    isElement: true,\n  },\n  rules: {\n    delete: {\n      start: 'lift',\n    },\n  },\n});\n```\n\nWhen a selection spans multiple blocks, Plate deletes the selected content and then calls `editor.tf.mergeNodes()` at the end boundary. That means cross-block deletion can still enter the merge pipeline below.\n\n## Merge Behavior\n\nMerge behavior decides whether two nodes can join and whether empty nodes at the boundary disappear.\n\n`editor.tf.mergeNodes()` calls `editor.api.shouldMergeNodes(prev, next, options)` before it applies the merge. Plate's merge rules add three important guards:\n\n| Case | Behavior |\n| --- | --- |\n| Empty text node before the merge point | Remove it when it is not the first child. |\n| Empty previous sibling | Remove it only when the owning plugin has `rules.merge.removeEmpty: true`. |\n| Target node is void | Do not delete the void target by default; remove the current empty node instead when possible. |\n\nUse `removeEmpty: true` for text-like blocks such as paragraphs and headings.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { H1Plugin } from '@platejs/basic-nodes/react';\n\nexport const AppH1Plugin = H1Plugin.configure({\n  rules: {\n    merge: {\n      removeEmpty: true,\n    },\n  },\n});\n```\n\nKeep `removeEmpty: false` for structural nodes that own layout, children, or wrappers. Tables, rows, cells, columns, and callouts should not disappear just because a merge crosses their boundary.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { CalloutPlugin } from '@platejs/callout/react';\n\nexport const StableCalloutPlugin = CalloutPlugin.configure({\n  rules: {\n    merge: {\n      removeEmpty: false,\n    },\n  },\n});\n```\n\n<Callout>\n  Merge rules are not table cell merge commands. `rules.merge` protects\n  document structure during node joins. Table cell merge and split commands live\n  on `editor.tf.table.merge()` and `editor.tf.table.split()`.\n</Callout>\n\n## Normalize Behavior\n\n`rules.normalize` runs during Slate normalization.\n\nUse `normalize.removeEmpty` for elements that should not exist without text content. Links use this shape because an empty link has no useful editing surface.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { LinkPlugin } from '@platejs/link/react';\n\nexport const AppLinkPlugin = LinkPlugin.configure({\n  rules: {\n    normalize: {\n      removeEmpty: true,\n    },\n  },\n});\n```\n\nKeep normalization rules boring. If a node needs a rich repair strategy, write a dedicated normalizer with `.overrideEditor()` so the behavior is explicit and testable.\n\n## Selection Behavior\n\n`rules.selection` controls how marks and inline-like boundaries behave while typing, deleting, and moving the cursor.\n\n| Affinity | Use for |\n| --- | --- |\n| `default` | Normal Slate boundary behavior. |\n| `directional` | Links and highlights where cursor direction decides whether typed text stays inside. |\n| `outward` | Comment and suggestion marks where edge typing should leave the mark. |\n| `hard` | Boundaries that should take an extra arrow-key step to cross. |\n\nNode flags also affect editing behavior. `node.isInline`, `node.isVoid`, `node.isSelectable`, and `node.isMarkableVoid` are resolved by the core override layer before selection and transform logic runs.\n\n## Recipes\n\n| Behavior | Configure |\n| --- | --- |\n| Heading resets to paragraph on Backspace. | `rules.delete.start: 'reset'` |\n| Heading splits into paragraph on Enter. | `rules.break.splitReset: true` |\n| Callout keeps Enter inside the block. | `rules.break.default: 'lineBreak'` |\n| Empty callout becomes a paragraph. | `rules.break.empty: 'reset'` or `rules.delete.start: 'reset'` |\n| Nested item outdents on Backspace. | `rules.delete.start: 'lift'` |\n| Empty text block disappears during merge. | `rules.merge.removeEmpty: true` |\n| Structural wrapper survives merge. | `rules.merge.removeEmpty: false` |\n| Empty inline element disappears. | `rules.normalize.removeEmpty: true` |\n| Link boundary follows cursor direction. | `rules.selection.affinity: 'directional'` |\n\nFor list metadata and code-block children, use `rules.match` so the feature plugin can apply its rule to the child block that actually contains the selection.\n\n## API Reference\n\n| Surface | Owner | Reference |\n| --- | --- | --- |\n| `rules.break` | Core rule engine plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesbreak) |\n| `rules.delete` | Core rule engine plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesdelete) |\n| `rules.merge` | Core rule engine plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesmerge) |\n| `rules.normalize` | Core rule engine plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesnormalize) |\n| `rules.selection` | Affinity core plugin plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesselection) |\n| `rules.match` | Core rule lookup plus feature plugin config | [Plugin Rules](/docs/plate/plugin-rules#rulesmatch) |\n| `editor.tf.mergeNodes()` | Slate transform patched by Plate | [Editor Transforms](/docs/plate/api/slate/editor-transforms#mergenodes) |\n| `editor.api.shouldMergeNodes()` | Slate editor API patched by Plate | [Editor API](/docs/plate/api/slate/editor-api#shouldmergenodes) |\n| `editor.tf.table.merge()` | Table feature package | [Table](/docs/plate/table#editing-behavior) |\n\nDone. Rules describe the policy; transforms do the work.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/editing-behavior.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "editor-methods-docs",
      "title": "Editor Methods",
      "description": "Read, mutate, and configure a Plate editor instance.",
      "files": [
        {
          "path": "../../content/docs/(guides)/editor-methods.mdx",
          "content": "---\ntitle: Editor Methods\ndescription: Read, mutate, and configure a Plate editor instance.\n---\n\nThe Plate editor exposes two main method surfaces: `editor.api` for reads and\nhelpers, and `editor.tf` for transforms that change editor state. In React, pick\nthe editor hook by how often the component should re-render.\n\n## Access the Editor\n\n| Need | Use |\n| --- | --- |\n| Read the editor inside callbacks without re-rendering. | `useEditorRef()` |\n| Re-render from one derived value. | `useEditorSelector(selector, deps)` |\n| Re-render on every editor change. | `useEditorState()` |\n| Reach the active editor from outside a `<Plate>` subtree. | `<PlateController>` plus the same hooks. |\n\n```tsx title=\"components/bold-button.tsx\" showLineNumbers\nimport { useEditorRef, useEditorSelector } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\n\nexport function BoldButton() {\n  const editor = useEditorRef();\n  const hasSelection = useEditorSelector(\n    (editor) => Boolean(editor.selection),\n    []\n  );\n\n  return (\n    <Button\n      disabled={!hasSelection}\n      onClick={() => editor.tf.toggleMark('bold')}\n    >\n      Bold\n    </Button>\n  );\n}\n```\n\n### `useEditorRef`\n\n`useEditorRef` returns the stable editor object. Use it for event handlers,\neffects, commands, and reads that should not cause a render.\n\n```tsx title=\"components/insert-paragraph-button.tsx\"\nconst editor = useEditorRef();\n\neditor.tf.insertNodes({\n  children: [{ text: 'Inserted paragraph' }],\n  type: 'p',\n});\n```\n\n### `useEditorSelector`\n\n`useEditorSelector` subscribes to a derived value. Return a primitive or provide\n`equalityFn` when the selected value needs custom comparison.\n\n```tsx title=\"components/selection-state.tsx\"\nconst isSelectionExpanded = useEditorSelector(\n  (editor) => editor.api.isExpanded(),\n  []\n);\n```\n\n### `useEditorState`\n\n`useEditorState` subscribes to the whole editor state. Use it only when the UI\nreally needs to update on every editor change.\n\n```tsx title=\"components/selection-debug.tsx\"\nconst editor = useEditorState();\n\nreturn <pre>{JSON.stringify(editor.selection, null, 2)}</pre>;\n```\n\n## Outside Plate\n\nWrap shared UI in `PlateController` when a toolbar, side panel, or inspector\nlives outside a single `<Plate>` tree.\n\n```tsx title=\"components/editor-shell.tsx\" showLineNumbers\nimport type React from 'react';\n\nimport { PlateController, useEditorMounted, useEditorRef } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\n\nexport function EditorShell({ children }: { children: React.ReactNode }) {\n  return (\n    <PlateController>\n      <ActiveEditorToolbar />\n      {children}\n    </PlateController>\n  );\n}\n\nfunction ActiveEditorToolbar() {\n  const editor = useEditorRef();\n  const mounted = useEditorMounted();\n\n  if (!mounted || editor.meta.isFallback) return null;\n\n  return (\n    <Button onClick={() => editor.tf.focus({ edge: 'end' })}>\n      Focus editor\n    </Button>\n  );\n}\n```\n\n`PlateController` resolves an editor by explicit `id`, focused editor, then\nprimary editors. If a controller exists but no editor store is ready, hooks\nreturn a fallback editor; check `useEditorMounted()` or\n`!editor.meta.isFallback` before running transforms.\n\n## Editor API and Transforms\n\nUse `editor.api` for queries and DOM/editor helpers. Use `editor.tf` for\noperations that change the document, selection, history, focus, or plugin state.\n`editor.transforms` is an alias for `editor.tf`.\n\n```ts title=\"editor-methods.ts\" showLineNumbers\nconst selectedText = editor.selection\n  ? editor.api.string(editor.selection)\n  : '';\n\nconst currentBlock = editor.api.block();\n\nif (selectedText && !editor.api.hasMark('bold')) {\n  editor.tf.toggleMark('bold');\n}\n\neditor.tf.insertNodes({\n  children: [{ text: 'New paragraph' }],\n  type: 'p',\n});\n```\n\n| Surface | Examples | Use for |\n| --- | --- | --- |\n| `editor.api` | `string`, `block`, `findPath`, `hasMark`, `isExpanded` | Reading editor state, resolving paths, checking selection. |\n| `editor.tf` | `insertNodes`, `setNodes`, `toggleMark`, `focus`, `reset` | Mutating document or editor state. |\n| `editor.transforms` | Same as `editor.tf` | Compatibility alias. Prefer `editor.tf` in new code. |\n\n## Plugin Methods\n\nPlugin helpers let TypeScript infer APIs and transforms from the plugin you\npass in.\n\n```ts title=\"table-methods.ts\" showLineNumbers\nimport { TablePlugin } from '@platejs/table/react';\n\nconst api = editor.getApi(TablePlugin);\nconst tf = editor.getTransforms(TablePlugin);\n\nconst cell = api.create.tableCell();\n\ntf.insert.tableRow();\n```\n\n| Method | Use for |\n| --- | --- |\n| `editor.getApi(plugin)` | Typed plugin APIs merged onto `editor.api`. |\n| `editor.getTransforms(plugin)` | Typed plugin transforms merged onto `editor.tf`. |\n| `editor.getPlugin(plugin)` | The resolved plugin instance for a key or plugin object. |\n| `editor.getType(pluginKey)` | The node type registered for a plugin key. |\n| `editor.getInjectProps(plugin)` | Resolved injected node props for a plugin. |\n\n## Plugin Options\n\nUse editor option methods when imperative code needs to read or write plugin\nconfiguration. Use `usePluginOption` or `usePluginOptions` when React UI needs\nto re-render from option state.\n\n```tsx title=\"components/find-replace-control.tsx\" showLineNumbers\nimport { FindReplacePlugin } from '@platejs/find-replace';\nimport { useEditorRef, usePluginOption } from 'platejs/react';\n\nexport function FindReplaceControl() {\n  const editor = useEditorRef();\n  const search = usePluginOption(FindReplacePlugin, 'search');\n\n  return (\n    <input\n      value={search}\n      onChange={(event) => {\n        editor.setOption(FindReplacePlugin, 'search', event.target.value);\n      }}\n    />\n  );\n}\n```\n\n```ts title=\"plugin-options.ts\"\nconst options = editor.getOptions(FindReplacePlugin);\n\neditor.setOptions(FindReplacePlugin, {\n  search: 'Plate',\n});\n\neditor.setOptions(FindReplacePlugin, (draft) => {\n  draft.search = draft.search.trim();\n});\n```\n\n| Method | Use for |\n| --- | --- |\n| `editor.getOption(plugin, key, ...args)` | One option or selector value. |\n| `editor.getOptions(plugin)` | Current option state for a plugin. |\n| `editor.setOption(plugin, key, value)` | One option update. |\n| `editor.setOptions(plugin, partial)` | Merge multiple option fields. |\n| `editor.setOptions(plugin, updater)` | Mutate option state through a draft updater. |\n| `editor.getOptionsStore(plugin)` | Low-level access to the plugin options store. |\n\n## Next Steps\n\n| Task | Guide |\n| --- | --- |\n| Configure editor creation. | [Editor Configuration](/docs/plate/editor) |\n| Add plugin APIs and transforms. | [Plugin Methods](/docs/plate/plugin-methods) |\n| Read plugin context inside components. | [Plugin Context](/docs/plate/plugin-context) |\n| Browse editor query contracts. | [Editor API](/docs/plate/api/slate/editor-api) |\n| Browse editor transform contracts. | [Editor Transforms](/docs/plate/api/slate/editor-transforms) |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/editor-methods.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "editor-docs",
      "title": "Editor Configuration",
      "description": "Learn how to configure and customize the Plate editor.",
      "files": [
        {
          "path": "../../content/docs/(guides)/editor.mdx",
          "content": "---\ntitle: Editor Configuration\ndescription: Learn how to configure and customize the Plate editor.\n---\n\nThis guide covers the configuration options for the Plate editor, including basic setup, plugin management, and advanced configuration techniques.\n\n## Basic Editor Configuration\n\nTo create a basic Plate editor, you can use the `createPlateEditor` function, or `usePlateEditor` in a React component:\n\n```ts\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin],\n});\n```\n\n### Initial Value\n\nSet the initial content of the editor:\n\n```ts\nconst editor = createPlateEditor({\n  value: [\n    {\n      type: 'p',\n      children: [{ text: 'Hello, Plate!' }],\n    },\n  ],\n});\n```\n\nYou can also initialize the editor with an HTML string and the associated plugins:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [BoldPlugin, ItalicPlugin],\n  value: '<p>This is <b>bold</b> and <i>italic</i> text!</p>',\n});\n```\n\nFor a comprehensive list of plugins that support HTML string deserialization, refer to the [Plugin Deserialization Rules](/docs/plate/html#plugin-deserialization-rules) section.\n\n### Async Initial Value\n\nIf you need to fetch the initial value asynchronously (e.g., from an API), you can pass an async function directly to the `value` option:\n\n```tsx\nfunction AsyncEditor() {\n  const editor = usePlateEditor({\n    value: async () => {\n      // Simulate fetching data from an API\n      const response = await fetch('/api/document');\n      const data = await response.json();\n      return data.content;\n    },\n    autoSelect: 'end',\n    onReady: ({ editor, value }) => {\n      console.info('Editor ready with loaded value:', value);\n    },\n  });\n\n  if (!editor.children.length) return <div>Loading…</div>;\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n### Adding Plugins\n\nYou can add plugins to your editor by including them in the `plugins` array:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin, ListPlugin],\n});\n```\n\n### Max Length\n\nSet the maximum length of the editor:\n\n```ts\nconst editor = createPlateEditor({\n  maxLength: 100,\n});\n```\n\n## Advanced Configuration\n\n### Editor ID\n\nSet a custom id for the editor:\n\n```ts\nconst editor = createPlateEditor({\n  id: 'my-custom-editor-id',\n});\n```\n\nIf defined, you should always pass the `id` as the first argument in any editor retrieval methods.\n\n### Node ID\n\nPlate includes a built-in system for automatically assigning unique IDs to nodes, which is crucial for certain plugins and for data persistence strategies that rely on stable identifiers.\n\nThis feature is enabled by default. You can customize its behavior or disable it entirely through the `nodeId` option.\n\n#### Configuration\n\nTo configure Node ID behavior, pass an object to the `nodeId` property when creating your editor:\n\n```ts\nconst editor = usePlateEditor({\n  // ... other plugins and options\n  nodeId: {\n    // Function to generate IDs (default: nanoid(10))\n    idCreator: () => uuidv4(),\n\n    // Exclude inline elements from getting IDs (default: true)\n    filterInline: true, \n\n    // Exclude text nodes from getting IDs (default: true)\n    filterText: true,\n\n    // Reuse IDs on undo/redo and copy/paste if not in document (default: false)\n    // Set to true if IDs should be stable across such operations.\n    reuseId: false,\n\n    // Control initial-value ID assignment (default: 'if-needed')\n    // Use 'always' to fill every missing ID in the initial value.\n    initialValueIds: 'always',\n    \n    // Prevent overriding IDs when inserting nodes with an existing id (default: false)\n    disableInsertOverrides: false,\n\n    // Only allow specific node types to receive IDs (default: all)\n    allow: ['p', 'h1'], \n\n    // Exclude specific node types from receiving IDs (default: [])\n    exclude: ['code_block'],\n\n    // Custom filter function to determine if a node should get an ID\n    filter: ([node, path]) => {\n      // Example: Only ID on top-level blocks\n      return path.length === 1;\n    },\n  },\n});\n```\n\n<Callout type=\"note\">\n  The `NodeIdPlugin` (which handles this) is part of the core plugins and is automatically included. You only need to specify the `nodeId` option if you want to customize its default behavior.\n</Callout>\n\n#### Disabling Node IDs\n\nIf you don't need automatic node IDs, you can disable the feature:\n\n```ts\nconst editor = usePlateEditor({\n  // ... other plugins and options\n  nodeId: false, // This will disable the NodeIdPlugin\n});\n```\n\nBy disabling this, certain plugins that rely on node IDs will not function properly. The following plugins require block IDs to work:\n\n- **[Block Selection](/docs/plate/block-selection)** - Needs IDs to track which blocks are selected\n- **[Block Menu](/docs/plate/block-menu)** - Requires IDs to show context menus for specific blocks  \n- **[Drag & Drop](/docs/plate/dnd)** - Uses IDs to identify blocks during drag operations\n- **[Table](/docs/plate/table)** - Relies on IDs for cell selection\n- **[Table of Contents](/docs/plate/toc)** - Needs heading IDs for navigation and scrolling\n- **[Toggle](/docs/plate/toggle)** - Uses IDs to track which toggles are open/closed\n\n### Navigation Feedback\n\nPlate also includes a built-in navigation feedback plugin for \"you landed here\"\nUX after TOC jumps, footnote navigation, search jumps, and custom outline\nmovement.\n\nThis feature is enabled by default. You only need to touch the\n`navigationFeedback` option when you want to change the flash duration or turn\nthe plugin off.\n\n#### Configuration\n\n```ts\nconst editor = createPlateEditor({\n  navigationFeedback: {\n    duration: 1200,\n  },\n});\n```\n\n<Callout type=\"note\">\n  The `NavigationFeedbackPlugin` is part of the core plugins and is\n  automatically included. Use `navigationFeedback` for normal configuration and\n  keep `rootPlugin.configurePlugin(...)` for advanced escape-hatch work.\n</Callout>\n\n#### Disabling Navigation Feedback\n\n```ts\nconst editor = createPlateEditor({\n  navigationFeedback: false,\n});\n```\n\n### Normalization\n\nControl whether the editor should normalize its content on initialization:\n\n```ts\nconst editor = createPlateEditor({\n  shouldNormalizeEditor: true,\n});\n```\n\nNote that normalization may take a few dozen milliseconds for large documents, such as the playground value.\n\n### Auto-selection\n\nConfigure the editor to automatically select a range:\n\n```ts\nconst editor = createPlateEditor({\n  autoSelect: 'end', // or 'start', or true\n});\n```\n\nThis is not the same as auto-focus: you can select text without focusing the editor.\n\n### Component Overrides\n\nOverride default components for plugins:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin],\n  components: {\n    [ParagraphPlugin.key]: CustomParagraphComponent,\n    [HeadingPlugin.key]: CustomHeadingComponent,\n  },\n});\n```\n\n### Plugin Overrides\n\nOverride specific plugin configurations:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin],\n  override: {\n    plugins: {\n      [ParagraphPlugin.key]: {\n        options: {\n          customOption: true,\n        },\n      },\n    },\n  },\n});\n```\n\n### Disable Plugins\n\nDisable specific plugins:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin, ListPlugin],\n  override: {\n    enabled: {\n      [HistoryPlugin.key]: false,\n    },\n  },\n});\n```\n\n### Overriding Plugins\n\nYou can override core plugins or previously defined plugins by adding a plugin with the same key. The last plugin with a given key wins:\n\n```ts\nconst CustomParagraphPlugin = createPlatePlugin({\n  key: 'p',\n  // Custom implementation\n});\n\nconst editor = createPlateEditor({\n  plugins: [CustomParagraphPlugin],\n});\n```\n\n### Root Plugin\n\nFrom the root plugin, you can configure any plugin:\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [HeadingPlugin],\n  rootPlugin: (plugin) =>\n    plugin.configurePlugin(LengthPlugin, {\n    options: {\n        maxLength: 100,\n      },\n    }),\n});\n```\n\n## Typed Editor\n\n`createPlateEditor` will automatically infer the types for your editor from the value and the plugins you pass in. For explicit type creation, use the generics:\n\n### Plugins Type\n\n```ts\nconst editor = createPlateEditor<Value, typeof TablePlugin | typeof LinkPlugin>({\n  plugins: [TablePlugin, LinkPlugin],\n});\n\n// Usage\neditor.tf.insert.tableRow()\n```\n\n### Value Type\n\nFor more complex editors, you can define your types in a separate file (e.g., `plate-types.ts`):\n\n```ts\nimport type { TElement, TText } from 'platejs';\nimport type { TPlateEditor } from 'platejs/react';\n\n// Define custom element types\ninterface ParagraphElement extends TElement {\n  align?: 'left' | 'center' | 'right' | 'justify';\n  children: RichText[];\n  type: typeof ParagraphPlugin.key;\n}\n\ninterface ImageElement extends TElement {\n  children: [{ text: '' }]\n  type: typeof ImagePlugin.key;\n  url: string;\n}\n\n// Define custom text types\ninterface FormattedText extends TText {\n  bold?: boolean;\n  italic?: boolean;\n}\n\nexport type MyRootBlock = ParagraphElement | ImageElement;\n\n// Define the editor's value type\nexport type MyValue = MyRootBlock[];\n\n// Define the custom editor type\nexport type MyEditor = TPlateEditor<MyValue, typeof TablePlugin | typeof LinkPlugin>;\n\nexport const useMyEditorRef = () => useEditorRef<MyEditor>();\n\n// Usage\nconst value: MyValue = [{\n  type: 'p',\n  children: [{ text: 'Hello, Plate!' }],\n}]\n\nconst editorInferred = createPlateEditor({\n  plugins: [TablePlugin, LinkPlugin],\n  value,\n});\n\n// or \nconst editorExplicit = createPlateEditor<MyValue, typeof TablePlugin | typeof LinkPlugin>({\n  plugins: [TablePlugin, LinkPlugin],\n  value,\n});\n```\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/editor.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "feature-kits-docs",
      "title": "Feature Kits",
      "description": "Use registry kits to add groups of Plate plugins and UI wiring.",
      "files": [
        {
          "path": "../../content/docs/(guides)/feature-kits.mdx",
          "content": "---\ntitle: Feature Kits\ndescription: Use registry kits to add groups of Plate plugins and UI wiring.\n---\n\nFeature kits are app-owned registry files that group related Plate plugins, components, shortcuts, input rules, and helper UI.\nUse them to start from working Plate UI wiring, then edit the installed kit when your app needs different behavior.\n\n## Kit Types\n\n| Kit type | Example | Use for |\n| --- | --- | --- |\n| Client/UI kit | `basic-nodes-kit`, `table-kit`, `media-kit` | Editable React editors with Plate UI components. |\n| Base kit | `basic-blocks-base-kit`, `table-base-kit` | Static rendering and server-safe editor setup. |\n| Full editor kit | `editor-kit` | A complete client editor feature stack. |\n| Full base kit | `editor-base-kit` | A broad static/base plugin stack. |\n\nFeature kits live under `components/editor/plugins` after installation. Full\neditor kits like `editor-kit` and `editor-base-kit` live under\n`components/editor`. They are normal TypeScript files in your app, not package\nexports locked inside `node_modules`.\n\n## Use a Kit\n\nInstall the kit through the Plate registry, then import it from your app.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { BasicNodesKit } from '@/components/editor/plugins/basic-nodes-kit';\nimport { TableKit } from '@/components/editor/plugins/table-kit';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function MyEditor() {\n  const editor = usePlateEditor({\n    plugins: [...BasicNodesKit, ...TableKit],\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n`BasicNodesKit` composes `BasicBlocksKit` and `BasicMarksKit`. For example,\n`BasicBlocksKit` wires paragraph, headings, blockquote, and horizontal rule\nplugins to their Plate UI components; `BasicMarksKit` wires marks, input rules,\nshortcuts, and mark leaf components.\n\n## Choose the Right Kit\n\n| Need | Start with |\n| --- | --- |\n| Paragraphs, headings, blockquotes, marks. | `basic-nodes-kit` |\n| Tables with table UI components. | `table-kit` |\n| Images, video, audio, files, embeds, captions. | `media-kit` |\n| Comments and discussions. | `comment-kit` plus `discussion-kit` |\n| AI menu, AI nodes, cursor overlay, and Markdown support. | `ai-kit` |\n| Full editable editor stack. | `editor-kit` |\n| Static or server-rendered output. | `editor-base-kit` or focused `*-base-kit` files |\n\nPlugin pages show the recommended kit in their installation section. Use the kit\nsource when you need the exact plugin list, imported UI components, shortcuts,\nor registry dependencies.\n\n## Base Kits\n\nBase kits use base plugins and static components. They are the right starting\npoint for [Static Rendering](/docs/plate/static), [Node.js](/docs/plate/installation/node),\nand other non-editable pipelines.\n\n```ts title=\"static-editor.ts\"\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\nimport { createStaticEditor } from 'platejs/static';\n\nconst editor = createStaticEditor({\n  plugins: BaseEditorKit,\n});\n```\n\nUse client kits in editable React editors. Use base kits when React editor hooks,\neditable components, floating UI, or browser-only behavior should stay out of\nthe runtime.\n\n## Customize a Kit\n\nBecause kits are copied into your app, the cleanest customization is usually to\nedit the installed kit file.\n\n```tsx title=\"components/editor/plugins/my-basic-kit.tsx\"\nimport { H1Plugin } from '@platejs/basic-nodes/react';\nimport { ParagraphPlugin } from 'platejs/react';\n\nimport { H1Element } from '@/components/ui/heading-node';\nimport { ParagraphElement } from '@/components/ui/paragraph-node';\n\nexport const MyBasicKit = [\n  ParagraphPlugin.withComponent(ParagraphElement),\n  H1Plugin.configure({\n    node: {\n      component: H1Element,\n    },\n    shortcuts: {\n      toggle: { keys: 'mod+alt+1' },\n    },\n  }),\n];\n```\n\nUse manual plugin setup when you only need a small part of a kit, when the app\ndoes not use Plate UI components, or when a feature needs a different dependency\nboundary.\n\n## Registry Names\n\nKits are registry items. These names map to files in\n`components/editor/plugins` or `components/editor`.\n\n| Registry item | Installs |\n| --- | --- |\n| `basic-nodes-kit` | `BasicNodesKit`, `BasicBlocksKit`, and `BasicMarksKit` dependencies. |\n| `table-kit` | `TableKit` and table UI components. |\n| `media-kit` | `MediaKit` without a media upload API route. |\n| `media-uploadthing-kit` | `media-kit` plus the UploadThing API route. |\n| `editor-kit` | The full editable editor kit. |\n| `editor-base-kit` | The full static/base kit. |\n\n## Next Steps\n\n| Task | Guide |\n| --- | --- |\n| Install registry items. | [Plate UI](/docs/plate/installation/plate-ui) |\n| Configure editor creation. | [Editor Configuration](/docs/plate/editor) |\n| Compose plugin APIs and options. | [Plugin Methods](/docs/plate/plugin-methods) |\n| Render without an editable React editor. | [Static Rendering](/docs/plate/static) |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/feature-kits.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "form-docs",
      "title": "Form",
      "description": "How to integrate Plate editor with react-hook-form.",
      "files": [
        {
          "path": "../../content/docs/(guides)/form.mdx",
          "content": "---\ntitle: Form\ndescription: How to integrate Plate editor with react-hook-form.\n---\n\nWhile Plate is typically used as an **uncontrolled** input, there are valid scenarios where you want to integrate the editor within a form library like [**react-hook-form**](https://www.react-hook-form.com) or the [**Form**](https://ui.shadcn.com/docs/components/form) component from **shadcn/ui**. This guide walks through best practices and common pitfalls.\n\n## When to Integrate Plate with a Form\n\n- **Form Submission**: You want the editor's content to be included along with other fields (e.g., `<input>`, `<select>`) when the user submits the form.\n- **Validation**: You want to validate the editor's content (e.g., checking if it's empty) at the same time as other form fields.\n- **Form Data Management**: You want to store the editor content in the same store (like `react-hook-form`'s state) as other fields.\n\nHowever, keep in mind the warning about **fully controlling** the editor value. Plate strongly prefer an uncontrolled model. If you attempt to replace the editor's internal state too frequently, you can break **selection**, **history**, or cause performance issues. The recommended pattern is to treat the editor as uncontrolled, but still **sync** form data on certain events.\n\n## Approach 1: Sync on `onChange`\n\nThis is the most straightforward approach: each time the editor changes, update your form field's value. For small documents or infrequent changes, this is usually acceptable.\n\n### React Hook Form Example\n\n```tsx\nimport { useForm } from 'react-hook-form';\nimport type { Value } from 'platejs';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\ntype FormData = {\n  content: Value;\n};\n\nexport function RHFEditorForm() {\n  const initialValue = [\n    { type: 'p', children: [{ text: 'Hello from react-hook-form!' }] },\n  ]\n\n  // Setup react-hook-form\n  const { register, handleSubmit, setValue } = useForm<FormData>({\n    defaultValues: {\n      content: initialValue,\n    },\n  });\n\n  // Create/configure the Plate editor\n  const editor = usePlateEditor({ value: initialValue });\n\n  // Register the field for react-hook-form\n  register('content', { /* validation rules... */ });\n\n  const onSubmit = (data: FormData) => {\n    // data.content will have final editor content\n    console.info('Submitted:', data.content);\n  };\n\n  return (\n    <form onSubmit={handleSubmit(onSubmit)}>\n      <Plate\n        editor={editor}\n        onChange={({ value }) => {\n          // Sync editor changes to the form\n          setValue('content', value);\n        }}\n      >\n        <PlateContent placeholder=\"Type here...\" />\n      </Plate>\n\n      <button type=\"submit\">Submit</button>\n    </form>\n  );\n}\n```\n\n**Notes**:\n1. **`defaultValues.content`**: your initial editor content.\n2. **`register('content')`**: signals to RHF that the field is tracked.  \n3. **`onChange({ value })`**: calls `setValue('content', value)` each time.\n\nIf you expect large documents or fast typing, consider debouncing or switching to an `onBlur` approach to reduce form updates.\n\n### shadcn/ui Form Example\n\n[shadcn/ui](https://ui.shadcn.com/docs/components/form) provides a `<Form>` that integrates with react-hook-form. We'll use `<FormField>` to handle the field logic:\n\n```tsx\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { useForm } from 'react-hook-form';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\ntype FormValues = {\n  content: any;\n};\n\nexport function EditorForm() {\n  // 1. Create the form\n  const form = useForm<FormValues>({\n    defaultValues: {\n      content: [\n        { type: 'p', children: [{ text: 'Hello from shadcn/ui Form!' }] },\n      ],\n    },\n  });\n\n  // 2. Create the Plate editor\n  const editor = usePlateEditor();\n\n  const onSubmit = (data: FormValues) => {\n    console.info('Submitted data:', data.content);\n  };\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)}>\n        <FormField\n          control={form.control}\n          name=\"content\"\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Content</FormLabel>\n              <FormControl>\n                <Plate\n                  editor={editor}\n                  onChange={({ value }) => {\n                    // Sync to the form\n                    field.onChange(value);\n                  }}\n                >\n                  <PlateContent placeholder=\"Type...\" />\n                </Plate>\n              </FormControl>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n\n        <button type=\"submit\">Submit</button>\n      </form>\n    </Form>\n  );\n}\n```\n\nThis approach makes your editor content behave like any other field in the shadcn/ui form.\n\n## Approach 2: Sync on Blur (or Another Trigger)\n\nInstead of syncing on every keystroke, you might only need the final value when the user:\n- Leaves the editor (`onBlur`),\n- Clicks a “Save” button, or\n- Reaches certain form submission logic.\n\n```tsx\n<Plate editor={editor}>\n  <PlateContent\n    onBlur={() => {\n      // Only sync on blur\n      setValue('content', editor.children);\n    }}\n  />\n</Plate>\n```\n\nThis reduces overhead but your form state won't reflect partial updates while the user is typing.\n\n## Approach 3: Controlled Replacement (Advanced)\n\nIf you want the form to be the single source of truth (completely [controlled](/docs/plate/controlled)):\n```ts\neditor.tf.setValue(formStateValue);\n```\nBut this has known drawbacks:\n- Potentially breaks cursor position and undo/redo.\n- Can cause frequent full re-renders for large docs.\n\n**Recommendation**: Stick to a partially uncontrolled model if you can.\n\n## Example: Save & Reset\n\nHere's a more complete form demonstrating both saving and resetting the editor/form:\n\n```tsx\nimport { useForm } from 'react-hook-form';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nfunction MyForm() {\n  const form = useForm({\n    defaultValues: {\n      content: [\n        { type: 'p', children: [{ text: 'Initial content...' }] },\n      ],\n    },\n  });\n\n  const editor = usePlateEditor();\n\n  const onSubmit = (data) => {\n    alert(JSON.stringify(data, null, 2));\n  };\n\n  return (\n    <form onSubmit={form.handleSubmit(onSubmit)}>\n      <Plate\n        editor={editor}\n        onChange={({ value }) => form.setValue('content', value)}\n      >\n        <PlateContent />\n      </Plate>\n\n      <button type=\"submit\">Save</button>\n\n      <button\n        type=\"button\"\n        onClick={() => {\n          // Reset the editor\n          editor.tf.reset();\n          // Reset the form\n          form.reset();\n        }}\n      >\n        Reset\n      </button>\n    </form>\n  );\n}\n```\n\n- **`onChange`** -> updates form state.\n- **Reset** -> calls both `editor.tf.reset()` and `form.reset()` for consistency.\n\n## Migrating from a shadcn Textarea to Plate\n\nIf you have a standard [TextareaForm from shadcn/ui docs](https://ui.shadcn.com/docs/components/textarea#form) and want to replace `<Textarea>` with a Plate editor, you can follow these steps:\n\n```tsx\n// 1. Original code (TextareaForm)\n<FormField\n  control={form.control}\n  name=\"bio\"\n  render={({ field }) => (\n    <FormItem>\n      <FormLabel>Bio</FormLabel>\n      <FormControl>\n        <Textarea\n          placeholder=\"Tell us a bit about yourself\"\n          className=\"resize-none\"\n          {...field}\n        />\n      </FormControl>\n      <FormDescription>\n        You can <span>@mention</span> other users and organizations.\n      </FormDescription>\n      <FormMessage />\n    </FormItem>\n  )}\n/>\n```\n\nCreate a new `EditorField` component:\n\n```tsx\n// EditorField.tsx\n\"use client\";\n\nimport * as React from \"react\";\nimport type { Value } from \"platejs\";\nimport { Plate, PlateContent, usePlateEditor } from \"platejs/react\";\n\n/**\n * A reusable editor component that works like a standard <Textarea>,\n * accepting `value`, `onChange`, and optional placeholder.\n *\n * Usage:\n *\n * <FormField\n *   control={form.control}\n *   name=\"bio\"\n *   render={({ field }) => (\n *     <FormItem>\n *       <FormLabel>Bio</FormLabel>\n *       <FormControl>\n *         <EditorField\n *           {...field}\n *           placeholder=\"Tell us a bit about yourself\"\n *         />\n *       </FormControl>\n *       <FormDescription>Some helpful description...</FormDescription>\n *       <FormMessage />\n *     </FormItem>\n *   )}\n * />\n */\nexport interface EditorFieldProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  /**\n   * The current Plate Value. Should be an array of Plate nodes.\n   */\n  value?: Value;\n\n  /**\n   * Called when the editor value changes.\n   */\n  onChange?: (value: Value) => void;\n\n  /**\n   * Placeholder text to display when editor is empty.\n   */\n  placeholder?: string;\n}\n\nexport function EditorField({\n  value,\n  onChange,\n  placeholder = \"Type here...\",\n  ...props\n}: EditorFieldProps) {\n  // We create our editor instance with the provided initial `value`.\n  const editor = usePlateEditor({\n    value: value ?? [\n      { type: \"p\", children: [{ text: \"\" }] }, // Default empty paragraph\n    ],\n  });\n\n  return (\n    <Plate\n      editor={editor}\n      onChange={({ value }) => {\n        // Sync changes back to the caller via onChange prop\n        onChange?.(value);\n      }}\n      {...props}\n    >\n      <PlateContent placeholder={placeholder} />\n    </Plate>\n  );\n}\n```\n\n3. Replace the `<Textarea>` with a `<EditorField>` block:\n\n```tsx\n\"use client\";\n\nimport { z } from \"zod\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport { EditorField } from \"./EditorField\"; // Import the component above\n\n// 1. Define our validation schema with zod\nconst FormSchema = z.object({\n  bio: z\n    .string()\n    .min(10, { message: \"Bio must be at least 10 characters.\" })\n    .max(160, { message: \"Bio must not exceed 160 characters.\" }),\n});\n\n// 2. Build our main form component\nexport function EditorForm() {\n  // 3. Setup the form\n  const form = useForm<z.infer<typeof FormSchema>>({\n    resolver: zodResolver(FormSchema),\n    defaultValues: {\n      // Here \"bio\" is just a string, but our editor\n      // will interpret it as initial content if you parse it as JSON\n      bio: \"\",\n    },\n  });\n\n  // 4. Submission handler\n  function onSubmit(data: z.infer<typeof FormSchema>) {\n    alert(\"Submitted: \" + JSON.stringify(data, null, 2));\n  }\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-8\">\n        <FormField\n          control={form.control}\n          name=\"bio\"\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Bio</FormLabel>\n              <FormControl>\n                <EditorField\n                  {...field}\n                  placeholder=\"Tell us a bit about yourself...\"\n                />\n              </FormControl>\n              <FormDescription>\n                You can <span>@mention</span> other users and organizations.\n              </FormDescription>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <button type=\"submit\" className=\"py-2 px-4 bg-primary text-white\">\n          Submit\n        </button>\n      </form>\n    </Form>\n  );\n}\n```\n\n- Any existing form validations or error messages remain the same.\n- For default values, ensure `form.defaultValues.bio` is a valid Plate value (array of nodes) instead of a string.\n- For controlled values, use `editor.tf.setValue(formStateValue)` with moderation.\n\n## Best Practices\n\n1. **Use an Uncontrolled Editor**: Let Plate manage its own state, updating the form only when necessary.  \n2. **Minimize Replacements**: Avoid calling `editor.tf.setValue` too often; it can break selection, history, or degrade performance.  \n3. **Validate at the Right Time**: Decide if you need instant validation (typing) or upon blur/submit.  \n4. **Reset Both**: If you reset the form, call `editor.tf.reset()` to keep them in sync.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/form.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "performance-docs",
      "title": "Performance",
      "description": "Latest Plate vs Slate large-document benchmark snapshot and how to rerun it locally.",
      "files": [
        {
          "path": "../../content/docs/(guides)/performance.mdx",
          "content": "---\ntitle: Performance\ndescription: Latest Plate vs Slate large-document benchmark snapshot and how to rerun it locally.\n---\n\nPerformance is about whether the editor stays usable when the document gets\nlarge. This page shows the latest 10k Plate vs Slate snapshot from the docs\nbenchmark route and the command that reproduces it locally.\n\n## Latest Snapshot\n\nThis snapshot was captured on June 2, 2026 from the docs benchmark route in\nheadless Chrome. The benchmark uses a 10,000-block document with chunked\nrendering enabled.\n\n| Workload | Slate | Plate core | Plate with basic plugins |\n| --- | ---: | ---: | ---: |\n| Open a mixed document | `524 ms` | `866 ms` | `881 ms` |\n| Type in a mixed document | `26 ms` | `43 ms` | `35 ms` |\n| Open a code-heavy document | `526 ms` | `837 ms` | `1101 ms` |\n\nRead this as a workload snapshot, not as a universal editor score. Mount time,\ntyping latency, plugin cost, and document shape are different performance\nquestions.\n\n## Run The Benchmark\n\nStart the docs app:\n\n<CodeBlockCommand\n  __bunCommand__=\"bun run --filter www dev\"\n  __npmCommand__=\"npm run dev --workspace www\"\n  __pnpmCommand__=\"pnpm --filter www dev\"\n  __yarnCommand__=\"yarn workspace www dev\"\n/>\n\nThen run the public editor benchmark in another terminal:\n\n<CodeBlockCommand\n  __bunCommand__=\"bun run --filter www perf:editor:public\"\n  __npmCommand__=\"npm run perf:editor:public --workspace www\"\n  __pnpmCommand__=\"pnpm --filter www perf:editor:public\"\n  __yarnCommand__=\"yarn workspace www perf:editor:public\"\n/>\n\nThe runner writes a compact JSON summary to\n`apps/www/.tmp/editor-perf-public-summary.json`.\n\n## What It Measures\n\nThe benchmark keeps the public comparison small on purpose:\n\n| Measurement | What it tells you |\n| --- | --- |\n| Opening a mixed document | The cost of mounting a large editor with normal rich-text variety. |\n| Typing in a mixed document | Whether the editor still responds under a large loaded document. |\n| Opening a code-heavy document | The cost of a harder mark-heavy rendering profile. |\n\nPlate core isolates the editor wrapper cost. Plate with basic plugins adds the\ncommon block and mark plugins many editors start with.\n\n## Read The Numbers\n\nCompare like with like:\n\n- use mount numbers for startup cost\n- use typing numbers for interaction cost\n- use the basic plugin column when you care about a real Plate setup\n- rerun the benchmark on your machine before publishing a performance claim\n\nThe raw output also includes lower-level Plate diagnostics, such as node-id\ncost. Those are useful for debugging Plate itself, but they are not the public\nstory.\n\n## Source\n\nUse the [Huge Document](/docs/plate/examples/huge-document) demo for manual\ninspection. The benchmark route lives at `/dev/editor-perf`, and the runner\nlives in `apps/www/scripts/run-editor-perf.mts`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/performance.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "playwright-docs",
      "title": "Playwright Testing",
      "description": "Learn how to write Playwright tests that integrate with Plate.",
      "files": [
        {
          "path": "../../content/docs/(guides)/playwright.mdx",
          "content": "---\ntitle: Playwright Testing\ndescription: Learn how to write Playwright tests that integrate with Plate.\n---\n\n[Playwright](https://playwright.dev/) enables end-to-end testing in headless browsers. This guide covers integrating Playwright with Plate using `@platejs/playwright`.\n\n## Setup\n\n<Steps>\n\n### Install Dependencies\n\nFollow [Playwright's guide](https://playwright.dev/docs/intro) to install Playwright in your app and ensure that you can write basic end-to-end tests.\n\n```bash\nnpm install @platejs/playwright playwright\n```\n\n### Add PlaywrightPlugin\n\nIn order for your Playwright tests to access and interact with the editor, you'll need to add `PlaywrightPlugin` to your editor:\n\n```tsx\nconst editor = createPlateEditor({\n  plugins: [\n    // other plugins...\n    PlaywrightPlugin.configure({ enabled: process.env.NODE_ENV !== 'production' }),\n  ]\n})\n```\n\nThis exposes various utilities on `window.platePlaywrightAdapter`.\n\n### Get Editor Handle\n\n<Callout type=\"info\" title=\"What is an editor handle?\">\n  Most Playwright test code runs in a non-browser environment. Interacting with a Plate editor requires running JavaScript inside the browser context using Playwright's `evaluate` and `evaluateHandle` [APIs](https://playwright.dev/docs/evaluating).\n\n  A [handle](https://playwright.dev/docs/handles) references a JavaScript object within the browser. The editor handle refers to the `editor` instance of your Plate editor (`JSHandle<PlateEditor>`).\n</Callout>\n\nIn your Playwright test, get the editor handle before interacting with Plate:\n\n```ts\nconst editorHandle = await getEditorHandle(page);\n```\n\nFor multiple editors, specify the editable element:\n\n```ts\nconst editable = getEditable(page.getByTestId('my-editor-container'));\nconst editorHandle = await getEditorHandle(page, editable);\n```\n\nThe locator must match exactly one `[data-slate-editor]` element.\n\n### Start Writing Tests\n\nWith the `editorHandle`, you can now write Playwright tests for your editor.\n\n</Steps>\n\n## Examples\n\n### Get a node handle by its path\n\nUse `getNodeByPath` to get a handle referencing the node at a specific path. To make assertions about the value of the node, convert it to JSON using `.jsonValue()`.\n\n```ts\nconst nodeHandle = await getNodeByPath(page, editorHandle, [0]);\n\nexpect(await nodeHandle.jsonValue()).toBe({\n  type: 'p',\n  children: [{ text: 'My paragraph' }],\n});\n```\n\n### Get the type of a node\n\n```ts\nconst firstNodeType = await getTypeAtPath(page, editorHandle, [0]);\nexpect(firstNodeType).toBe('h1');\n```\n\n### Get the DOM node for a node\n\nOften in Playwright, you'll want to reference a specific DOM element in order to make assertions about its state or perform operations involving it.\n\n`getDOMNodeByPath` returns an [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) for the DOM node corresponding to the Plate node at a given path.\n\n```ts\nconst firstNodeEl = await getDOMNodeByPath(page, elementHandle, [0]);\nawait firstNodeEl.hover();\n```\n\n### Click a node\n\n```ts\nawait clickAtPath(page, elementHandle, [0]);\n```\n\n### Get the selection\n\n```ts\nconst selection = await getSelection(page, editorHandle);\n\nexpect(selection).toBe({\n  anchor: { path: [0, 0], offset: 0 },\n  focus: { path: [0, 0], offset: 7 },\n});\n```\n\n### Select a point or range\n\nIn order to type at a specific point in the editor, you'll need to select that point using `setSelection`.\n\nIf you select a single point (consisting of a `path` and an `offset`), the cursor will be placed at that point. If you select a range (consisting of an `anchor` and a `focus`), that range will be selected. If you select a path, the entire node at that path will be selected.\n\nMake sure you focus the editor before setting the selection. Focusing the editor using `editable.focus()` may not work correctly in WebKit, so the best way of doing this is with `clickAtPath`.\n\n```ts\n// Click the first paragraph to focus the editor \nawait clickAtPath(page, editorHandle, [0]);\n\nawait setSelection(page, editorHandle, {\n  path: [0, 0],\n  offset: 2,\n});\n\nawait page.keyboard.type('Hello world!');\n```\n\n## Imported queries and transforms\n\nYou may want to import a query or a transform such as `getBlockAbove` or `insertNodes` into your Playwright test and use it.\n\nUnfortunately, this is not possible. You can only interact directly with the `editor` instance inside the browser context (using `evaluate` or `evaluateHandle`), and it isn't possible to pass imported functions from Playwright's scope into the browser. This is because neither the `editor` object nor JavaScript functions can be adequately serialized.\n\nThe best workaround is to interact with the editor in the same way that a user would, without using any imported queries or transforms. This will make your Playwright tests more likely to catch bugs in your application.\n\nIf this isn't practical, you can instead call a method on the `editor` object inside an `evaluate` or `evaluateHandle`. (Use `evaluateHandle` if you need to return a reference to a DOM node or a JavaScript object from the browser. Use `evaluate` if you need to return a serialized copy of a JavaScript object, or if you don't need to return any value.)\n\nNote that while these queries and transforms can't be directly used in Playwright tests, they are available when working with the editor instance in your application code. For more information on how to use these methods in your application, refer to the [Editor Methods](/docs/plate/editor-methods) documentation.\n\nSee [Playwright's docs](https://playwright.dev/docs/evaluating) for more information about `evaluate` and `evaluateHandle`.\n\n```ts\nawait editorHandle.evaluate((editor) => {\n  editor.tf.insertNodes(/* ... */);\n});\n```\n\nSee [Playwright's docs](https://playwright.dev/docs/evaluating) for more about `evaluate` and `evaluateHandle`.\n\n## API\n\n### `getEditorHandle`\n\nGets a handle to the Plate editor instance.\n\n<API name=\"getEditorHandle\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editable\" type=\"Locator\" optional>\n    Locator for editable element. Defaults to first [data-slate-editor].\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"EditorHandle\">\n  Handle to Plate editor instance.\n</APIReturns>\n</API>\n\n### `getNodeByPath`\n\nRetrieves a node at the specified path.\n\n<API name=\"getNodeByPath\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"JSHandle<TNode>\">\n  Handle to node at path.\n</APIReturns>\n</API>\n\n### `getDOMNodeByPath`\n\nGets the DOM node for a Plate node at the given path.\n\n<API name=\"getDOMNodeByPath\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"ElementHandle\">\n  ElementHandle for corresponding DOM node.\n</APIReturns>\n</API>\n\n### `clickAtPath`\n\nSimulates a click on the node at the specified path.\n\n<API name=\"clickAtPath\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to node to click.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `getSelection`\n\nRetrieves the current editor selection.\n\n<API name=\"getSelection\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Selection\">\n  Current editor selection.\n</APIReturns>\n</API>\n\n### `setSelection`\n\nSets the editor selection to the specified range.\n\n<API name=\"setSelection\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n  <APIItem name=\"at\" type=\"Location\">\n    Location to set selection.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `getTypeAtPath`\n\nGets the type of the node at the specified path.\n\n<API name=\"getTypeAtPath\">\n<APIParameters>\n  <APIItem name=\"page\" type=\"Page\">\n    Playwright page object.\n  </APIItem>\n  <APIItem name=\"editorHandle\" type=\"EditorHandle\">\n    Handle to editor instance.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"string\">\n  Type of node at path.\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/playwright.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-components-docs",
      "title": "Plugin Components",
      "description": "Render Plate plugin nodes with React components.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-components.mdx",
          "content": "---\ntitle: Plugin Components\ndescription: Render Plate plugin nodes with React components.\n---\n\nPlugin components are the React rendering layer for Plate node plugins. Use\nPlate UI components first when the registry already has the node you need, then\ncustomize with `PlateElement`, `PlateLeaf`, `.withComponent`, or the editor\n`components` map. This page shows which registration path to use.\n\n## Start with Plate UI\n\nPlate UI components are copied into your app. That makes them the fastest path\nfor production styling and the safest starting point for customization.\n\n| Start here | Use when |\n| --- | --- |\n| [Plate UI](/docs/plate/installation/plate-ui) | You want registry components copied into your app. |\n| [Feature Kits](/docs/plate/feature-kits) | You want plugin groups that already wire components, shortcuts, and options. |\n| This page | You are writing or replacing a component by hand. |\n\nThe package owns plugin behavior. Your app owns copied component files and their\nstyles.\n\n## Component Primitives\n\nUse `PlateElement` for element nodes and `PlateLeaf` for mark or leaf nodes.\nBoth components merge Slate attributes, Plate node props, `className`, and\n`style` onto the rendered DOM element.\n\n<Callout type=\"info\" title=\"Render children\">\n  Always render `children`. Slate needs the children in the DOM even when the\n  element is void or the visible UI comes from surrounding controls.\n</Callout>\n\n### PlateElement\n\nElement components render block, inline, and void element nodes.\n\n```tsx title=\"components/ui/blockquote-node.tsx\" showLineNumbers\n'use client';\n\nimport { type PlateElementProps, PlateElement } from 'platejs/react';\n\nexport function BlockquoteElement({\n  children,\n  ...props\n}: PlateElementProps) {\n  return (\n    <PlateElement\n      as=\"blockquote\"\n      className=\"my-1 border-l-2 pl-6 italic\"\n      {...props}\n    >\n      {children}\n    </PlateElement>\n  );\n}\n```\n\n`PlateElement` renders a `div` by default. Pass `as` when the node should render\nas a specific HTML element.\n\n### PlateLeaf\n\nLeaf components render marks and decorated text ranges.\n\n```tsx title=\"components/ui/code-node.tsx\" showLineNumbers\n'use client';\n\nimport { type PlateLeafProps, PlateLeaf } from 'platejs/react';\n\nexport function CodeLeaf({ children, ...props }: PlateLeafProps) {\n  return (\n    <PlateLeaf\n      as=\"code\"\n      className=\"whitespace-pre-wrap rounded-md bg-muted px-[0.3em] py-[0.2em] font-mono text-sm\"\n      {...props}\n    >\n      {children}\n    </PlateLeaf>\n  );\n}\n```\n\n`PlateLeaf` renders a `span` by default. Use it for plugins with\n`node.isLeaf: true`.\n\n## Register Components\n\nUse the narrowest registration path that fits the job.\n\n### withComponent\n\nUse `.withComponent()` when you only need to attach a React component to a\nplugin.\n\n```tsx title=\"components/editor/plugins.tsx\" showLineNumbers\nimport {\n  BlockquotePlugin,\n  CodePlugin,\n} from '@platejs/basic-nodes/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { CodeLeaf } from '@/components/ui/code-node';\n\nexport const plugins = [\n  BlockquotePlugin.withComponent(BlockquoteElement),\n  CodePlugin.withComponent(CodeLeaf),\n];\n```\n\n`.withComponent(Component)` sets both `node.component` and `render.node` for the\nplugin.\n\n### node.component\n\nUse `node.component` inside `.configure()` when the same plugin call also owns\nrules, shortcuts, options, or parser behavior.\n\n```tsx title=\"components/editor/plugins.tsx\" showLineNumbers\nimport { CodeRules } from '@platejs/basic-nodes';\nimport { CodePlugin } from '@platejs/basic-nodes/react';\n\nimport { CodeLeaf } from '@/components/ui/code-node';\n\nexport const plugins = [\n  CodePlugin.configure({\n    inputRules: [CodeRules.markdown()],\n    node: { component: CodeLeaf },\n    shortcuts: { toggle: { keys: 'mod+e' } },\n  }),\n];\n```\n\nDuring plugin resolution, Plate keeps `node.component` and `render.node` in sync.\n\n### Editor components\n\nUse the editor `components` option when a single editor owns the component map.\nThis is useful for replacing several components in one place.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers\nimport {\n  BlockquotePlugin,\n  CodePlugin,\n} from '@platejs/basic-nodes/react';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { CodeLeaf } from '@/components/ui/code-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function AppEditor() {\n  const editor = usePlateEditor({\n    components: {\n      [BlockquotePlugin.key]: BlockquoteElement,\n      [CodePlugin.key]: CodeLeaf,\n    },\n    plugins: [BlockquotePlugin, CodePlugin],\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\nThe keys are plugin keys, not file names or component names.\n\n## Render Without a Custom Component\n\nUse `render.as` when the default `PlateElement` or `PlateLeaf` wrapper is enough\nand you only need a different HTML tag.\n\n```ts title=\"quote-plugin.ts\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const QuotePlugin = createPlatePlugin({\n  key: 'quote',\n  node: {\n    isElement: true,\n    type: 'quote',\n  },\n  render: {\n    as: 'blockquote',\n  },\n});\n```\n\nReach for a custom component once you need classes, nested controls, popovers,\ntoolbars, resize handles, or plugin options inside the render tree.\n\n## Styling\n\nPrefer component-local styles. Plate also adds a `slate-<node-type>` class while\nrendering plugin nodes, so global CSS can target stable node types when you need\neditor-wide styling.\n\n```css title=\"app/globals.css\"\n.slate-p {\n  margin-block: 0.25rem;\n}\n\n.slate-code {\n  border-radius: 0.375rem;\n  font-family: var(--font-mono);\n}\n```\n\nUse global selectors sparingly. Component files are easier to copy, inspect, and\nreplace from the registry.\n\n## API Reference\n\n| API | Use for | Notes |\n| --- | --- | --- |\n| `PlateElement` | Element nodes. | Defaults to `div`; accepts `as`, `className`, `style`, and Plate render props. |\n| `PlateLeaf` | Leaf and mark nodes. | Defaults to `span`; use with plugins where `node.isLeaf` is true. |\n| `plugin.withComponent(Component)` | Simple component attachment. | Sets `node.component` and `render.node`. |\n| `plugin.configure({ node: { component } })` | Component plus plugin config. | Plate syncs `node.component` to `render.node` during resolution. |\n| `render.as` | Default wrapper with a different tag. | Works when no custom `render.node` component is set. |\n| `components` | Editor-wide component overrides. | Merged into the root plugin's component overrides. |\n| `override.components` | Advanced plugin-level component overrides. | Higher-priority plugins win when a target already has a component. |\n\nFor plugin method details, see [Plugin Methods](/docs/plate/plugin-methods). For static\nrendering components, see [Static Rendering](/docs/plate/static).\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-components.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-context-docs",
      "title": "Plugin Context",
      "description": "Use editor, plugin, option, API, and transform context inside Plate plugins.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-context.mdx",
          "content": "---\ntitle: Plugin Context\ndescription: Use editor, plugin, option, API, and transform context inside Plate plugins.\n---\n\nPlugin context is the object Plate passes to plugin configuration callbacks,\nhandlers, extensions, transforms, and render components. It gives you the\nresolved editor, current plugin, node type, `api`, `tf`, and option helpers\nwithout reaching through global state. Use it inside plugin-owned code; use\neditor methods or React hooks when code runs outside a plugin callback.\n\n## Context Shape\n\n`PlatePluginContext` extends the shared plugin context with a React\n`PlateEditor`. The same helper names are available in headless Slate plugins,\nbut the editor type is `SlateEditor`.\n\n| Property | Use for |\n| --- | --- |\n| `editor` | The resolved editor instance. |\n| `plugin` | The resolved plugin configuration for the current plugin. |\n| `type` | The plugin node type, usually `plugin.node.type`. |\n| `api` | Editor API plus plugin-specific API methods. |\n| `tf` | Editor transforms plus plugin-specific transforms. |\n| `getOption(key, ...args)` | Read an option, selector, or `'state'` from the current plugin. |\n| `getOptions()` | Read the full option state for the current plugin. |\n| `setOption(key, value)` | Update one option in the current plugin store. |\n| `setOptions(optionsOrDraft)` | Update multiple options or mutate a draft. |\n\n## Plugin Methods\n\nHandlers receive context plus the event or payload for that handler. Use the\ncontext helpers instead of closing over editor state.\n\n```ts title=\"counter-plugin.ts\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const CounterPlugin = createPlatePlugin({\n  key: 'counter',\n  options: {\n    count: 0,\n    enabled: true,\n  },\n  handlers: {\n    onKeyDown: ({ event, getOption, setOption, type }) => {\n      if (!getOption('enabled')) return;\n\n      if (event.key === '+') {\n        setOption('count', getOption('count') + 1);\n        console.info(`${type} count incremented`);\n      }\n    },\n  },\n});\n```\n\n`getOption` and `setOption` are scoped to `CounterPlugin` in this example.\n\n## Extension Callbacks\n\nConfiguration, extension, selector, API, transform, and editor override callbacks\nalso receive plugin context.\n\n```ts title=\"counter-plugin.ts\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const CounterPlugin = createPlatePlugin({\n  key: 'counter',\n  options: {\n    count: 0,\n  },\n})\n  .extendSelectors(({ getOptions }) => ({\n    label: () => `Count: ${getOptions().count}`,\n  }))\n  .extendApi(({ getOption }) => ({\n    isEmpty: () => getOption('count') === 0,\n  }));\n```\n\nSelectors are readable through `getOption` and subscribable through\n`usePluginOption`.\n\n## Another Plugin\n\nUse `getEditorPlugin(editor, Plugin)` when plugin-owned code needs another\nplugin's context. The editor argument is required.\n\n```ts title=\"link-aware-plugin.ts\" showLineNumbers\nimport { LinkPlugin } from '@platejs/link/react';\nimport { createPlatePlugin, getEditorPlugin } from 'platejs/react';\n\nexport const LinkAwarePlugin = createPlatePlugin({\n  key: 'linkAware',\n  handlers: {\n    onKeyDown: ({ editor, event }) => {\n      if (event.key !== 'Enter') return;\n\n      const link = getEditorPlugin(editor, LinkPlugin);\n\n      console.info(`Link node type: ${link.type}`);\n    },\n  },\n});\n```\n\nUse this for cross-plugin reads. Keep cross-plugin writes rare; they couple two\nplugins tightly.\n\n## React Components\n\nUse `useEditorPlugin` inside a component rendered under `<Plate>`. It returns\nthe same context plus the editor store.\n\n```tsx title=\"counter-badge.tsx\" showLineNumbers\nimport { useEditorPlugin, usePluginOption } from 'platejs/react';\n\nimport { CounterPlugin } from './counter-plugin';\n\nexport function CounterBadge() {\n  const { type } = useEditorPlugin(CounterPlugin);\n  const count = usePluginOption(CounterPlugin, 'count');\n  const label = usePluginOption(CounterPlugin, 'label');\n\n  return (\n    <span data-plugin-type={type}>\n      {label} ({count})\n    </span>\n  );\n}\n```\n\nUse `usePluginOptions` when a component needs a derived value from several\noptions.\n\n```tsx title=\"counter-badge.tsx\" showLineNumbers\nimport { usePluginOptions } from 'platejs/react';\n\nimport { CounterPlugin } from './counter-plugin';\n\nexport function CounterStatus() {\n  const status = usePluginOptions(CounterPlugin, (state) =>\n    state.count === 0 ? 'empty' : 'active'\n  );\n\n  return <span>{status}</span>;\n}\n```\n\nFor code outside the nearest `<Plate>` provider, pass an editor explicitly with\n`useEditorPluginOption` or `useEditorPluginOptions`.\n\n## Option State\n\nPlugin options are stored per editor. Updating one editor's plugin options does\nnot update another editor.\n\n```ts title=\"counter-plugin.ts\" showLineNumbers\nexport const CounterPluginWithInitialCount = CounterPlugin.configure(\n  ({ getOptions }) => ({\n    options: {\n      count: getOptions().count + 1,\n    },\n  })\n);\n```\n\n`setOptions` accepts either a partial object or a draft callback.\n\n```ts title=\"counter-actions.ts\" showLineNumbers\nimport { getEditorPlugin, type PlateEditor } from 'platejs/react';\n\nimport { CounterPlugin } from './counter-plugin';\n\nexport function resetCounter(editor: PlateEditor) {\n  const { setOptions } = getEditorPlugin(editor, CounterPlugin);\n\n  setOptions({\n    count: 1,\n  });\n\n  setOptions((draft) => {\n    draft.count += 1;\n  });\n}\n```\n\nPlate reports `OPTION_UNDEFINED` through the debug API when `getOption`,\n`setOption`, or `usePluginOption` targets a missing option or selector.\n\n## API Reference\n\n| Helper | Scope | Notes |\n| --- | --- | --- |\n| `getEditorPlugin(editor, plugin)` | Any editor code. | Returns plugin context for the given editor and plugin. |\n| `useEditorPlugin(plugin, id?)` | React under `<Plate>`. | Returns plugin context plus `store`. |\n| `usePluginOption(plugin, key, ...args)` | React under `<Plate>`. | Subscribes to one option, selector, or `'state'`. |\n| `usePluginOptions(plugin, selector, options?)` | React under `<Plate>`. | Subscribes to a selected value from the option state. |\n| `useEditorPluginOption(editor, plugin, key, ...args)` | React with explicit editor. | Use outside the closest editor provider. |\n| `useEditorPluginOptions(editor, plugin, selector, options?)` | React with explicit editor. | Explicit-editor variant of `usePluginOptions`. |\n\nFor plugin extension methods, see [Plugin Methods](/docs/plate/plugin-methods). For\nplugin configuration, see [Plugin](/docs/plate/plugin).\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-context.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-input-rules-docs",
      "title": "Plugin Input Rules",
      "description": "Typed editor rules for markdown shortcuts, block fences, autolinks, and text substitutions.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-input-rules.mdx",
          "content": "---\ntitle: Plugin Input Rules\ndescription: Typed editor rules for markdown shortcuts, block fences, autolinks, and text substitutions.\n---\n\nPlugin Input Rules convert typed editor patterns: markdown prefixes become headings, fences become code blocks, URLs become links, and `->` becomes `→`.\nUse [Plugin Rules](/docs/plate/plugin-rules) for node behavior policy such as how `Enter` or `Backspace` works inside a block.\nThis page covers shipped markdown rules, local substitutions, custom authoring, execution order, and helper reference.\n\n## On This Page\n\n- [What Plugin Input Rules Are](#what-plugin-input-rules-are)\n- [Quick Start](#quick-start)\n- [Feature-Owned Markdown Rules](#feature-owned-markdown-rules)\n- [Local Copied Shortcuts](#local-copied-shortcuts)\n- [Custom Rules](#custom-rules)\n- [How Rule Execution Works](#how-rule-execution-works)\n- [API Reference](#api-reference)\n\n## What Plugin Input Rules Are\n\nWhen you type a character, press Enter, or paste data, Plate asks every\nregistered input rule \"does this fire?\" before running the default transform.\nEach rule declares a target (`insertText`, `insertBreak`, or `insertData`), an\noptional `enabled` gate, a `resolve` function that looks at the current\nselection and returns a match payload, and an `apply` function that performs the\ntransform. The first rule whose `resolve` returns a non-undefined payload gets\nto run; if nothing matches, the default transform runs as usual.\n\nOwnership splits cleanly into three lanes:\n\n- **Core.** Owns dispatch, selection helpers, and the low-level authoring\n  surfaces: `createMarkInputRule`, `createBlockStartInputRule`,\n  `createBlockFenceInputRule`, `createTextSubstitutionInputRule`,\n  `createRuleFactory`, and `defineInputRule`.\n- **Feature packages.** Own semantic rule families like `HeadingRules`,\n  `BlockquoteRules`, `CodeBlockRules`, `BulletedListRules`, `MathRules`,\n  `LinkRules`. Each family exports factory functions that return concrete rule\n  instances.\n- **Kits and apps.** Own activation. Nothing is turned on just because a plugin exists — you pass rule instances to `inputRules: [...]` when you configure a plugin.\n\n| Lane | Owner | Example |\n| ---- | ----- | ------- |\n| Feature markdown rule | Package | `HeadingRules.markdown()` |\n| Feature interaction rule | Package | `LinkRules.autolink({ variant: 'space' })` |\n| Local text substitution | App / local kit | `createTextSubstitutionInputRule({ patterns })` |\n| Raw custom rule | App or package | `defineInputRule({ target, trigger, resolve, apply })` |\n\n<Callout>\n**Input rules are always explicit.** Registering a plugin does not activate any\nrules; you must pass them to `inputRules`. There is no hidden default set and no\nstring-keyed activation layer.\n</Callout>\n\n## Quick Start\n\nInput rules ship as concrete instances you pass into a plugin's `inputRules` array. The two fastest setup paths are:\n\n1. Drop in feature kits that register feature-owned markdown rules — headings, marks, code blocks, lists, math, links.\n2. Drop in a local `AutoformatKit` to get common text substitutions like `->` → `→` or `(c)` → `©`.\n\nYou can use one, both, or neither.\n\n### Add Feature-Owned Markdown Rules\n\nUse the same kits you already use for nodes and marks. Each kit registers its own markdown rules on the right plugins, so you don't wire anything by hand:\n\n```tsx showLineNumbers\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\nimport { CodeBlockKit } from '@/components/editor/plugins/code-block-kit';\nimport { LinkKit } from '@/components/editor/plugins/link-kit';\nimport { ListKit } from '@/components/editor/plugins/list-kit';\nimport { MathKit } from '@/components/editor/plugins/math-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    ...BasicBlocksKit,\n    ...BasicMarksKit,\n    ...CodeBlockKit,\n    ...ListKit,\n    ...LinkKit,\n    ...MathKit,\n  ],\n});\n```\n\nTyping `# ` creates an H1, `**bold**` turns on the bold mark, a triple-backtick\nfence creates a code block, `- ` starts a bulleted list, `[label](url)` creates\na link, and so on. Each kit owns its rule wiring — the kit source shows exactly\nwhich rules it registers and on which plugins.\n\n### Add Local Text Substitutions\n\n`AutoformatKit` is copied registry code that lives in your app and uses\n`createTextSubstitutionInputRule` under the hood. You own the code and can edit\nthe patterns.\n\n```tsx showLineNumbers\nimport { createPlateEditor } from 'platejs/react';\n\nimport { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';\n\nconst editor = createPlateEditor({\n  plugins: [...AutoformatKit],\n});\n```\n\nType `->` and get `→`. Type `(c)` and get `©`. The full pattern list is visible in the kit source — change it however you like.\n\n<Callout>\n**Feature-owned rules and text substitutions are different lanes.** Markdown\nshortcuts live in the feature packages that own the semantics\n(`@platejs/basic-nodes`, `@platejs/link`, `@platejs/math`, ...). Text\nsubstitutions are glyph-for-glyph replacements that live in your app.\n</Callout>\n\nKits are the quick path. If you'd rather wire rules by hand — pick which\nmarkdown variants are active, override priorities, or gate a rule per-app — jump\nto [Feature-Owned Markdown Rules](#feature-owned-markdown-rules) for the manual\npath.\n\nThat's the whole surface in under a minute. Type `# `, type `->`, watch them land.\n\n## Feature-Owned Markdown Rules\n\nFeature packages export semantic rule families. Each family exposes one or more\nfactory functions that return a concrete rule you pass into the matching\nplugin's `inputRules`.\n\n### Basic Blocks\n\nBasic block rules ship with `@platejs/basic-nodes`. Register them per plugin.\n\n**Headings.** `HeadingRules.markdown()` is the same factory for H1 through H6.\nIt derives the markdown prefix from the plugin key (`#`, `##`, `###`, ...), so\nyou pass it once on each heading plugin.\n\n```tsx showLineNumbers\nimport { HeadingRules } from '@platejs/basic-nodes';\nimport {\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  H4Plugin,\n  H5Plugin,\n  H6Plugin,\n} from '@platejs/basic-nodes/react';\n\nH1Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nH2Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nH3Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nH4Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nH5Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nH6Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\n```\n\n**Blockquote.** `BlockquoteRules.markdown()` fires on `>` followed by space and\nwraps the current block. Because blockquote is a wrapper/container node, the\nrule nests cleanly inside an existing quote instead of trying to retag the\nparagraph in place. The rule is gated with `enabled` so it won't fire inside a\ncode block.\n\n```tsx showLineNumbers\nimport { BlockquoteRules } from '@platejs/basic-nodes';\nimport { BlockquotePlugin } from '@platejs/basic-nodes/react';\n\nBlockquotePlugin.configure({\n  inputRules: [BlockquoteRules.markdown()],\n}),\n```\n\n**Horizontal rule.** `HorizontalRuleRules.markdown()` takes a `variant` so you can register more than one trigger.\n\n```tsx showLineNumbers\nimport { HorizontalRuleRules } from '@platejs/basic-nodes';\nimport { HorizontalRulePlugin } from '@platejs/basic-nodes/react';\n\nHorizontalRulePlugin.configure({\n  inputRules: [\n    HorizontalRuleRules.markdown({ variant: '-' }),\n    HorizontalRuleRules.markdown({ variant: '_' }),\n  ],\n}),\n```\n\n`---` and `___` both create a horizontal rule. Register only the variants you want to support.\n\n### Basic Marks\n\nMark rules live in the same package. Each factory returns a single rule, so register one per trigger you want to support.\n\n**Bold, italic, underline.**\n\n```tsx showLineNumbers\nimport {\n  BoldRules,\n  ItalicRules,\n  UnderlineRules,\n} from '@platejs/basic-nodes';\nimport {\n  BoldPlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\n\nBoldPlugin.configure({\n  inputRules: [\n    BoldRules.markdown({ variant: '*' }),\n    BoldRules.markdown({ variant: '_' }),\n  ],\n}),\nItalicPlugin.configure({\n  inputRules: [\n    ItalicRules.markdown({ variant: '*' }),\n    ItalicRules.markdown({ variant: '_' }),\n  ],\n}),\nUnderlinePlugin.configure({\n  inputRules: [UnderlineRules.markdown()],\n}),\n```\n\nRegister both `*` and `_` variants to accept `**bold**` and `__bold__`. Underline uses a fixed `__x__` form, so its factory takes no options.\n\n**Combos.** `MarkComboRules.markdown()` is a single factory that covers\nmulti-delimiter patterns like `***bold italic***`. Register combos alongside the\nsingle-mark rules on the plugin that owns the dominant mark.\n\n```tsx showLineNumbers\nimport { BoldRules, MarkComboRules } from '@platejs/basic-nodes';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\n\nBoldPlugin.configure({\n  inputRules: [\n    BoldRules.markdown({ variant: '*' }),\n    BoldRules.markdown({ variant: '_' }),\n    MarkComboRules.markdown({ variant: 'boldItalic' }),\n    MarkComboRules.markdown({ variant: 'boldUnderline' }),\n    MarkComboRules.markdown({ variant: 'boldItalicUnderline' }),\n    MarkComboRules.markdown({ variant: 'italicUnderline' }),\n  ],\n}),\n```\n\nCombo variants: `'boldItalic' | 'boldUnderline' | 'boldItalicUnderline' | 'italicUnderline'`.\n\n**Inline code, strikethrough, subscript, superscript, highlight.**\n\n```tsx showLineNumbers\nimport {\n  CodeRules,\n  HighlightRules,\n  StrikethroughRules,\n  SubscriptRules,\n  SuperscriptRules,\n} from '@platejs/basic-nodes';\nimport {\n  CodePlugin,\n  HighlightPlugin,\n  StrikethroughPlugin,\n  SubscriptPlugin,\n  SuperscriptPlugin,\n} from '@platejs/basic-nodes/react';\n\nCodePlugin.configure({\n  inputRules: [CodeRules.markdown()],\n}),\nStrikethroughPlugin.configure({\n  inputRules: [StrikethroughRules.markdown()],\n}),\nSubscriptPlugin.configure({\n  inputRules: [SubscriptRules.markdown()],\n}),\nSuperscriptPlugin.configure({\n  inputRules: [SuperscriptRules.markdown()],\n}),\nHighlightPlugin.configure({\n  inputRules: [\n    HighlightRules.markdown({ variant: '==' }),\n    HighlightRules.markdown({ variant: '≡' }),\n  ],\n}),\n```\n\nInline code uses `` `x` ``. Strikethrough uses `~~x~~`. Subscript is `~x~`.\nSuperscript is `^x^`. Highlight accepts `==x==` or `≡x≡` — pick either, both,\nor pass a different variant.\n\n### Code Blocks\n\nCode blocks are fenced, which makes them different from simple marks or block prefixes. They ship as a **block fence rule**, and you must pass `on` to pick when the fence commits.\n\n```tsx showLineNumbers\nimport { CodeBlockRules } from '@platejs/code-block';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\n\nCodeBlockPlugin.configure({\n  inputRules: [CodeBlockRules.markdown({ on: 'match' })],\n}),\n```\n\n`on: 'match'` commits the moment the fence text becomes complete — typing the third backtick of the opening fence converts the paragraph to a code block immediately.\n\n```tsx showLineNumbers\nCodeBlockPlugin.configure({\n  inputRules: [CodeBlockRules.markdown({ on: 'break' })],\n}),\n```\n\n`on: 'break'` waits for you to press Enter after the fence is complete. Same matcher, different commit point.\n\n<Callout>\n**Why `on` is required.** `match` and `break` are meaningfully different UX choices — instant vs deferred. The factory refuses to guess which one you want.\n</Callout>\n\nIf you need to suppress code blocks inside a specific context, pass `enabled`:\n\n```tsx\nCodeBlockRules.markdown({\n  on: 'match',\n  enabled: ({ editor }) => !isInsideSomeCustomContainer(editor),\n}),\n```\n\n### Lists\n\nList rules live in `@platejs/list` and register on the single `ListPlugin`. Each factory targets one shape.\n\n```tsx showLineNumbers\nimport {\n  BulletedListRules,\n  OrderedListRules,\n  TaskListRules,\n} from '@platejs/list';\nimport { ListPlugin } from '@platejs/list/react';\n\nListPlugin.configure({\n  inputRules: [\n    BulletedListRules.markdown({ variant: '-' }),\n    BulletedListRules.markdown({ variant: '*' }),\n    OrderedListRules.markdown({ variant: '.' }),\n    OrderedListRules.markdown({ variant: ')' }),\n    TaskListRules.markdown({ checked: false }),\n    TaskListRules.markdown({ checked: true }),\n  ],\n}),\n```\n\n- `BulletedListRules.markdown({ variant })` accepts `'-'` or `'*'`.\n- `OrderedListRules.markdown({ variant })` accepts `'.'` or `')'`. The rule parses the leading number and starts the list from it, so `3. ` creates a list that begins at 3.\n- `TaskListRules.markdown({ checked })` maps `[]` to an unchecked task and `[x]` to a checked task.\n\nAll list rules use `enabled` to opt out inside code blocks.\n\n### Math\n\nMath has two shapes: inline `$x$` and block `$$x$$`. They're intentionally split so apps can enable one without the other.\n\n```tsx showLineNumbers\nimport { MathRules } from '@platejs/math';\nimport { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';\n\nInlineEquationPlugin.configure({\n  inputRules: [MathRules.markdown({ variant: '$' })],\n}),\nEquationPlugin.configure({\n  inputRules: [MathRules.markdown({ variant: '$$', on: 'break' })],\n}),\n```\n\n`variant: '$'` is inline — a delimited mark rule. `variant: '$$'` is a block\nfence, so — like `CodeBlockRules` — it takes `on: 'match' | 'break'`. The\nexample uses `on: 'break'`, which commits the block equation when you press\nEnter after the fence.\n\n### Links\n\nLink rules are not just substitutions — they validate URLs and wrap text with a full link node.\n\n```tsx showLineNumbers\nimport { LinkRules } from '@platejs/link';\nimport { LinkPlugin } from '@platejs/link/react';\n\nLinkPlugin.configure({\n  inputRules: [\n    LinkRules.markdown(),\n    LinkRules.autolink({ variant: 'paste' }),\n    LinkRules.autolink({ variant: 'space' }),\n    LinkRules.autolink({ variant: 'break' }),\n  ],\n}),\n```\n\n- `LinkRules.markdown()` handles `[label](https://...)`.\n- `LinkRules.autolink({ variant: 'paste' })` turns a pasted URL into a link.\n- `LinkRules.autolink({ variant: 'space' })` detects a URL when you type a trailing space.\n- `LinkRules.autolink({ variant: 'break' })` detects a URL when you press Enter.\n\nRegister whichever variants you want — you can pick one, two, or all four.\n\nThat's the full feature-owned catalog. Every package you add contributes its own rules; the editor picks them up the moment you register them.\n\n## Local Copied Shortcuts\n\nSometimes you want small text substitutions — smart quotes, arrows, fractions,\ntrademarks — that don't belong to any feature package. Use\n`createTextSubstitutionInputRule` for this.\n\n### createTextSubstitutionInputRule\n\nThe helper takes a `patterns` array and builds a single `insertText` rule. Each pattern has a `match` (what you type) and a `format` (what you end up with).\n\n```tsx showLineNumbers\nimport {\n  createSlatePlugin,\n  createTextSubstitutionInputRule,\n  KEYS,\n  type SlateEditor,\n} from 'platejs';\n\nconst isInCodeBlock = (editor: SlateEditor) =>\n  editor.api.some({\n    match: { type: [editor.getType(KEYS.codeBlock)] },\n  });\n\nconst arrowsRule = createTextSubstitutionInputRule({\n  enabled: ({ editor }) => !isInCodeBlock(editor),\n  patterns: [\n    { format: '→', match: '->' },\n    { format: '←', match: '<-' },\n    { format: '⇒', match: '=>' },\n    { format: '⇐', match: ['<=', '≤='] },\n  ],\n});\n\nexport const ArrowsShortcutsPlugin = createSlatePlugin({\n  key: 'arrowsShortcuts',\n  inputRules: [arrowsRule],\n});\n```\n\nA few details worth knowing:\n\n- `match` can be a string or an array of strings — all entries are checked before formatting.\n- `format` can be a single string or a `[open, close]` tuple. A tuple wraps the typed text as `open + content + close`, useful for smart quotes and brackets.\n- `trigger` defaults to the last character of each match, which is almost always what you want. Override it only if you need a distinct commit char.\n- `enabled` gates the whole rule, so you don't have to guard each pattern individually.\n\n### Use AutoformatKit As A Starting Point\n\nThe `autoformat-kit.tsx` file in Plate's registry wires up a full set of\nsubstitutions — arrows, comparisons, equalities, fractions, legal symbols, smart\nquotes, sub/superscript numerals — all gated against code blocks. Copy it into\nyour app and edit the patterns.\n\n```tsx showLineNumbers title=\"autoformat-kit.tsx\"\nimport {\n  createSlatePlugin,\n  createTextSubstitutionInputRule,\n  KEYS,\n  type SlateEditor,\n} from 'platejs';\n\nconst isTextSubstitutionBlocked = (editor: SlateEditor) =>\n  editor.api.some({\n    match: { type: [editor.getType(KEYS.codeBlock)] },\n  });\n\nconst createAutoformatTextSubstitutionRule = ({\n  patterns,\n}: {\n  patterns: Parameters<typeof createTextSubstitutionInputRule>[0]['patterns'];\n}) =>\n  createTextSubstitutionInputRule({\n    enabled: ({ editor }) => !isTextSubstitutionBlocked(editor),\n    patterns,\n  });\n\nconst legalRule = createAutoformatTextSubstitutionRule({\n  patterns: [\n    { format: '™', match: ['(tm)', '(TM)'] },\n    { format: '®', match: ['(r)', '(R)'] },\n    { format: '©', match: ['(c)', '(C)'] },\n  ],\n});\n\nconst smartQuotesRule = createAutoformatTextSubstitutionRule({\n  patterns: [\n    { format: ['“', '”'], match: '\"' },\n    { format: ['‘', '’'], match: \"'\" },\n  ],\n});\n\nconst AutoformatShortcutsPlugin = createSlatePlugin({\n  key: 'autoformatShortcuts',\n  inputRules: [legalRule, smartQuotesRule],\n});\n\nexport const AutoformatKit = [AutoformatShortcutsPlugin];\n```\n\n<Callout>\n**Use copied code for local substitutions.** `@platejs/autoformat` still ships\nas a compatibility package for autoformat imports. Author project-specific\nsubstitutions in copied registry code or your own local kit so the rules stay\nvisible and editable.\n</Callout>\n\n### When To Reach For defineInputRule\n\nText substitution covers the glyph-for-glyph case. When you need more — reading\nthe current block, dispatching a richer transform, or detecting a pattern that\nisn't a simple character match — drop down to `defineInputRule` or one of the\nlow-level builders.\n\n## Custom Rules\n\nEverything above is sugar on top of three low-level authoring surfaces:\n\n- `defineInputRule`, an identity function that types a raw rule inline.\n- The specialized builders: `createMarkInputRule`, `createBlockStartInputRule`,\n  `createBlockFenceInputRule`, `createTextSubstitutionInputRule`.\n- `createRuleFactory`, the package-authoring helper used to expose semantic\n  families like `MathRules.markdown(...)` or `LinkRules.autolink(...)` without\n  re-declaring shared runtime fields.\n\nUse this section when none of the shipped families fit your need or when you're authoring your own reusable rule family.\n\n### Register Explicit Rule Instances\n\nRules are concrete objects. You pass them into `inputRules` exactly as-is. To\noverride a field like `priority` or `enabled` on a finished instance, spread the\nrule and replace the field:\n\n```tsx showLineNumbers\nimport { HeadingRules } from '@platejs/basic-nodes';\nimport { LinkRules } from '@platejs/link';\nimport { H1Plugin } from '@platejs/basic-nodes/react';\nimport { LinkPlugin } from '@platejs/link/react';\n\nH1Plugin.configure({\n  inputRules: [HeadingRules.markdown()],\n}),\nLinkPlugin.configure({\n  inputRules: [\n    LinkRules.markdown(),\n    { ...LinkRules.autolink({ variant: 'paste' }), priority: 200 },\n  ],\n}),\n```\n\nWhy the spread? Package factories are intentionally narrow — most don't take a\n`priority` option. Overriding is the caller's job, and spreading the returned\nrule keeps it obvious that you're changing one field on an already-built\ninstance.\n\nThe same pattern works for `enabled`. If a family ships with a sensible default but you need stricter gating in one app, spread the rule and override:\n\n```tsx\n{\n  ...BlockquoteRules.markdown(),\n  enabled: ({ editor }) => !isInsideCallout(editor),\n},\n```\n\n### Plugin-Side Factories\n\nFor plugin authors, the `inputRules` option also accepts a function that\nreceives a `rule` builder. Use this form when your rules depend on plugin-local\nstate or when you want your rule wiring to live next to the plugin's types.\n\n```tsx showLineNumbers\nimport { createSlatePlugin, KEYS } from 'platejs';\n\nconst CustomPlugin = createSlatePlugin({\n  key: 'custom',\n  inputRules: ({ rule }) => [\n    rule.mark({\n      trigger: '*',\n      start: '*',\n    }),\n    rule.blockStart({\n      trigger: ' ',\n      match: '>',\n      mode: 'wrap',\n      node: 'blockquote',\n    }),\n    rule.blockFence({\n      fence: '```',\n      on: 'match',\n      apply: (context, match) => {\n        context.editor.tf.delete({ at: match.range });\n        context.editor.tf.setNodes(\n          { type: context.editor.getType(KEYS.codeBlock) },\n          { at: match.path }\n        );\n      },\n    }),\n  ],\n});\n```\n\nThe `rule` builder exposes six primitives:\n\n| Method | Wraps |\n| ------ | ----- |\n| `rule.mark(config)` | `createMarkInputRule` |\n| `rule.blockStart(config)` | `createBlockStartInputRule` |\n| `rule.blockFence(config)` | `createBlockFenceInputRule` |\n| `rule.insertText(rule)` | typed `defineInputRule` for `insertText` |\n| `rule.insertBreak(rule)` | typed `defineInputRule` for `insertBreak` |\n| `rule.insertData(rule)` | typed `defineInputRule` for `insertData` |\n\nUse the factory form when you need that co-location; use the array form when you just want to register a handful of concrete instances.\n\n### Author A Rule Family\n\nWhen you're shipping a package, you usually want a public factory like\n`MyNodeRules.markdown(...)` that takes a narrow options object and hides the\nlow-level rule plumbing. Reach for `createRuleFactory`.\n\n```tsx showLineNumbers\nimport { createRuleFactory, KEYS } from 'platejs';\n\nexport const BlockquoteRules = {\n  markdown: createRuleFactory<{}, { marker: string }>({\n    type: 'blockStart',\n    marker: '>',\n    trigger: ' ',\n    mode: 'wrap',\n    match: ({ marker }) => marker,\n    enabled: ({ editor }) =>\n      !editor.api.some({ match: { type: [editor.getType(KEYS.codeBlock)] } }),\n  }),\n};\n```\n\nA few things to notice:\n\n- `type` picks the underlying builder. Use `'mark'`, `'blockStart'`, `'blockFence'`, or `'textSubstitution'`.\n- The two generic parameters model your public API: `TRequired` (the first) are\n  options callers must pass; `TDefaults` (the second) are options you supply a\n  default for — here, `marker: '>'`.\n- Factory values can be plain values (`'>'`) or functions of the input\n  (`({ marker }) => marker`). The input includes the runtime context, your\n  defaults, and any required options the caller passes.\n- For `blockStart`, core already owns the base match payload: `{ range, text }`.\n  If you provide `resolveMatch`, return only your extra fields — core merges\n  them onto the base payload before `apply` runs.\n- `enabled` and `priority` stay available as runtime overrides on the returned rule instance — callers can override them even when your factory sets a default.\n\nThat's the pattern every `*Rules.markdown(...)` family in Plate uses. Clone it when you need your own.\n\nDone. Between `defineInputRule`, the four specialized builders, the plugin-side\n`rule` builder, and `createRuleFactory`, every rule in Plate — yours included —\ncomes from the same small core.\n\n## How Rule Execution Works\n\nInput rules run inside the `insertText`, `insertBreak`, and `insertData`\ntransforms. For each call, the runtime walks every registered rule for that\ntarget in priority order, and the first rule that passes `enabled` and produces\na non-undefined `resolve` gets to call `apply`.\n\n### Targets\n\nA rule's `target` field picks which lane it runs in.\n\n| Target | Fires on |\n| ------ | -------- |\n| `insertText` | Each character typed into the editor |\n| `insertBreak` | Each time the user presses `Enter` |\n| `insertData` | Each time data is pasted or dropped |\n\nThe high-level factories set `target` for you:\n\n- `createMarkInputRule` and `createBlockStartInputRule` always produce `insertText` rules.\n- `createBlockFenceInputRule` produces an `insertText` rule when `on: 'match'` and an `insertBreak` rule when `on: 'break'`.\n- `createTextSubstitutionInputRule` always produces an `insertText` rule.\n\n`defineInputRule` lets you pick any target by setting the `target` field directly.\n\n### Selection Context\n\nEvery `enabled`, `resolve`, and `apply` call receives a context object with the live editor, the selection state, and a handful of lazy helpers.\n\n| Field | Returns |\n| ----- | ------- |\n| `editor` | The current `SlateEditor` |\n| `isCollapsed` | Whether the selection is collapsed |\n| `pluginKey` | The key of the plugin the rule is attached to |\n| `getBlockEntry()` | The current block's `NodeEntry`, or `undefined` |\n| `getBlockStartRange()` | The range from block start to current selection |\n| `getBlockStartText()` | The text from block start to current selection |\n| `getBlockTextBeforeSelection()` | The text in the current block before the cursor |\n| `getCharBefore()` | The character immediately before the cursor |\n| `getCharAfter()` | The character immediately after the cursor |\n\nThe `get*` helpers are memoized — calling them twice inside the same rule evaluation doesn't recompute. That matters because multiple rules can share the same evaluation pass.\n\nTarget-specific context fields:\n\n- `insertText` rules additionally receive `text`, `cause: 'insertText'`, and an `insertText` callback for default fallthrough.\n- `insertBreak` rules receive `cause: 'insertBreak'` and an `insertBreak` callback.\n- `insertData` rules receive `data: DataTransfer`, `text`, `cause: 'insertData'`, and an `insertData` callback.\n\n### Lifecycle\n\nFor each transform call, the runtime walks rules in priority order (highest first). For each rule, it runs the following steps:\n\n1. **`enabled`.** A boolean gate. If it returns `false`, skip to the next rule.\n2. **`resolve`.** Computes a match payload. If it returns `undefined`, skip to the next rule.\n3. **`apply`.** Performs the transform. If it returns `false`, the runtime\n   treats the rule as not consumed and continues; any other return value\n   consumes the input and short-circuits the rest of the walk.\n\nIf no rule consumes the input, the default Slate transform runs as normal.\n\n| Field | Purpose |\n| ----- | ------- |\n| `trigger` | Restricts an `insertText` rule to fire only when the typed character matches (string or array) |\n| `enabled` | Policy gate, evaluated first |\n| `resolve` | Computes the match payload passed to `apply` |\n| `apply` | Performs the transform |\n| `priority` | Sort order for rules on the same target |\n| `on` | Block-fence commit mode: `'match'` or `'break'` |\n| `mimeTypes` | Narrows an `insertData` rule to specific MIME types |\n\n<Callout>\n**Use `enabled` for policy, not `match`.** The matcher should own the *syntax*\nof a rule (what pattern counts as a hit). Gating — \"don't fire in code blocks\",\n\"only fire when this plugin is active\" — belongs in `enabled`. Returning\n`undefined` from `resolve` just to suppress a rule works, but it hides intent\nand makes rules harder to compose.\n</Callout>\n\n## API Reference\n\nLow-level surface. Reach for this when the high-level factories don't cover your case. Everything below is exported from `platejs`.\n\n### Rule Targets\n\n```ts\ntype InputRuleTarget = 'insertText' | 'insertBreak' | 'insertData';\n```\n\n### defineInputRule\n\nIdentity function that types a rule inline.\n\n```ts\nfunction defineInputRule<TRule extends AnyInputRule>(rule: TRule): TRule;\n```\n\n```ts\nimport { defineInputRule } from 'platejs';\n\nconst copyrightRule = defineInputRule({\n  target: 'insertText',\n  trigger: ')',\n  resolve: (context) => {\n    if (context.text !== ')') return;\n    if (!context.getBlockTextBeforeSelection().endsWith('(c')) return;\n\n    return { replacement: '©' };\n  },\n  apply: ({ editor }, match) => {\n    editor.tf.delete({ distance: 2, reverse: true, unit: 'character' });\n    editor.tf.insertText(match.replacement);\n  },\n});\n```\n\nUse it when you want a raw rule object typed against `InsertTextInputRule`, `InsertBreakInputRule`, or `InsertDataInputRule` without going through a builder.\n\n### createRuleFactory\n\nPackage-facing helper for semantic rule families. Use it when you want to expose\na narrow public factory like `BlockquoteRules.markdown(...)`, keep shared\nruntime fields like `enabled` and `priority`, and hide the low-level rule\nconstruction details.\n\n```ts\nimport { createRuleFactory, KEYS } from 'platejs';\n\nexport const BlockquoteRules = {\n  markdown: createRuleFactory<{}, { marker: string }>({\n    type: 'blockStart',\n    marker: '>',\n    trigger: ' ',\n    match: ({ marker }) => marker,\n    mode: 'wrap',\n    enabled: ({ editor }) =>\n      !editor.api.some({\n        match: { type: [editor.getType(KEYS.codeBlock)] },\n      }),\n  }),\n};\n```\n\nThe returned function is your public rule family. Concrete values in the config\nbecome default public options (`marker: '>'`), while the generic type parameters\nlet you model required options and defaults for the family. The created rule\ninstance still supports the shared runtime overrides: `enabled` and `priority`.\n\n### createMarkInputRule\n\nDelimited inline marks (`**bold**`, `` `code` ``, `~~strike~~`).\n\n```ts\nfunction createMarkInputRule(config: {\n  start: string;\n  end?: string;\n  trigger: string;\n  mark?: string;\n  marks?: string[];\n  trim?: 'allow' | 'reject';\n  enabled?: (context: InsertTextInputRuleContext) => boolean;\n  priority?: number;\n}): InsertTextInputRule;\n```\n\n```ts\nimport { createMarkInputRule } from 'platejs';\n\ncreateMarkInputRule({\n  start: '**',\n  end: '*',\n  trigger: '*',\n});\n```\n\n`start` is the opening delimiter. `end` is an optional closing delimiter; when\nomitted, the rule does not look for a separate closing delimiter before the\ntrigger. `trigger` is the character that commits the match. `trim: 'reject'`\nrefuses spans with leading or trailing whitespace. `mark` and `marks` restrict\nwhich mark(s) the rule applies.\n\n### createBlockStartInputRule\n\nBlock-start patterns typed at the beginning of a block (`# `, `> `, `- `, `1. `).\n\n```ts\nfunction createBlockStartInputRule<TMatch extends object = {}>(config: {\n  trigger: string;\n  match:\n    | RegExp\n    | string\n    | ((context: InsertTextInputRuleContext) => RegExp | string | undefined);\n  mode?: 'set' | 'toggle' | 'wrap';\n  node?: string;\n  removeMatchedText?: boolean;\n  resolveMatch?: (args: {\n    match: RegExpMatchArray | string;\n    range: TRange;\n    text: string;\n  }) => TMatch | undefined;\n  apply?: (\n    context: InsertTextInputRuleContext,\n    match: BlockStartInputRuleMatch & TMatch\n  ) => boolean | void;\n  enabled?: (context: InsertTextInputRuleContext) => boolean;\n  priority?: number;\n}): InsertTextInputRule<TMatch>;\n```\n\n```ts\nimport { createBlockStartInputRule } from 'platejs';\n\ncreateBlockStartInputRule({\n  trigger: ' ',\n  match: '>',\n  mode: 'wrap',\n});\n```\n\n- `trigger` is the commit character — typically `' '`.\n- `match` is the block-start text: a string, a `RegExp`, or a function that returns one based on context.\n- `mode` picks the transform: `'set'` replaces the block type, `'toggle'` flips it, `'wrap'` wraps the block in a new element.\n- `node` is the target element type used by `mode`.\n- `apply` overrides the built-in transform entirely — supply your own when none\n  of the modes fit. That also means you own matched-text cleanup; if you still\n  want the shorthand removed, delete `match.range` yourself.\n- `resolveMatch` returns extra fields only. Core still provides the base `{ range, text }` payload automatically, and `apply` receives the merged object.\n\n### createBlockFenceInputRule\n\nFenced block patterns like triple-backtick code fences or `$$` math fences.\n\n```ts\nfunction createBlockFenceInputRule<TMatch>(config: {\n  fence: string;\n  on: 'break' | 'match';\n  apply: (context: SelectionInputRuleContext, match: TMatch) => boolean | void;\n  block?: string;\n  resolveMatch?: (args: {\n    fence: string;\n    path: Path;\n    range: TRange;\n    text: string;\n  }) => TMatch | undefined;\n  enabled?: (context: SelectionInputRuleContext) => boolean;\n  priority?: number;\n}): InsertTextInputRule<TMatch> | InsertBreakInputRule<TMatch>;\n```\n\n```ts\nimport { createBlockFenceInputRule, KEYS } from 'platejs';\n\ncreateBlockFenceInputRule({\n  fence: '```',\n  on: 'match',\n  apply: (context, match) => {\n    context.editor.tf.delete({ at: match.range });\n    context.editor.tf.setNodes(\n      { type: context.editor.getType(KEYS.codeBlock) },\n      { at: match.path }\n    );\n  },\n});\n```\n\n`on: 'match'` commits when the fence becomes complete inside the current\nparagraph. `on: 'break'` commits when Enter is pressed after the fence is\ncomplete — useful when the user may want to type more before committing.\n\nThe runtime owns the matcher: it checks that the selection is collapsed, the\ncursor is at the block's end, and the block text equals `fence`. If you pass\n`block`, the matcher also requires that block type. You own `apply`, which\nperforms the replacement. The returned rule targets `insertText` when\n`on: 'match'` and `insertBreak` when `on: 'break'`.\n\n### createTextSubstitutionInputRule\n\nGlyph-for-glyph substitutions.\n\n```ts\nfunction createTextSubstitutionInputRule(config: {\n  patterns: Array<{\n    format: readonly [string, string] | string;\n    match: readonly string[] | string;\n    trigger?: readonly string[] | string;\n  }>;\n  enabled?: (context: InsertTextInputRuleContext) => boolean;\n  priority?: number;\n}): InsertTextInputRule;\n```\n\n```ts\nimport { createTextSubstitutionInputRule } from 'platejs';\n\ncreateTextSubstitutionInputRule({\n  patterns: [\n    { format: '→', match: '->' },\n    { format: ['«', '»'], match: '<<' },\n  ],\n});\n```\n\n`format` is either a replacement string or a `[open, close]` tuple that wraps\nthe typed content. `match` is a string or array of strings that trigger the\nreplacement. `trigger` defaults to the last character of each match and rarely\nneeds overriding.\n\n### matchDelimitedInline\n\nLow-level matcher used under `createMarkInputRule`. Returns a `{ content, deleteRange }` match for a delimited inline pattern, or `undefined`.\n\n```ts\nimport { matchDelimitedInline } from 'platejs';\n\nconst match = matchDelimitedInline(context, {\n  open: '**',\n  close: '*',\n  requireClosingDelimiter: true,\n  trim: 'reject',\n});\n```\n\nUse it when you're authoring a custom `insertText` rule that needs the same matching shape as a mark without going through `createMarkInputRule`.\n\n### matchBlockStart / matchBlockFence\n\nCompanion matchers for block-start and block-fence rules. Both take the current\ncontext plus a matcher config and return a match payload or `undefined`. Use\nthem when you want the matcher logic without the factory's `apply` wiring.\n\n```ts\nimport { matchBlockFence, matchBlockStart } from 'platejs';\n\nconst startMatch = matchBlockStart(context, { match: '>' });\nconst fenceMatch = matchBlockFence(context, { fence: '```' });\n```\n\n### Package Rule Families\n\nEvery family below is a single export from its package. Each `.markdown()` (or `.autolink()`) call returns a concrete rule you pass into the matching plugin's `inputRules`.\n\n| Family | Package | Description |\n| ------ | ------- | ----------- |\n| `HeadingRules` | `@platejs/basic-nodes` | Markdown prefix rules for H1–H6, derived from plugin key |\n| `BlockquoteRules` | `@platejs/basic-nodes` | `>` block-wrap rule, gated out of code blocks |\n| `HorizontalRuleRules` | `@platejs/basic-nodes` | `---` and `___` variant rules |\n| `BoldRules` | `@platejs/basic-nodes` | `**x**` / `__x__` mark rule |\n| `ItalicRules` | `@platejs/basic-nodes` | `*x*` / `_x_` mark rule |\n| `UnderlineRules` | `@platejs/basic-nodes` | `__x__` mark rule |\n| `CodeRules` | `@platejs/basic-nodes` | `` `x` `` inline code mark rule |\n| `StrikethroughRules` | `@platejs/basic-nodes` | `~~x~~` mark rule |\n| `SubscriptRules` | `@platejs/basic-nodes` | `~x~` mark rule |\n| `SuperscriptRules` | `@platejs/basic-nodes` | `^x^` mark rule |\n| `HighlightRules` | `@platejs/basic-nodes` | `==x==` / `≡x≡` mark rule |\n| `MarkComboRules` | `@platejs/basic-nodes` | Multi-mark combo rules (bold/italic/underline) |\n| `CodeBlockRules` | `@platejs/code-block` | Triple-backtick block fence rule, requires `on` |\n| `BulletedListRules` | `@platejs/list` | `-` / `*` bulleted list rule |\n| `OrderedListRules` | `@platejs/list` | `1.` / `1)` ordered list rule, preserves start number |\n| `TaskListRules` | `@platejs/list` | `[]` / `[x]` task list rule |\n| `MathRules` | `@platejs/math` | Inline `$x$` and block `$$x$$` rules |\n| `LinkRules` | `@platejs/link` | Markdown `[label](url)` and autolink rules |\n\nEach family's factory takes a narrow options object — see the per-section examples above for the exact shape.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-input-rules.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-methods-docs",
      "title": "Plugin Methods",
      "description": "Configure, extend, and override Plate plugins.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-methods.mdx",
          "content": "---\ntitle: Plugin Methods\ndescription: Configure, extend, and override Plate plugins.\n---\n\nPlugin methods return new plugin instances, so you can keep a base plugin stable and derive app-specific behavior from it. Use `.configure()` for existing fields, `.extend*()` for typed additions, and `.overrideEditor()` only when wrapping editor APIs or transforms that already exist. This guide maps each method to the runtime surface it changes.\n\n## On This Page\n\n- [Method Map](#method-map)\n- [Configure Existing Fields](#configure-existing-fields)\n- [Configure Nested Plugins](#configure-nested-plugins)\n- [Extend The Plugin](#extend-the-plugin)\n- [Selectors](#selectors)\n- [API And Transforms](#api-and-transforms)\n- [Override Editor Methods](#override-editor-methods)\n- [Components](#components)\n- [Convert Slate Plugins](#convert-slate-plugins)\n- [API Reference](#api-reference)\n\n## Method Map\n\n| Method | Use it for | Writes to |\n| --- | --- | --- |\n| `.configure()` | Change existing plugin fields without widening the public type. | The current plugin. |\n| `.configurePlugin()` | Change an existing nested plugin. | A child plugin already present in `plugins`. |\n| `.extend()` | Add typed options, handlers, renderers, rules, or runtime hooks. | The current plugin. |\n| `.extendPlugin()` | Extend a nested plugin, or add a keyed nested plugin when missing. | A child plugin under `plugins`. |\n| `.extendSelectors()` | Add computed option selectors. | `getOption()` and `usePluginOption()`. |\n| `.extendApi()` | Add plugin-specific API methods. | `editor.api[plugin.key]`. |\n| `.extendEditorApi()` | Add editor-wide API methods. | `editor.api`. |\n| `.extendTransforms()` | Add plugin-specific transforms. | `editor.tf[plugin.key]`. |\n| `.extendEditorTransforms()` | Add editor-wide transforms. | `editor.tf`. |\n| `.overrideEditor()` | Wrap existing editor API or transform methods. | `editor.api` and `editor.tf`. |\n| `.withComponent()` | Attach a node component to a plugin. | `plugin.node.component` and `plugin.render.node`. |\n| `.clone()` | Copy a plugin definition. | A new plugin object. |\n\nPlugin method callbacks receive the same context described in [Plugin Context](/docs/plate/plugin-context): `editor`, `plugin`, `api`, `tf`, `getOption`, `getOptions`, `setOption`, `setOptions`, and `type`.\n\n## Configure Existing Fields\n\nUse `.configure()` when the plugin already has the field and you only need to change its value.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { H1Plugin } from '@platejs/basic-nodes/react';\n\nexport const AppH1Plugin = H1Plugin.configure({\n  shortcuts: {\n    toggle: { keys: 'mod+alt+1' },\n  },\n});\n```\n\nFunction configs run when the plugin resolves inside an editor, so they can read the current plugin options.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { NavigationFeedbackPlugin } from 'platejs/react';\n\nconst LongerFlashPlugin = NavigationFeedbackPlugin.configure(\n  ({ getOption }) => ({\n    options: {\n      duration: getOption('duration') + 400,\n    },\n  })\n);\n```\n\nObject configs are merged with the plugin through Plate's plugin merge rules: objects merge deeply, arrays are replaced, and `options` are shallow merged.\n\n<Callout type=\"info\" title=\"Configure does not widen types\">\n  `.configure()` is for existing plugin fields. If you need TypeScript to know\n  about a new option, API method, transform, selector, handler, or renderer,\n  use `.extend()` or the narrower `.extend*()` method.\n</Callout>\n\n## Configure Nested Plugins\n\nUse `.configurePlugin()` when a parent plugin owns a child plugin and you want to adjust that child without replacing the whole parent.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nconst CellPlugin = createPlatePlugin({\n  key: 'cell',\n  options: {\n    padding: 12,\n  },\n});\n\nexport const GridPlugin = createPlatePlugin({\n  key: 'grid',\n  plugins: [CellPlugin],\n}).configurePlugin(CellPlugin, {\n  options: {\n    padding: 8,\n  },\n});\n```\n\n`.configurePlugin()` searches nested `plugins` recursively. If the target plugin is not found, Plate leaves the parent unchanged.\n\nUse `.extendPlugin()` when the child needs new typed behavior.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nexport const GridWithCellShortcutPlugin = GridPlugin.extendPlugin(CellPlugin, {\n  shortcuts: {\n    insertBelow: {\n      keys: 'mod+enter',\n      handler: ({ event }) => {\n        event.preventDefault();\n\n        return true;\n      },\n    },\n  },\n});\n```\n\n<Callout type=\"note\" title=\"Missing nested plugins\">\n  `.configurePlugin()` does not add a missing child. `.extendPlugin()` does:\n  when the target key is not found, Plate adds a keyed plugin at the top level\n  of the parent's `plugins` array and applies the extension there.\n</Callout>\n\n## Extend The Plugin\n\nUse `.extend()` for broad plugin additions. Object extensions merge immediately; function extensions run during plugin resolution with the current editor context.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const MentionPlugin = createPlatePlugin({\n  key: 'mention',\n  node: {\n    isElement: true,\n    isInline: true,\n  },\n}).extend(({ editor }) => ({\n  handlers: {\n    onKeyDown: ({ event }) => {\n      if (event.key === 'Escape') {\n        editor.tf.deselect();\n        event.preventDefault();\n      }\n    },\n  },\n  options: {\n    trigger: '@',\n  },\n}));\n```\n\nUse `.extend()` when one extension naturally touches several plugin fields. Use the narrower methods below when the addition is specifically an API method, transform, selector, or editor override.\n\n## Selectors\n\nUse `.extendSelectors()` for derived option values that components can subscribe to. Selectors are available through `getOption()` and React hooks such as `usePluginOption()`.\n\n```tsx title=\"counter-plugin.tsx\" showLineNumbers\nimport { type PluginConfig } from 'platejs';\nimport { createTPlatePlugin, usePluginOption } from 'platejs/react';\n\ntype CounterOptions = {\n  value: number;\n};\n\ntype CounterSelectors = {\n  doubled: (factor: number) => number;\n  isEven: () => boolean;\n};\n\ntype CounterConfig = PluginConfig<\n  'counter',\n  CounterOptions,\n  {},\n  {},\n  CounterSelectors\n>;\n\nexport const CounterPlugin = createTPlatePlugin<CounterConfig>({\n  key: 'counter',\n  options: {\n    value: 1,\n  },\n}).extendSelectors<CounterSelectors>(({ getOptions }) => ({\n  doubled: (factor) => getOptions().value * factor,\n  isEven: () => getOptions().value % 2 === 0,\n}));\n\nexport function CounterValue() {\n  const doubled = usePluginOption(CounterPlugin, 'doubled', 2);\n  const isEven = usePluginOption(CounterPlugin, 'isEven');\n  const value = usePluginOption(CounterPlugin, 'value');\n\n  return (\n    <span>\n      {value} / {doubled} / {isEven ? 'even' : 'odd'}\n    </span>\n  );\n}\n```\n\nSelectors are the right place for derived state. Use `.extendApi()` when the method is a query or utility that should not subscribe React components to option changes.\n\n## API And Transforms\n\nUse API methods for reads and utilities. Use transforms for operations that mutate editor state.\n\n| Method | Access path | Typical use |\n| --- | --- | --- |\n| `.extendApi()` | `editor.api.counter.isEmpty()` | Plugin-specific query or utility. |\n| `.extendEditorApi()` | `editor.api.counterLabel()` | Editor-wide query or utility. |\n| `.extendTransforms()` | `editor.tf.counter.increment()` | Plugin-specific mutation. |\n| `.extendEditorTransforms()` | `editor.tf.resetCounter()` | Editor-wide mutation. |\n\n```tsx title=\"counter-plugin.tsx\" showLineNumbers\nimport { type PluginConfig } from 'platejs';\nimport { createTPlatePlugin } from 'platejs/react';\n\ntype CounterOptions = {\n  value: number;\n};\n\ntype CounterPluginApi = {\n  isEmpty: () => boolean;\n};\n\ntype CounterEditorApi = {\n  counterLabel: () => string;\n};\n\ntype CounterPluginTransforms = {\n  increment: () => void;\n};\n\ntype CounterEditorTransforms = {\n  resetCounter: () => void;\n};\n\ntype CounterConfig = PluginConfig<'counter', CounterOptions>;\n\nexport const CounterPlugin = createTPlatePlugin<CounterConfig>({\n  key: 'counter',\n  options: {\n    value: 0,\n  },\n})\n  .extendApi<CounterPluginApi>(({ getOption }) => ({\n    isEmpty: () => getOption('value') === 0,\n  }))\n  .extendEditorApi<CounterEditorApi>(({ getOption }) => ({\n    counterLabel: () => `Count: ${getOption('value')}`,\n  }))\n  .extendTransforms<CounterPluginTransforms>(({ getOption, setOption }) => ({\n    increment: () => setOption('value', getOption('value') + 1),\n  }))\n  .extendEditorTransforms<CounterEditorTransforms>(({ setOption }) => ({\n    resetCounter: () => setOption('value', 0),\n  }));\n```\n\nAfter the plugin resolves in an editor, call those methods from their resolved surfaces.\n\n```ts\neditor.api.counter.isEmpty();\neditor.api.counterLabel();\neditor.tf.counter.increment();\neditor.tf.resetCounter();\n```\n\n`editor.tf` is the short alias for `editor.transforms`; both access the same transform tree.\n\n## Override Editor Methods\n\nUse `.overrideEditor()` when you need to wrap existing editor API or transform methods and keep access to the original method.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const LimitExclamationPlugin = createPlatePlugin({\n  key: 'limitExclamation',\n}).overrideEditor(({ tf: { insertText } }) => ({\n  transforms: {\n    insertText(text, options) {\n      insertText(text === '!' ? '.' : text, options);\n    },\n  },\n}));\n```\n\nThe callback can override API methods, transform methods, or both.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nconst TrimStringPlugin = createPlatePlugin({\n  key: 'trimString',\n}).overrideEditor(({ api: { string } }) => ({\n  api: {\n    string(at, options) {\n      return string(at, options).trim();\n    },\n  },\n}));\n```\n\nKeep new editor methods in `.extendEditorApi()` or `.extendEditorTransforms()`. `.overrideEditor()` is for changing behavior while preserving the original call path.\n\n## Components\n\nUse `.withComponent()` to bind a Plate UI node component to a plugin. It writes both `node.component` and `render.node`, which keeps the component available to the editor renderer and plugin metadata.\n\n```tsx title=\"plugins.tsx\" showLineNumbers\nimport { ParagraphPlugin } from 'platejs/react';\n\nimport { ParagraphElement } from '@/components/ui/paragraph-node';\n\nexport const AppParagraphPlugin =\n  ParagraphPlugin.withComponent(ParagraphElement);\n```\n\nUse `.withComponent()` for the common one-component case. Use `.extend()` when the same plugin also needs render wrappers, handlers, options, or rules.\n\n## Convert Slate Plugins\n\nUse `toPlatePlugin()` when you have a headless Slate plugin and need to add React-only fields such as `render`, `handlers`, or `useHooks`.\n\n```tsx title=\"mention-plugin.tsx\" showLineNumbers\nimport { createTSlatePlugin } from 'platejs';\nimport { toPlatePlugin } from 'platejs/react';\n\nimport { MentionElement } from '@/components/ui/mention-node';\n\nconst BaseMentionPlugin = createTSlatePlugin({\n  key: 'mention',\n  node: {\n    isElement: true,\n    isInline: true,\n  },\n});\n\nexport const MentionPlugin = toPlatePlugin(BaseMentionPlugin, {\n  render: {\n    node: MentionElement,\n  },\n});\n```\n\n`toPlatePlugin()` wraps the same plugin methods, so a converted plugin can still use `.configure()`, `.extend()`, `.extendApi()`, `.overrideEditor()`, and `.withComponent()`.\n\n## API Reference\n\n| Method | Accepts | Resolution behavior |\n| --- | --- | --- |\n| `.configure(config)` | Object or callback returning a partial plugin config. | Stores one configuration callback and applies it before function extensions. |\n| `.configurePlugin(plugin, config)` | Target plugin and object or callback config. | Recursively configures an existing nested plugin; missing target is ignored. |\n| `.extend(config)` | Object or callback returning a partial plugin config. | Object configs merge immediately; callback configs run during plugin resolution. |\n| `.extendPlugin(plugin, config)` | Target plugin and object or callback extension. | Recursively extends a nested plugin; missing target is added by key. |\n| `.extendSelectors(callback)` | Callback returning selector functions. | Extends the plugin option store selectors. |\n| `.extendApi(callback)` | Callback returning functions. | Merges into `editor.api[plugin.key]` and `plugin.api[plugin.key]`. |\n| `.extendEditorApi(callback)` | Callback returning functions or one-level nested function objects. | Merges into `editor.api` and `plugin.api`. |\n| `.extendTransforms(callback)` | Callback returning functions. | Merges into `editor.tf[plugin.key]` and `plugin.transforms[plugin.key]`. |\n| `.extendEditorTransforms(callback)` | Callback returning functions or one-level nested function objects. | Merges into `editor.tf` and `plugin.transforms`. |\n| `.overrideEditor(callback)` | Callback returning `{ api?, transforms? }`. | Deep-merges overrides into editor and plugin API/transform objects. |\n| `.withComponent(component)` | A node component. | Sets `node.component` and `render.node`. |\n| `.clone()` | No arguments. | Returns a merged copy of the plugin definition. |\n\nDone. Use configuration for existing fields, extension methods for new typed surface, and overrides only when the editor method already exists.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-methods.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-rules-docs",
      "title": "Plugin Rules",
      "description": "Configure common editing behaviors.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-rules.mdx",
          "content": "---\ntitle: Plugin Rules\ndescription: Configure common editing behaviors.\n---\n\nPlugin Rules control how editor nodes respond to common user actions. Instead of overriding the editor methods, you can configure these behaviors directly on a plugin's `rules` property.\n\nThis guide shows you how to use `rules.break`, `rules.delete`, `rules.merge`, `rules.normalize`, `rules.selection`\n and `rules.match` to create intuitive editing experiences.\n\n<ComponentPreview name=\"plugin-rules-demo\" />\n\n## Actions\n\nPlugin rules use specific action names to define behavior:\n\n- **`'default'`**: Default Slate behavior.\n- **`'reset'`**: Changes the current block to a default paragraph, keeping content.\n- **`'exit'`**: Exits the current block, inserting a new paragraph after it. See [Exit Break](/docs/plate/exit-break) to learn more about this behavior.\n- **`'lift'`**: Lifts the current block out of the nearest matching ancestor container, changing one structural level at a time.\n- **`'deleteExit'`**: Deletes content then exits the block.\n- **`'lineBreak'`**: Inserts a line break (`\\n`) instead of splitting the block.\n\n### `default`\n\nStandard Slate behavior. For `rules.break`, splits the block. For `rules.delete`, merges with the previous block.\n\n```tsx\n<p>\n  Hello world|\n</p>\n```\n\nAfter pressing `Enter`:\n\n```tsx\n<p>Hello world</p>\n<p>\n  |\n</p>\n```\n\nAfter pressing `Backspace`:\n\n```tsx\n<p>Hello world|</p>\n```\n\n### `reset`\n\nConverts the current block to a default paragraph while preserving content. Custom properties are removed.\n\n```tsx\n<h3 listStyleType=\"disc\">\n  |\n</h3>\n```\n\nAfter pressing `Enter` with `rules: { break: { empty: 'reset' } }`:\n\n```tsx\n<p>\n  |\n</p>\n```\n\n### `exit`\n\nExits the current block structure by inserting a new paragraph after it.\n\n```tsx\n<blockquote>\n  |\n</blockquote>\n```\n\nAfter pressing `Enter` with `rules: { break: { empty: 'exit' } }`:\n\n```tsx\n<blockquote>\n  <text />\n</blockquote>\n<p>\n  |\n</p>\n```\n\n### `lift`\n\nLifts the current block out of the nearest matching ancestor container.\n\n```tsx\n<blockquote>\n  <p>\n    |\n  </p>\n</blockquote>\n```\n\nAfter pressing `Enter` with `rules: { break: { empty: 'lift' } }`:\n\n```tsx\n<p>\n  |\n</p>\n```\n\n### `deleteExit`\n\nDeletes content then exits the block.\n\n```tsx\n<blockquote>\n  line1\n  |\n</blockquote>\n```\n\nAfter pressing `Enter` with `rules: { break: { emptyLineEnd: 'deleteExit' } }`:\n\n```tsx\n<blockquote>line1</blockquote>\n<p>\n  |\n</p>\n```\n\n### `lineBreak`\n\nInserts a soft line break (`\\n`) instead of splitting the block.\n\n```tsx\n<blockquote>\n  Hello|\n</blockquote>\n```\n\nAfter pressing `Enter` with `rules: { break: { default: 'lineBreak' } }`:\n\n```tsx\n<blockquote>\n  Hello\n  |\n</blockquote>\n```\n\n## `rules.break`\n\nControls what happens when users press `Enter` within specific block types.\n\n### Configuration\n\n```tsx\nCalloutPlugin.configure({\n  rules: {\n    break: {\n      // Action when Enter is pressed normally\n      default: 'default' | 'lineBreak' | 'exit' | 'deleteExit',\n      \n      // Action when Enter is pressed in an empty block\n      empty: 'default' | 'reset' | 'exit' | 'lift' | 'deleteExit',\n      \n      // Action when Enter is pressed at end of empty line\n      emptyLineEnd: 'default' | 'exit' | 'deleteExit',\n\n      // If true, the new block after splitting will be reset\n      splitReset: boolean,\n    },\n  },\n});\n```\n\nEach property controls a specific scenario:\n\n- `default`\n  - [`'default'`](#default)\n  - [`'lineBreak'`](#linebreak)\n  - [`'exit'`](#exit)\n  - [`'deleteExit'`](#deleteexit)\n\n- `empty`\n  - [`'default'`](#default)\n  - [`'reset'`](#reset)\n  - [`'exit'`](#exit)\n  - [`'lift'`](#lift)\n  - [`'deleteExit'`](#deleteexit)\n\n- `emptyLineEnd`\n  - [`'default'`](#default)\n  - [`'exit'`](#exit)\n  - [`'deleteExit'`](#deleteexit)\n\n- `splitReset`: If `true`, resets the new block to the default type after a split. This is useful for exiting a formatted block like a heading.\n\n### Examples\n\n**Reset heading on break:**\n\n```tsx\nimport { H1Plugin } from '@platejs/heading/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  H1Plugin.configure({\n    rules: {\n      break: {\n        splitReset: true,\n      },\n    },\n  }),\n];\n```\n\nBefore pressing `Enter`:\n\n```tsx\n<h1>\n  Heading|text\n</h1>\n```\n\nAfter (split and reset):\n\n```tsx\n<h1>\n  Heading\n</h1>\n<p>\n  |text\n</p>\n```\n\n**Callout with line breaks and smart exits:**\n\n```tsx\nimport { CalloutPlugin } from '@platejs/callout/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CalloutPlugin.configure({\n    rules: {\n      break: {\n        default: 'lineBreak',\n        empty: 'reset',\n        emptyLineEnd: 'deleteExit',\n      },\n    },\n  }),\n];\n```\n\nBefore pressing `Enter` in callout:\n```tsx\n<callout>\n  Quote text|\n</callout>\n```\n\nAfter (line break):\n```tsx\n<callout>\n  Quote text\n  |\n</callout>\n```\n\n**Code block with custom empty handling:**\n\n```tsx\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CodeBlockPlugin.configure({\n    rules: {\n      delete: { empty: 'reset' },\n      match: ({ editor, rule }) => {\n        return rule === 'delete.empty' && isCodeBlockEmpty(editor);\n      },\n    },\n  }),\n];\n```\n\nBefore pressing `Backspace` in empty code block:\n```tsx\n<code_block>\n  <code_line>\n    |\n  </code_line>\n</code_block>\n```\n\nAfter (reset):\n```tsx\n<p>\n  |\n</p>\n```\n\n## `rules.delete`\n\nControls what happens when users press `Backspace` at specific positions.\n\n### Configuration\n\n```tsx\nHeadingPlugin.configure({\n  rules: {\n    delete: {\n      // Action when Backspace is pressed at block start\n      start: 'default' | 'reset' | 'lift',\n      \n      // Action when Backspace is pressed in empty block\n      empty: 'default' | 'reset',\n    },\n  },\n});\n```\n\nEach property controls a specific scenario:\n\n- `start`\n  - [`'default'`](#default)\n  - [`'reset'`](#reset)\n  - [`'lift'`](#lift)\n\n- `empty`\n  - [`'default'`](#default)\n  - [`'reset'`](#reset)\n\n### Examples\n\n**Reset callouts at start:**\n\n```tsx\nimport { CalloutPlugin } from '@platejs/callout/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CalloutPlugin.configure({\n    rules: {\n      delete: { start: 'reset' },\n    },\n  }),\n];\n```\n\nBefore pressing `Backspace` at start:\n```tsx\n<callout>\n  |Callout content\n</callout>\n```\n\nAfter (reset):\n```tsx\n<p>\n  |Callout content\n</p>\n```\n\n**List items with start reset:**\n\n```tsx\nimport { ListPlugin } from '@platejs/list/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  ListPlugin.configure({\n    rules: {\n      delete: { start: 'reset' },\n      match: ({ rule, node }) => {\n        return rule === 'delete.start' && Boolean(node.listStyleType);\n      },\n    },\n  }),\n];\n```\n\nBefore pressing `Backspace` at start of list item:\n```tsx\n<p listStyleType=\"disc\">\n  |List item content\n</p>\n```\n\nAfter (reset):\n```tsx\n<p>\n  |List item content\n</p>\n```\n\n## `rules.merge`\n\nControls how blocks behave when merging with previous blocks.\n\n### Configuration\n\n```tsx\nParagraphPlugin.configure({\n  rules: {\n    merge: {\n      // Whether to remove empty blocks when merging\n      removeEmpty: boolean,\n    },\n  },\n});\n```\n\n### Examples\n\nOnly paragraph and heading plugins enable removal by default. Most other plugins use `false`:\n\n```tsx\nimport { H1Plugin, ParagraphPlugin } from 'platejs/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  H1Plugin, // rules.merge: { removeEmpty: true } by default\n  ParagraphPlugin, // rules.merge: { removeEmpty: true } by default\n];\n```\n\nBefore pressing `Backspace` at start:\n```tsx\n<p>\n  <text />\n</p>\n<h1>\n  |Heading content\n</h1>\n```\n\nAfter (empty paragraph removed):\n```tsx\n<h1>\n  |Heading content\n</h1>\n```\n\n**Callout with removal disabled:**\n\n```tsx\nimport { CalloutPlugin } from '@platejs/callout/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CalloutPlugin.configure({\n    rules: {\n      merge: { removeEmpty: false }, // Default\n    },\n  }),\n];\n```\n\nBefore pressing `Backspace` at start:\n```tsx\n<p>\n  <text />\n</p>\n<callout>\n  |Callout content\n</callout>\n```\n\nAfter (empty paragraph preserved):\n```tsx\n<p>\n  |Code content\n</p>\n```\n\n**Table cells preserve structure during merge:**\n\n```tsx\nimport { TablePlugin } from '@platejs/table/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  TablePlugin, // Table cells have rules.merge: { removeEmpty: false }\n];\n```\n\nBefore pressing `Delete` at end of paragraph:\n```tsx\n<p>\n  Content|\n</p>\n<table>\n  <tr>\n    <td>\n      <p>Cell data</p>\n    </td>\n    <td>\n      <p>More data</p>\n    </td>\n  </tr>\n</table>\n```\n\nAfter (cell content merged, structure preserved):\n```tsx\n<p>\n  Content|Cell data\n</p>\n<table>\n  <tr>\n    <td>\n      <p>\n        <text />\n      </p>\n    </td>\n    <td>\n      <p>More data</p>\n    </td>\n  </tr>\n</table>\n```\n\n<Callout>\nSlate's default is `true` since the default block (paragraph) is first-class, while Plate plugins are likely used to define other node behaviors that shouldn't automatically remove empty predecessors.\n</Callout>\n\n## `rules.normalize`\n\nControls how nodes are normalized during the normalization process.\n\n### Configuration\n\n```tsx\nLinkPlugin.configure({\n  rules: {\n    normalize: {\n      // Whether to remove nodes with empty text\n      removeEmpty: boolean,\n    },\n  },\n});\n```\n\n### Examples\n\n**Remove empty link nodes:**\n\n```tsx\nimport { LinkPlugin } from '@platejs/link/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  LinkPlugin.configure({\n    rules: {\n      normalize: { removeEmpty: true },\n    },\n  }),\n];\n```\n\nBefore normalization:\n```tsx\n<p>\n  <a href=\"http://google.com\">\n    <text />\n  </a>\n  <cursor />\n</p>\n```\n\nAfter normalization (empty link removed):\n```tsx\n<p>\n  <cursor />\n</p>\n```\n\n## `rules.match`\n\nThe `match` function in plugin rules allows you to override the default behavior of specific plugins based on node properties beyond just type matching. This is particularly useful when you want to extend existing node types with new behaviors.\n\n### Examples\n\n**Code block with custom empty detection:**\n\n```tsx\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CodeBlockPlugin.configure({\n    rules: {\n      delete: { empty: 'reset' },\n      match: ({ rule, node }) => {\n        return rule === 'delete.empty' && isCodeBlockEmpty(editor);\n      },\n    },\n  }),\n];\n```\n\nSince the list plugin extends existing blocks that already have their own plugin configuration (e.g. `ParagraphPlugin`), using `rules.match` allows you to override those behaviors.\n\n**List override for paragraphs:**\n\n```tsx\nimport { ListPlugin } from '@platejs/list/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  ListPlugin.configure({\n    rules: {\n      match: ({ editor, rule }) => {\n        return rule === 'delete.empty' && isCodeBlockEmpty(editor);\n      },\n    },\n  }),\n];\n```\n\n## Custom Reset Logic\n\nSome plugins need special reset behavior beyond the standard paragraph conversion. You can override the `resetBlock` transform:\n\n**List plugin reset (outdents instead of converting to paragraph):**\n\n```tsx\nconst ListPlugin = createPlatePlugin({\n  key: 'list',\n  // ... other config\n}).overrideEditor(({ editor, tf: { resetBlock } }) => ({\n  transforms: {\n    resetBlock(options) {\n      if (editor.api.block(options)?.[0]?.listStyleType) {\n        outdentList();\n        return;\n      }\n      \n      return resetBlock(options);\n    },\n  },\n}));\n```\n\n**Code block reset (unwraps instead of converting):**\n\n```tsx\nconst CodeBlockPlugin = createPlatePlugin({\n  key: 'code_block',\n  // ... other config\n}).overrideEditor(({ editor, tf: { resetBlock } }) => ({\n  transforms: {\n    resetBlock(options) {\n      if (editor.api.block({\n        at: options?.at,\n        match: { type: 'code_block' },\n      })) {\n        unwrapCodeBlock();\n        return;\n      }\n      \n      return resetBlock(options);\n    },\n  },\n}));\n```\n\n## Combining Rules\n\nYou can combine different rules for comprehensive block behavior:\n\n```tsx\nimport { H1Plugin } from '@platejs/heading/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  H1Plugin.configure({\n    rules: {\n      break: {\n        empty: 'reset',\n        splitReset: true,\n      },\n      delete: {\n        start: 'reset',\n      },\n    },\n  }),\n];\n```\n\n**Line break behavior (default):**\n```tsx\n<blockquote>\n  Hello|\n</blockquote>\n```\n\nAfter `Enter`:\n```tsx\n<blockquote>\n  Hello\n  |\n</blockquote>\n```\n\n**Empty reset behavior:**\n```tsx\n<blockquote>\n  |\n</blockquote>\n```\n\nAfter `Enter`:\n```tsx\n<p>\n  |\n</p>\n```\n\n**Start reset behavior:**\n```tsx\n<blockquote>\n  |Quote content\n</blockquote>\n```\nAfter `Backspace`:\n```tsx\n<p>\n  |Quote content\n</p>\n```\n\n## Advanced\n\nFor complex scenarios beyond simple rules, you can override editor transforms directly using [`.overrideEditor`](/docs/plate/plugin-methods#overrideeditor). This gives you complete control over transforms like [`resetBlock`](/docs/plate/plugin-methods#extendtransforms) and [`insertExitBreak`](/docs/plate/plugin-methods#extendtransforms):\n\n```tsx\nconst CustomPlugin = createPlatePlugin({\n  key: 'custom',\n  // ... other config\n}).overrideEditor(({ editor, tf: { insertBreak, deleteBackward, resetBlock } }) => ({\n  transforms: {\n    insertBreak() {\n      const block = editor.api.block();\n      \n      if (/* Custom condition */) {\n        // Custom behavior\n        return;\n      }\n      \n      // Default behavior\n      insertBreak();\n    },\n    \n    deleteBackward(unit) {\n      const block = editor.api.block();\n      \n      if (/* Custom condition */) {\n        // Custom behavior\n        return;\n      }\n      \n      deleteBackward(unit);\n    },\n    \n    resetBlock(options) {\n      if (/* Custom condition */) {\n        // Custom behavior\n        return true;\n      }\n      \n      return resetBlock(options);\n    },\n  },\n}));\n```\n\n## `rules.selection`\n\nControls how cursor positioning and text insertion behave at node boundaries, particularly for marks and inline elements.\n\n### Configuration\n\n```tsx\nBoldPlugin.configure({\n  rules: {\n    selection: {\n      // Define selection behavior at boundaries\n      affinity: 'default' | 'directional' | 'outward' | 'hard',\n    },\n  },\n});\n```\n\n### Affinity Options\n\nThe `affinity` property determines how the cursor behaves when positioned at the boundary between different marks or inline elements:\n\n#### `default`\n\nUses Slate's default behavior. For marks, the cursor has outward affinity at the start edge (typing before the mark doesn't apply it) and inward affinity at the end edge (typing after the mark extends it).\n\n**At end of mark (inward affinity):**\n```tsx\n<p>\n  <text bold>Bold text|</text><text>Normal text</text>\n</p>\n```\n\nTyping would extend the bold formatting to new text.\n\n**At start of mark (outward affinity):**\n```tsx\n<p>\n  <text>Normal text|</text><text bold>Bold text</text>\n</p>\n```\n\nTyping would not apply bold formatting to new text.\n\n#### `directional`\n\nSelection affinity is determined by the direction of cursor movement. When the cursor moves to a boundary, it maintains the affinity based on where it came from.\n\n```tsx\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  BoldPlugin.configure({\n    rules: {\n      selection: { affinity: 'directional' },\n    },\n  }),\n];\n```\n\n**Movement from right (inward affinity):**\n```tsx\n<p>\n  <text>Normal</text><text bold>B|old text</text>\n</p>\n```\n\nAfter pressing `←`:\n```tsx\n<p>\n  <text>Normal</text><text bold>|Bold text</text>\n</p>\n```\n\nTyping would extend the bold formatting, which is not possible with `default` affinity.\n\n```tsx\nimport { LinkPlugin } from '@platejs/link/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  LinkPlugin.configure({\n    rules: {\n      selection: { affinity: 'directional' },\n    },\n  }),\n];\n```\n\n**Movement from right (outward affinity):**\n```tsx\n<p>\n  Visit <a href=\"https://example.com\">our website</a> |for more information text.\n</p>\n```\n\nAfter pressing `←`:\n```tsx\n<p>\n  Visit <a href=\"https://example.com\">our website</a>| for more information text.\n</p>\n```\n\nCursor movement direction determines whether new text extends the link or creates new text outside it.\n\n#### `outward`\n\nForces outward affinity, automatically clearing marks when typing at their boundaries. This creates a natural \"exit\" behavior from formatted text.\n\n```tsx\nimport { CommentPlugin } from '@platejs/comment/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CommentPlugin.configure({\n    rules: {\n      selection: { affinity: 'outward' },\n    },\n  }),\n];\n```\n\n**At end of marked text:**\n```tsx\n<p>\n  <text comment>Commented text|</text><text>Normal</text>\n</p>\n```\n\nAfter typing:\n```tsx\n<p>\n  <text comment>Commented text</text><text>x|Normal</text>\n</p>\n```\n\nUsers automatically exit comment formatting by typing at the end of commented text.\n\n#### `hard`\n\nCreates a \"hard\" edge that requires two key presses to move across. This provides precise cursor control for elements that need exact positioning.\n\n```tsx\nimport { CodePlugin } from '@platejs/basic-nodes/react';\n\nconst plugins = [\n  // ...otherPlugins,\n  CodePlugin.configure({\n    rules: {\n      selection: { affinity: 'hard' },\n    },\n  }),\n];\n```\n\n**Moving across hard edges:**\n```tsx\n<p>\n  <text>Before</text><text code>code|</text><text>After</text>\n</p>\n```\n\nFirst `→` press changes affinity:\n```tsx\n<p>\n  <text>Before</text><text code>code</text>|<text>After</text>\n</p>\n```\n\nSecond `→` press moves cursor:\n```tsx\n<p>\n  <text>Before</text><text code>code</text><text>A|fter</text>\n</p>\n```\n\nThis allows users to position the cursor precisely at the boundary and choose whether new text should be inside or outside the code formatting.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-rules.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-shortcuts-docs",
      "title": "Plugin Shortcuts",
      "description": "Configure keyboard shortcuts on Plate plugins.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin-shortcuts.mdx",
          "content": "---\ntitle: Plugin Shortcuts\ndescription: Configure keyboard shortcuts on Plate plugins.\n---\n\nPlugin shortcuts map key combinations to plugin methods or explicit handlers. Plate resolves shortcuts during plugin setup, stores them on `editor.meta.shortcuts`, and renders them through `EditorHotkeysEffect` inside the editable. This guide covers linked methods, custom handlers, overrides, priorities, and default shortcut ownership.\n\n## How Shortcuts Resolve\n\nEach plugin owns a `shortcuts` object. At resolution time Plate namespaces every shortcut as `${plugin.key}.${shortcutName}`.\n\nWhen a shortcut has no `handler`, Plate looks for a matching plugin-specific method in this order:\n\n1. `editor.tf[plugin.key][shortcutName]`\n2. `editor.api[plugin.key][shortcutName]`\n\nIf neither method exists and no `handler` is provided, the shortcut is ignored by `EditorHotkeysEffect`.\n\n| Field | Meaning |\n| --- | --- |\n| `keys` | Key combination passed to `useHotkeys`. Use a string like `'mod+b'` or arrays like `[[Key.Mod, 'b']]`. |\n| `handler` | Explicit callback receiving `{ editor, event, eventDetails }`. |\n| `priority` | Shortcut priority. Defaults to the parent plugin priority. |\n| `preventDefault` | Passed through to `useHotkeys`. When omitted, Plate calls `event.preventDefault()` and `event.stopPropagation()` after handled shortcuts. |\n| `null` | Removes that named shortcut from the plugin. |\n\n## Linked Transform Shortcuts\n\nUse a linked transform when the shortcut name and plugin transform name are the same.\n\n```tsx title=\"plugins/signature-plugin.tsx\" showLineNumbers\nimport { Key, createPlatePlugin } from 'platejs/react';\n\nexport const SignaturePlugin = createPlatePlugin({\n  key: 'signature',\n})\n  .extendTransforms(({ editor }) => ({\n    insertSignature: () => {\n      editor.tf.insertText(' - Plate');\n    },\n  }))\n  .extend({\n    shortcuts: {\n      insertSignature: {\n        keys: [[Key.Mod, Key.Shift, 's']],\n      },\n    },\n  });\n```\n\nPressing `Mod+Shift+S` calls `editor.tf.signature.insertSignature()`.\n\n## Linked API Shortcuts\n\nIf there is no matching transform, Plate falls back to the plugin-specific API method.\n\n```tsx title=\"plugins/inspect-plugin.tsx\" showLineNumbers\nimport { Key, createPlatePlugin } from 'platejs/react';\n\nexport const InspectPlugin = createPlatePlugin({\n  key: 'inspect',\n})\n  .extendApi(({ editor }) => ({\n    logText: () => {\n      editor.api.debug.info('Editor text', editor.api.string([]));\n    },\n  }))\n  .extend({\n    shortcuts: {\n      logText: {\n        keys: [[Key.Mod, Key.Alt, 'l']],\n      },\n    },\n  });\n```\n\nPressing `Mod+Alt+L` calls `editor.api.inspect.logText()`.\n\n<Callout type=\"info\" title=\"Transforms win\">\n  If a transform and an API method share the same shortcut name, Plate uses the\n  transform. Pick distinct names when you need both actions.\n</Callout>\n\n## Custom Handlers\n\nUse a `handler` when the shortcut needs the keyboard event, custom branching, or work that should not live as a plugin API/transform method.\n\n```tsx title=\"plugins/draft-plugin.tsx\" showLineNumbers\nimport { Key, createPlatePlugin } from 'platejs/react';\n\nexport const DraftPlugin = createPlatePlugin({\n  key: 'draft',\n}).extend({\n  shortcuts: {\n    saveDraft: {\n      keys: [[Key.Mod, 's']],\n      handler: ({ editor }) => {\n        const text = editor.api.string([]);\n\n        if (text.trim().length === 0) return false;\n\n        editor.api.debug.info('Draft text', text);\n\n        return true;\n      },\n    },\n  },\n});\n```\n\nReturning `false` means \"not handled\"; Plate will not call `preventDefault()` for that key press. Returning `true` or `undefined` means handled when `preventDefault` is omitted.\n\n## Prevent Default\n\nPlate has two layers of default-prevention behavior:\n\n| Configuration | Behavior |\n| --- | --- |\n| `preventDefault` omitted and handler returns anything except `false` | Plate calls `event.preventDefault()` and `event.stopPropagation()`. |\n| Handler returns `false` | Plate leaves the event alone. |\n| `preventDefault` is set | Plate passes the option to `useHotkeys` and skips its own `preventDefault()` call. |\n\nUse the default omission for normal editor commands. Set `preventDefault` only when you intentionally want `useHotkeys` to own that behavior.\n\n## Configure Existing Shortcuts\n\nConfigure a named shortcut to change its keys.\n\n```tsx title=\"plugins/basic-marks.tsx\" showLineNumbers\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\nimport { Key } from 'platejs/react';\n\nexport const AppBoldPlugin = BoldPlugin.configure({\n  shortcuts: {\n    toggle: {\n      keys: [[Key.Mod, Key.Shift, 'b']],\n    },\n  },\n});\n```\n\nSet a shortcut to `null` to remove it.\n\n```tsx title=\"plugins/basic-marks.tsx\" showLineNumbers\nimport { ItalicPlugin } from '@platejs/basic-nodes/react';\n\nexport const AppItalicPlugin = ItalicPlugin.configure({\n  shortcuts: {\n    toggle: null,\n  },\n});\n```\n\nThe `null` value removes `italic.toggle` from `editor.meta.shortcuts`.\n\n## Multiple Shortcuts\n\nA plugin can declare multiple shortcut names. Keep each name aligned with the method it should call.\n\n```tsx title=\"plugins/review-plugin.tsx\" showLineNumbers\nimport { Key, createPlatePlugin } from 'platejs/react';\n\nexport const ReviewPlugin = createPlatePlugin({\n  key: 'review',\n})\n  .extendTransforms(({ editor }) => ({\n    accept: () => editor.tf.insertText('Accepted'),\n    reject: () => editor.tf.insertText('Rejected'),\n  }))\n  .extend({\n    shortcuts: {\n      accept: {\n        keys: [[Key.Mod, Key.Alt, 'a']],\n      },\n      reject: {\n        keys: [[Key.Mod, Key.Alt, 'r']],\n      },\n    },\n  });\n```\n\nThis creates `review.accept` and `review.reject` in `editor.meta.shortcuts`.\n\n## Priority\n\nShortcut priority defaults to the parent plugin priority. Set `priority` on a shortcut when two handlers use the same key combination and one should win.\n\n```tsx title=\"plugins/priority-plugin.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const PriorityPlugin = createPlatePlugin({\n  key: 'priority',\n  priority: 20,\n}).extend({\n  shortcuts: {\n    openCommandMenu: {\n      keys: 'mod+k',\n      priority: 200,\n      handler: ({ editor }) => {\n        editor.api.debug.info('Open command menu');\n\n        return true;\n      },\n    },\n  },\n});\n```\n\nPlate stores the resolved priority with the shortcut and passes it to `useHotkeys`.\n\n## Editor-Level Shortcuts\n\n`createPlateEditor({ shortcuts })` attaches shortcuts to the root plugin. Use it for editor-wide commands that do not belong to one feature plugin.\n\n```tsx title=\"editor.ts\" showLineNumbers\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  shortcuts: {\n    reportWordCount: {\n      keys: 'mod+shift+w',\n      handler: ({ editor }) => {\n        const words = editor.api.string([]).trim().split(/\\s+/).filter(Boolean);\n\n        editor.api.debug.info('Word count', words.length);\n\n        return true;\n      },\n    },\n  },\n});\n```\n\nInternally this becomes a root shortcut, so plugin-owned shortcuts are still the better fit for feature-owned behavior.\n\n## Default Shortcuts\n\n| Plugin | Shortcut name | Keys |\n| --- | --- | --- |\n| `BoldPlugin` | `toggle` | `Mod+B` |\n| `ItalicPlugin` | `toggle` | `Mod+I` |\n| `UnderlinePlugin` | `toggle` | `Mod+U` |\n| `ParagraphPlugin` | `toggleParagraph` | `Mod+Alt+0`, `Mod+Shift+0` |\n| `CopilotPlugin` | `accept` | `Tab` |\n| `CopilotPlugin` | `reject` | `Escape` |\n\nOther plugins often expose `toggle`, `insert`, or feature-specific transforms without default keys. Add shortcuts in your app when those commands should be keyboard-accessible.\n\n## API Reference\n\n```ts title=\"Shortcut type\"\ntype Shortcut = HotkeysOptions & {\n  keys?: Keys | null;\n  priority?: number;\n  handler?: (ctx: {\n    editor: PlateEditor;\n    event: KeyboardEvent;\n    eventDetails: HotkeysEvent;\n  }) => boolean | void;\n};\n```\n\nDone. Name shortcuts after plugin-specific transforms or API methods by default, and use handlers only when the keyboard event is part of the behavior.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin-shortcuts.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "plugin-docs",
      "title": "Plugin Configuration",
      "description": "How to configure and customize Plate plugins.",
      "files": [
        {
          "path": "../../content/docs/(guides)/plugin.mdx",
          "content": "---\ntitle: Plugin Configuration\ndescription: How to configure and customize Plate plugins.\n---\n\nPlate plugins are highly configurable, allowing you to customize their behavior to suit your needs. This guide will walk you through the most common configuration options and how to use them.\n\n- [Getting Started: Components](/docs/plate/installation#components) - Instructions for adding plugins to your editor\n- [PlatePlugin API](/docs/plate/api/core/plate-plugin) - The complete API reference for creating plugins\n\n## Basic Plugin Configuration\n\n### New Plugin\n\nThe most basic plugin configuration requires only a `key`:\n\n```ts\nconst MyPlugin = createPlatePlugin({\n  key: 'minimal',\n});\n```\n\nWhile this plugin doesn't do anything yet, it's a starting point for more complex configurations.\n\n### Existing Plugin\n\nThe `.configure` method allows you to configure an existing plugin:\n\n```ts\nconst ConfiguredPlugin = MyPlugin.configure({\n  options: {\n    myOption: 'new value',\n  },\n});\n```\n\n## Node Plugins\n\nNode plugins are used to define new types of nodes in your editor using the `node` property. These can be elements (either block or inline) or leaf nodes (for text-level formatting).\n\n\n### Elements\n\nTo create a new type of element, use the `node.isElement` option:\n\n```ts\nconst ParagraphPlugin = createPlatePlugin({\n  key: 'p',\n  node: {\n    isElement: true,\n    type: 'p',\n  },\n});\n```\n\nYou can associate a component with your element. See [Plugin Components](/docs/plate/plugin-components) for more information.\n\n```ts\nconst ParagraphPlugin = createPlatePlugin({\n  key: 'p',\n  node: {\n    isElement: true,\n    type: 'p',\n    component: ParagraphElement,\n  },\n});\n```\n\n### Inline, Void, and Leaf Nodes\n\nFor inline elements, void elements, or leaf nodes, use the appropriate node options:\n\n```ts\nconst LinkPlugin = createPlatePlugin({\n  key: 'link',\n  node: {\n    isElement: true,\n    isInline: true,\n    type: 'a',\n  },\n});\n\nconst ImagePlugin = createPlatePlugin({\n  key: 'image',\n  node: {\n    isElement: true,\n    isVoid: true,\n    type: 'img',\n  },\n});\n\nconst BoldPlugin = createPlatePlugin({\n  key: 'bold',\n  node: {\n    isLeaf: true,\n  },\n});\n```\n\n## Behavioral Plugins\n\nRather than render an element or a mark, you may want to customize the behavior of your editor. Various plugin options are available to modify the behavior of Plate.\n\n### Plugin Rules\n\nThe `rules` property allows you to configure common editing behaviors like breaking, deleting, and merging nodes without overriding editor methods. This is a powerful way to define intuitive interactions for your custom elements.\n\nFor example, you can define what happens when a user presses `Enter` in an empty heading, or `Backspace` at the start of a blockquote.\n\n```ts\nimport { H1Plugin } from '@platejs/heading/react';\n\nH1Plugin.configure({\n  rules: {\n    break: { empty: 'reset' },\n  },\n});\n```\n\nSee the [Plugin Rules guide](/docs/plate/plugin-rules) for a complete list of available rules and actions.\n\n### Event Handlers\n\nThe recommended way to respond to user-generated events from inside a plugin is with the `handlers` plugin option. A handler should be a function that takes a `PlatePluginContext & { event }` object.\n\nThe `onChange` handler, which is called when the editor value changes, is an exception to this rule; the context object includes the changed `value` instead of `event`.\n\n```ts showLineNumbers\nconst ExamplePlugin = createPlatePlugin({\n  key: 'example',\n  handlers: {\n    onChange: ({ editor, value })  => {\n      console.info(editor, value);\n    },\n    onKeyDown: ({ editor, event }) => {\n      console.info(`You pressed ${event.key}`);\n    },\n  },\n});\n```\n\n### Inject Props\n\nYou may want to inject a class name or CSS property into any node having a certain property. For example, the following plugin sets the `textAlign` CSS property on paragraphs with an `align` property.\n\n```ts showLineNumbers\nimport { KEYS } from 'platejs';\n\nconst TextAlignPlugin = createPlatePlugin({\n  key: 'align',\n  inject: {\n    nodeProps: {\n      defaultNodeValue: 'start',\n      nodeKey: 'align',\n      styleKey: 'textAlign',\n      validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],\n    },\n    targetPlugins: [KEYS.p],\n    // This is injected into all `targetPlugins`. In this example, ParagraphPlugin will be able to deserialize `textAlign` style.\n    targetPluginToInject: ({ editor, plugin }) => ({\n      parsers: {\n        html: {\n          deserializer: {\n            parse: ({ element, node }) => {\n              if (element.style.textAlign) {\n                node[editor.getType('align')] = element.style.textAlign;\n              }\n            },\n          },\n        },\n      },\n    }),\n  },\n});\n```\n\nA paragraph node affected by the above plugin would look like this:\n\n```ts showLineNumbers {3}\n{\n  type: 'p',\n  align: 'right',\n  children: [{ text: 'This paragraph is aligned to the right!' }],\n}\n```\n\n### Override Editor Methods\n\nThe `overrideEditor` method provides a way to override existing editor methods while maintaining access to the original implementations. This is particularly useful when you want to modify the behavior of core editor functionality.\n\n```ts\nconst CustomPlugin = createPlatePlugin({\n  key: 'custom',\n}).overrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({\n  // Override transforms\n  transforms: {\n    deleteForward(options) {\n      // Custom logic before deletion\n      console.info('Deleting forward...');\n      \n      // Call original transform\n      deleteForward(options);\n      \n      // Custom logic after deletion\n      console.info('Deleted forward');\n    },\n  },\n  // Override API methods\n  api: {\n    isInline(element) {\n      // Custom inline element check\n      if (element.type === 'custom-inline') {\n        return true;\n      }\n      \n      // Fall back to original behavior\n      return isInline(element);\n    },\n  },\n}));\n```\n\n- Access to original methods via destructured `tf` (transforms) and `api` parameters\n- Type-safe overrides of existing methods\n- Clean separation between transforms and API methods\n- Plugin context and options access\n\nExample with typed options:\n\n```ts\ntype CustomConfig = PluginConfig<\n  'custom',\n  { allowDelete: boolean }\n>;\n\nconst CustomPlugin = createTPlatePlugin<CustomConfig>({\n  key: 'custom',\n  options: { allowDelete: true },\n}).overrideEditor(({ editor, tf: { deleteForward }, getOptions }) => ({\n  transforms: {\n    deleteForward(options) {\n      // Use typed options to control behavior\n      if (!getOptions().allowDelete) {\n        return;\n      }\n      \n      deleteForward(options);\n    },\n  },\n}));\n```\n\n### Extend Editor (Advanced)\n\nYou can extend the editor for complex functionality. To do this, you can use the `extendEditor` plugin option to directly mutate properties of the `editor` object after its creation.\n\n```ts showLineNumbers {20}\nconst CustomNormalizerPlugin = createPlatePlugin({\n  key: 'customNormalizer',\n  extendEditor: ({ editor }) => {\n    editor.customState = true;\n    \n    return editor;\n  },\n});\n```\n\n<Callout type=\"info\" title=\"Difference between extendEditor and overrideEditor\">\n- Use `extendEditor` when integrating legacy Slate plugins like `withYjs` that need direct editor mutation. There is only one `extendEditor` per plugin.\n- Prefer using `overrideEditor` for modifying editor behavior as it has single purpose responsibility and better type safety. It can be called multiple times to layer different overrides.\n</Callout>\n\n## Advanced Plugin Configuration\n\n### Plugin Store\n\nEach plugin has its own store, which can be used to manage plugin-specific state.\n\n```ts\nconst MyPlugin = createPlatePlugin({\n  key: 'myPlugin',\n  options: {\n    count: 0,\n  },\n}).extend(({ editor, plugin, setOption }) => ({\n  handlers: {\n    onClick: () => {\n      setOption('count', 1);\n    },\n  },\n}));\n```\n\nYou can access and update the store using the following methods:\n\n```ts\n// Get the current value\nconst count = editor.getOption(MyPlugin, 'count');\n\n// Set a new value\neditor.setOption(MyPlugin, 'count', 5);\n\n// Update the value based on the previous state\neditor.setOption(MyPlugin, 'count', (prev) => prev + 1);\n```\n\nIn React components, you can use the `usePluginOption` or `usePluginOptions` hook to subscribe to store changes:\n\n```tsx\nconst MyComponent = () => {\n  const count = usePluginOption(MyPlugin, 'count');\n  return <div>Count: {count}</div>;\n};\n```\n\nSee more in [Plugin Context](/docs/plate/plugin-context) and [Editor Methods](/docs/plate/editor-methods) guides.\n\n\n### Dependencies\n\nYou can specify plugin dependencies using the `dependencies` property. This ensures that the required plugins are loaded before the current plugin.\n\n```ts\nconst MyPlugin = createPlatePlugin({\n  key: 'myPlugin',\n  dependencies: ['paragraphPlugin', 'listPlugin'],\n});\n```\n\n### Enabled Flag\n\nThe `enabled` property allows you to conditionally enable or disable a plugin:\n\n```ts\nconst MyPlugin = createPlatePlugin({\n  key: 'myPlugin',\n  enabled: true, // or false to disable\n});\n```\n\n### Nested Plugins\n\nPlate supports nested plugins, allowing you to create plugin hierarchies. Use the `plugins` property to define child plugins:\n\n```ts\nconst ParentPlugin = createPlatePlugin({\n  key: 'parent',\n  plugins: [\n    createPlatePlugin({ key: 'child1' }),\n    createPlatePlugin({ key: 'child2' }),\n  ],\n});\n```\n\n### Plugin Priority\n\nThe `priority` property determines the order in which plugins are registered and executed. Plugins with higher priority values are processed first:\n\n```ts\nconst HighPriorityPlugin = createPlatePlugin({\n  key: 'highPriority',\n  priority: 100,\n});\n\nconst LowPriorityPlugin = createPlatePlugin({\n  key: 'lowPriority',\n  priority: 50,\n});\n```\n\nThis is particularly useful when you need to ensure certain plugins are initialized or run before others.\n\n### Custom Parsers\n\nThe `parsers` property accepts string keys to build your own parsers:\n\n```ts\nconst MyPlugin = createPlatePlugin({\n  key: 'myPlugin',\n  parsers: {\n    myCustomParser: {\n      deserializer: {\n        parse: // ...\n      },\n      serializer: {\n        parse: // ...\n      }\n    },\n  },\n});\n```\n\nCore plugins includes `html` and `htmlReact` parsers.\n\n## Typed Plugins\n\nUsing above methods, plugin types are automatically inferred from the given configuration.\n\nIf you need to pass an explicit type as generic, you can use `createTPlatePlugin`.\n\n### Using createTPlatePlugin\n\nThe `createTPlatePlugin` function allows you to create a typed plugin:\n\n```ts\ntype CodeBlockConfig = PluginConfig<\n  // key\n  'code_block',\n  // options\n  { syntax: boolean; syntaxPopularFirst: boolean },\n  // api\n  {\n    plugin: {\n      getSyntaxState: () => boolean;\n    };\n    toggleSyntax: () => void;\n  },\n  // transforms\n  {\n    insert: {\n      codeBlock: (options: { language: string }) => void;\n    }\n  }\n>;\n\nconst CodeBlockPlugin = createTPlatePlugin<CodeBlockConfig>({\n  key: 'code_block',\n  options: { syntax: true, syntaxPopularFirst: false },\n}).extendEditorApi<CodeBlockConfig['api']>(() => ({\n  plugin: {\n    getSyntaxState: () => true,\n  },\n  toggleSyntax: () => {},\n})).extendEditorTransforms<CodeBlockConfig['transforms']>(() => ({\n  insert: {\n    codeBlock: ({ editor, getOptions }) => {\n      editor.tf.insertBlock({ type: 'code_block', language: getOptions().language });\n    },\n  },\n}));\n```\n\n### Using Typed Plugins\n\nWhen using typed plugins, you get full type checking and autocompletion ✨\n\n```ts\nconst editor = createPlateEditor({\n  plugins: [ExtendedCodeBlockPlugin],\n});\n\n// Type-safe access to options\nconst options = editor.getOptions(ExtendedCodeBlockPlugin);\noptions.syntax;\noptions.syntaxPopularFirst;\noptions.hotkey;\n\n// Type-safe API\neditor.api.toggleSyntax();\neditor.api.plugin.getSyntaxState();\neditor.api.plugin2.setLanguage('python');\neditor.api.plugin.getLanguage();\n\n// Type-safe Transforms\neditor.tf.insert.codeBlock({ language: 'typescript' });\n```\n\n## See also\n\nSee the [PlatePlugin API](/docs/plate/api/core/plate-plugin) for more plugin options.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/plugin.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "static-docs",
      "title": "Static Rendering",
      "description": "A minimal, memoized, read-only version of Plate with RSC/SSR support.",
      "files": [
        {
          "path": "../../content/docs/(guides)/static.mdx",
          "content": "---\ntitle: Static Rendering\ndescription: A minimal, memoized, read-only version of Plate with RSC/SSR support.\n---\n\n`<PlateStatic>` is a **fast, read-only** React component for rendering Plate content, optimized for **server-side** or **React Server Component** (RSC) environments. It avoids client-side editing logic and memoizes node renders for better performance compared to using [`<Plate>`](/docs/plate/api/core/plate-components) in read-only mode.\n\nIt's a core part of [`serializeHtml`](/docs/plate/api/core/plate-plugin#serializehtml) for HTML export and is ideal for any server or RSC context needing a non-interactive, presentational view of Plate content.\n\n## Key Advantages\n\n-   **Server-Safe:** No browser API dependencies; works in SSR/RSC.\n-   **No Plate Editor Overhead:** Excludes interactive features like selections or event handlers.\n-   **Memoized Rendering:** Uses `_memo` and structural checks to re-render only changed nodes.\n-   **Partial Re-Renders:** Changes in one part of the document don't force a full re-render.\n-   **Lightweight:** Smaller bundle size as it omits interactive editor code.\n\n## When to Use `<PlateStatic>`\n\n-   Generating HTML with [HTML Serialization](/docs/plate/html).\n-   Displaying server-rendered previews in Next.js (especially with RSC).\n-   Building static sites with read-only Plate content.\n-   Optimizing performance-critical read-only views.\n-   Rendering AI-streaming content.\n\n<Callout type=\"info\" title=\"Interactive vs. Static\">\n  For interactive read-only features (like comment popovers or selections), use the standard `<Plate>` component in the browser. For purely server-rendered, non-interactive content, `<PlateStatic>` is the recommended choice.\n</Callout>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to enable static rendering is with the `BaseEditorKit`, which includes pre-configured base plugins that work seamlessly with server-side rendering.\n\n<ComponentSource name=\"editor-base-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createSlateEditor } from 'platejs';\nimport { PlateStatic } from 'platejs/static';\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\n\nconst editor = createSlateEditor({\n  plugins: BaseEditorKit,\n  value: [\n    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },\n    { type: 'p', children: [{ text: 'This content is rendered statically.' }] },\n  ],\n});\n\n// Render statically\nexport default function MyStaticPage() {\n  return <PlateStatic editor={editor} />;\n}\n```\n\n### Example\n\nSee a complete server-side static rendering example:\n\n<ComponentSource name=\"slate-to-html\" />\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Create a Slate Editor\n\nInitialize a Slate editor instance using `createSlateEditor` with your required plugins and components. This is analogous to using `usePlateEditor` for the interactive `<Plate>` component.\n\n```tsx title=\"lib/plate-static-editor.ts\"\nimport { createSlateEditor } from 'platejs';\n// Import your desired base plugins (e.g., BaseHeadingPlugin, MarkdownPlugin)\n// Ensure you are NOT importing from /react subpaths for server environments.\n\nconst editor = createSlateEditor({\n  plugins: [\n    // Add your list of base plugins here\n    // Example: BaseHeadingPlugin, MarkdownPlugin.configure({...})\n  ],\n  value: [ // Example initial value\n    {\n      type: 'p',\n      children: [{ text: 'Hello from a static Plate editor!' }],\n    },\n  ],\n});\n```\n\n### Define Static Node Components\n\nIf your interactive editor uses client-side components (e.g., with `use client` or event handlers), you **must** create static, server-safe equivalents. These components should render pure HTML without browser-specific logic.\n\n```tsx title=\"components/ui/paragraph-node-static.tsx\"\nimport React from 'react';\nimport type { SlateElementProps } from 'platejs/static';\n\nexport function ParagraphElementStatic(props: SlateElementProps) {\n  return (\n    <SlateElement {...props}>\n      {props.children}\n    </SlateElement>\n  );\n}\n```\nCreate similar static components for headings, images, links, etc.\n\n### Map Plugin Keys to Static Components\n\nCreate an object that maps plugin keys or node types to their corresponding static React components, then pass it to the editor.\n\n```ts title=\"components/static-components.ts\"\nimport { ParagraphElementStatic } from './ui/paragraph-node-static';\nimport { HeadingElementStatic } from './ui/heading-node-static';\n// ... import other static components\n\nexport const staticComponents = {\n  p: ParagraphElementStatic,\n  h1: HeadingElementStatic,\n  // ... add mappings for all your element and leaf types\n};\n```\n\n### Render `<PlateStatic>`\n\nUse the `<PlateStatic>` component, providing the `editor` instance configured with your components.\n\n```tsx title=\"app/my-static-page/page.tsx (RSC Example)\"\nimport { createSlateEditor } from 'platejs';\nimport { PlateStatic } from 'platejs/static';\n// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes'; // etc.\nimport { staticComponents } from '@/components/static-components';\n\nexport default async function MyStaticPage() {\n  // Example: Fetch or define editor value\n  const initialValue = [\n    { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },\n    { type: 'p', children: [{ text: 'Content rendered statically.' }] },\n  ];\n\n  const editor = createSlateEditor({\n    plugins: [/* your base plugins */],\n    components: staticComponents,\n    value: initialValue,\n  });\n\n  return (\n    <PlateStatic\n      editor={editor}\n      style={{ padding: 16 }}\n      className=\"my-plate-static-content\"\n    />\n  );\n}\n```\n\n<Callout type=\"note\" title=\"Value Override\">\n  If you pass a `value` prop directly to `<PlateStatic>`, it will override `editor.children`.\n  ```tsx\n  <PlateStatic\n    editor={editor}\n    value={[\n      { type: 'p', children: [{ text: 'Overridden content.' }] }\n    ]}\n  />\n  ```\n</Callout>\n\n### Memoization Details\n\n`<PlateStatic>` enhances performance through memoization:\n-   Each `<ElementStatic>` and `<LeafStatic>` is wrapped in `React.memo`.\n-   **Reference Equality:** Unchanged node references prevent re-renders.\n-   **`_memo` Field:** Setting `node._memo = true` (or any stable value) on an element or leaf can force Plate to skip re-rendering that specific node, even if its content changes. This is useful for fine-grained control over updates.\n\n</Steps>\n\n## Client-Side Alternative: `PlateView`\n\nFor cases where you need **minimal interactivity** with static content, use `<PlateView>`. This component wraps `<PlateStatic>` and adds client-side event handlers for user interactions while maintaining the performance benefits of static rendering.\n\n### Example: Server Component with Both Static Views\n\n```tsx title=\"app/document/page.tsx\"\nimport { createStaticEditor, PlateStatic } from 'platejs/static';\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\nimport { InteractiveViewer } from './interactive-viewer';\n\nexport default async function DocumentPage() {\n  const content = await fetchDocument(); // Your document data\n  \n  // Server-side static editor\n  const editor = createStaticEditor({\n    plugins: BaseEditorKit,\n    value: content,\n  });\n\n  return (\n    <div className=\"grid grid-cols-2 gap-4\">\n      {/* Pure static rendering - no interactivity */}\n      <div>\n        <h2>Static View (Server Rendered)</h2>\n        <PlateStatic editor={editor} />\n      </div>\n\n      {/* Interactive view - rendered on client */}\n      <div>\n        <h2>Interactive View</h2>\n        <InteractiveViewer value={content} />\n      </div>\n    </div>\n  );\n}\n```\n\n### Example: Client Component with PlateView\n\n```tsx title=\"app/document/interactive-viewer.tsx\"\n'use client';\n\nimport { usePlateViewEditor } from 'platejs/react';\nimport { PlateView } from 'platejs/react';\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\n\nexport function InteractiveViewer({ value }) {\n  const editor = usePlateViewEditor({\n    plugins: BaseEditorKit,\n    value,\n  });\n\n  return <PlateView editor={editor} />;\n}\n```\n\n### Key Features of `PlateView`\n\n- **Client-side only**: Requires `'use client'` directive\n- **Adds interactivity**: Enables user interactions with the content (e.g., text selection, copying, future interactions like tooltips, highlights, etc.)\n- **Minimal overhead**: Still uses `PlateStatic` internally for rendering\n- **Use with `usePlateViewEditor`**: Creates a static editor optimized for view-only React components\n- **ViewPlugin included**: The static editor automatically includes `ViewPlugin` which provides event handling capabilities\n\n<Callout type=\"warning\" title=\"Server Component Compatibility\">\n  `PlateView` cannot be used in Server Components. If you're passing an editor from a server component to a client component, you'll encounter serialization errors. Use `PlateStatic` on the server side, or create the editor client-side with `usePlateViewEditor`.\n</Callout>\n\n## `PlateStatic` vs. `PlateView` vs. `Plate` + `readOnly`\n\n| Aspect                | `<PlateStatic>`                                       | `<PlateView>`                                          | `<Plate>` + `readOnly`                                 |\n| --------------------- | ----------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |\n| **Environment**       | Server/Client (SSR/RSC safe)                          | Client-only                                            | Client-only                                            |\n| **Interactivity**     | None                                                  | Minimal (selection, copy, toolbar, etc.)     | Full interactive features (browser-only)               |\n| **Browser APIs**      | Not used                                              | Minimal (event handlers)                               | Full usage                                             |\n| **Performance**       | Best - static HTML only                               | Good - static rendering + event delegation             | Heavier - full editor internals                        |\n| **Bundle Size**       | Smallest                                              | Small                                                  | Largest                                                |\n| **Use Cases**         | Server rendering, HTML export                         | Client-side content with basic interactions            | Full read-only editor with all features                |\n| **Recommendation**    | SSR/RSC without any interactions                      | Client-side content needing light interactivity        | Client-side with complex interactive needs             |\n\n## RSC/SSR Example\n\nIn a Next.js App Router (or similar RSC environment), `<PlateStatic>` can be used directly in Server Components:\n\n```tsx title=\"app/preview/page.tsx (RSC)\"\nimport { createSlateEditor } from 'platejs';\nimport { PlateStatic } from 'platejs/static';\n// Example base plugins (ensure non-/react imports)\n// import { BaseHeadingPlugin } from '@platejs/basic-nodes';\nimport { staticComponents } from '@/components/static-components'; // Your static components mapping\n\nexport default async function Page() {\n  // Fetch or define content server-side\n  const serverContent = [\n    { type: 'h1', children: [{ text: 'Rendered on the Server! 🎉' }] },\n    { type: 'p', children: [{ text: 'This content is static and server-rendered.' }] },\n  ];\n\n  const editor = createSlateEditor({\n    // plugins: [BaseHeadingPlugin, /* ...other base plugins */],\n    plugins: [], // Add your base plugins\n    components: staticComponents,\n    value: serverContent,\n  });\n\n  return (\n    <PlateStatic\n      editor={editor}\n      className=\"my-static-preview-container\"\n    />\n  );\n}\n```\nThis renders the content to HTML on the server without needing a client-side JavaScript bundle for `PlateStatic` itself.\n\n## Pairing with `serializeHtml`\n\nFor generating a complete HTML string (e.g., for emails, PDFs, or external systems), use `serializeHtml`. It utilizes `<PlateStatic>` internally.\n\n```ts title=\"lib/html-serializer.ts\"\nimport { createSlateEditor } from 'platejs';\nimport { serializeHtml } from 'platejs/static';\nimport { staticComponents } from '@/components/static-components';\n// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes';\n\nasync function getDocumentAsHtml(value: any[]) {\n  const editor = createSlateEditor({\n    plugins: [/* ...your base plugins... */],\n    components: staticComponents,\n    value,\n  });\n\n  const html = await serializeHtml(editor, {\n    // editorComponent: PlateStatic, // Optional: Defaults to PlateStatic\n    props: { className: 'prose max-w-none' }, // Example: Pass props to the root div\n  });\n\n  return html;\n}\n\n// Example Usage:\n// const mySlateValue = [ { type: 'h1', children: [{ text: 'My Document' }] } ];\n// getDocumentAsHtml(mySlateValue).then(console.log);\n```\nFor more details, see the [HTML Serialization guide](/docs/plate/html).\n\n## API Reference\n\n### `<PlateStatic>` Props\n\n```ts\nimport type React from 'react';\nimport type { Descendant } from 'slate';\nimport type { PlateEditor } from 'platejs/core'; // Adjust imports as per your setup\n\ninterface PlateStaticProps extends React.HTMLAttributes<HTMLDivElement> {\n  /**\n   * The Plate editor instance, created via `createSlateEditor`.\n   * Must include plugins and components relevant to the content being rendered.\n   */\n  editor: PlateEditor;\n\n  /**\n   * Optional Plate `Value` (array of `Descendant` nodes).\n   * If provided, this will be used for rendering instead of `editor.children`.\n   */\n  value?: Descendant[];\n\n  /** Inline CSS styles for the root `div` element. */\n  style?: React.CSSProperties;\n\n  // Other HTMLDivElement attributes like `className`, `id`, etc., are also supported.\n}\n```\n\n-   **`editor`**: An instance of `PlateEditor` created with `createSlateEditor`, including components configuration.\n-   **`value`**: Optional. If provided, this array of `Descendant` nodes will be rendered, overriding the content currently in `editor.children`.\n\n## Next Steps\n\n-   Explore [HTML Serialization](/docs/plate/html) for exporting content.\n-   Learn about using Plate in [React Server Components](/docs/plate/installation/rsc).\n-   Refer to individual plugin documentation for their base (non-React) imports.",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/static.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "troubleshooting-docs",
      "title": "Troubleshooting",
      "description": "Diagnose common Plate setup and runtime issues.",
      "files": [
        {
          "path": "../../content/docs/(guides)/troubleshooting.mdx",
          "content": "---\ntitle: Troubleshooting\ndescription: Diagnose common Plate setup and runtime issues.\n---\n\nMost Plate issues come from version skew, duplicate React/Slate runtimes, or\ncrossing the server/client import boundary. Start with package alignment, then\ncheck runtime ownership, then inspect plugin resolution with `DebugPlugin`.\n\n## Start Here\n\n| Symptom | First check |\n| --- | --- |\n| Invalid hook call, null dispatcher, or React hook crashes. | Duplicate `react` / `react-dom` or mixed package-manager installs. |\n| Plugins render as plain text or components do not attach. | Missing plugin, wrong `/react` versus base import, or wrong component map. |\n| Server Component crashes on import. | A Server Component imported `platejs/react` or `@platejs/*/react`. |\n| `OPTION_UNDEFINED` in the console. | Code reads or writes an option that is not declared in the plugin. |\n| `PLUGIN_DEPENDENCY_MISSING` in the console. | A plugin dependency key is not present in the editor plugin list. |\n| Markdown, HTML, or static rendering misses nodes. | The static/base plugin kit does not include the serializer or node plugin. |\n\nUse the focused guide when the issue belongs to one area:\n\n| Area | Guide |\n| --- | --- |\n| Editor creation and options | [Editor](/docs/plate/editor) |\n| Plugin configuration | [Plugin](/docs/plate/plugin) |\n| Plugin option state | [Plugin Context](/docs/plate/plugin-context) |\n| Debug logging | [Debugging](/docs/plate/debugging) |\n| Server Components | [RSC](/docs/plate/installation/rsc) |\n| Static rendering | [Static Rendering](/docs/plate/static) |\n\n## Align Plate Packages\n\nKeep `platejs` and every `@platejs/*` package on the same release family.\n`depset` is the fastest way to update a project that already has several Plate\npackages installed.\n\n```bash\nnpx depset@latest @platejs --latest --install --yes\nnpx depset@latest platejs --latest --install --yes\n```\n\nFor pnpm projects:\n\n```bash\npnpm dlx depset@latest @platejs --latest --install --yes\npnpm dlx depset@latest platejs --latest --install --yes\n```\n\nThen inspect what actually resolved.\n\n```bash\nnpm ls platejs @platejs/core @platejs/slate react react-dom slate slate-dom slate-react\n```\n\n```bash\npnpm why platejs\npnpm why @platejs/core\npnpm why @platejs/slate\npnpm why react react-dom slate slate-dom slate-react\n```\n\nIf the tree shows multiple Plate majors, align the Plate packages first. If it\nshows multiple React or Slate copies, fix the package that pulls the extra copy\ninstead of papering over the tree with a random override.\n\n## Fix Runtime Boundaries\n\nUse React entrypoints only in client-side editor code.\n\n| Runtime | Import from |\n| --- | --- |\n| Editable React editor | `platejs/react`, `@platejs/*/react` |\n| Server Component static output | `platejs/static`, `platejs`, `@platejs/*` |\n| Node script or route handler without React UI | `platejs`, `@platejs/*` |\n\n<Callout type=\"warning\" title=\"No React plugins on the server\">\n  Server Components and Node scripts should not import `platejs/react` or\n  `@platejs/*/react`. Use base plugins such as `BaseH1Plugin` from\n  `@platejs/basic-nodes`, not `H1Plugin` from `@platejs/basic-nodes/react`.\n</Callout>\n\nIf a server route only needs serialization or transforms, use the Node path from\n[Node.js](/docs/plate/installation/node). If it renders read-only React output, use\n[Static Rendering](/docs/plate/static).\n\n## Check Component Wiring\n\nWhen a node appears as plain text or a default `div`, check the component path.\n\n```tsx title=\"components/editor/plugins.tsx\" showLineNumbers\nimport { H1Plugin } from '@platejs/basic-nodes/react';\n\nimport { H1Element } from '@/components/ui/heading-node';\n\nexport const plugins = [H1Plugin.withComponent(H1Element)];\n```\n\nFor copied Plate UI kits, inspect the kit that owns the feature before adding\nmanual component overrides. Feature kits usually wire plugins, components,\nshortcuts, and options together.\n\n| Component surface | Guide |\n| --- | --- |\n| Copied registry components | [Plate UI](/docs/plate/installation/plate-ui) |\n| Feature-owned kits | [Feature Kits](/docs/plate/feature-kits) |\n| Manual node components | [Plugin Components](/docs/plate/plugin-components) |\n\n## Inspect Plugin Resolution\n\nEnable `DebugPlugin` when plugin options, dependencies, or runtime behavior do\nnot match what the editor receives.\n\n```tsx title=\"components/editor/plugins.tsx\" showLineNumbers\nimport { DebugPlugin } from 'platejs';\n\nexport const plugins = [\n  DebugPlugin.configure({\n    options: {\n      logLevel: 'warn',\n    },\n  }),\n];\n```\n\nCommon debug errors:\n\n| Message | Meaning |\n| --- | --- |\n| `OPTION_UNDEFINED` | `editor.getOption`, `editor.setOption`, or plugin context used an option key missing from `plugin.options`. |\n| `PLUGIN_DEPENDENCY_MISSING` | A plugin lists a dependency key that is not registered. |\n\nUse [Plugin](/docs/plate/plugin#advanced-plugin-configuration) to check `dependencies`,\n`priority`, `enabled`, `plugins`, and `override.*` behavior.\n\n## Reset The Install\n\nWhen the dependency tree looks correct but the runtime still behaves like two\nReact or Slate copies are loaded, reinstall from the existing lockfile.\n\n```bash\nrm -rf node_modules\nnpm install\n```\n\n```bash\nrm -rf node_modules\npnpm install\n```\n\nDelete the lockfile only when you intentionally want a fresh dependency\nresolution. That is a package-management decision, not a Plate fix.\n\n## Report A Reproduction\n\nWhen you open an issue, include the smallest editor that reproduces the problem:\n\n- Package manager and lockfile type.\n- Output from the relevant `npm ls` or `pnpm why` commands.\n- The plugin list passed to `usePlateEditor`, `createPlateEditor`,\n  `createSlateEditor`, or `createStaticEditor`.\n- The failing value, if the issue depends on document content.\n- The exact error message from `DebugPlugin` or the browser console.\n\nSmall reproductions beat screenshots. The package tree and plugin list usually\ntell the story.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/troubleshooting.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "typescript-docs",
      "title": "TypeScript",
      "description": "Configure TypeScript (tsconfig) for using Plate, including module resolution solutions.",
      "files": [
        {
          "path": "../../content/docs/(guides)/typescript.mdx",
          "content": "---\ntitle: TypeScript\ndescription: Configure TypeScript (tsconfig) for using Plate, including module resolution solutions.\n---\n\nPlate provides ESM packages, which require certain TypeScript (and bundler) configurations to ensure compatibility, especially when importing subpath modules like `platejs/react`. Below are several solutions and workarounds to make TypeScript happy.\n\n## Quick Summary\n\n1. **Recommended (Easiest):** Use TypeScript **5.0+** and set `\"moduleResolution\": \"bundler\"` in your `tsconfig.json`.\n2. **Alternate (Node resolution):** Keep `\"moduleResolution\": \"node\"` and map paths to `dist/react` (and potentially alias them in your bundler config).\n3. **Up-to-date Packages:** Use `depset` to upgrade Plate dependencies.\n\n\n## Recommended: `\"moduleResolution\": \"bundler\"`\n\nThe simplest approach for modern bundlers (Vite, Next.js 14, etc.) is to enable the new TypeScript \"bundler\" resolution mode. Example:\n\n```jsonc\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    // ...\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    // ...\n  }\n}\n```\n\nThis aligns TypeScript's resolution logic more closely with modern bundlers and ESM packages. Below is a working excerpt from [Plate template](https://github.com/udecode/plate-template):\n\n```jsonc\n{\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"strictNullChecks\": true,\n    \"allowUnusedLabels\": false,\n    \"allowUnreachableCode\": false,\n    \"exactOptionalPropertyTypes\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitOverride\": true,\n    \"noImplicitReturns\": false,\n    \"noPropertyAccessFromIndexSignature\": false,\n    \"noUncheckedIndexedAccess\": false,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n\n    \"isolatedModules\": true,\n\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"jsx\": \"preserve\",\n    \"module\": \"esnext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"bundler\",\n    \"moduleDetection\": \"force\",\n    \"resolveJsonModule\": true,\n    \"noEmit\": true,\n    \"incremental\": true,\n    \"sourceMap\": true,\n\n    \"baseUrl\": \"src\",\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \".next/types/**/*.ts\",\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n```\n\n- **`\"moduleResolution\": \"bundler\"`** was introduced in TypeScript 5.0.\n- If your TS version is older than 5.0, you **must** upgrade or stick to `\"moduleResolution\": \"node\"` plus manual path aliases.\n\n```jsonc\n// package.json\n{\n  \"devDependencies\": {\n    \"typescript\": \"^5.0.0\"\n  }\n}\n```\n\nIf you see an error like `TS5023: Unknown compiler option 'moduleResolution'` (for `bundler`), it likely means your TypeScript version is below 5.0.\n\n## Workaround: `\"moduleResolution\": \"node\"` + Path Aliases\n\nIf upgrading your entire project to TS 5.0 or changing the resolution mode is not possible:\n\n1. Keep `\"moduleResolution\": \"node\"`.\n2. Map each Plate subpath import to its `dist/react` types in `tsconfig.json` using `paths`.\n3. Alias these paths in your bundler config.\n\n### Example `tsconfig.json`\n\n```jsonc\n{\n  \"compilerOptions\": {\n    \"moduleResolution\": \"node\",\n    \"paths\": {\n      \"platejs/react\": [\n        \"./node_modules/platejs/dist/react/index.d.ts\"\n      ],\n      \"@platejs/core/react\": [\n        \"./node_modules/@platejs/core/dist/react/index.d.ts\"\n      ],\n      \"@platejs/list/react\": [\n        \"./node_modules/@platejs/list/dist/react/index.d.ts\"\n      ]\n      // ...repeat for all @platejs/*/react packages\n    }\n  }\n}\n```\n\n### Example `vite.config.ts`\n\n```ts\nimport { defineConfig } from 'vite';\nimport path from 'path';\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      'platejs/react': path.resolve(\n        __dirname,\n        'node_modules/platejs/dist/react'\n      ),\n      '@platejs/core/react': path.resolve(\n        __dirname,\n        'node_modules/@platejs/core/dist/react'\n      ),\n      '@platejs/list/react': path.resolve(\n        __dirname,\n        'node_modules/@platejs/list/dist/react'\n      ),\n\n      // Non-/react base aliases:\n      'platejs': path.resolve(\n        __dirname,\n        'node_modules/platejs'\n      ),\n      '@platejs/core': path.resolve(\n        __dirname,\n        'node_modules/@platejs/core'\n      ),\n      '@platejs/list': path.resolve(\n        __dirname,\n        'node_modules/@platejs/list'\n      )\n    }\n  }\n});\n```\n\n**Note:**\n- You must do this for every `@platejs/*/react` import you use.  \n- For testing/Jest, replicate these aliases via `moduleNameMapper` or similar.\n\n## Ensure Matching Plate Versions\n\nSay you're upgrading one package to `52.0.1`, double-check that all your `platejs*` packages are on the **latest version up to `52.0.1`** (one package could stay at `52.0.0` if it has no `52.0.1` release). Mixing versions often leads to mismatches.\n\nTo easily manage and synchronize your `platejs*` package versions, you can use the `depset` CLI. For example, to ensure all your `@platejs/*` scope packages are aligned to the latest compatible with version `52.x.y`:\n\n```bash\nnpx depset@latest @platejs 52 && npx depset@latest platejs 52\n```\n\nOr, for a specific version like `52.0.1` (this will set all packages in the scope to `52.0.1` if available, or the latest before it if not):\n\n```bash\nnpx depset@latest @platejs 52.0.1 && npx depset@latest platejs 52.0.1\n```\n\nThis helps prevent version conflicts by ensuring all related Plate packages are on compatible versions.\n\n## FAQ\n\n> I updated `moduleResolution` to `bundler` but it broke my older imports.\"\n\nIf your codebase has older TS usage or relies on `node` resolution, try the path alias approach or fully migrate to a TS 5+ / ESM environment.\n\n> \"I'm seeing `TS2305` about missing exports. Is that a resolution error or a real missing export?\"  \n\nIt can be either:\n- If the entire package is \"not found,\" it's likely a resolution issue.  \n- If it's specifically \"no exported member,\" double-check that you spelled the import correctly (no typos) and that your installed version actually has that export.\n\n> \"Which minimum TS version do I need for `moduleResolution: bundler`?\"  \n\nTypeScript 5.0 or higher.\n\n> \"We switched to bundler resolution, but some older libraries in our project break.\"  \n\nIf your older libraries aren't ESM-friendly, you might stick to `node` resolution and do manual path aliases. Some large codebases gradually upgrade or create separate build pipelines for legacy code.\n\n> \"We see the error in Jest but not in Vite.\"  \n\nYou'll need to replicate your alias/resolution changes for Jest. For example:\n\n```js\n// jest.config.js\nmodule.exports = {\n  // ...\n  moduleNameMapper: {\n    '^platejs/react$': '<rootDir>/node_modules/platejs/dist/react',\n    '^@platejs/core/react$': '<rootDir>/node_modules/@platejs/core/dist/react',\n    // ...\n  }\n};\n```\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/typescript.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "unit-testing-docs",
      "title": "Unit Testing Plate",
      "description": "Learn how to unit test Plate editor and plugins.",
      "files": [
        {
          "path": "../../content/docs/(guides)/unit-testing.mdx",
          "content": "---\ntitle: Unit Testing Plate\ndescription: Learn how to unit test Plate editor and plugins.\n---\n\nThis guide outlines best practices for unit testing Plate plugins and components using `@platejs/test-utils`.\n\n## Installation\n\n```bash\nnpm install @platejs/test-utils\n```\n\n## Setting Up Tests\n\nAdd the JSX pragma at the top of your test file:\n\n```typescript\n/** @jsx jsx */\n\nimport { jsx } from '@platejs/test-utils';\n\njsx; // so Biome doesn't remove unused imports\n```\n\nThis allows you to use JSX syntax for creating editor values.\n\n## Creating Test Cases\n\n### Editor State Representation\n\nUse JSX to represent editor states:\n\n```typescript\nconst input = (\n  <editor>\n    <hp>\n      Hello<cursor /> world\n    </hp>\n  </editor>\n) as any as PlateEditor;\n```\n\nNode elements like `<hp />`, `<hul />`, `<hli />` represent different types of nodes.\n\nSpecial elements like `<cursor />`, `<anchor />`, and `<focus />` represent selection states.\n\n### Testing Transforms\n\n1. Create an input state\n2. Define the expected output state\n3. Use `createPlateEditor` to set up the editor\n4. Apply the transform(s) directly\n5. Assert the editor's new state\n\nExample testing bold formatting:\n\n```typescript\nit('should apply bold formatting', () => {\n  const input = (\n    <editor>\n      <hp>\n        Hello <anchor />\n        world\n        <focus />\n      </hp>\n    </editor>\n  ) as any as PlateEditor;\n\n  const output = (\n    <editor>\n      <hp>\n        Hello <htext bold>world</htext>\n      </hp>\n    </editor>\n  ) as any as PlateEditor;\n\n  const editor = createPlateEditor({\n    plugins: [BoldPlugin],\n    value: input.children,\n    selection: input.selection,\n  });\n\n  // Apply transform directly\n  editor.tf.toggleMark('bold');\n\n  expect(editor.children).toEqual(output.children);\n});\n```\n\n### Testing Selection\n\nTest how operations affect the editor's selection:\n\n```typescript\nit('should collapse selection on backspace', () => {\n  const input = (\n    <editor>\n      <hp>\n        He<anchor />llo wor<focus />ld\n      </hp>\n    </editor>\n  ) as any as PlateEditor;\n\n  const output = (\n    <editor>\n      <hp>\n        He<cursor />ld\n      </hp>\n    </editor>\n  ) as any as PlateEditor;\n\n  const editor = createPlateEditor({\n    value: input.children,\n    selection: input.selection,\n  });\n\n  editor.tf.deleteBackward();\n\n  expect(editor.children).toEqual(output.children);\n  expect(editor.selection).toEqual(output.selection);\n});\n```\n\n## Testing Key Events\n\nWhen you need to test keyboard handlers directly:\n\n```typescript\nit('should call the onKeyDown handler', () => {\n  const input = (\n    <editor>\n      <hp>\n        Hello <anchor />world<focus />\n      </hp>\n    </editor>\n  ) as any as PlateEditor;\n\n  // Create a mock handler to verify it's called\n  const onKeyDownMock = jest.fn();\n\n  const editor = createPlateEditor({\n    value: input.children,\n    selection: input.selection,\n    plugins: [\n      {\n        key: 'test',\n        handlers: {\n          onKeyDown: onKeyDownMock,\n        },\n      },\n    ],\n  });\n\n  // Create the keyboard event\n  const event = new KeyboardEvent('keydown', {\n    key: 'Enter',\n  }) as any;\n\n  // Call the handler directly\n  editor.plugins.test.handlers.onKeyDown({\n    ...getEditorPlugin(editor, { key: 'test' }),\n    event,\n  });\n\n  // Verify the handler was called\n  expect(onKeyDownMock).toHaveBeenCalled();\n});\n```\n\n## Testing Complex Scenarios\n\nFor complex plugins like tables, test various scenarios by directly applying transforms:\n\n```typescript\ndescribe('Table plugin', () => {\n  it('should insert a table', () => {\n    const input = (\n      <editor>\n        <hp>\n          Test<cursor />\n        </hp>\n      </editor>\n    ) as any as PlateEditor;\n\n    const output = (\n      <editor>\n        <hp>Test</hp>\n        <htable>\n          <htr>\n            <htd>\n              <hp>\n                <cursor />\n              </hp>\n            </htd>\n            <htd>\n              <hp></hp>\n            </htd>\n          </htr>\n          <htr>\n            <htd>\n              <hp></hp>\n            </htd>\n            <htd>\n              <hp></hp>\n            </htd>\n          </htr>\n        </htable>\n      </editor>\n    ) as any as PlateEditor;\n\n    const editor = createPlateEditor({\n      value: input.children,\n      selection: input.selection,\n      plugins: [TablePlugin],\n    });\n\n    // Call transform directly\n    editor.tf.insertTable({ rows: 2, columns: 2 });\n\n    expect(editor.children).toEqual(output.children);\n    expect(editor.selection).toEqual(output.selection);\n  });\n});\n```\n\n## Testing Plugins with Options\n\nTest how different plugin options affect behavior:\n\n```typescript\ndescribe('when keepSelectedTextOnPaste is disabled', () => {\n  it('replaces the selected text with the pasted url', () => {\n    const input = (\n      <fragment>\n        <hp>\n          start <anchor />\n          of regular text\n          <focus />\n        </hp>\n      </fragment>\n    ) as any;\n\n    const output = (\n      <fragment>\n        <hp>\n          start <ha url=\"https://google.com\">https://google.com</ha>\n          <htext />\n        </hp>\n      </fragment>\n    ) as any;\n\n    const editor = createPlateEditor({\n      plugins: [\n        LinkPlugin.configure({\n          options: {\n            keepSelectedTextOnPaste: false,\n          },\n        }),\n      ],\n      value: input,\n    });\n\n    editor.tf.insertData({\n      getData: (type: string) => (type === 'text/plain' ? 'https://google.com' : ''),\n    } as any);\n\n    expect(input.children).toEqual(output.children);\n  });\n});\n```\n\n## Mocking vs. Real Transforms\n\nWhile mocking can be useful for isolating specific behaviors, Plate tests often assess actual editor children and selection after transforms. This approach ensures that plugins work correctly with the entire editor state.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(guides)/unit-testing.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "ai-docs",
      "title": "AI",
      "description": "AI-powered writing assistance.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(ai)/ai.mdx",
          "content": "---\ntitle: AI\ndescription: AI-powered writing assistance.\ndocs:\n  - route: https://pro.platejs.org/docs/examples/ai\n    title: Plus\n---\n\n<ComponentPreview name=\"ai-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Context-aware command menu** that adapts to cursor, text selection, and block selection workflows.\n- **Streaming Markdown/MDX insertion** with table, column, and code block support powered by `streamInsertChunk`.\n- **Insert and chat review modes** with localized insert previews plus undo-safe batching via `withAIBatch` and `tf.ai.undo()`.\n- **Block selection aware transforms** to replace or append entire sections using `tf.aiChat.replaceSelection` and `tf.aiChat.insertBelow`.\n- **Direct integration with `@ai-sdk/react`** so `api.aiChat.submit` can stream responses from Vercel AI SDK helpers.\n- **Suggestion and comment utilities** that diff AI edits, accept/reject changes, and map AI feedback back to document ranges.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add AI functionality is with the `AIKit`. It ships the configured `AIPlugin`, `AIChatPlugin`, Markdown streaming helpers, cursor overlay, and their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"ai-kit\" />\n\n- [`AIMenu`](/docs/plate/components/ai-menu): Floating command surface for prompts, tool shortcuts, and chat review.\n- [`AILoadingBar`](/docs/plate/components/ai-loading-bar): Displays streaming status at the editor container.\n- [`AIAnchorElement`](/docs/plate/components/ai-anchor-element): Invisible anchor node used to position the floating menu during streaming.\n- [`AILeaf`](/docs/plate/components/ai-leaf): Renders AI-marked text with subtle styling.\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { AIKit } from '@/components/editor/plugins/ai-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...AIKit,\n  ],\n});\n```\n\n### Add API Route\n\nExpose a streaming command endpoint that proxies your model provider:\n\n<ComponentSource name=\"ai-api\" />\n\n### Configure Environment\n\nSet your AI Gateway key locally (replace with your provider secret if you are not using a gateway):\n\n```bash title=\".env.local\"\nAI_GATEWAY_API_KEY=\"your-api-key\"\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/ai @platejs/markdown @platejs/selection @ai-sdk/react ai\n```\n\n`@platejs/suggestion` is optional but required for diff-based edit suggestions.\n\n### Add Plugins\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { AIChatPlugin, AIPlugin } from '@platejs/ai/react';\nimport { BlockSelectionPlugin } from '@platejs/selection/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    BlockSelectionPlugin,\n    MarkdownPlugin,\n    AIPlugin,\n    AIChatPlugin, // extended in the next step\n  ],\n});\n```\n\n- `BlockSelectionPlugin`: Enables multi-block selections that `AIChatPlugin` relies on for insert/replace transforms.\n- `MarkdownPlugin`: Provides Markdown serialization used by streaming utilities.\n- `AIPlugin`: Adds the AI mark and transforms for undoing AI batches.\n- `AIChatPlugin`: Supplies the AI combobox, API helpers, and transforms.\n\nUse `AIPlugin.withComponent` with your own element (or [`AILeaf`](/docs/plate/components/ai-leaf)) to highlight AI-generated text.\n\n### Configure AIChatPlugin\n\nExtend `AIChatPlugin` to hook streaming and edits. The example mirrors the core logic from `AIKit` while keeping the UI headless.\n\n```tsx\nimport cloneDeep from 'lodash/cloneDeep';\nimport { BaseAIPlugin, withAIBatch } from '@platejs/ai';\nimport {\n  AIChatPlugin,\n  applyAISuggestions,\n  getInsertPreviewStart,\n  streamInsertChunk,\n  useChatChunk,\n} from '@platejs/ai/react';\nimport { ElementApi, getPluginType, KEYS, PathApi } from 'platejs';\nimport { usePluginOption } from 'platejs/react';\n\nexport const aiChatPlugin = AIChatPlugin.extend({\n  options: {\n    chatOptions: {\n      api: '/api/ai/command',\n      body: {\n        model: 'openai/gpt-4o-mini',\n      },\n    },\n    trigger: ' ',\n    triggerPreviousCharPattern: /^\\s?$/,\n  },\n  useHooks: ({ editor, getOption }) => {\n    const mode = usePluginOption(AIChatPlugin, 'mode');\n    const toolName = usePluginOption(AIChatPlugin, 'toolName');\n\n    useChatChunk({\n      onChunk: ({ chunk, isFirst, text }) => {\n        if (isFirst && mode === 'insert') {\n          const { startBlock, startInEmptyParagraph } =\n            getInsertPreviewStart(editor);\n\n          editor.getTransforms(BaseAIPlugin).ai.beginPreview({\n            originalBlocks:\n              startInEmptyParagraph &&\n              startBlock &&\n              ElementApi.isElement(startBlock)\n                ? [cloneDeep(startBlock)]\n                : [],\n          });\n\n          editor.setOption(AIChatPlugin, 'streaming', true);\n\n          editor.tf.withoutSaving(() => {\n            editor.tf.insertNodes(\n              {\n                children: [{ text: '' }],\n                type: getPluginType(editor, KEYS.aiChat),\n              },\n              {\n                at: PathApi.next(editor.selection!.focus.path.slice(0, 1)),\n              }\n            );\n          });\n        }\n\n        if (mode === 'insert') {\n          editor.tf.withoutSaving(() => {\n            if (!getOption('streaming')) return;\n\n            editor.tf.withScrolling(() => {\n              streamInsertChunk(editor, chunk, {\n                textProps: {\n                  [getPluginType(editor, KEYS.ai)]: true,\n                },\n              });\n            });\n          });\n        }\n\n        if (toolName === 'edit' && mode === 'chat') {\n          withAIBatch(\n            editor,\n            () => {\n              applyAISuggestions(editor, text);\n            },\n            { split: isFirst }\n          );\n        }\n      },\n      onFinish: () => {\n        editor.setOption(AIChatPlugin, 'streaming', false);\n        editor.setOption(AIChatPlugin, '_blockChunks', '');\n        editor.setOption(AIChatPlugin, '_blockPath', null);\n        editor.setOption(AIChatPlugin, '_mdxName', null);\n      },\n    });\n  },\n});\n```\n\n- `useChatChunk`: Watches `UseChatHelpers` status and yields incremental chunks.\n- `tf.ai.beginPreview`: Captures the rollback slice and selection for insert-mode preview before the first streamed chunk is written.\n- `streamInsertChunk`: Streams Markdown/MDX into the document, reusing the existing block when possible.\n- `applyAISuggestions`: Converts responses into transient suggestion nodes when `toolName === 'edit'`.\n- `withAIBatch`: Marks saved AI batches so suggestion review and accepted AI changes stay undo-safe.\n\nProvide your own `render` components (toolbar button, floating menu, etc.) when you extend the plugin.\n\n### Build API Route\n\nHandle `api.aiChat.submit` requests on the server. Each request includes the chat `messages` from `@ai-sdk/react` and a `ctx` payload that contains the editor `children`, current `selection`, and last `toolName`.\n[Complete API example](https://github.com/udecode/plate-playground-template/blob/main/src/app/api/ai/command/route.ts)\n\n```ts title=\"app/api/ai/command/route.ts\"\nimport {\n  convertToModelMessages,\n  createGateway,\n  createUIMessageStreamResponse,\n  streamText,\n  toUIMessageStream,\n} from 'ai';\nimport { createSlateEditor } from 'platejs';\n\nimport { BaseEditorKit } from '@/registry/components/editor/editor-base-kit';\nimport { markdownJoinerTransform } from '@/registry/lib/markdown-joiner-transform';\n\nexport async function POST(req: Request) {\n  const { apiKey, ctx, messages, model } = await req.json();\n\n  const editor = createSlateEditor({\n    plugins: BaseEditorKit,\n    selection: ctx.selection,\n    value: ctx.children,\n  });\n\n  const gateway = createGateway({\n    apiKey: apiKey ?? process.env.AI_GATEWAY_API_KEY!,\n  });\n\n  const result = streamText({\n    experimental_transform: markdownJoinerTransform(),\n    instructions:\n      ctx.toolName === 'edit'\n        ? 'You are an editor that rewrites user text.'\n        : undefined,\n    messages: await convertToModelMessages(messages),\n    model: gateway(model ?? 'openai/gpt-4o-mini'),\n  });\n\n  return createUIMessageStreamResponse({\n    stream: toUIMessageStream({\n      originalMessages: messages,\n      stream: result.stream,\n    }),\n  });\n}\n```\n\n- `ctx.children` and `ctx.selection` are rehydrated into a Slate editor so you can build rich prompts (see [Prompt Templates](#prompt-templates)).\n- Forward provider settings (model, apiKey, temperature, gateway flags, etc.) through `chatOptions.body`; everything you add is passed verbatim in the JSON payload and can be read before calling `createGateway`.\n- Always read secrets from the server. The client should only send opaque identifiers or short-lived tokens.\n- Return a streaming response so `useChat` and `useChatChunk` can process tokens incrementally.\n\n### Connect `useChat`\n\nBridge the editor and your model endpoint with `@ai-sdk/react`. Store helpers on the plugin so transforms can reload, stop, or show chat state.\n\n```tsx\nimport { useEffect } from 'react';\n\nimport { type UIMessage, DefaultChatTransport } from 'ai';\nimport { type UseChatHelpers, useChat } from '@ai-sdk/react';\nimport { AIChatPlugin } from '@platejs/ai/react';\nimport { useEditorPlugin } from 'platejs/react';\n\ntype ChatMessage = UIMessage<{}, { toolName: 'comment' | 'edit' | 'generate'; comment?: unknown }>;\n\nexport const useEditorAIChat = () => {\n  const { editor, setOption } = useEditorPlugin(AIChatPlugin);\n\n  const chat = useChat<ChatMessage>({\n    id: 'editor',\n    api: '/api/ai/command',\n    transport: new DefaultChatTransport(),\n    onData(data) {\n      if (data.type === 'data-toolName') {\n        editor.setOption(AIChatPlugin, 'toolName', data.data);\n      }\n    },\n  });\n\n  useEffect(() => {\n    setOption('chat', chat as UseChatHelpers<ChatMessage>);\n  }, [chat, setOption]);\n\n  return chat;\n};\n```\n\nCombine the helper with `useEditorChat` to keep the floating menu anchored correctly:\n\n```tsx\nimport { useEditorChat } from '@platejs/ai/react';\n\nuseEditorChat({\n  onOpenChange: (open) => {\n    if (!open) chat.stop?.();\n  },\n});\n```\n\nNow you can submit prompts programmatically:\n\n```tsx\nimport { AIChatPlugin } from '@platejs/ai/react';\n\neditor.getApi(AIChatPlugin).aiChat.submit('', {\n  prompt: {\n    default: 'Continue the document after {block}',\n    selecting: 'Rewrite {selection} with a clearer tone',\n  },\n  toolName: 'generate',\n});\n```\n\n</Steps>\n\n## Prompt Templates\n\n### Client Prompting\n\n- `api.aiChat.submit` accepts an `EditorPrompt`. Provide a string, an object with `default`/`selecting`/`blockSelecting`, or a function that receives `{ editor, isSelecting, isBlockSelecting }`. The helper `getEditorPrompt` in the client turns that value into the final string.\n- Combine it with `replacePlaceholders(editor, template, { prompt })` to expand `{editor}`, `{block}`, `{blockSelection}`, and `{prompt}` using Markdown generated by `@platejs/ai`.\n\n```tsx\nimport { replacePlaceholders } from '@platejs/ai';\n\neditor.getApi(AIChatPlugin).aiChat.submit('Improve tone', {\n  prompt: ({ isSelecting }) =>\n    isSelecting\n      ? replacePlaceholders(editor, 'Rewrite {blockSelection} using a friendly tone.')\n      : replacePlaceholders(editor, 'Continue {block} with two more sentences.'),\n  toolName: 'generate',\n});\n```\n\n### Server Prompting\n\nThe demo backend in `apps/www/src/app/api/ai/command` reconstructs the editor from `ctx` and builds structured prompts:\n\n- `getChooseToolPrompt` decides whether the request is `generate`, `edit`, or `comment`.\n- `getGeneratePrompt`, `getEditPrompt`, and `getCommentPrompt` transform the current editor state into instructions tailored to each mode.\n- Utility helpers like `getMarkdown`, `getMarkdownWithSelection`, and `buildStructuredPrompt` (see `apps/www/src/app/api/ai/command/prompts.ts`) make it easy to embed block ids, selections, and MDX tags into the LLM request.\n\nAugment the payload you send from the client to fine-tune server prompts:\n\n```ts\neditor.setOption(aiChatPlugin, 'chatOptions', {\n  api: '/api/ai/command',\n  body: {\n    model: 'openai/gpt-4o-mini',\n    tone: 'playful',\n    temperature: 0.4,\n  },\n});\n```\n\nEverything under `chatOptions.body` arrives in the route handler, letting you swap providers, pass user-specific metadata, or branch into different prompt templates.\n\n## Keyboard Shortcuts\n\n<KeyTable>\n  <KeyTableItem hotkey=\"Space\">Open the AI menu in an empty block (cursor mode)</KeyTableItem>\n  <KeyTableItem hotkey=\"Cmd + J\">Show the AI menu (set via `shortcuts.show`)</KeyTableItem>\n  <KeyTableItem hotkey=\"Escape\">Hide the AI menu and stop streaming</KeyTableItem>\n</KeyTable>\n\n## Streaming\n\nThe streaming utilities keep complex layouts intact while responses arrive:\n\n- `streamInsertChunk(editor, chunk, options)` deserializes Markdown chunks, updates the current block in place, and appends new blocks as needed. Use `textProps`/`elementProps` to tag streamed nodes (e.g., mark AI text).\n- `streamDeserializeMd` and `streamDeserializeInlineMd` provide lower-level access if you need to control streaming for custom node types.\n- `streamSerializeMd` mirrors the editor state so you can detect drift between streamed content and the response buffer.\n\nReset the internal `_blockChunks`, `_blockPath`, and `_mdxName` options when streaming finishes to start the next response from a clean slate.\n\n## Streaming Example\n\n<ComponentPreview name=\"markdown-streaming-demo\" />\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"ai-pro\" />\n\n## API Reference\n\n### `AIPlugin`\n\nAdds an `ai` mark to streamed text and exposes transforms to remove AI nodes or undo the last AI batch. Use `.withComponent` to render AI-marked text with a custom component.\n\n<API name=\"AIPlugin\">\n  <APIOptions>\n    <APIItem name=\"node.isLeaf\" type=\"true\">AI content is stored on text nodes.</APIItem>\n    <APIItem name=\"node.isDecoration\" type=\"false\">AI marks are regular text properties, not decorations.</APIItem>\n  </APIOptions>\n</API>\n\n### `AIChatPlugin`\n\nMain plugin that powers the AI menu, chat state, and transforms.\n\n<API name=\"AIChatPlugin\">\n  <APIOptions>\n    <APIItem name=\"trigger\" type=\"RegExp | string | string[]\" optional>Character(s) that open the command menu. Defaults to `' '`.</APIItem>\n    <APIItem name=\"triggerPreviousCharPattern\" type=\"RegExp\" optional>Pattern that must match the character before the trigger. Defaults to `/^\\s?$/`.</APIItem>\n    <APIItem name=\"triggerQuery\" type=\"(editor: SlateEditor) => boolean\" optional>Return `false` to cancel opening in specific contexts.</APIItem>\n    <APIItem name=\"chat\" type=\"UseChatHelpers&lt;ChatMessage&gt;\" optional>Store helpers from `useChat` so API calls can access them.</APIItem>\n    <APIItem name=\"chatNodes\" type=\"TIdElement[]\" optional>Snapshot of nodes used to diff edit suggestions (managed internally).</APIItem>\n    <APIItem name=\"chatSelection\" type=\"TRange | null\" optional>Selection captured before submitting a prompt (managed internally).</APIItem>\n    <APIItem name=\"mode\" type=\"'chat' | 'insert'\">Controls whether responses stream directly into the document or open a review panel. Defaults to `'insert'`.</APIItem>\n    <APIItem name=\"open\" type=\"boolean\" optional>Whether the AI menu is visible. Defaults to `false`.</APIItem>\n    <APIItem name=\"streaming\" type=\"boolean\" optional>True while a response is streaming. Defaults to `false`.</APIItem>\n    <APIItem name=\"toolName\" type=\"'comment' | 'edit' | 'generate' | null\" optional>Active tool used to interpret the response.</APIItem>\n  </APIOptions>\n</API>\n\n### `api.aiChat.submit(input, options?)`\n\nSubmits a prompt to your model provider. When `mode` is omitted it defaults to `'insert'` for a collapsed cursor and `'chat'` otherwise.\n\n<API name=\"submit\">\n<APIParameters>\n  <APIItem name=\"input\" type=\"string\">Raw input from the user.</APIItem>\n  <APIItem name=\"options\" type=\"object\" optional>Fine-tune submission behaviour.</APIItem>\n</APIParameters>\n<APIOptions type=\"object\">\n  <APIItem name=\"mode\" type=\"'chat' | 'insert'\" optional>Override the response mode.</APIItem>\n  <APIItem name=\"options\" type=\"ChatRequestOptions\" optional>Forwarded to `chat.sendMessage` (model, headers, etc.).</APIItem>\n  <APIItem name=\"prompt\" type=\"EditorPrompt\" optional>String, config, or function processed by `getEditorPrompt`.</APIItem>\n  <APIItem name=\"toolName\" type=\"'comment' | 'edit' | 'generate' | null\" optional>Tags the submission so hooks can react differently.</APIItem>\n</APIOptions>\n</API>\n\n### `api.aiChat.reset(options?)`\n\nClears chat state, removes AI nodes, and optionally undoes the last AI batch.\n\n<API name=\"reset\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ undo?: boolean }\" optional>Pass `undo: false` to keep streamed content.</APIItem>\n</APIParameters>\n</API>\n\n### `api.aiChat.node(options?)`\n\nRetrieves the first AI node that matches the specified criteria.\n\n<API name=\"node\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"EditorNodesOptions &amp; { anchor?: boolean; streaming?: boolean }\" optional>Set `anchor: true` to get the anchor node or `streaming: true` to retrieve the node currently being streamed into.</APIItem>\n</APIParameters>\n<APIReturns type=\"NodeEntry | undefined\">Matching node entry, if found.</APIReturns>\n</API>\n\n### `api.aiChat.reload()`\n\nReplays the last prompt using the stored `UseChatHelpers`, restoring the original selection or block selection before resubmitting.\n\n### `api.aiChat.stop()`\n\nStops streaming and calls `chat.stop`.\n\n### `api.aiChat.show()`\n\nOpens the AI menu, clears previous chat messages, and resets tool state.\n\n### `api.aiChat.hide(options?)`\n\nCloses the AI menu, optionally undoing the last AI batch and refocusing the editor.\n\n<API name=\"hide\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ focus?: boolean; undo?: boolean }\" optional>Set `focus: false` to keep focus outside the editor or `undo: false` to preserve inserted content.</APIItem>\n</APIParameters>\n</API>\n\n### `tf.aiChat.accept()`\n\nAccepts the latest response. In insert mode it removes AI marks and places the caret at the end of the streamed content. In chat mode it applies the pending suggestions.\n\n### `tf.aiChat.insertBelow(sourceEditor, options?)`\n\nInserts the chat preview (`sourceEditor`) below the current selection or block selection.\n\n<API name=\"insertBelow\">\n<APIParameters>\n  <APIItem name=\"sourceEditor\" type=\"SlateEditor\">Editor containing the generated content.</APIItem>\n  <APIItem name=\"options\" type=\"{ format?: 'all' | 'none' | 'single' }\" optional>Copy formatting from the source selection. Defaults to `'single'`.</APIItem>\n</APIParameters>\n</API>\n\n### `tf.aiChat.replaceSelection(sourceEditor, options?)`\n\nReplaces the current selection or block selection with the chat preview.\n\n<API name=\"replaceSelection\">\n<APIParameters>\n  <APIItem name=\"sourceEditor\" type=\"SlateEditor\">Editor containing the generated content.</APIItem>\n  <APIItem name=\"options\" type=\"{ format?: 'all' | 'none' | 'single' }\" optional>Controls how much formatting from the original selection should be applied.</APIItem>\n</APIParameters>\n</API>\n\n### `tf.aiChat.removeAnchor(options?)`\n\nRemoves the temporary anchor node used to position the AI menu.\n\n<API name=\"removeAnchor\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"EditorNodesOptions\" optional>Filters the nodes to remove.</APIItem>\n</APIParameters>\n</API>\n\n### `tf.ai.insertNodes(nodes, options?)`\n\nInserts nodes tagged with the AI mark at the current selection (or `options.target`).\n\n### `tf.ai.removeMarks(options?)`\n\nClears the AI mark from matching nodes.\n\n### `tf.ai.removeNodes(options?)`\n\nRemoves text nodes that are marked as AI-generated.\n\n### `tf.ai.beginPreview(options?)`\n\nCaptures the rollback slice and selection for insert-mode AI preview. Call it once before writing the first unsaved preview chunk.\n\n<API name=\"beginPreview\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ originalBlocks?: Value }\" optional>Top-level blocks that the preview will overwrite. Use `[]` when preview inserts after existing content.</APIItem>\n</APIParameters>\n<APIReturns type=\"boolean\">Returns `true` when a new preview rollback point was stored, or `false` when preview state already exists.</APIReturns>\n</API>\n\n### `tf.ai.acceptPreview()`\n\nCommits the active preview as one fresh undoable batch, strips preview-only markers, and clears preview bookkeeping.\n\n<API name=\"acceptPreview\">\n<APIReturns type=\"boolean\">Returns `true` when an active preview was committed.</APIReturns>\n</API>\n\n### `tf.ai.cancelPreview()`\n\nRestores the rollback point for the active preview and clears preview bookkeeping.\n\n<API name=\"cancelPreview\">\n<APIReturns type=\"boolean\">Returns `true` when an active preview was restored.</APIReturns>\n</API>\n\n### `tf.ai.discardPreview()`\n\nClears preview bookkeeping without restoring content. Use it when the previewed content should stay in place.\n\n<API name=\"discardPreview\">\n<APIReturns type=\"boolean\">Returns `true` when active preview bookkeeping was cleared.</APIReturns>\n</API>\n\n### `tf.ai.hasPreview()`\n\nReports whether an insert-mode preview rollback point is currently active.\n\n<API name=\"hasPreview\">\n<APIReturns type=\"boolean\">Returns `true` when preview rollback state exists.</APIReturns>\n</API>\n\n### `tf.ai.undo()`\n\nUndoes the latest AI history entry when it was created by `withAIBatch`. If an insert-mode preview is active, it cancels that preview first instead of replaying every streamed chunk. In both cases it avoids re-applying AI output from redo.\n\n### `useAIChatEditor`\n\nRegisters an auxiliary editor for chat previews and deserializes Markdown with block-level memoization.\n\n<API name=\"useAIChatEditor\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"SlateEditor\">Editor instance dedicated to the chat preview.</APIItem>\n  <APIItem name=\"content\" type=\"string\">Markdown content returned by the model.</APIItem>\n  <APIItem name=\"options\" type=\"DeserializeMdOptions\" optional>Pass `parser` to filter tokens before deserialization.</APIItem>\n</APIParameters>\n</API>\n\n```tsx\nimport { usePlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { AIChatPlugin, useAIChatEditor } from '@platejs/ai/react';\n\nconst aiPreviewEditor = usePlateEditor({\n  plugins: [MarkdownPlugin, AIChatPlugin],\n});\n\nuseAIChatEditor(aiPreviewEditor, responseMarkdown, {\n  parser: { exclude: ['space'] },\n});\n```\n\n### `useEditorChat`\n\nConnects `UseChatHelpers` to editor state so the AI menu knows whether to anchor to cursor, selection, or block selection.\n\n<API name=\"useEditorChat\">\n<APIParameters>\n  <APIItem name=\"onOpenBlockSelection\" type=\"(blocks: NodeEntry[]) => void\" optional>Called when the menu opens on block selection.</APIItem>\n  <APIItem name=\"onOpenChange\" type=\"(open: boolean) => void\" optional>Called whenever the menu opens or closes.</APIItem>\n  <APIItem name=\"onOpenCursor\" type=\"() => void\" optional>Called when the menu opens at the cursor.</APIItem>\n  <APIItem name=\"onOpenSelection\" type=\"() => void\" optional>Called when the menu opens on a text selection.</APIItem>\n</APIParameters>\n</API>\n\n### `useChatChunk`\n\nStreams chat responses chunk-by-chunk and gives you full control over insertion.\n\n<API name=\"useChatChunk\">\n<APIParameters>\n  <APIItem name=\"onChunk\" type=\"(chunk: { chunk: string; isFirst: boolean; nodes: TText[]; text: string }) => void\">Handle each streamed chunk.</APIItem>\n  <APIItem name=\"onFinish\" type=\"({ content }: { content: string }) => void\" optional>Called when streaming finishes.</APIItem>\n</APIParameters>\n</API>\n\n### `withAIBatch`\n\nGroups editor operations into a single history batch and flags it as AI-generated so `tf.ai.undo()` removes it safely.\n\n<API name=\"withAIBatch\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"SlateEditor\">Target editor.</APIItem>\n  <APIItem name=\"fn\" type=\"() => void\">Operations to run.</APIItem>\n  <APIItem name=\"options\" type=\"{ split?: boolean }\" optional>Set `split: true` to start a new history batch.</APIItem>\n</APIParameters>\n</API>\n\n### `applyAISuggestions`\n\nDiffs AI output against stored `chatNodes` and writes transient suggestion nodes. Requires `@platejs/suggestion`.\n\n<API name=\"applyAISuggestions\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"SlateEditor\">Editor to apply suggestions to.</APIItem>\n  <APIItem name=\"content\" type=\"string\">Markdown response from the model.</APIItem>\n</APIParameters>\n</API>\n\nComplementary helpers allow you to finalize or discard the diff:\n\n- `acceptAISuggestions(editor)`: Converts transient suggestion nodes into permanent suggestions.\n- `rejectAISuggestions(editor)`: Removes transient suggestion nodes and clears suggestion marks.\n\n### `aiCommentToRange`\n\nMaps streamed comment metadata back to document ranges so comments can be inserted automatically.\n\n<API name=\"aiCommentToRange\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"PlateEditor\">Editor instance.</APIItem>\n  <APIItem name=\"options\" type=\"{ blockId: string; comment: string; content: string }\">Block id and text used to locate the range.</APIItem>\n</APIParameters>\n<APIReturns type=\"{ start: BasePoint; end: BasePoint } | null\">Range matching the comment or `null` if it cannot be found.</APIReturns>\n</API>\n\n### `findTextRangeInBlock`\n\nFuzzy-search helper that uses LCS to find the closest match inside a block.\n\n<API name=\"findTextRangeInBlock\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">Block node to search.</APIItem>\n  <APIItem name=\"searchText\" type=\"string\">Text snippet to locate.</APIItem>\n</APIParameters>\n<APIReturns type=\"{ start: { path: Path; offset: number }; end: { path: Path; offset: number } } | null\">Matched range or `null`.</APIReturns>\n</API>\n\n### `getEditorPrompt`\n\nGenerates prompts that respect cursor, selection, or block selection states.\n\n<API name=\"getEditorPrompt\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"SlateEditor\">Editor providing context.</APIItem>\n  <APIItem name=\"options\" type=\"{ prompt?: EditorPrompt }\">String, config, or function describing the prompt.</APIItem>\n</APIParameters>\n<APIReturns type=\"string\">Contextualized prompt string.</APIReturns>\n</API>\n\n### `replacePlaceholders`\n\nReplaces placeholders like `{editor}`, `{blockSelection}`, and `{prompt}` with serialized Markdown.\n\n<API name=\"replacePlaceholders\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"SlateEditor\">Editor providing content.</APIItem>\n  <APIItem name=\"text\" type=\"string\">Template text.</APIItem>\n  <APIItem name=\"options\" type=\"{ prompt?: string }\" optional>Prompt value injected into `{prompt}`.</APIItem>\n</APIParameters>\n<APIReturns type=\"string\">Template with placeholders replaced by Markdown.</APIReturns>\n</API>\n\n## Customization\n\n### Adding Custom AI Commands\n\n<ComponentSource name=\"ai-menu\" />\n\nExtend the `aiChatItems` map to add new commands. Each command receives `{ aiEditor, editor, input }` and can dispatch `api.aiChat.submit` with custom prompts or transforms.\n\n#### Simple Custom Command\n\n```tsx\nsummarizeInBullets: {\n  icon: <ListIcon />,\n  label: 'Summarize in bullets',\n  value: 'summarizeInBullets',\n  onSelect: ({ editor }) => {\n    void editor.getApi(AIChatPlugin).aiChat.submit('', {\n      prompt: 'Summarize the current selection using bullet points',\n      toolName: 'generate',\n    });\n  },\n},\n```\n\n#### Command with Complex Logic\n\n```tsx\ngenerateTOC: {\n  icon: <BookIcon />,\n  label: 'Generate table of contents',\n  value: 'generateTOC',\n  onSelect: ({ editor }) => {\n    const headings = editor.api.nodes({\n      match: (n) => ['h1', 'h2', 'h3'].includes(n.type as string),\n    });\n\n    const prompt =\n      headings.length === 0\n        ? 'Create a realistic table of contents for this document'\n        : 'Generate a table of contents that reflects the existing headings';\n\n    void editor.getApi(AIChatPlugin).aiChat.submit('', {\n      mode: 'insert',\n      prompt,\n      toolName: 'generate',\n    });\n  },\n},\n```\n\nThe menu automatically switches between command and suggestion states:\n\n- `cursorCommand`: Cursor is collapsed and no response yet.\n- `selectionCommand`: Text is selected and no response yet.\n- `cursorSuggestion` / `selectionSuggestion`: A response exists, so actions like Accept, Try Again, or Insert Below are shown.\n\nUse `toolName` (`'generate' | 'edit' | 'comment'`) to control how streaming hooks process the response. For example, `'edit'` enables diff-based suggestions, and `'comment'` allows you to convert streamed comments into discussion threads with `aiCommentToRange`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(ai)/ai.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "copilot-docs",
      "title": "Copilot",
      "description": "AI-powered text completion suggestions.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(ai)/copilot.mdx",
          "content": "---\ntitle: Copilot\ndescription: AI-powered text completion suggestions.\ndocs:\n  - route: https://pro.platejs.org/docs/examples/copilot\n    title: Plus\n  - route: /docs/components/ghost-text\n    title: Ghost Text\n---\n\n<ComponentPreview name=\"copilot-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Renders ghost text suggestions as you type\n- Two trigger modes:\n  - Shortcut (e.g. `Ctrl+Space`). Press again for alternative suggestions.\n  - Debounce mode: automatically triggers after a space at paragraph ends\n- Accept suggestions with Tab or word-by-word with `Cmd+→`\n- Built-in support for Vercel AI SDK completion API\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add Copilot functionality is with the `CopilotKit`, which includes pre-configured `CopilotPlugin` along with `MarkdownKit` and their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"copilot-kit\" />\n\n- [`GhostText`](/docs/plate/components/ghost-text): Renders the ghost text suggestions.\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CopilotKit } from '@/components/editor/plugins/copilot-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CopilotKit,\n    // Place tab-using plugins after CopilotKit to avoid conflicts\n    // IndentPlugin,\n    // TabbablePlugin,\n  ],\n});\n```\n\n**Tab Key Handling**: The Copilot plugin uses the Tab key to accept suggestions. To avoid conflicts with other plugins that use Tab (like `IndentPlugin` or `TabbablePlugin`), ensure `CopilotKit` is placed before them in your plugin configuration.\n\n### Add API Route\n\nCopilot requires a server-side API endpoint to communicate with the AI model. Add the pre-configured Copilot API route:\n\n<ComponentSource name=\"copilot-api\" />\n\n### Configure Environment\n\nEnsure your AI Gateway key is set in your environment variables:\n\n```bash title=\".env.local\"\nAI_GATEWAY_API_KEY=\"your-api-key\"\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/ai @platejs/markdown\n```\n\n### Add Plugins\n\n```tsx\nimport { CopilotPlugin } from '@platejs/ai/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    MarkdownPlugin,\n    CopilotPlugin,\n    // Place tab-using plugins after CopilotPlugin to avoid conflicts\n    // IndentPlugin,\n    // TabbablePlugin,\n  ],\n});\n```\n\n- `MarkdownPlugin`: Required for serializing editor content to send as a prompt.\n- `CopilotPlugin`: Enables AI-powered text completion.\n\n**Tab Key Handling**: The Copilot plugin uses the Tab key to accept suggestions. To avoid conflicts with other plugins that use Tab (like `IndentPlugin` or `TabbablePlugin`), ensure `CopilotPlugin` is placed before them in your plugin configuration.\n\n### Configure Plugins\n\n```tsx\nimport { CopilotPlugin } from '@platejs/ai/react';\nimport { serializeMd, stripMarkdown } from '@platejs/markdown';\nimport { GhostText } from '@/components/ui/ghost-text';\n\nconst plugins = [\n  // ...otherPlugins,\n  MarkdownPlugin.configure({\n    options: {\n      remarkPlugins: [remarkMath, remarkGfm, remarkMdx],\n    },\n  }),\n  CopilotPlugin.configure(({ api }) => ({\n    options: {\n      completeOptions: {\n        api: '/api/ai/copilot',\n        onError: () => {\n          // Mock the API response. Remove when you implement the route /api/ai/copilot\n          api.copilot.setBlockSuggestion({\n            text: stripMarkdown('This is a mock suggestion.'),\n          });\n        },\n        onFinish: (_, completion) => {\n          if (completion === '0') return;\n\n          api.copilot.setBlockSuggestion({\n            text: stripMarkdown(completion),\n          });\n        },\n      },\n      debounceDelay: 500,\n      renderGhostText: GhostText,\n    },\n    shortcuts: {\n      accept: { keys: 'tab' },\n      acceptNextWord: { keys: 'mod+right' },\n      reject: { keys: 'escape' },\n      triggerSuggestion: { keys: 'ctrl+space' },\n    },\n  })),\n];\n```\n\n- `completeOptions`: Configures the Vercel AI SDK `useCompletion` hook.\n  - `api`: The endpoint for your AI completion route.\n  - `onError`: A callback for handling errors (used for mocking during development).\n  - `onFinish`: A callback to handle the completed suggestion. Here, it sets the suggestion in the editor.\n- `debounceDelay`: The delay in milliseconds for auto-triggering suggestions after the user stops typing.\n- `renderGhostText`: The React component used to display the suggestion inline.\n- `shortcuts`: Defines keyboard shortcuts for interacting with Copilot suggestions.\n\n### Add API Route\n\nCreate an API route handler at `app/api/ai/copilot/route.ts` to process AI requests. This endpoint will receive the prompt from the editor and call the AI model.\n\n```tsx title=\"app/api/ai/copilot/route.ts\"\nimport type { NextRequest } from 'next/server';\n\nimport { createGateway, generateText } from 'ai';\nimport { NextResponse } from 'next/server';\n\nexport async function POST(req: NextRequest) {\n  const {\n    apiKey: key,\n    instructions,\n    model = 'gpt-4o-mini',\n    prompt,\n  } = await req.json();\n\n  const apiKey = key || process.env.AI_GATEWAY_API_KEY;\n\n  if (!apiKey) {\n    return NextResponse.json(\n      { error: 'Missing AI Gateway API key.' },\n      { status: 401 }\n    );\n  }\n\n  const gateway = createGateway({ apiKey });\n\n  try {\n    const result = await generateText({\n      abortSignal: req.signal,\n      instructions,\n      maxOutputTokens: 50,\n      model: gateway(`openai/${model}`),\n      prompt,\n      temperature: 0.7,\n    });\n\n    return NextResponse.json(result);\n  } catch (error) {\n    if (error instanceof Error && error.name === 'AbortError') {\n      return NextResponse.json(null, { status: 408 });\n    }\n\n    return NextResponse.json(\n      { error: 'Failed to process AI request' },\n      { status: 500 }\n    );\n  }\n}\n```\n\nThen, set your `AI_GATEWAY_API_KEY` in `.env.local`.\n\n### Instructions\n\nInstructions define the AI's role and behavior. Modify the `body.instructions` property in `completeOptions`:\n\n```tsx\nCopilotPlugin.configure(({ api }) => ({\n  options: {\n    completeOptions: {\n      api: '/api/ai/copilot',\n      body: {\n        instructions: `You are an advanced AI writing assistant, similar to VSCode Copilot but for general text. Your task is to predict and generate the next part of the text based on the given context.\n\nRules:\n- Continue the text naturally up to the next punctuation mark (., ,, ;, :, ?, or !).\n- Maintain style and tone. Don't repeat given text.\n- For unclear context, provide the most likely continuation.\n- Handle code snippets, lists, or structured text if needed.\n- Don't include \"\"\" in your response.\n- CRITICAL: Always end with a punctuation mark.\n- CRITICAL: Avoid starting a new block. Do not use block formatting like >, #, 1., 2., -, etc. The suggestion should continue in the same block as the context.\n- If no context is provided or you can't generate a continuation, return \"0\" without explanation.`,\n      },\n      // ... other options\n    },\n    // ... other plugin options\n  },\n})),\n```\n\n### User Prompt\n\nThe user prompt (via `getPrompt`) determines what context is sent to the AI. You can customize it to include more context or format it differently:\n\n```tsx\nCopilotPlugin.configure(({ api }) => ({\n  options: {\n    getPrompt: ({ editor }) => {\n        const contextEntry = editor.api.block({ highest: true });\n\n        if (!contextEntry) return '';\n\n        const prompt = serializeMd(editor, {\n          value: [contextEntry[0] as TElement],\n        });\n\n        return `Continue the text up to the next punctuation mark:\n\"\"\"\n${prompt}\n\"\"\"`;\n      },\n    // ... other options\n  },\n})),\n```\n\n</Steps>\n\n\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"copilot-pro\" />\n\n## Customization\n\n### Switching AI Models\n\nConfigure different AI models and providers in your API route:\n\n```tsx title=\"app/api/ai/copilot/route.ts\"\nimport { createGateway, generateText } from 'ai';\n\nexport async function POST(req: NextRequest) {\n  const {\n    instructions,\n    model = 'openai/gpt-4o-mini',\n    prompt,\n  } = await req.json();\n\n  const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY });\n\n  const result = await generateText({\n    instructions,\n    maxOutputTokens: 50,\n    model: gateway(model),\n    prompt,\n    temperature: 0.7,\n  });\n\n  return NextResponse.json(result);\n}\n```\n\nConfigure the model in your `CopilotPlugin`:\n\n```tsx\nCopilotPlugin.configure(({ api }) => ({\n  options: {\n    completeOptions: {\n      api: '/api/ai/copilot',\n      body: {\n        instructions: 'Continue the current paragraph in the same tone.',\n        model: 'anthropic/claude-3-haiku-20240307',\n      },\n    },\n    // ... other options\n  },\n})),\n```\n\nFor more AI providers and models, see the [Vercel AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers).\n\n### Custom Trigger Conditions\n\nControl when suggestions are automatically triggered:\n\n```tsx\nCopilotPlugin.configure(({ api }) => ({\n  options: {\n    triggerQuery: ({ editor }) => {\n      // Only trigger in paragraph blocks\n      const block = editor.api.block();\n      if (!block || block[0].type !== 'p') return false;\n      \n      // Standard checks\n      return editor.selection && \n             !editor.api.isExpanded() && \n             editor.api.isAtEnd();\n    },\n    autoTriggerQuery: ({ editor }) => {\n      // Custom conditions for auto-triggering\n      const block = editor.api.block();\n      if (!block) return false;\n      \n      const text = editor.api.string(block[0]);\n      \n      // Trigger after question words\n      return /\\b(what|how|why|when|where)\\s*$/i.test(text);\n    },\n    // ... other options\n  },\n})),\n```\n\n### Security Considerations\n\nImplement security best practices for Copilot API:\n\n```tsx title=\"app/api/ai/copilot/route.ts\"\nexport async function POST(req: NextRequest) {\n  const { instructions, prompt } = await req.json();\n\n  // Validate prompt length\n  if (!prompt || prompt.length > 1000) {\n    return NextResponse.json({ error: 'Invalid prompt' }, { status: 400 });\n  }\n\n  // Rate limiting (implement with your preferred solution)\n  // await rateLimit(req);\n\n  // Content filtering for sensitive content\n  if (containsSensitiveContent(prompt)) {\n    return NextResponse.json({ error: 'Content filtered' }, { status: 400 });\n  }\n\n  // Process AI request...\n}\n```\n\n**Security Guidelines:**\n- **Input Validation**: Limit prompt length and validate content\n- **Rate Limiting**: Prevent abuse with request limits\n- **Content Filtering**: Filter sensitive or inappropriate content\n- **API Key Security**: Never expose API keys client-side\n- **Timeout Handling**: Handle request timeouts gracefully\n\n## Plugins\n\n### `CopilotPlugin`\n\nPlugin for AI-powered text completion suggestions.\n\n<API name=\"CopilotPlugin\">\n<APIOptions>\n  <APIItem name=\"autoTriggerQuery\" type=\"(options: { editor: PlateEditor }) => boolean\" optional>\n    Additional conditions to auto trigger copilot.\n    - **Default:** Checks:\n      - Block above is not empty\n      - Block above ends with a space\n      - No existing suggestion\n  </APIItem>\n  <APIItem name=\"completeOptions\" type=\"Partial<CompleteOptions>\">\n    AI completion configuration options. See [AI SDK useCompletion Parameters](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-completion#parameters).\n  </APIItem>\n  <APIItem name=\"debounceDelay\" type=\"number\" optional>\n    Delay for debouncing auto-triggered suggestions.\n    - **Default:** `0`\n  </APIItem>\n  <APIItem name=\"getNextWord\" type=\"(options: { text: string }) => { firstWord: string; remainingText: string }\" optional>\n    Function to extract the next word from suggestion text.\n  </APIItem>\n  <APIItem name=\"getPrompt\" type=\"(options: { editor: PlateEditor }) => string\" optional>\n    Function to generate the prompt for AI completion.\n    - **Default:** Uses markdown serialization of ancestor node\n  </APIItem>\n  <APIItem name=\"renderGhostText\" type=\"(() => React.ReactNode) | null\" optional>\n    Component to render ghost text suggestions.\n  </APIItem>\n  <APIItem name=\"triggerQuery\" type=\"(options: { editor: PlateEditor }) => boolean\" optional>\n    Conditions to trigger copilot.\n    - **Default:** Checks:\n      - Selection is not expanded\n      - Selection is at block end\n  </APIItem>\n</APIOptions>\n</API>\n\n## Transforms\n\n### `tf.copilot.accept()`\n\nAccepts the current suggestion and applies it to the editor content.\n\nDefault Shortcut: `Tab`\n\n### `tf.copilot.acceptNextWord()`\n\nAccepts only the next word of the current suggestion, allowing for granular acceptance of suggestions.\n\nExample Shortcut: `Cmd + →`\n\n## API\n\n### `api.copilot.reject()`\n\nResets the plugin state to its initial condition:\nDefault Shortcut: `Escape`\n\n### `api.copilot.triggerSuggestion()`\n\nTriggers a new suggestion request. The request may be debounced based on the plugin configuration.\n\nExample Shortcut: `Ctrl + Space`\n\n### `api.copilot.setBlockSuggestion()`\n\nSets suggestion text for a block.\n\n<API name=\"setBlockSuggestion\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"SetBlockSuggestionOptions\">\n    Options for setting the block suggestion.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"SetBlockSuggestionOptions\">\n  <APIItem name=\"text\" type=\"string\">\n    The suggestion text to set.\n  </APIItem>\n  <APIItem name=\"id\" type=\"string\" optional>\n    Target block ID.\n    - **Default:** Current block\n  </APIItem>\n</APIOptions>\n</API>\n\n### `api.copilot.stop()`\n\nStops ongoing suggestion requests and cleans up:\n\n- Cancels debounced trigger calls\n- Aborts current API request\n- Resets abort controller\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(ai)/copilot.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "comment-docs",
      "title": "Comment",
      "description": "Documentation for Comment",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(collaboration)/comment.mdx",
          "content": "---\ntitle: Comment\ndocs:\n  - route: https://pro.platejs.org/docs/examples/discussion\n    title: Plus\n  - route: /docs/components/comment-node\n    title: Comment Leaf\n  - route: /docs/components/comment-toolbar-button\n    title: Comment Toolbar Button\n  - route: /docs/components/block-discussion\n    title: Block Discussion\n---\n\n<ComponentPreview name=\"discussion-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Text Comments:** Add comments as text marks with inline annotations\n- **Overlapping Comments:** Support multiple comments on the same text\n- **Draft Comments:** Create draft comments before finalizing\n- **State Tracking:** Track comment state and user interactions\n- **Discussion Integration:** Works with discussion plugin for complete collaboration\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add comment functionality is with the `CommentKit`, which includes pre-configured `commentPlugin` and related components along with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"comment-kit\" />\n\n- [`CommentLeaf`](/docs/plate/components/comment-node): Renders comment text marks\n- [`BlockDiscussion`](/docs/plate/components/block-discussion): Renders discussion UI with comments integration\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CommentKit } from '@/components/editor/plugins/comment-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CommentKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/comment\n```\n\n### Extend Comment Plugin\n\nCreate the comment plugin with extended configuration for state management:\n\n```tsx\nimport { type ExtendConfig, type Path, isSlateString } from 'platejs';\nimport {\n  type BaseCommentConfig,\n  BaseCommentPlugin,\n  getDraftCommentKey,\n} from '@platejs/comment';\nimport { toTPlatePlugin } from 'platejs/react';\nimport { CommentLeaf } from '@/components/ui/comment-node';\n\ntype CommentConfig = ExtendConfig<\n  BaseCommentConfig,\n  {\n    activeId: string | null;\n    commentingBlock: Path | null;\n    hoverId: string | null;\n  }\n>;\n\nexport const commentPlugin = toTPlatePlugin<CommentConfig>(\n  BaseCommentPlugin,\n  ({ editor }) => ({\n    options: {\n      activeId: null,\n      commentingBlock: null,\n      hoverId: null,\n    },\n    render: {\n      node: CommentLeaf,\n    },\n  })\n);\n```\n\n- `options.activeId`: Currently active comment ID for visual highlighting\n- `options.commentingBlock`: Path of the block currently being commented\n- `options.hoverId`: Currently hovered comment ID for hover effects\n- `render.node`: Assigns [`CommentLeaf`](/docs/plate/components/comment-node) to render comment text marks\n\n### Add Click Handler\n\nAdd click handling to manage active comment state:\n\n```tsx\nexport const commentPlugin = toTPlatePlugin<CommentConfig>(\n  BaseCommentPlugin,\n  ({ editor }) => ({\n    handlers: {\n      // Set active comment when clicking on comment marks\n      onClick: ({ api, event, setOption, type }) => {\n        let leaf = event.target as HTMLElement;\n        let isSet = false;\n\n        const unsetActiveComment = () => {\n          setOption('activeId', null);\n          isSet = true;\n        };\n\n        if (!isSlateString(leaf)) unsetActiveComment();\n\n        while (leaf.parentElement) {\n          if (leaf.classList.contains(`slate-${type}`)) {\n            const commentsEntry = api.comment.node();\n\n            if (!commentsEntry) {\n              unsetActiveComment();\n              break;\n            }\n\n            const id = api.comment.nodeId(commentsEntry[0]);\n            setOption('activeId', id ?? null);\n            isSet = true;\n            break;\n          }\n\n          leaf = leaf.parentElement;\n        }\n\n        if (!isSet) unsetActiveComment();\n      },\n    },\n    // ... previous options and render\n  })\n);\n```\n\nThe click handler tracks which comment is currently active:\n\n- **Detects comment clicks**: Traverses DOM to find comment elements\n- **Sets active state**: Updates `activeId` when clicking on comments\n- **Clears state**: Unsets `activeId` when clicking outside comments\n- **Visual feedback**: Enables hover/active styling in comment components\n\n### Extend Transforms\n\nExtend the `setDraft` transform for enhanced functionality:\n\n```tsx\nexport const commentPlugin = toTPlatePlugin<CommentConfig>(\n  BaseCommentPlugin,\n  ({ editor }) => ({\n    // ... previous configuration\n  })\n)\n  .extendTransforms(\n    ({\n      editor,\n      setOption,\n      tf: {\n        comment: { setDraft },\n      },\n    }) => ({\n      setDraft: () => {\n        if (editor.api.isCollapsed()) {\n          editor.tf.select(editor.api.block()![1]);\n        }\n\n        setDraft();\n\n        editor.tf.collapse();\n        setOption('activeId', getDraftCommentKey());\n        setOption('commentingBlock', editor.selection!.focus.path.slice(0, 1));\n      },\n    })\n  )\n  .configure({\n    node: { component: CommentLeaf },\n    shortcuts: {\n      setDraft: { keys: 'mod+shift+m' },\n    },\n  });\n```\n\n### Add Toolbar Button\n\nYou can add [`CommentToolbarButton`](/docs/plate/components/comment-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to add comments on selected text.\n\n### Add Plugins\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    commentPlugin,\n  ],\n});\n```\n\n### Discussion Integration\n\nThe comment plugin works with the [discussion plugin](/docs/plate/discussion) for complete collaboration:\n\n```tsx\nimport { discussionPlugin } from '@/components/editor/plugins/discussion-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    discussionPlugin,\n    commentPlugin,\n  ],\n});\n```\n\n</Steps>\n\n## Keyboard Shortcuts\n\n<KeyTable>\n  <KeyTableItem hotkey=\"Cmd + Shift + M\">\n    Add a comment on the selected text.\n  </KeyTableItem>\n</KeyTable>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"discussion-pro\" />\n\n## Plugins\n\n### `CommentPlugin`\n\nPlugin for creating and managing text comments with state tracking and discussion integration.\n\n<API name=\"CommentPlugin\">\n  <APIOptions>\n    <APIItem name=\"activeId\" type=\"string | null\">\n      Currently active comment ID for visual highlighting. Used internally to\n      track state.\n    </APIItem>\n    <APIItem name=\"commentingBlock\" type=\"Path | null\">\n      Path of the block currently being commented on.\n    </APIItem>\n    <APIItem name=\"hoverId\" type=\"string | null\">\n      Currently hovered comment ID for hover effects.\n    </APIItem>\n  </APIOptions>\n</API>\n\n## API\n\n### `api.comment.has`\n\nChecks if a comment with the given ID exists in the editor.\n\n<API name=\"has\">\n  <APIParameters>\n    <APIItem name=\"options\" type=\"{ id: string }\">\n      Options containing the comment ID to check.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"boolean\">Whether the comment exists.</APIReturns>\n</API>\n\n### `api.comment.node`\n\nGets a comment node entry.\n\n<API name=\"node\">\n  <APIOptions\n    type=\"EditorNodesOptions & { id?: string; isDraft?: boolean }\"\n    optional\n  >\n    Options for finding the node.\n  </APIOptions>\n  <APIReturns type=\"NodeEntry<TCommentText> | undefined\">\n    The comment node entry if found.\n  </APIReturns>\n</API>\n\n### `api.comment.nodeId`\n\nGets the ID of a comment from a leaf node.\n\n<API name=\"nodeId\">\n  <APIParameters>\n    <APIItem name=\"leaf\" type=\"TCommentText\">\n      The comment leaf node.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"string | undefined\">The comment ID if found.</APIReturns>\n</API>\n\n### `api.comment.nodes`\n\nGets all comment node entries matching the options.\n\n<API name=\"nodes\">\n  <APIOptions\n    type=\"EditorNodesOptions & { id?: string; isDraft?: boolean }\"\n    optional\n  >\n    Options for finding the nodes.\n  </APIOptions>\n  <APIReturns type=\"NodeEntry<TCommentText>[]\">\n    Array of comment node entries.\n  </APIReturns>\n</API>\n\n## Transforms\n\n### `tf.comment.removeMark`\n\nRemoves the comment mark from the current selection or a specified location.\n\n<API name=\"removeMark\" />\n\n### `tf.comment.setDraft`\n\nSets a draft comment mark at the current selection.\n\n<API name=\"setDraft\">\n  <APIOptions type=\"SetNodesOptions\" optional>\n    Options for setting the draft comment.\n  </APIOptions>\n</API>\n\n### `tf.comment.unsetMark`\n\nUnsets comment nodes with the specified ID from the editor.\n\n<API name=\"unsetMark\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ id: string; transient?: boolean }\">\n    Options for unsetting comment marks.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"id\" type=\"string\">\n    The comment ID to unset.\n  </APIItem>\n  <APIItem name=\"transient\" type=\"boolean\" optional>\n    When true, removes all AI comments at once.\n    - **Default:** `false`\n  </APIItem>\n</APIOptions>\n</API>\n\n## Utilities\n\n### `getCommentCount`\n\nGets the count of non-draft comments in a comment node.\n\n<API name=\"getCommentCount\">\n  <APIParameters>\n    <APIItem name=\"node\" type=\"TCommentText\">\n      The comment node.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"number\">The count of comments.</APIReturns>\n</API>\n\n### `getCommentKey`\n\nGenerates a comment key based on the provided ID.\n\n<API name=\"getCommentKey\">\n  <APIParameters>\n    <APIItem name=\"id\" type=\"string\">\n      The ID of the comment.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"string\">The generated comment key.</APIReturns>\n</API>\n\n### `getCommentKeyId`\n\nExtracts the comment ID from a comment key.\n\n<API name=\"getCommentKeyId\">\n  <APIParameters>\n    <APIItem name=\"key\" type=\"string\">\n      The comment key.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"string\">The extracted comment ID.</APIReturns>\n</API>\n\n### `getCommentKeys`\n\nReturns an array of comment keys present in the given node.\n\n<API name=\"getCommentKeys\">\n  <APIParameters>\n    <APIItem name=\"node\" type=\"TCommentText\">\n      The node to check for comment keys.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"string[]\">Array of comment keys.</APIReturns>\n</API>\n\n### `getDraftCommentKey`\n\nGets the key used for draft comments.\n\n<API name=\"getDraftCommentKey\">\n  <APIReturns type=\"string\">The draft comment key.</APIReturns>\n</API>\n\n### `isCommentKey`\n\nChecks if a given key is a comment key.\n\n<API name=\"isCommentKey\">\n  <APIParameters>\n    <APIItem name=\"key\" type=\"string\">\n      The key to check.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"boolean\">Whether the key is a comment key.</APIReturns>\n</API>\n\n### `isCommentNodeById`\n\nChecks if a given node is a comment with the specified ID.\n\n<API name=\"isCommentNodeById\">\n  <APIParameters>\n    <APIItem name=\"node\" type=\"TNode\">\n      The node to check.\n    </APIItem>\n    <APIItem name=\"id\" type=\"string\">\n      The ID of the comment.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"boolean\">\n    Whether the node is a comment with the specified ID.\n  </APIReturns>\n</API>\n\n## Types\n\n### `TCommentText`\n\nText nodes that can contain comments.\n\n<API name=\"TCommentText\">\n  <APIAttributes>\n    <APIItem name=\"comment\" type=\"boolean\" optional>\n      Whether this text node contains comments.\n    </APIItem>\n    <APIItem name=\"comment_<id>\" type=\"boolean\" optional>\n      Comment data keyed by comment ID. Multiple comments can exist in one text\n      node.\n    </APIItem>\n  </APIAttributes>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(collaboration)/comment.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "discussion-docs",
      "title": "Discussion",
      "description": "Documentation for Discussion",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(collaboration)/discussion.mdx",
          "content": "---\ntitle: Discussion\ndocs:\n  - route: https://pro.platejs.org/docs/examples/discussion\n    title: Plus\n  - route: /docs/components/block-discussion\n    title: Block Discussion\n---\n\n<ComponentPreview name=\"discussion-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **User Management**: Store and manage user data with avatars and names\n- **Discussion Threads**: Manage discussion data structures with comments\n- **Current User Tracking**: Track the current active user for collaboration\n- **Data Storage**: Pure UI plugin for storing collaboration state\n- **Selector API**: Easy access to user data through plugin selectors\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add discussion functionality is with the `DiscussionKit`, which includes the pre-configured `discussionPlugin` along with its [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"discussion-kit\" />\n\n- [`BlockDiscussion`](/docs/plate/components/block-discussion): Renders discussion UI above nodes\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { DiscussionKit } from '@/components/editor/plugins/discussion-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...DiscussionKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/comment @platejs/suggestion\n```\n\n### Create Plugin\n\n```tsx\nimport { createPlatePlugin } from 'platejs/react';\nimport { BlockDiscussion } from '@/components/ui/block-discussion';\n\nexport interface TDiscussion {\n  id: string;\n  comments: TComment[];\n  createdAt: Date;\n  isResolved: boolean;\n  userId: string;\n  documentContent?: string;\n}\n\nconst usersData = {\n  alice: {\n    id: 'alice',\n    avatarUrl: 'https://api.dicebear.com/9.x/glass/svg?seed=alice6',\n    name: 'Alice',\n  },\n  bob: {\n    id: 'bob', \n    avatarUrl: 'https://api.dicebear.com/9.x/glass/svg?seed=bob4',\n    name: 'Bob',\n  },\n};\n\nexport const discussionPlugin = createPlatePlugin({\n  key: 'discussion',\n  options: {\n    currentUserId: 'alice',\n    discussions: [],\n    users: usersData,\n  },\n})\n  .configure({\n    render: { aboveNodes: BlockDiscussion },\n  })\n  .extendSelectors(({ getOption }) => ({\n    currentUser: () => getOption('users')[getOption('currentUserId')],\n    user: (id: string) => getOption('users')[id],\n  }));\n```\n\n- `options.currentUserId`: ID of the current active user\n- `options.discussions`: Array of discussion data structures  \n- `options.users`: Object mapping user IDs to user data\n- `render.aboveNodes`: Renders [`BlockDiscussion`](/docs/plate/components/block-discussion) above nodes for discussion UI\n- `selectors.currentUser`: Gets the current user data\n- `selectors.user`: Gets user data by ID\n\n### Add Plugin\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    discussionPlugin,\n  ],\n});\n```\n\n</Steps>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"discussion-pro\" />\n\n## Plugins\n\n### `discussionPlugin`\n\nPure UI plugin for managing collaboration state including users and discussion data.\n\n<API name=\"discussionPlugin\">\n<APIOptions>\n  <APIItem name=\"currentUserId\" type=\"string\">\n    ID of the current active user in the collaboration session.\n  </APIItem>\n  <APIItem name=\"discussions\" type=\"TDiscussion[]\">\n    Array of discussion objects containing comments and metadata.\n  </APIItem>\n  <APIItem name=\"users\" type=\"Record<string, UserData>\">\n    Object mapping user IDs to user information including name and avatar.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Selectors\n\n### `currentUser`\n\nGets the current user data.\n\n<API name=\"currentUser\">\n<APIReturns type=\"UserData\">\n  The current user's data including id, name, and avatarUrl.\n</APIReturns>\n</API>\n\n### `user`\n\nGets user data by ID.\n\n<API name=\"user\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    The user ID to look up.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"UserData | undefined\">\n  The user data if found, undefined otherwise.\n</APIReturns>\n</API>\n\n## Types\n\n### `TDiscussion`\n\nDiscussion data structure containing comments and metadata.\n\n<API name=\"TDiscussion\">\n<APIAttributes>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the discussion.\n  </APIItem>\n  <APIItem name=\"comments\" type=\"TComment[]\">\n    Array of comments in the discussion thread.\n  </APIItem>\n  <APIItem name=\"createdAt\" type=\"Date\">\n    When the discussion was created.\n  </APIItem>\n  <APIItem name=\"isResolved\" type=\"boolean\">\n    Whether the discussion has been resolved.\n  </APIItem>\n  <APIItem name=\"userId\" type=\"string\">\n    ID of the user who created the discussion.\n  </APIItem>\n  <APIItem name=\"documentContent\" type=\"string\" optional>\n    Content from the document related to this discussion.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `UserData`\n\nUser information structure for collaboration.\n\n<API name=\"UserData\">\n<APIAttributes>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the user.\n  </APIItem>\n  <APIItem name=\"name\" type=\"string\">\n    Display name of the user.\n  </APIItem>\n  <APIItem name=\"avatarUrl\" type=\"string\">\n    URL for the user's avatar image.\n  </APIItem>\n  <APIItem name=\"hue\" type=\"number\" optional>\n    Optional color hue for user identification.\n  </APIItem>\n</APIAttributes>\n</API> ",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(collaboration)/discussion.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "suggestion-docs",
      "title": "Suggestion",
      "description": "Documentation for Suggestion",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(collaboration)/suggestion.mdx",
          "content": "---\ntitle: Suggestion\ndocs:\n  - route: https://pro.platejs.org/docs/examples/discussion\n    title: Plus\n  - route: /docs/components/suggestion-node\n    title: Suggestion Leaf\n  - route: /docs/components/suggestion-toolbar-button\n    title: Suggestion Toolbar Button\n  - route: /docs/components/block-suggestion\n    title: Block suggestion\n  - route: /docs/components/block-discussion\n    title: Block discussion\n---\n\n<ComponentPreview name=\"discussion-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Text Suggestions:** Add suggestions as text marks with inline annotations\n- **Block Suggestions:** Create suggestions for entire blocks of content\n- **State Tracking:** Track suggestion state and user interactions\n- **Undo/Redo Support:** Full undo/redo support for suggestion changes\n- **Discussion Integration:** Works with discussion plugin for complete collaboration\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add suggestion functionality is with the `SuggestionKit`, which includes pre-configured `SuggestionPlugin` and related components along with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"suggestion-kit\" />\n\n- [`SuggestionLeaf`](/docs/plate/components/suggestion-node): Renders suggestion text marks\n- [`BlockSuggestion`](/docs/plate/components/block-suggestion): Renders block-level suggestions\n- [`SuggestionLineBreak`](/docs/plate/components/suggestion-node): Handles line breaks in suggestions\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...SuggestionKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/suggestion\n```\n\n### Extend Suggestion Plugin\n\nCreate the suggestion plugin with extended configuration for state management:\n\n```tsx\nimport {\n  type ExtendConfig,\n  isSlateEditor,\n  isSlateElement,\n  isSlateString,\n} from 'platejs';\nimport {\n  type BaseSuggestionConfig,\n  BaseSuggestionPlugin,\n} from '@platejs/suggestion';\nimport { createPlatePlugin, toTPlatePlugin } from 'platejs/react';\nimport { BlockSuggestion } from '@/components/ui/block-suggestion';\nimport { SuggestionLeaf } from '@/components/ui/suggestion-node';\n\nexport type SuggestionConfig = ExtendConfig<\n  BaseSuggestionConfig,\n  {\n    activeId: string | null;\n    hoverId: string | null;\n  }\n>;\n\nexport const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(\n  BaseSuggestionPlugin,\n  ({ editor }) => ({\n    options: {\n      activeId: null,\n      currentUserId: 'alice', // Set your current user ID\n      hoverId: null,\n    },\n    render: {\n      node: SuggestionLeaf,\n      belowRootNodes: ({ api, element }) => {\n        if (!api.suggestion!.isBlockSuggestion(element)) {\n          return null;\n        }\n\n        return <BlockSuggestion element={element} />;\n      },\n    },\n  })\n);\n```\n\n- `options.activeId`: Currently active suggestion ID for visual highlighting\n- `options.currentUserId`: ID of the current user creating suggestions  \n- `options.hoverId`: Currently hovered suggestion ID for hover effects\n- `render.node`: Assigns [`SuggestionLeaf`](/docs/plate/components/suggestion-node) to render suggestion text marks\n- `render.belowRootNodes`: Renders [`BlockSuggestion`](/docs/plate/components/block-suggestion) for block-level suggestions\n\n### Add Click Handler\n\nAdd click handling to manage active suggestion state:\n\n```tsx\nexport const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(\n  BaseSuggestionPlugin,\n  ({ editor }) => ({\n    handlers: {\n      // Unset active suggestion when clicking outside of suggestion\n      onClick: ({ api, event, setOption, type }) => {\n        let leaf = event.target as HTMLElement;\n        let isSet = false;\n\n        const unsetActiveSuggestion = () => {\n          setOption('activeId', null);\n          isSet = true;\n        };\n\n        if (!isSlateString(leaf)) unsetActiveSuggestion();\n\n        while (\n          leaf.parentElement &&\n          !isSlateElement(leaf.parentElement) &&\n          !isSlateEditor(leaf.parentElement)\n        ) {\n          if (leaf.classList.contains(`slate-${type}`)) {\n            const suggestionEntry = api.suggestion!.node({ isText: true });\n\n            if (!suggestionEntry) {\n              unsetActiveSuggestion();\n              break;\n            }\n\n            const id = api.suggestion!.nodeId(suggestionEntry[0]);\n            setOption('activeId', id ?? null);\n            isSet = true;\n            break;\n          }\n\n          leaf = leaf.parentElement;\n        }\n\n        if (!isSet) unsetActiveSuggestion();\n      },\n    },\n    // ... previous options and render\n  })\n);\n```\n\nThe click handler tracks which suggestion is currently active:\n- **Detects suggestion clicks**: Traverses DOM to find suggestion elements\n- **Sets active state**: Updates `activeId` when clicking on suggestions\n- **Clears state**: Unsets `activeId` when clicking outside suggestions\n- **Visual feedback**: Enables hover/active styling in suggestion components\n\n### Add Plugins\n\n```tsx\nimport { createPlateEditor, createPlatePlugin } from 'platejs/react';\nimport { SuggestionLineBreak } from '@/components/ui/suggestion-node';\n\nconst suggestionLineBreakPlugin = createPlatePlugin({\n  key: 'suggestionLineBreak',\n  render: { belowNodes: SuggestionLineBreak as any },\n});\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    suggestionPlugin,\n    suggestionLineBreakPlugin,\n  ],\n});\n```\n\n- `render.belowNodes`: Renders [`SuggestionLineBreak`](/docs/plate/components/suggestion-node) below nodes to handle line break suggestions\n\n### Enable Suggestion Mode\n\nUse the plugin's API to control suggestion mode:\n\n```tsx\nimport { useEditorRef, usePluginOption } from 'platejs/react';\n\nfunction SuggestionToolbar() {\n  const editor = useEditorRef();\n  const isSuggesting = usePluginOption(suggestionPlugin, 'isSuggesting');\n\n  const toggleSuggesting = () => {\n    editor.setOption(suggestionPlugin, 'isSuggesting', !isSuggesting);\n  };\n\n  return (\n    <button onClick={toggleSuggesting}>\n      {isSuggesting ? 'Stop Suggesting' : 'Start Suggesting'}\n    </button>\n  );\n}\n```\n\n### Add Toolbar Button\n\nYou can add [`SuggestionToolbarButton`](/docs/plate/components/suggestion-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to toggle suggestion mode in the editor.\n\n### Discussion Integration\n\nThe suggestion plugin works with the [discussion plugin](/docs/plate/discussion) for complete collaboration:\n\n```tsx\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    discussionPlugin,\n    suggestionPlugin.configure({\n      options: {\n        currentUserId: 'alice',\n      },\n    }),\n    suggestionLineBreakPlugin,\n  ],\n});\n```\n\n</Steps>\n\n## Keyboard Shortcuts\n\n<KeyTable>\n  <KeyTableItem hotkey=\"Cmd + Shift + S\">\n    Add a suggestion on the selected text.\n  </KeyTableItem>\n</KeyTable>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"discussion-pro\" />\n\n## Plugins\n\n### `SuggestionPlugin`\n\nPlugin for creating and managing text and block suggestions with state tracking and discussion integration.\n\n<API name=\"SuggestionPlugin\">\n<APIOptions>\n  <APIItem name=\"currentUserId\" type=\"string | null\">\n    ID of the current user creating suggestions. Required for proper suggestion attribution.\n  </APIItem>\n  <APIItem name=\"isSuggesting\" type=\"boolean\">\n    Whether the editor is currently in suggestion mode. Used internally to track state.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.suggestion.dataList`\n\nGets suggestion data from a text node.\n\n<API name=\"dataList\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TSuggestionText\">\n    The suggestion text node.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"TInlineSuggestionData[]\">\n  Array of suggestion data.\n</APIReturns>\n</API>\n\n### `api.suggestion.isBlockSuggestion`\n\nChecks if a node is a block suggestion element.\n\n<API name=\"isBlockSuggestion\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TElement\">\n    The node to check.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"node is TSuggestionElement\">\n  Whether the node is a block suggestion.\n</APIReturns>\n</API>\n\n### `api.suggestion.node`\n\nGets a suggestion node entry.\n\n<API name=\"node\">\n<APIOptions type=\"EditorNodesOptions & { id?: string; isText?: boolean }\" optional>\n  Options for finding the node.\n</APIOptions>\n<APIReturns type=\"NodeEntry<TSuggestionElement | TSuggestionText> | undefined\">\n  The suggestion node entry if found.\n</APIReturns>\n</API>\n\n### `api.suggestion.nodeId`\n\nGets the ID of a suggestion from a node.\n\n<API name=\"nodeId\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TElement | TSuggestionText\">\n    The node to get ID from.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"string | undefined\">\n  The suggestion ID if found.\n</APIReturns>\n</API>\n\n### `api.suggestion.nodes`\n\nGets all suggestion node entries matching the options.\n\n<API name=\"nodes\">\n<APIOptions type=\"EditorNodesOptions\" optional>\n  Options for finding the nodes.\n</APIOptions>\n<APIReturns type=\"NodeEntry<TElement | TSuggestionText>[]\">\n  Array of suggestion node entries.\n</APIReturns>\n</API>\n\n### `api.suggestion.suggestionData`\n\nGets suggestion data from a node.\n\n<API name=\"suggestionData\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TElement | TSuggestionText\">\n    The node to get suggestion data from.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"TInlineSuggestionData | TSuggestionElement['suggestion'] | undefined\">\n  The suggestion data if found.\n</APIReturns>\n</API>\n\n### `api.suggestion.withoutSuggestions`\n\nTemporarily disables suggestions while executing a function.\n\n<API name=\"withoutSuggestions\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    The function to execute.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Types\n\n### `TSuggestionText`\n\nText nodes that can contain suggestions.\n\n<API name=\"TSuggestionText\">\n<APIAttributes>\n  <APIItem name=\"suggestion\" type=\"boolean\" optional>\n    Whether this is a suggestion.\n  </APIItem>\n  <APIItem name=\"suggestion_<id>\" type=\"TInlineSuggestionData\" optional>\n    Suggestion data. Multiple suggestions can exist in one text node.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `TSuggestionElement`\n\nBlock elements that contain suggestion metadata.\n\n<API name=\"TSuggestionElement\">\n<APIAttributes>\n  <APIItem name=\"suggestion\" type=\"TSuggestionData\">\n    Block-level suggestion data including type, user, and timing information.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `TInlineSuggestionData`\n\nData structure for inline text suggestions.\n\n<API name=\"TInlineSuggestionData\">\n<APIAttributes>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the suggestion.\n  </APIItem>\n  <APIItem name=\"userId\" type=\"string\">\n    ID of the user who created the suggestion.\n  </APIItem>\n  <APIItem name=\"createdAt\" type=\"number\">\n    Timestamp when the suggestion was created.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'insert' | 'remove' | 'update'\">\n    Type of suggestion operation.\n  </APIItem>\n  <APIItem name=\"newProperties\" type=\"object\" optional>\n    For update suggestions, the new mark properties being suggested.\n  </APIItem>\n  <APIItem name=\"properties\" type=\"object\" optional>\n    For update suggestions, the previous mark properties.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `TSuggestionData`\n\nData structure for block-level suggestions.\n\n<API name=\"TSuggestionData\">\n<APIAttributes>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the suggestion.\n  </APIItem>\n  <APIItem name=\"userId\" type=\"string\">\n    ID of the user who created the suggestion.\n  </APIItem>\n  <APIItem name=\"createdAt\" type=\"number\">\n    Timestamp when the suggestion was created.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'insert' | 'remove'\">\n    Type of block suggestion operation.\n  </APIItem>\n  <APIItem name=\"isLineBreak\" type=\"boolean\" optional>\n    Whether this suggestion represents a line break insertion.\n  </APIItem>\n</APIAttributes>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(collaboration)/suggestion.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "yjs-docs",
      "title": "Collaboration",
      "description": "Real-time collaboration with Yjs",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(collaboration)/yjs.mdx",
          "content": "---\ntitle: Collaboration\ndescription: Real-time collaboration with Yjs\ntoc: true\n---\n\n<ComponentPreview name=\"collaboration-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Multi-Provider Support:** Enables real-time collaboration using [Yjs](https://github.com/yjs/yjs) and [slate-yjs](https://docs.slate-yjs.dev/). Supports multiple synchronization providers simultaneously (e.g., IndexedDB + Hocuspocus + WebRTC) working on a shared `Y.Doc`.\n- **Built-in Providers:** Includes support for [IndexedDB](https://github.com/yjs/y-indexeddb) local persistence, [Hocuspocus](https://tiptap.dev/hocuspocus) server-based collaboration, and [WebRTC](https://github.com/yjs/y-webrtc) peer-to-peer collaboration.\n- **Custom Providers:** Extensible architecture allows adding custom providers by implementing the `UnifiedProvider` interface.\n- **Awareness & Cursors:** Integrates Yjs Awareness protocol for sharing cursor locations and other ephemeral state between users. Includes [`RemoteCursorOverlay`](/docs/plate/components/remote-cursor-overlay) for rendering remote cursors.\n- **Customizable Cursors:** Cursor appearance (name, color) can be customized via `cursors`.\n- **Manual Lifecycle:** Provides explicit `init` and `destroy` methods for managing the Yjs connection lifecycle.\n\n</PackageInfo>\n\n## Usage\n\n<Steps>\n\n### Installation\n\nInstall the core Yjs plugin.\n\n```bash\nnpm install @platejs/yjs\n```\n\nFor Hocuspocus server-based collaboration:\n\n```bash\nnpm install @hocuspocus/provider\n```\n\nFor WebRTC peer-to-peer collaboration:\n\n```bash\nnpm install y-webrtc\n```\n\n### Add Plugin\n\n```tsx\nimport { YjsPlugin } from '@platejs/yjs/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    YjsPlugin,\n  ],\n  // Important: Skip Plate's default initialization when using Yjs\n  skipInitialization: true,\n});\n```\n\n<Callout type=\"warning\" title=\"Required Editor Configuration\">\n  It's crucial to set `skipInitialization: true` when creating the editor. Yjs manages the initial document state, so Plate's default value initialization should be skipped to avoid conflicts.\n</Callout>\n\n### Configure YjsPlugin\n\nConfigure the plugin with providers and cursor settings:\n\n```tsx\nimport { YjsPlugin } from '@platejs/yjs/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { RemoteCursorOverlay } from '@/components/ui/remote-cursor-overlay';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    YjsPlugin.configure({\n      render: {\n        afterEditable: RemoteCursorOverlay,\n      },\n      options: {\n        // Configure local user cursor appearance\n        cursors: {\n          data: {\n            name: 'User Name', // Replace with dynamic user name\n            color: '#aabbcc', // Replace with dynamic user color\n          },\n        },\n        // Configure providers. All providers share the same Y.Doc and Awareness instance.\n        providers: [\n          // Example: IndexedDB provider for local persistence\n          {\n            type: 'indexeddb',\n            options: {\n              docName: 'my-document-id', // Unique IndexedDB database name\n            },\n          },\n          // Example: Hocuspocus provider\n          {\n            type: 'hocuspocus',\n            options: {\n              name: 'my-document-id', // Unique identifier for the document\n              url: 'ws://localhost:8888', // Your Hocuspocus server URL\n            },\n          },\n          // Example: WebRTC provider (can be used alongside Hocuspocus)\n          {\n            type: 'webrtc',\n            options: {\n              roomName: 'my-document-id', // Must match the document identifier\n              signaling: ['ws://localhost:4444'], // Optional: Your signaling server URLs\n            },\n          },\n        ],\n      },\n    }),\n  ],\n  skipInitialization: true,\n});\n```\n\n- `render.afterEditable`: Assigns [`RemoteCursorOverlay`](/docs/plate/components/remote-cursor-overlay) to render remote user cursors.\n- `cursors.data`: Configures the local user's cursor appearance with name and color.\n- `providers`: Array of collaboration providers to use (Hocuspocus, WebRTC, or custom providers).\n\n### Add Editor Container\n\nThe `RemoteCursorOverlay` requires a positioned container around the editor content. Use [`EditorContainer`](/docs/plate/components/editor) component or `PlateContainer` from `platejs/react`:\n\n```tsx\nimport { Plate } from 'platejs/react';\nimport { EditorContainer } from '@/components/ui/editor';\n\nreturn (\n  <Plate editor={editor}>\n    <EditorContainer>\n      <Editor />\n    </EditorContainer>\n  </Plate>\n);\n```\n\n### Initialize Yjs Connection\n\nYjs connection and state initialization are handled manually, typically within a `useEffect` hook:\n\n```tsx\nimport React, { useEffect } from 'react';\nimport { YjsPlugin } from '@platejs/yjs/react';\nimport { useMounted } from '@/hooks/use-mounted'; // Or your own mounted check\n\nconst MyEditorComponent = ({ documentId, initialValue }) => {\n  const editor = usePlateEditor(/** editor config from previous steps **/);\n  const mounted = useMounted();\n\n  useEffect(() => {\n    // Ensure component is mounted and editor is ready\n    if (!mounted) return;\n\n    // Initialize Yjs connection, sync document, and set initial editor state\n    editor.getApi(YjsPlugin).yjs.init({\n      id: documentId,          // Unique identifier for the Yjs document\n      value: initialValue,     // Initial content if the Y.Doc is empty\n    });\n\n    // Clean up: Destroy connection when component unmounts\n    return () => {\n      editor.getApi(YjsPlugin).yjs.destroy();\n    };\n  }, [editor, mounted]);\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n};\n```\n\n<Callout>\n  **Initial Value**: The `value` passed to `init` is only used to populate the Y.Doc if it's completely empty on the backend/peer network. If the document already exists, its content will be synced, and this initial value will be ignored.\n  \n  **Lifecycle Management**: You **must** call `editor.api.yjs.init()` to establish the connection and `editor.api.yjs.destroy()` on component unmount to clean up resources.\n</Callout>\n\n### Monitor Connection Status (Optional)\n\nAccess provider states and add event handlers for connection monitoring:\n\n```tsx\nimport React from 'react';\nimport { YjsPlugin } from '@platejs/yjs/react';\nimport { usePluginOption } from 'platejs/react';\n\nfunction EditorStatus() {\n  // Access provider states directly (read-only)\n  const providers = usePluginOption(YjsPlugin, '_providers');\n  const isConnected = usePluginOption(YjsPlugin, '_isConnected');\n\n  return (\n    <div>\n      {providers.map((provider) => (\n        <span key={provider.type}>\n          {provider.type}: {provider.isConnected ? 'Connected' : 'Disconnected'} ({provider.isSynced ? 'Synced' : 'Syncing'})\n        </span>\n      ))}\n    </div>\n  );\n}\n\n// Add event handlers for connection events:\nYjsPlugin.configure({\n  options: {\n    // ... other options\n    onConnect: ({ type }) => console.debug(`Provider ${type} connected!`),\n    onDisconnect: ({ type }) => console.debug(`Provider ${type} disconnected.`),\n    onSyncChange: ({ type, isSynced }) => console.debug(`Provider ${type} sync status: ${isSynced}`),\n    onError: ({ type, error }) => console.error(`Error in provider ${type}:`, error),\n  },\n});\n```\n\n</Steps>\n\n## Provider Types\n\n### Hocuspocus Provider\n\nServer-based collaboration using [Hocuspocus](https://tiptap.dev/hocuspocus). Requires a running Hocuspocus server.\n\n```tsx\ntype HocuspocusProviderConfig = {\n  type: 'hocuspocus',\n  options: {\n    name: string;     // Document identifier\n    url: string;      // WebSocket server URL\n    token?: string;   // Authentication token\n    wsOptions?: HocuspocusProviderWebsocketConfiguration; // Advanced websocket config (headers, protocols, etc.)\n  }\n}\n```\n\n#### `wsOptions`\n\nYou can pass a `wsOptions` field to configure advanced websocket options for the Hocuspocus provider. This is useful for custom headers, authentication, protocols, or other websocket settings supported by [`HocuspocusProviderWebsocket`](https://tiptap.dev/hocuspocus/api/provider#websocket-configuration).\n\nExample usage:\n\n```tsx\n{\n  type: 'hocuspocus',\n  options: {\n    name: 'my-document-id',\n  },\n  wsOptions: {\n    url: 'ws://localhost:8888',\n    maxAttempts: 5,\n    parameters: {\n      // request parameters\n    }\n  },\n}\n```\n\n### WebRTC Provider\n\nPeer-to-peer collaboration using [y-webrtc](https://github.com/yjs/y-webrtc).\n\n```tsx\ntype WebRTCProviderConfig = {\n  type: 'webrtc',\n  options: {\n    roomName: string;      // Room name for collaboration\n    signaling?: string[];  // Signaling server URLs\n    password?: string;     // Room password\n    maxConns?: number;     // Max connections\n    peerOpts?: object;     // WebRTC peer options\n  }\n}\n```\n\n### IndexedDB Provider\n\nLocal browser persistence using [y-indexeddb](https://github.com/yjs/y-indexeddb). Use it alongside a network provider when the editor should restore local state before remote sync finishes.\n\n```tsx\ntype IndexeddbProviderConfig = {\n  type: 'indexeddb',\n  options: {\n    docName: string; // Stable IndexedDB database name for this document\n  }\n}\n```\n\n### Custom Provider\n\nCreate custom providers by implementing the `UnifiedProvider` interface:\n\n```typescript\ninterface UnifiedProvider {\n  awareness: Awareness;\n  document: Y.Doc;\n  type: string;\n  connect: () => void;\n  destroy: () => void;\n  disconnect: () => void;\n  isConnected: boolean;\n  isSynced: boolean;\n}\n```\n\nUse custom providers directly in the providers array:\n\n```tsx\nconst customProvider = new MyCustomProvider({ doc: ydoc, awareness });\n\nYjsPlugin.configure({\n  options: {\n    providers: [customProvider],\n  },\n});\n```\n\n## Backend Setup\n\n### IndexedDB Local Persistence\n\nIndexedDB runs in the browser and uses the shared `Y.Doc` created by `YjsPlugin`. Use the same document identifier for `docName` and your network provider room/name when you combine providers:\n\n```tsx\n{\n  type: 'indexeddb',\n  options: {\n    docName: 'document-1',\n  },\n}\n```\n\nIndexedDB does not transport remote awareness or cursors. It persists document updates locally; Hocuspocus or WebRTC still own multi-user transport.\n\n### Hocuspocus Server\n\nSet up a [Hocuspocus server](https://tiptap.dev/hocuspocus/getting-started) for server-based collaboration. Ensure the `url` and `name` in your provider options match your server configuration.\n\n### WebRTC Setup\n\n#### Signaling Server\n\nWebRTC requires signaling servers for peer discovery. Public servers work for testing but use your own for production:\n\n```bash\nnpm install y-webrtc\nPORT=4444 node ./node_modules/y-webrtc/bin/server.js\n```\n\nConfigure your client to use custom signaling:\n\n```tsx\n{\n  type: 'webrtc',\n  options: {\n    roomName: 'document-1',\n    signaling: ['ws://your-signaling-server.com:4444'],\n  },\n}\n```\n\n#### TURN Servers\n\n<Callout type=\"warning\">\n  WebRTC connections can fail due to firewalls. Use TURN servers or combine with Hocuspocus for production reliability.\n</Callout>\n\nConfigure TURN servers for reliable connections:\n\n```tsx\n{\n  type: 'webrtc',\n  options: {\n    roomName: 'document-1',\n    signaling: ['ws://your-signaling-server.com:4444'],\n    peerOpts: {\n      config: {\n        iceServers: [\n          { urls: 'stun:stun.l.google.com:19302' },\n          {\n            urls: 'turn:your-turn-server.com:3478',\n            username: 'username',\n            credential: 'password'\n          }\n        ]\n      }\n    }\n  }\n}\n```\n\n## Security\n\n**Authentication & Authorization:**\n- Use Hocuspocus's `onAuthenticate` hook to validate users\n- Implement document-level access control on your backend\n- Pass authentication tokens via the `token` option\n\n**Transport Security:**\n- Use `wss://` URLs in production for encrypted communication\n- Configure secure TURN servers with the `turns://` protocol\n\n**WebRTC Security:**\n- Use the `password` option for basic room access control\n- Configure secure signaling servers\n\nExample secure configuration:\n\n```tsx\nYjsPlugin.configure({\n  options: {\n    providers: [\n      {\n        type: 'hocuspocus',\n        options: {\n          name: 'secure-document-id',\n          url: 'wss://your-hocuspocus-server.com',\n          token: 'user-auth-token',\n        },\n      },\n      {\n        type: 'webrtc',\n        options: {\n          roomName: 'secure-document-id',\n          password: 'strong-room-password',\n          signaling: ['wss://your-secure-signaling.com'],\n          peerOpts: {\n            config: {\n              iceServers: [\n                {\n                  urls: 'turns:your-turn-server.com:443?transport=tcp',\n                  username: 'user',\n                  credential: 'pass'\n                }\n              ]\n            }\n          }\n        },\n      },\n    ],\n  },\n});\n```\n\n## Troubleshooting\n\n### Connection Issues\n\n**Check URLs and Names:**\n- Verify `url` (Hocuspocus) and `signaling` URLs (WebRTC) are correct\n- Ensure `docName`, `name`, or `roomName` matches exactly across providers that share one document\n- Use `ws://` for local development, `wss://` for production\n\n**Server Status:**\n- Verify Hocuspocus and signaling servers are running\n- Check server logs for errors\n- Test TURN server connectivity if using WebRTC\n\n**Network Issues:**\n- Firewalls may block WebSocket or WebRTC traffic\n- Use TURN servers configured for TCP (port 443) for better traversal\n- Check browser console for provider errors\n\n### Multiple Documents\n\n**Separate Instances:**\n- Create separate `Y.Doc` instances for each document\n- Use unique document identifiers for `name`/`roomName`\n- Pass unique `ydoc` and `awareness` instances to each editor\n\n### Sync Issues\n\n**Editor Initialization:**\n- Always set `skipInitialization: true` when creating the editor\n- Use `editor.api.yjs.init({ value })` for initial content\n- Ensure all providers use the exact same document identifier\n\n**Content Conflicts:**\n- Avoid manually manipulating the shared `Y.Doc`\n- Let Yjs handle all document operations through the editor\n\n### Cursor Issues\n\n**Overlay Setup:**\n- Include [`RemoteCursorOverlay`](/docs/plate/components/remote-cursor-overlay) in plugin render config\n- Use positioned container (`EditorContainer` or `PlateContainer`)\n- Verify `cursors.data` (name, color) is set correctly for local user\n\n## Related\n\n- [Yjs](https://github.com/yjs/yjs) - CRDT framework for collaboration\n- [slate-yjs](https://docs.slate-yjs.dev/) - Yjs bindings for Slate\n- [y-indexeddb](https://github.com/yjs/y-indexeddb) - IndexedDB persistence provider\n- [Hocuspocus](https://tiptap.dev/hocuspocus) - Backend server for Yjs\n- [y-webrtc](https://github.com/yjs/y-webrtc) - WebRTC provider\n- [RemoteCursorOverlay](/docs/plate/components/remote-cursor-overlay) - Remote cursor component\n- [EditorContainer](/docs/plate/components/editor) - Editor container component\n\n## Plugins\n\n### `YjsPlugin`\n\nEnables real-time collaboration using Yjs with support for multiple providers and remote cursors.\n\n<API name=\"YjsPlugin\">\n<APIOptions>\n  <APIItem name=\"providers\" type=\"(UnifiedProvider | YjsProviderConfig)[]\">\n    Array of provider configurations or pre-instantiated provider instances. The plugin will create instances from configurations and use existing instances directly. All providers will share the same Y.Doc and Awareness. Each configuration object specifies a provider `type` (for example `'indexeddb'`, `'hocuspocus'`,\n    or `'webrtc'`) and its specific `options`. Custom provider instances must conform to the\n    `UnifiedProvider` interface.\n  </APIItem>\n  <APIItem name=\"cursors\" type=\"WithCursorsOptions | null\" optional>\n    Configuration for remote cursors. Set to `null` to explicitly disable cursors. If omitted, cursors are enabled by default if providers are specified. Passed to `withTCursors`. See [WithCursorsOptions API](https://docs.slate-yjs.dev/api/slate-yjs-core/cursor-plugin#withcursors). Includes `data` for local user info and `autoSend` (default `true`).\n  </APIItem>\n  <APIItem name=\"ydoc\" type=\"Y.Doc\" optional>\n    Optional shared Y.Doc instance. If not provided, a new one will be created internally by the plugin. Provide your own if integrating with other Yjs tools or managing multiple documents.\n  </APIItem>\n  <APIItem name=\"awareness\" type=\"Awareness\" optional>\n    Optional shared Awareness instance. If not provided, a new one will be created.\n  </APIItem>\n  <APIItem name=\"onConnect\" type=\"(props: { type: YjsProviderType }) => void\" optional>\n    Callback fired when any provider successfully connects.\n  </APIItem>\n  <APIItem name=\"onDisconnect\" type=\"(props: { type: YjsProviderType }) => void\" optional>\n    Callback fired when any provider disconnects.\n  </APIItem>\n  <APIItem name=\"onError\" type=\"(props: { error: Error; type: YjsProviderType }) => void\" optional>\n    Callback fired when any provider encounters an error (e.g., connection failure).\n  </APIItem>\n  <APIItem name=\"onSyncChange\" type=\"(props: { isSynced: boolean; type: YjsProviderType }) => void\" optional>\n    Callback fired when the sync status (`provider.isSynced`) of any individual provider changes.\n  </APIItem>\n</APIOptions>\n<APIAttributes>\n  {/* Attributes are internal state, generally use options or event handlers instead */}\n  <APIItem name=\"_isConnected\" type=\"boolean\">\n    Internal state: Whether at least one provider is currently connected.\n  </APIItem>\n  <APIItem name=\"_isSynced\" type=\"boolean\">\n    Internal state: Reflects overall sync status.\n  </APIItem>\n  <APIItem name=\"_providers\" type=\"UnifiedProvider[]\">\n    Internal state: Array of all active, instantiated provider instances.\n  </APIItem>\n</APIAttributes>\n</API>\n\n## API\n\n### `api.yjs.init`\n\nInitializes the Yjs connection, binds it to the editor, sets up providers based on plugin configuration, potentially populates the Y.Doc with initial content, and connects providers. **Must be called after the editor is mounted.**\n\n<API name=\"editor.api.yjs.init\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Configuration object for initialization.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"id\" type=\"string\" optional>\n    A unique identifier for the Yjs document (e.g., room name, document ID). If not provided, `editor.id` is used. Essential for ensuring collaborators connect to the same document state.\n  </APIItem>\n  <APIItem name=\"value\" type=\"Value | string | ((editor: PlateEditor) => Value | Promise<Value>)\" optional>\n    The initial content for the editor. **This is only applied if the Y.Doc associated with the `id` is completely empty in the shared state (backend/peers).** If the document already exists, its content will be synced, ignoring this value. Can be Plate JSON (`Value`), an HTML string, or a function returning/resolving to `Value`. If omitted or empty, a default empty paragraph is used for initialization if the Y.Doc is new.\n  </APIItem>\n  <APIItem name=\"autoConnect\" type=\"boolean\" optional>\n    Whether to automatically call `provider.connect()` for all configured providers during initialization. Default: `true`. Set to `false` if you want to manage connections manually using `editor.api.yjs.connect()`.\n  </APIItem>\n  <APIItem name=\"autoSelect\" type=\"'start' | 'end'\" optional>\n    If set, automatically focuses the editor and places the cursor at the 'start' or 'end' of the document after initialization and sync.\n  </APIItem>\n  <APIItem name=\"selection\" type=\"Location\" optional>\n    Specific Plate `Location` to set the selection to after initialization, overriding `autoSelect`.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Promise<void>\">\n  Resolves when the initial setup (including potential async `value` resolution and YjsEditor binding) is complete. Note that provider connection and synchronization happen asynchronously.\n</APIReturns>\n</API>\n\n### `api.yjs.destroy`\n\nDisconnects all providers, cleans up Yjs bindings (detaches editor from Y.Doc), and destroys the awareness instance. **Must be called when the editor component unmounts** to prevent memory leaks and stale connections.\n\n### `api.yjs.connect`\n\nManually connects to providers. Useful if `autoConnect: false` was used during `init`.\n\n<API name=\"editor.api.yjs.connect\">\n<APIParameters>\n <APIItem name=\"type\" type=\"YjsProviderType | YjsProviderType[]\" optional>\n   If provided, only connects to providers of the specified type(s). If omitted, connects to all configured providers that are not already connected.\n </APIItem>\n</APIParameters>\n</API>\n\n### `api.yjs.disconnect`\n\nManually disconnects from providers.\n\n<API name=\"editor.api.yjs.disconnect\">\n<APIParameters>\n <APIItem name=\"type\" type=\"YjsProviderType | YjsProviderType[]\" optional>\n   If provided, only disconnects from providers of the specified type(s). If omitted, disconnects from all currently connected providers.\n </APIItem>\n</APIParameters>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(collaboration)/yjs.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "basic-blocks-docs",
      "title": "Basic Blocks",
      "description": "Paragraphs, headings, blockquotes, and horizontal rules.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/basic-blocks.mdx",
          "content": "---\ntitle: Basic Blocks\ndescription: Paragraphs, headings, blockquotes, and horizontal rules.\ndocs:\n  - route: /docs/components/paragraph-node\n    title: Paragraph Element\n  - route: /docs/components/heading-node\n    title: Heading Element\n  - route: /docs/components/blockquote-node\n    title: Blockquote Element\n  - route: /docs/components/hr-node\n    title: Horizontal Rule Element\n---\n\nBasic Blocks is the registry kit for the common structural blocks in a Plate editor. It wires paragraphs, six heading levels, blockquotes, and horizontal rules to Plate UI components. Use the leaf docs when you need the behavior details for one block type.\n\n<Cards>\n\n<Card icon=\"heading\" title=\"Heading\" href=\"/docs/plate/heading\">\nStructure content with H1 through H6 blocks.\n</Card>\n\n<Card icon=\"blockquote\" title=\"Blockquote\" href=\"/docs/plate/blockquote\">\nWrap quoted or emphasized content in a blockquote.\n</Card>\n\n<Card icon=\"horizontal-rule\" title=\"Horizontal Rule\" href=\"/docs/plate/horizontal-rule\">\nInsert a void divider between sections.\n</Card>\n\n</Cards>\n\n<ComponentPreview name=\"basic-blocks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Paragraph, H1-H6, blockquote, and horizontal rule components.\n- Markdown shortcuts for headings, blockquotes, and horizontal rules.\n- Keyboard shortcuts for heading toggles and blockquote toggling.\n- Static rendering companion through `basic-blocks-base-kit`.\n- Leaf docs for block-specific setup and API details.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`BasicBlocksKit` is the normal app/editor kit. It installs the React plugins and the matching registry UI components.\n\n<ComponentSource name=\"basic-blocks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BasicBlocksKit,\n});\n```\n\n### Add Static Rendering\n\nUse `BaseBasicBlocksKit` in static or server-safe rendering paths.\n\n<ComponentSource name=\"basic-blocks-base-kit\" />\n\n```tsx\nimport { createStaticEditor } from 'platejs';\n\nimport { BaseBasicBlocksKit } from '@/components/editor/plugins/basic-blocks-base-kit';\n\nconst value = [\n  {\n    children: [{ text: 'Static content' }],\n    type: 'p',\n  },\n];\n\nexport const staticEditor = createStaticEditor({\n  plugins: BaseBasicBlocksKit,\n  value,\n});\n```\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `platejs/react` | Package | Exports `ParagraphPlugin`. |\n| `@platejs/basic-nodes` | Package | Exports block rules and base block plugins. |\n| `@platejs/basic-nodes/react` | Package | Exports `BlockquotePlugin`, `HeadingPlugin`, `H1Plugin` through `H6Plugin`, `HorizontalRulePlugin`, and `BasicBlocksPlugin`. |\n| `basic-blocks-kit` | Registry | Adds React UI components, input rules, break rules, and shortcuts. |\n| `basic-blocks-base-kit` | Registry | Adds static UI components for static rendering. |\n| Leaf pages | Docs | Own block-specific behavior: [`Heading`](/docs/plate/heading), [`Blockquote`](/docs/plate/blockquote), and [`Horizontal Rule`](/docs/plate/horizontal-rule). |\n\n`BasicBlocksPlugin` is a package-level grouping plugin. It does not install the registry UI components; use `BasicBlocksKit` when you want Plate UI rendering.\n\n## Included Plugins\n\n| Block | Plugin | Registry Component | Notes |\n|-------|--------|--------------------|-------|\n| Paragraph | `ParagraphPlugin` | `ParagraphElement` | Comes from `platejs/react`; the registry kit adds the UI component. |\n| Heading 1-6 | `H1Plugin` through `H6Plugin` | `H1Element` through `H6Element` | Each level gets a markdown rule and `mod+alt+<level>` shortcut. |\n| Blockquote | `BlockquotePlugin` | `BlockquoteElement` | Uses a wrapping transform and `mod+shift+period`. |\n| Horizontal rule | `HorizontalRulePlugin` | `HrElement` | Void block; supports dash and underscore markdown rules. |\n\n## Markdown Shortcuts\n\n| Shortcut | Result |\n|----------|--------|\n| `# ` through `###### ` | Converts the current paragraph into H1 through H6. |\n| `> ` | Wraps the current block in a blockquote. |\n| `---` | Converts the current block into a horizontal rule, then inserts a paragraph after it. |\n| `___ ` | Converts the current block into a horizontal rule, then inserts a paragraph after it. |\n\nSee [`Plugin Input Rules`](/docs/plate/plugin-input-rules) for the rule engine and trigger model.\n\n## Manual Setup\n\nUse manual setup when you want the package plugins without the registry kit.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\n```tsx\nimport {\n  BlockquoteRules,\n  HeadingRules,\n  HorizontalRuleRules,\n} from '@platejs/basic-nodes';\nimport {\n  BlockquotePlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  H4Plugin,\n  H5Plugin,\n  H6Plugin,\n  HorizontalRulePlugin,\n} from '@platejs/basic-nodes/react';\nimport { createPlateEditor, ParagraphPlugin } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    ParagraphPlugin,\n    H1Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+1' } },\n    }),\n    H2Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+2' } },\n    }),\n    H3Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+3' } },\n    }),\n    H4Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+4' } },\n    }),\n    H5Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+5' } },\n    }),\n    H6Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+alt+6' } },\n    }),\n    BlockquotePlugin.configure({\n      inputRules: [BlockquoteRules.markdown()],\n      shortcuts: { toggle: { keys: 'mod+shift+period' } },\n    }),\n    HorizontalRulePlugin.configure({\n      inputRules: [\n        HorizontalRuleRules.markdown({ variant: '-' }),\n        HorizontalRuleRules.markdown({ variant: '_' }),\n      ],\n    }),\n  ],\n});\n```\n\n## API Reference\n\n| API | Use |\n|-----|-----|\n| `BasicBlocksPlugin` | Package grouping plugin for blockquote, heading, and horizontal rule behavior. |\n| `BaseBasicBlocksPlugin` | Static/headless grouping plugin for blockquote, heading, and horizontal rule behavior. |\n| `HeadingRules.markdown()` | Creates heading input rules based on the configured H1-H6 plugin key. |\n| `BlockquoteRules.markdown()` | Creates the `> ` block-start rule. |\n| `HorizontalRuleRules.markdown({ variant })` | Creates dash or underscore thematic-break rules. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/basic-blocks.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "blockquote-docs",
      "title": "Blockquote",
      "description": "Documentation for Blockquote",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/blockquote.mdx",
          "content": "---\ntitle: Blockquote\ndocs:\n  - route: /docs/components/blockquote-node\n    title: Blockquote Element\n---\n\n<ComponentPreview name=\"basic-blocks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Create blockquotes to emphasize important information or highlight quotes from external sources.\n- Renders as `<blockquote>` HTML element by default.\n- Supports nested block content such as paragraphs, lists, and nested blockquotes.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add the blockquote plugin is with the `BasicBlocksKit`, which includes pre-configured `BlockquotePlugin` along with other basic block elements and their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"basic-blocks-kit\" />\n\n- [`BlockquoteElement`](/docs/plate/components/blockquote-node): Renders blockquote elements.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...BasicBlocksKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\n### Add Plugin\n\nInclude `BlockquotePlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { BlockquotePlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    BlockquotePlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nYou can configure the `BlockquotePlugin` with options such as a specific rendering component or custom keyboard shortcuts.\n\n```tsx\nimport { BlockquotePlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    BlockquotePlugin.configure({\n      node: { component: BlockquoteElement },\n      shortcuts: { toggle: 'mod+shift+.' },\n    }),\n  ],\n});\n```\n\n- `node.component`: Assigns [`BlockquoteElement`](/docs/plate/components/blockquote-node) to render blockquote elements.\n- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plate/plugin-shortcuts) to toggle blockquote formatting.\n\n### Turn Into Toolbar Button\n\nYou can add blockquote to the [Turn Into Toolbar Button](/docs/plate/toolbar#turn-into-toolbar-button) to toggle blockquotes:\n\n```tsx\n{\n  icon: <QuoteIcon />,\n  label: 'Quote',\n  value: KEYS.blockquote,\n}\n```\n\n### Insert Toolbar Button\n\nYou can add blockquote to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert blockquotes:\n\n```tsx\n{\n  icon: <QuoteIcon />,\n  label: 'Quote',\n  value: KEYS.blockquote,\n}\n```\n\n</Steps>\n\n## Plugins\n\n### `BlockquotePlugin`\n\nPlugin for blockquote container elements. Renders as `<blockquote>` HTML element by default.\n\n## Transforms\n\n### `tf.blockquote.toggle`\n\nWraps the current block or selection in a blockquote. If the selection is already inside a blockquote, it unwraps the quoted blocks.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/blockquote.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "callout-docs",
      "title": "Callout",
      "description": "Documentation for Callout",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/callout.mdx",
          "content": "---\ntitle: Callout\ndocs:\n  - route: https://pro.platejs.org/docs/components/callout-node\n    title: Plus\n  - route: /docs/components/callout\n    title: Callout Element\n---\n\n<ComponentPreview name=\"callout-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Customizable callout blocks for highlighting important information\n- Support for different callout variants (e.g., info, warning, error)\n- Ability to set custom icons or emojis for callouts\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add the callout plugin is with the `CalloutKit`, which includes pre-configured `CalloutPlugin` with the [Plate UI](/docs/plate/installation/plate-ui) component.\n\n<ComponentSource name=\"callout-kit\" />\n\n- [`CalloutElement`](/docs/plate/components/callout-node): Renders callout elements.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CalloutKit } from '@/components/editor/plugins/callout-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CalloutKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/callout\n```\n\n### Add Plugin\n\nInclude `CalloutPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { CalloutPlugin } from '@platejs/callout/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CalloutPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nYou can configure the `CalloutPlugin` with a custom component to render callout elements.\n\n```tsx\nimport { CalloutPlugin } from '@platejs/callout/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { CalloutElement } from '@/components/ui/callout-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CalloutPlugin.withComponent(CalloutElement),\n  ],\n});\n```\n\n- `withComponent`: Assigns [`CalloutElement`](/docs/plate/components/callout-node) to render callout elements.\n\n</Steps>\n\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"callout-pro\" />\n\n## Plugins\n\n### `CalloutPlugin`\n\nCallout element plugin.\n\n## Transforms\n\n### `tf.insert.callout`\n\nInsert a callout element into the editor.\n\n<API name=\"callout\">\n<APIOptions type=\"object\">\n  <APIItem name=\"variant\" type=\"string\" optional>\n    The variant of the callout to insert.\n  </APIItem>\n  <APIItem name=\"...InsertNodesOptions\" type=\"InsertNodesOptions<V>\">\n    Other options from `InsertNodesOptions`.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useCalloutEmojiPicker`\n\nManage the emoji picker functionality for callouts.\n\n<API name=\"useCalloutEmojiPicker\">\n<APIOptions type=\"UseCalloutEmojiPickerOptions\">\n  <APIItem name=\"isOpen\" type=\"boolean\">\n    Whether the emoji picker is open.\n  </APIItem>\n  <APIItem name=\"setIsOpen\" type=\"(isOpen: boolean) => void\">\n    Function to set the open state of the emoji picker.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"emojiToolbarDropdownProps\" type=\"object\">\n    Props for the emoji toolbar dropdown.\n    <APISubList>\n      <APISubListItem parent=\"emojiToolbarDropdownProps\" name=\"isOpen\" type=\"boolean\">\n        Whether the emoji picker is open.\n      </APISubListItem>\n      <APISubListItem parent=\"emojiToolbarDropdownProps\" name=\"setIsOpen\" type=\"(v: boolean) => void\">\n        Function to set the open state of the emoji picker, respecting read-only mode.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"props\" type=\"object\">\n    Props for the emoji picker component.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"isOpen\" type=\"boolean\">\n        Whether the emoji picker is open.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"setIsOpen\" type=\"(isOpen: boolean) => void\">\n        Function to set the open state of the emoji picker.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"onSelectEmoji\" type=\"(options: { emojiValue?: any; icon?: any }) => void\">\n        Function called when an emoji is selected. It updates the callout's icon and closes the picker.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n## Types\n\n### `TCalloutElement`\n\n```typescript\ninterface TCalloutElement extends TElement {\n  variant?: string;\n  icon?: string;\n}\n```\n\n<API name=\"TCalloutElement\">\n<APIAttributes>\n  <APIItem name=\"variant\" type=\"string\" optional>\n    The variant of the callout.\n  </APIItem>\n  <APIItem name=\"icon\" type=\"string\" optional>\n    The icon or emoji to display.\n  </APIItem>\n</APIAttributes>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/callout.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "code-block-docs",
      "title": "Code Block",
      "description": "Documentation for Code Block",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/code-block.mdx",
          "content": "---\ntitle: Code Block\ndocs:\n  - route: https://pro.platejs.org/docs/components/code-block-node\n    title: Plus\n  - route: /docs/components/code-block-node\n    title: Code Block Element\n---\n\n<ComponentPreview name=\"code-block-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Syntax highlighting for code blocks\n- Support for multiple programming languages\n- Customizable language selection\n- Proper indentation handling\n- Markdown code fence shortcuts through `CodeBlockRules.markdown({ on })`\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add code block functionality is with the `CodeBlockKit`, which includes pre-configured `CodeBlockPlugin`, `CodeLinePlugin`, and `CodeSyntaxPlugin` with syntax highlighting, the shipped triple-backtick input rule, and [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"code-block-kit\" />\n\n- [`CodeBlockElement`](/docs/plate/components/code-block-node): Renders code block containers.\n- [`CodeLineElement`](/docs/plate/components/code-block-node): Renders individual code lines.\n- [`CodeSyntaxLeaf`](/docs/plate/components/code-block-node): Renders syntax highlighted text.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CodeBlockKit } from '@/components/editor/plugins/code-block-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CodeBlockKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/code-block lowlight\n```\n\n### Add Plugins\n\nInclude the code block plugins in your Plate plugins array when creating the editor.\n\n```tsx\nimport { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CodeBlockPlugin,\n    CodeLinePlugin,\n    CodeSyntaxPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the plugins with syntax highlighting and custom components.\n\n**Basic Setup with All Languages:**\n\n```tsx\nimport { CodeBlockRules } from '@platejs/code-block';\nimport { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';\nimport { all, createLowlight } from 'lowlight';\nimport { createPlateEditor } from 'platejs/react';\nimport { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '@/components/ui/code-block-node';\n\n// Create a lowlight instance with all languages\nconst lowlight = createLowlight(all);\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CodeBlockPlugin.configure({\n      inputRules: [CodeBlockRules.markdown({ on: 'match' })],\n      node: { component: CodeBlockElement },\n      options: { lowlight },\n      shortcuts: { toggle: { keys: 'mod+alt+8' } },\n    }),\n    CodeLinePlugin.withComponent(CodeLineElement),\n    CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),\n  ],\n});\n```\n\n**Custom Language Setup (Optimized Bundle):**\n\nFor optimized bundle size, you can register only specific languages:\n\n```tsx\nimport { createLowlight } from 'lowlight';\nimport css from 'highlight.js/lib/languages/css';\nimport js from 'highlight.js/lib/languages/javascript';\nimport ts from 'highlight.js/lib/languages/typescript';\nimport html from 'highlight.js/lib/languages/xml';\n\n// Create a lowlight instance\nconst lowlight = createLowlight();\n\n// Register only the languages you need\nlowlight.register('html', html);\nlowlight.register('css', css);\nlowlight.register('js', js);\nlowlight.register('ts', ts);\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CodeBlockPlugin.configure({\n      inputRules: [CodeBlockRules.markdown({ on: 'match' })],\n      node: { component: CodeBlockElement },\n      options: {\n        lowlight,\n        defaultLanguage: 'js', // Set default language (optional)\n      },\n      shortcuts: { toggle: { keys: 'mod+alt+8' } },\n    }),\n    CodeLinePlugin.withComponent(CodeLineElement),\n    CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),\n  ],\n});\n```\n\n- `node.component`: Assigns [`CodeBlockElement`](/docs/plate/components/code-block-node) to render code block containers.\n- `inputRules`: Registers the triple-backtick fence rule. Use `on: 'match'` to commit when the fence becomes complete or `on: 'break'` to commit on `Enter`.\n- `options.lowlight`: Lowlight instance for syntax highlighting.\n- `options.defaultLanguage`: Default language when no language is specified.\n- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plate/plugin-shortcuts) to toggle code blocks.\n- `withComponent`: Assigns components for code lines and syntax highlighting.\n\nFor the runtime model, see [Plugin Input Rules](/docs/plate/plugin-input-rules).\n\n### Turn Into Toolbar Button\n\nYou can add this item to the [Turn Into Toolbar Button](/docs/plate/toolbar#turn-into-toolbar-button) to convert blocks into code blocks:\n\n```tsx\n{\n  icon: <FileCodeIcon />,\n  label: 'Code',\n  value: KEYS.codeBlock,\n}\n```\n\n### Insert Toolbar Button\n\nYou can add this item to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert code block elements:\n\n```tsx\n{\n  icon: <FileCodeIcon />,\n  label: 'Code',\n  value: KEYS.codeBlock,\n}\n```\n\n</Steps>\n\n## Plugins\n\n### `CodeBlockPlugin`\n\n<API name=\"CodeBlockPlugin\">\n<APIOptions>\n  <APIItem name=\"defaultLanguage\" type=\"string | null\" optional>\n    Default language to use when no language is specified. Set to null to disable syntax highlighting by default.\n  </APIItem>\n  <APIItem name=\"lowlight\" type=\"ReturnType<typeof createLowlight> | null\" optional>\n    Lowlight instance to use for highlighting. If not provided, syntax highlighting will be disabled.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `isCodeBlockEmpty`\n\n<API name=\"isCodeBlockEmpty\">\n<APIReturns type=\"boolean\">\n  Whether the selection is in an empty code block.\n</APIReturns>\n</API>\n\n### `isSelectionAtCodeBlockStart`\n\n<API name=\"isSelectionAtCodeBlockStart\">\n<APIReturns type=\"boolean\">\n  Whether the selection is at the start of the first code line in a code block.\n</APIReturns>\n</API>\n\n### `indentCodeLine`\n\nIndents the code line if the selection is expanded or there are no non-whitespace characters at left of the cursor. The indentation is 2 spaces by default.\n\n<API name=\"indentCodeLine\">\n<APIOptions type=\"IndentCodeLineOptions\">\n  <APIItem name=\"codeLine\" type=\"ElementEntry\">\n    The code line to be indented.\n  </APIItem>\n  <APIItem name=\"indentDepth\" type=\"number\">\n    The size of indentation for the code line.\n    - **Default:** `2`\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertCodeBlock`\n\nInserts a code block by setting the node to code line and wrapping it with a code block. If the cursor is not at the block start, it inserts a break before the code block.\n\n<API name=\"insertCodeBlock\">\n<APIParameters>\n  <APIItem name=\"insertNodesOptions\" type=\"Omit<InsertNodesOptions, 'match'>\" optional>\n    Options for inserting nodes.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `insertCodeLine`\n\nInserts a code line starting with the specified indentation depth.\n\n<API name=\"insertCodeLine\">\n<APIParameters>\n  <APIItem name=\"indentDepth\" type=\"number\" optional>\n    The depth of indentation for the code line.\n    - **Default:** `0`\n  </APIItem>\n</APIParameters>\n</API>\n\n### `outdentCodeLine`\n\nOutdents a code line, removing two whitespace characters if present.\n\n<API name=\"outdentCodeLine\">\n<APIOptions type=\"OutdentCodeLineOptions\">\n  <APIItem name=\"codeLine\" type=\"ElementEntry\">\n    The code line to be outdented.\n  </APIItem>\n  <APIItem name=\"codeBlock\" type=\"ElementEntry\">\n    The code block containing the code line to be outdented.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `toggleCodeBlock`\n\nToggles the code block in the editor.\n\n### `unwrapCodeBlock`\n\nUnwraps the code block in the editor. \n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/code-block.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "code-drawing-docs",
      "title": "Code Drawing",
      "description": "Documentation for Code Drawing",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/code-drawing.mdx",
          "content": "---\ntitle: Code Drawing\ndocs:\n  - route: /docs/components/code-drawing-node\n    title: Code Drawing Node\n---\n\n<ComponentPreview name=\"code-drawing-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Support for multiple diagram types: PlantUml, Graphviz, Flowchart, and Mermaid\n- Inline code editing with code-block-like styling\n- Real-time preview with debounced rendering (500ms)\n- Multiple view modes: Both (code and preview), Code only, Image only\n- Responsive layout: horizontal on desktop (code left, preview right), vertical on mobile (preview top, code bottom)\n- Border separator between code editor and preview in Both mode\n- Toolbar controls: language selector and view mode selector (always visible on mobile, hover to show on desktop)\n- Download rendered diagrams as images\n- Floating toolbar with delete and download actions\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add code drawing functionality is with the `CodeDrawingKit`, which includes the pre-configured `CodeDrawingPlugin` with its [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"code-drawing-kit\" />\n\n- [`CodeDrawingElement`](/docs/plate/components/code-drawing-node): Renders code drawing elements with inline editing and preview.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CodeDrawingKit } from '@/components/editor/plugins/code-drawing-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CodeDrawingKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/code-drawing\n```\n\n### Add Plugin\n\nInclude `CodeDrawingPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { CodeDrawingPlugin } from '@platejs/code-drawing/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CodeDrawingPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure the code drawing plugin with custom components.\n\n```tsx\nimport { CodeDrawingPlugin } from '@platejs/code-drawing/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { CodeDrawingElement } from '@/components/ui/code-drawing-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CodeDrawingPlugin.withComponent(CodeDrawingElement),\n  ],\n});\n```\n\n- `withComponent`: Assigns [`CodeDrawingElement`](/docs/plate/components/code-drawing-node) to render code drawing elements.\n\n### Add Toolbar Button\n\nYou can add a toolbar button to insert code drawing elements. Add this item to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button):\n\n```tsx\n{\n  icon: <Code2Icon />,\n  label: 'Code Drawing',\n  value: KEYS.codeDrawing,\n}\n```\n\n</Steps>\n\n## Plugins\n\n### CodeDrawingPlugin\n\nPlugin for rendering code drawings (PlantUml, Graphviz, Flowchart, Mermaid) with inline editing and preview capabilities.\n\n## API\n\n### editor.tf.insert.codeDrawing\n\nInserts a code drawing element at the current selection.\n\n<API name=\"insert.codeDrawing\">\n<APIParameters>\n<APIItem name=\"props\" type=\"NodeProps<TCodeDrawingElement>\" optional>\nProps for the code drawing element.\n<APISubList>\n<APISubListItem parent=\"props\" name=\"data\" type=\"CodeDrawingData\" optional>\nThe data for the code drawing element.\n<APISubList>\n<APISubListItem parent=\"data\" name=\"drawingType\" type=\"CodeDrawingType\" optional>\nThe type of diagram to render.\n\n- **Default:** `'Mermaid'`\n\n- **Options:** `'PlantUml'`, `'Graphviz'`, `'Flowchart'`, `'Mermaid'`\n\n</APISubListItem>\n<APISubListItem parent=\"data\" name=\"drawingMode\" type=\"ViewMode\" optional>\nThe view mode for the code drawing.\n\n- **Default:** `'Both'`\n\n- **Options:** `'Both'`, `'Code'`, `'Image'`\n\n</APISubListItem>\n<APISubListItem parent=\"data\" name=\"code\" type=\"string\" optional>\nThe code content for the diagram.\n\n- **Default:** `''`\n\n</APISubListItem>\n</APISubList>\n</APISubListItem>\n</APISubList>\n</APIItem>\n<APIItem name=\"options\" type=\"InsertNodesOptions\" optional>\nThe options for inserting the code drawing node.\n</APIItem>\n</APIParameters>\n</API>\n\n## Transforms\n\n### `tf.insert.codeDrawing`\n\nInserts a code drawing element at the current selection.\n\n<API name=\"insert.codeDrawing\">\n<APIParameters>\n<APIItem name=\"props\" type=\"NodeProps<TCodeDrawingElement>\" optional>\nProps for the code drawing element.\n</APIItem>\n<APIItem name=\"options\" type=\"InsertNodesOptions\" optional>\nThe options for inserting the code drawing node.\n</APIItem>\n</APIParameters>\n</API>\n\n## Hooks\n\n### `useCodeDrawingElement`\n\nA hook for a code drawing element that handles rendering and state management.\n\n<API name=\"useCodeDrawingElement\">\n<APIParameters>\n<APIItem name=\"element\" type=\"TCodeDrawingElement\">\nThe code drawing element.\n</APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem name=\"loading\" type=\"boolean\">\nWhether the diagram is currently being rendered.\n</APIItem>\n<APIItem name=\"error\" type=\"string | null\">\nThe error message if rendering failed, or `null` if there's no error.\n</APIItem>\n<APIItem name=\"image\" type=\"string\">\nThe rendered image data URL.\n</APIItem>\n<APIItem name=\"updateNode\" type=\"(data: Partial<CodeDrawingData>) => void\">\nFunction to update the code drawing element's data.\n</APIItem>\n<APIItem name=\"removeNode\" type=\"() => void\">\nFunction to remove the code drawing element.\n</APIItem>\n</APIReturns>\n</API>\n\n## Types\n\n### `TCodeDrawingElement`\n\nThe code drawing element type.\n\n<API name=\"TCodeDrawingElement\">\n<APIAttributes>\n<APIItem name=\"type\" type=\"string\">\nThe element type. Always `KEYS.codeDrawing`.\n</APIItem>\n<APIItem name=\"data\" type=\"CodeDrawingData\" optional>\nThe data for the code drawing element.\n</APIItem>\n</APIAttributes>\n</API>\n\n### `CodeDrawingData`\n\nThe data structure for code drawing elements.\n\n<API name=\"CodeDrawingData\">\n<APIAttributes>\n<APIItem name=\"drawingType\" type=\"CodeDrawingType\" optional>\nThe type of diagram to render.\n\n- **Options:** `'PlantUml'`, `'Graphviz'`, `'Flowchart'`, `'Mermaid'`\n\n</APIItem>\n<APIItem name=\"drawingMode\" type=\"ViewMode\" optional>\nThe view mode for the code drawing.\n\n- **Options:** `'Both'`, `'Code'`, `'Image'`\n\n</APIItem>\n<APIItem name=\"code\" type=\"string\" optional>\nThe code content for the diagram.\n</APIItem>\n</APIAttributes>\n</API>\n\n### `CodeDrawingType`\n\nThe type of diagram supported.\n\n<API name=\"CodeDrawingType\">\n<APIReturns>\n<APIItem type=\"'PlantUml' | 'Graphviz' | 'Flowchart' | 'Mermaid'\">\nThe diagram type.\n</APIItem>\n</APIReturns>\n</API>\n\n### `ViewMode`\n\nThe view mode for code drawing elements.\n\n<API name=\"ViewMode\">\n<APIReturns>\n<APIItem type=\"'Both' | 'Code' | 'Image'\">\nThe view mode.\n\n- `'Both'`: Shows both code editor and preview side by side (horizontal on desktop, vertical on mobile with preview on top)\n- `'Code'`: Shows only the code editor\n- `'Image'`: Shows only the rendered preview\n\n</APIItem>\n</APIReturns>\n</API>\n\n## Utilities\n\n### `renderCodeDrawing`\n\nRenders a diagram from code based on the drawing type.\n\n<API name=\"renderCodeDrawing\">\n<APIParameters>\n<APIItem name=\"type\" type=\"CodeDrawingType\">\nThe type of diagram to render.\n</APIItem>\n<APIItem name=\"content\" type=\"string\">\nThe code content for the diagram.\n</APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"Promise<string>\">\nA promise that resolves to the rendered image data URL.\n</APIItem>\n</APIReturns>\n</API>\n\n### `downloadImage`\n\nDownloads an image from a data URL.\n\n<API name=\"downloadImage\">\n<APIParameters>\n<APIItem name=\"imageDataUrl\" type=\"string\">\nThe image data URL to download.\n</APIItem>\n<APIItem name=\"filename\" type=\"string\" optional>\nThe filename for the downloaded image.\n\n- **Default:** `'code-drawing.png'`\n\n</APIItem>\n</APIParameters>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/code-drawing.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "column-docs",
      "title": "Column",
      "description": "Documentation for Column",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/column.mdx",
          "content": "---\ntitle: Column\ndocs:\n  - route: /docs/components/column-node\n    title: Column Nodes\n---\n\n<ComponentPreview name=\"column-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Add columns to your document.\n- Choose from a variety of column layouts using `column-group-node` toolbar.\n- [ ] Resizable columns\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add column functionality is with the `ColumnKit`, which includes pre-configured `ColumnPlugin` and `ColumnItemPlugin` with [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"column-kit\" />\n\n- [`ColumnGroupElement`](/docs/plate/components/column-node): Renders column group containers.\n- [`ColumnElement`](/docs/plate/components/column-node): Renders individual column items.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { ColumnKit } from '@/components/editor/plugins/column-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...ColumnKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/layout\n```\n\n### Add Plugins\n\nInclude the column plugins in your Plate plugins array when creating the editor.\n\n```tsx\nimport { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ColumnPlugin,\n    ColumnItemPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the plugins with custom components to render column layouts.\n\n```tsx\nimport { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { ColumnGroupElement, ColumnElement } from '@/components/ui/column-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ColumnPlugin.withComponent(ColumnGroupElement),\n    ColumnItemPlugin.withComponent(ColumnElement),\n  ],\n});\n```\n\n- `withComponent`: Assigns [`ColumnGroupElement`](/docs/plate/components/column-node) to render column group containers and [`ColumnElement`](/docs/plate/components/column-node) to render individual columns.\n\n### Turn Into Toolbar Button\n\nYou can add this item to the [Turn Into Toolbar Button](/docs/plate/toolbar#turn-into-toolbar-button) to convert blocks into column layouts:\n\n```tsx\n{\n  icon: <Columns3Icon />,\n  label: '3 columns',\n  value: 'action_three_columns',\n}\n```\n\n</Steps>\n\n## Plugins\n\n### `ColumnPlugin`\n\nAdd Column Plugin to your document.\n\n### `ColumnItemPlugin`\n\nAdd Column Item Plugin to your document.\n\n## Types\n\n### `TColumnGroupElement`\n\nExtends `TElement`.\n\n### `TColumnElement`\n\nExtends `TElement`.\n\n<API name=\"TColumnElement\">\n<APIAttributes>\n  <APIItem name=\"width\" type=\"string\" optional>\n    The column's width (must end with `%`)\n  </APIItem>\n</APIAttributes>\n</API>\n\n## Transforms\n\n### `insertColumnGroup`\n\nInsert a columnGroup with two empty columns.\n\n<API name=\"insertColumnGroup\">\n<APIOptions type=\"InsertNodesOptions & { columns?: number[] | number }\">\n    - `columns`: Array of column widths or number of equal-width columns (default: 2)\n    - Other `InsertNodesOptions` to control insert behavior\n  <APIItem name=\"columns\" type=\"number[] | number\" optional>\n    Array of column widths or number of equal-width columns (default: 2)\n  </APIItem>\n  <APIItem name=\"...InsertNodesOptions\" type=\"InsertNodesOptions\">\n    Other options to control insert behavior\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertColumn`\n\nInsert an empty column.\n\n<API name=\"insertColumn\">\n<APIOptions type=\"InsertNodesOptions & { width?: string }\">\n  <APIItem name=\"width\" type=\"string\" optional>\n    Column width (default: \"33%\")\n  </APIItem>\n  <APIItem name=\"...InsertNodesOptions\" type=\"InsertNodesOptions\">\n    Other options to control insert behavior\n  </APIItem>\n</APIOptions>\n</API>\n\n### `moveMiddleColumn`\n\nMove the middle column to the left or right.\n\n<API name=\"moveMiddleColumn\">\n<APIParameters>\n  <APIItem name=\"nodeEntry\" type=\"NodeEntry\">\n    The node entry of `column` element\n  </APIItem>\n  <APIItem name=\"options\" type=\"{ direction: 'left' | 'right' }\">\n    Control the direction the middle column moves to\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  Returns `false` if the middle node is empty (and removes it), `true` otherwise.\n</APIReturns>\n</API>\n\n### `toggleColumnGroup`\n\nConvert a block into a column group layout or update an existing column group's layout.\n\n<API name=\"toggleColumnGroup\">\n- If the target block is not a column group, wraps it in a new column group with the specified number of columns\n- If the target block is already a column group, updates its column layout using `setColumns`\n- The original content becomes the content of the first column\n- Additional columns are created with empty paragraphs\n<APIOptions type=\"object\">\n  <APIItem name=\"at\" type=\"Location\" optional>\n    The location to toggle the column group at.\n  </APIItem>\n  <APIItem name=\"columns\" type=\"number\" optional>\n    Number of equal-width columns to create (default: 2)\n  </APIItem>\n  <APIItem name=\"widths\" type=\"string[]\" optional>\n    Array of column widths (e.g., ['50%', '50%']). Takes precedence over `columns`.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `setColumns`\n\nUpdate the column layout of an existing column group.\n\n<API name=\"setColumns\">\n- When increasing columns:\n  - Keeps existing column content\n  - Adds new empty columns with specified widths\n- When decreasing columns:\n  - Merges content from removed columns into the last remaining column\n  - Updates widths of remaining columns\n- When keeping same number of columns:\n  - Only updates column widths\n<APIOptions type=\"object\">\n  <APIItem name=\"at\" type=\"Path\">\n    The path to the column group element.\n  </APIItem>\n  <APIItem name=\"columns\" type=\"number\" optional>\n    Number of equal-width columns to create.\n  </APIItem>\n  <APIItem name=\"widths\" type=\"string[]\" optional>\n    Array of column widths (e.g., ['33%', '67%']). Takes precedence over `columns`.\n  </APIItem>\n</APIOptions>\n</API>\n\n\n## Hooks\n\n### `useDebouncePopoverOpen`\n\n<API name=\"useDebouncePopoverOpen\">\n<APIReturns type=\"boolean\">\n  Whether the popover is open.\n</APIReturns>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/column.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "date-docs",
      "title": "Date",
      "description": "Inline void date elements with canonical and raw date storage.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/date.mdx",
          "content": "---\ntitle: Date\ndescription: Inline void date elements with canonical and raw date storage.\ndocs:\n  - route: /docs/components/date-node\n    title: Date Element\n  - route: https://pro.platejs.org/docs/components/date-node\n    title: Plus\n---\n\nDate adds inline void elements that display a date label inside text. Canonical dates are stored as `YYYY-MM-DD`; non-normalizable input is kept as `rawDate`. This page covers kit setup, insertion, value shape, picker behavior, Markdown serialization, and the small query API.\n\n<ComponentPreview name=\"date-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Inline void `date` element.\n- Bound `editor.tf.insert.date` transform.\n- Direct `insertDate(editor, options)` helper.\n- Canonical `date` storage with `rawDate` fallback.\n- Calendar editing in the registry UI.\n- Static renderer for read-only output.\n- Markdown round-trip through `<date value=\"YYYY-MM-DD\" />` and child-text date tags.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`DateKit` installs `DatePlugin` with the registry `DateElement`.\n\n<ComponentSource name=\"date-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { DateKit } from '@/components/editor/plugins/date-kit';\n\nexport const editor = createPlateEditor({\n  plugins: DateKit,\n});\n```\n\n### Render The Element\n\n`date-node` owns the inline wrapper, display label, popover, calendar picker, and static element.\n\n<ComponentSource name=\"date-node\" />\n\n### Add An Insert Action\n\nThe registry insert toolbar maps `KEYS.date` to `insertDate(editor, { select: true })`.\n\n```tsx title=\"components/editor/transforms.ts\"\nimport { insertDate } from '@platejs/date';\nimport { KEYS } from 'platejs';\n\nexport const insertInlineMap = {\n  [KEYS.date]: (editor) => insertDate(editor, { select: true }),\n};\n```\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/date` | Package | Exports `BaseDatePlugin`, `insertDate`, date value helpers, and `isPointNextToNode`. |\n| `@platejs/date/react` | Package | Exports `DatePlugin`. |\n| `date-kit` | Registry | Adds `DatePlugin.withComponent(DateElement)`. |\n| `date-base-kit` | Registry | Adds `BaseDatePlugin.withComponent(DateElementStatic)`. |\n| `date-node` | Registry UI | Renders the editable popover/calendar element and static element. |\n| `@platejs/markdown` | Package | Converts date MDX tags to canonical `date` or fallback `rawDate` values. |\n\n`BaseDatePlugin` is inline and void. The text child exists only to satisfy Slate's element shape.\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/date\n```\n\n### Add The Plugin\n\nUse the React plugin when the editor renders the calendar popover.\n\n```tsx\nimport { DatePlugin } from '@platejs/date/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { DateElement } from '@/components/ui/date-node';\n\nexport const editor = createPlateEditor({\n  plugins: [DatePlugin.withComponent(DateElement)],\n});\n```\n\n### Add Static Rendering\n\nUse the base kit when rendering read-only output with `platejs/static`.\n\n<ComponentSource name=\"date-base-kit\" />\n\n### Insert A Date\n\n`DatePlugin` binds `insertDate` to `editor.tf.insert.date`.\n\n```tsx\neditor.tf.insert.date({\n  date: '2026-03-23',\n  select: true,\n});\n```\n\nUse the package helper directly when you are outside plugin-bound transform access.\n\n```tsx\nimport { insertDate } from '@platejs/date';\n\ninsertDate(editor, {\n  date: '2026-03-23',\n  select: true,\n});\n```\n\n</Steps>\n\n## Value Shape\n\nCanonical date values live in `date`. Invalid or intentionally loose date text lives in `rawDate`.\n\n```tsx\nconst value = [\n  {\n    children: [\n      { text: 'Due ' },\n      {\n        children: [{ text: '' }],\n        date: '2026-03-23',\n        type: 'date',\n      },\n      { text: '.' },\n    ],\n    type: 'p',\n  },\n];\n```\n\n| Field | Type | Notes |\n|-------|------|-------|\n| `type` | `'date'` | Plugin key and node type from `KEYS.date`. |\n| `children` | `[{ text: '' }]` | Required Slate child for the inline void element. |\n| `date` | `string` | Canonical `YYYY-MM-DD` value. |\n| `rawDate` | `string` | Fallback for non-normalizable input. |\n\n## Date Normalization\n\n`normalizeDateValue` decides which field is written.\n\n| Input | Stored Value |\n|-------|--------------|\n| `Date` object | `date: formatDateValue(value)` when the object is valid. |\n| `YYYY-MM-DD` | `date` when the calendar date is valid. |\n| Invalid canonical string | `rawDate`. |\n| `Mon Mar 23 2026` | `date: '2026-03-23'` when JavaScript can parse it. |\n| Blank string | no date fields. |\n| Other text | `rawDate`. |\n\n`getDateDisplayLabel` returns `Today`, `Yesterday`, `Tomorrow`, a localized long date, or the raw fallback string.\n\n## Picker Behavior\n\nThe registry element is display-only while read-only. In editable mode, clicking the inline label opens a calendar popover.\n\n| State | Behavior |\n|-------|----------|\n| `date` exists | The trigger shows `getDateDisplayLabel(element)`. |\n| `rawDate` exists | The trigger shows the raw string. |\n| no date fields | The trigger shows `Pick a date`. |\n| calendar selection | The node is set to `{ date: formatDateValue(date), rawDate: undefined }`. |\n\nThe registry element uses `contentEditable={false}` on the inline wrapper, so users edit the date through the calendar instead of typing inside the void node.\n\n## Markdown\n\nCanonical values serialize as a self-closing date tag with a `value` attribute.\n\n```mdx\nDate: <date value=\"2026-03-23\" />\n```\n\nRaw fallback values serialize as child text.\n\n```mdx\nDate: <date>sometime next week</date>\n```\n\nThe deserializer also accepts child text such as `<date>Mon Mar 23 2026</date>` and normalizes it to `date: '2026-03-23'` when the date is safe to parse.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseDatePlugin` | `@platejs/date` | Headless inline void date plugin. |\n| `DatePlugin` | `@platejs/date/react` | React date plugin. |\n| `insertDate(editor, options)` | `@platejs/date` | Inserts a date element plus a trailing text space. |\n| `editor.tf.insert.date(options)` | plugin-bound transform | Bound transform registered by `BaseDatePlugin`. |\n| `normalizeDateValue(value)` | `@platejs/date` | Returns `{ date }`, `{ rawDate }`, or an empty object. |\n| `formatDateValue(date)` | `@platejs/date` | Formats a `Date` object as `YYYY-MM-DD`. |\n| `parseCanonicalDateValue(value)` | `@platejs/date` | Parses only valid canonical date strings. |\n| `getDateDisplayLabel(options)` | `@platejs/date` | Builds the visible date label. |\n| `isPointNextToNode(editor, options)` | `@platejs/date` | Checks whether a point is adjacent to a node type. Throws when neither `options.at` nor editor selection exists. |\n| `TDateElement` | `platejs` | Element shape with optional `date` and `rawDate`. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/date.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "equation-docs",
      "title": "Equation",
      "description": "Block and inline LaTeX equation nodes rendered with KaTeX.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/equation.mdx",
          "content": "---\ntitle: Equation\ndescription: Block and inline LaTeX equation nodes rendered with KaTeX.\ndocs:\n  - route: /docs/components/equation-node\n    title: Equation Element\n  - route: /docs/components/equation-toolbar-button\n    title: Equation Toolbar Button\n  - route: https://pro.platejs.org/docs/examples/equation\n    title: Plus\n---\n\nEquation adds block and inline void nodes for LaTeX expressions. Both nodes store source in `texExpression` and render through KaTeX. This page covers kit setup, block versus inline ownership, insertion, input rules, Markdown serialization, and registry UI behavior.\n\n<ComponentPreview name=\"equation-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Block `equation` element.\n- Inline void `inline_equation` element.\n- Bound `editor.tf.insert.equation` and `editor.tf.insert.inlineEquation` transforms.\n- Direct `insertEquation` and `insertInlineEquation` helpers.\n- KaTeX rendering in editable and static UI.\n- `$...$` inline input rule and `$$` block input rule.\n- Markdown round-trip for inline math and block math.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`MathKit` installs both equation plugins, their registry components, and the default math input rules.\n\n<ComponentSource name=\"math-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { MathKit } from '@/components/editor/plugins/math-kit';\n\nexport const editor = createPlateEditor({\n  plugins: MathKit,\n});\n```\n\n### Render The Nodes\n\n`equation-node` owns the block equation, inline equation, editable popover, textarea input, KaTeX render target, static elements, and DOCX fallback elements.\n\n<ComponentSource name=\"equation-node\" />\n\n### Add The Toolbar Button\n\n`equation-toolbar-button` inserts an inline equation with `insertInlineEquation(editor)`.\n\n<ComponentSource name=\"equation-toolbar-button\" />\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/math` | Package | Exports base plugins, transforms, `MathRules`, and `getEquationHtml`. |\n| `@platejs/math/react` | Package | Exports React plugins plus `useEquationElement` and `useEquationInput`. |\n| `math-kit` | Registry | Adds block and inline React plugins with input rules and interactive UI components. |\n| `math-base-kit` | Registry | Adds static equation components for read-only rendering. |\n| `equation-node` | Registry UI | Renders editable, static, and DOCX equation elements. |\n| `equation-toolbar-button` | Registry UI | Inserts inline equations from the toolbar. |\n| `@platejs/markdown` | Package | Serializes and deserializes `math` and `inlineMath` nodes when `remark-math` is configured. |\n\nBlock and inline equations share the `TEquationElement` shape, but they are different node types with different plugin keys.\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/math\n```\n\n### Add Plugins\n\nUse both React plugins when your editor supports block and inline equations.\n\n```tsx\nimport { MathRules } from '@platejs/math';\nimport {\n  EquationPlugin,\n  InlineEquationPlugin,\n} from '@platejs/math/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport {\n  EquationElement,\n  InlineEquationElement,\n} from '@/components/ui/equation-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    InlineEquationPlugin.configure({\n      inputRules: [MathRules.markdown({ variant: '$' })],\n      node: { component: InlineEquationElement },\n    }),\n    EquationPlugin.configure({\n      inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })],\n      node: { component: EquationElement },\n    }),\n  ],\n});\n```\n\n### Add Static Rendering\n\nUse the base kit when rendering read-only output with `platejs/static`.\n\n<ComponentSource name=\"math-base-kit\" />\n\n### Insert Equations\n\nUse plugin-bound transforms when you already have a configured editor.\n\n```tsx\neditor.tf.insert.equation({ select: true });\neditor.tf.insert.inlineEquation('E = mc^2', { select: true });\n```\n\nUse package helpers directly from toolbar, slash-command, or app-local action code.\n\n```tsx\nimport { insertEquation, insertInlineEquation } from '@platejs/math';\n\ninsertEquation(editor, { select: true });\ninsertInlineEquation(editor, 'E = mc^2', { select: true });\n```\n\n</Steps>\n\n## Value Shape\n\nBoth equation nodes store source in `texExpression`. The child text is only the Slate-required child for a void element.\n\n```tsx\nconst value = [\n  {\n    children: [\n      { text: 'Mass-energy equivalence: ' },\n      {\n        children: [{ text: '' }],\n        texExpression: 'E = mc^2',\n        type: 'inline_equation',\n      },\n      { text: '.' },\n    ],\n    type: 'p',\n  },\n  {\n    children: [{ text: '' }],\n    texExpression: '\\\\\\\\int_{a}^{b} f(x) \\\\\\\\, dx = F(b) - F(a)',\n    type: 'equation',\n  },\n];\n```\n\n| Node | Type | Behavior |\n|------|------|----------|\n| `BaseEquationPlugin` | `equation` | Block void equation. |\n| `BaseInlineEquationPlugin` | `inline_equation` | Inline void equation. |\n| `TEquationElement.texExpression` | `string` | LaTeX source rendered by KaTeX. |\n\n## Input Rules\n\n`MathRules.markdown` creates editor input rules. It is separate from Markdown serialization.\n\n| Rule | Trigger | Behavior |\n|------|---------|----------|\n| `MathRules.markdown({ variant: '$' })` | `$...$` | Deletes the delimited text and inserts an inline equation with the matched expression. |\n| `MathRules.markdown({ on: 'break', variant: '$$' })` | `$$` then line break | Replaces the paragraph fence with a block equation. |\n| `MathRules.markdown({ on: 'match', variant: '$$' })` | `$$...$$` match | Creates a block equation on match. |\n\nMath input rules are disabled inside code blocks, block equations, and inline equations.\n\n## Rendering\n\nThe registry components render KaTeX and keep source editing in a popover.\n\n| Surface | Behavior |\n|---------|----------|\n| Editable block equation | `useEquationElement` calls `katex.render` with display-mode options. |\n| Editable inline equation | Opens a popover when the inline void node is selected and the selection is collapsed. |\n| Popover input | `useEquationInput` writes `texExpression` as the textarea changes. |\n| `Enter` | Submits and closes the input. |\n| `Escape` | Dismisses; inline equations restore the initial expression. |\n| Inline left/right edge arrows | Move selection out of the inline equation. |\n| Static rendering | `getEquationHtml` calls `katex.renderToString`. |\n\nKaTeX is configured with `throwOnError: false`, `strict: 'warn'`, and `trust: false` in the registry UI.\n\n## Markdown\n\nMarkdown math support comes from `@platejs/markdown` plus `remark-math`, as configured by the registry `MarkdownKit`.\n\n```mdx\nInline $x+1$ math\n```\n\n```mdx\n$$\nx+1\n$$\n```\n\nInline math deserializes to `inline_equation`. Block math deserializes to `equation`. Serialization writes the same Markdown math shapes from `texExpression`.\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"equation-pro\" />\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseEquationPlugin` | `@platejs/math` | Headless block equation plugin. |\n| `BaseInlineEquationPlugin` | `@platejs/math` | Headless inline equation plugin. |\n| `EquationPlugin` | `@platejs/math/react` | React block equation plugin. |\n| `InlineEquationPlugin` | `@platejs/math/react` | React inline equation plugin. |\n| `insertEquation(editor, options)` | `@platejs/math` | Inserts a blank block equation. |\n| `insertInlineEquation(editor, texExpression?, options?)` | `@platejs/math` | Inserts an inline equation. Defaults to the selected string when no expression is passed. |\n| `editor.tf.insert.equation(options)` | plugin-bound transform | Bound block equation insert transform. |\n| `editor.tf.insert.inlineEquation(texExpression?, options?)` | plugin-bound transform | Bound inline equation insert transform. |\n| `MathRules.markdown(options)` | `@platejs/math` | Creates inline or block math input rules. |\n| `useEquationElement(options)` | `@platejs/math/react` | Renders an equation into a KaTeX DOM target. |\n| `useEquationInput(options)` | `@platejs/math/react` | Wires the equation textarea and keyboard behavior. |\n| `getEquationHtml(options)` | `@platejs/math` | Returns static KaTeX HTML. |\n| `TEquationElement` | `platejs` | Element shape with `texExpression`. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/equation.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "excalidraw-docs",
      "title": "Excalidraw",
      "description": "Void Excalidraw drawing blocks stored inside Plate values.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/excalidraw.mdx",
          "content": "---\ntitle: Excalidraw\ndescription: Void Excalidraw drawing blocks stored inside Plate values.\ndocs:\n  - route: /docs/components/excalidraw-node\n    title: Excalidraw Element\n---\n\nExcalidraw adds a void `excalidraw` element that embeds the Excalidraw canvas in the editor. The node stores Excalidraw `elements` and `state` under `data`. This page covers kit setup, insertion, persistence shape, and the client-only registry UI.\n\n<ComponentPreview name=\"excalidraw-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Void `excalidraw` block element.\n- Direct `insertExcalidraw(editor, props, options)` helper.\n- Excalidraw `elements` and app `state` stored on the node.\n- Dynamic Excalidraw component loading in the React hook.\n- Change deduplication before writing canvas data back to Slate.\n- Read-only mode through Excalidraw `viewModeEnabled`.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`ExcalidrawKit` installs `ExcalidrawPlugin` with the registry `ExcalidrawElement`.\n\n<ComponentSource name=\"excalidraw-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { ExcalidrawKit } from '@/components/editor/plugins/excalidraw-kit';\n\nexport const editor = createPlateEditor({\n  plugins: ExcalidrawKit,\n});\n```\n\n### Render The Element\n\n`excalidraw-node` owns the client component, Excalidraw CSS import, fixed canvas frame, and read-only view mode.\n\n<ComponentSource name=\"excalidraw-node\" />\n\n### Add An Insert Action\n\nThe registry insert toolbar maps `KEYS.excalidraw` to `insertExcalidraw(editor, {}, { select: true })`.\n\n```tsx title=\"components/editor/transforms.ts\"\nimport { insertExcalidraw } from '@platejs/excalidraw';\nimport { KEYS } from 'platejs';\n\nexport const insertBlockMap = {\n  [KEYS.excalidraw]: (editor) =>\n    insertExcalidraw(editor, {}, { select: true }),\n};\n```\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/excalidraw` | Package | Exports `BaseExcalidrawPlugin`, `TExcalidrawElement`, `ExcalidrawDataState`, and `insertExcalidraw`. |\n| `@platejs/excalidraw/react` | Package | Exports `ExcalidrawPlugin`, `useExcalidrawElement`, and React Excalidraw prop types. |\n| `excalidraw-kit` | Registry | Adds `ExcalidrawPlugin.withComponent(ExcalidrawElement)`. |\n| `excalidraw-node` | Registry UI | Dynamically renders `@excalidraw/excalidraw` inside a Plate element. |\n| App persistence | App code | Stores the Plate value that contains Excalidraw element data. |\n\n`ExcalidrawPlugin` does not bind an `editor.tf.insert.excalidraw` transform. Use `insertExcalidraw` directly.\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/excalidraw\n```\n\n### Add The Plugin\n\nUse the React plugin when the editor renders the Excalidraw canvas.\n\n```tsx\nimport { ExcalidrawPlugin } from '@platejs/excalidraw/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { ExcalidrawElement } from '@/components/ui/excalidraw-node';\n\nexport const editor = createPlateEditor({\n  plugins: [ExcalidrawPlugin.withComponent(ExcalidrawElement)],\n});\n```\n\n### Insert A Drawing\n\n`insertExcalidraw` inserts after the current selection parent with `nextBlock: true`. If the editor has no selection or no selection parent, it returns without inserting.\n\n```tsx\nimport { insertExcalidraw } from '@platejs/excalidraw';\n\ninsertExcalidraw(\n  editor,\n  {\n    data: {\n      elements: [],\n      state: {\n        viewBackgroundColor: '#ffffff',\n      },\n    },\n  },\n  { select: true }\n);\n```\n\n</Steps>\n\n## Value Shape\n\n`TExcalidrawElement` is a void element. The drawing payload lives in `data`, not in text children.\n\n```tsx\nconst value = [\n  {\n    children: [{ text: '' }],\n    data: {\n      elements: [\n        {\n          id: 'shape-1',\n          type: 'rectangle',\n          x: 100,\n          y: 100,\n        },\n      ],\n      state: {\n        viewBackgroundColor: '#ffffff',\n      },\n    },\n    type: 'excalidraw',\n  },\n];\n```\n\n| Field | Type | Notes |\n|-------|------|-------|\n| `type` | `'excalidraw'` | Plugin key and node type from `KEYS.excalidraw`. |\n| `children` | `[{ text: '' }]` | Required Slate child for the void element. |\n| `data.elements` | Excalidraw elements | Stored as partial Excalidraw elements. |\n| `data.state` | Excalidraw app state | Stored as imported Excalidraw app state. |\n\nMarkdown serialization is not owned by `@platejs/excalidraw`. Persist the Plate value when you need to keep drawings.\n\n## UI Behavior\n\n`useExcalidrawElement` bridges the Plate node and the Excalidraw React component.\n\n| Surface | Behavior |\n|---------|----------|\n| Component loading | Dynamically imports `@excalidraw/excalidraw` and returns the loaded component. |\n| Initial data | Deep-clones `element.data.state`, `element.data.elements`, `libraryItems`, and `scrollToContent`. |\n| Editing | `onChange` writes `{ elements, state }` back to the node. |\n| Deduplication | Uses deep equality to skip writes when canvas data did not change. |\n| Read-only mode | Removes the write handler and enables Excalidraw `viewModeEnabled`. |\n| Canvas frame | Registry UI renders a bordered `aspect-video` frame capped at `600px`. |\n\nThe registry element imports `@excalidraw/excalidraw/index.css`, so custom copies need the same stylesheet.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseExcalidrawPlugin` | `@platejs/excalidraw` | Headless void element plugin. |\n| `ExcalidrawPlugin` | `@platejs/excalidraw/react` | React Excalidraw plugin. |\n| `insertExcalidraw(editor, props?, options?)` | `@platejs/excalidraw` | Inserts a void Excalidraw node after the current selection parent. |\n| `useExcalidrawElement(options)` | `@platejs/excalidraw/react` | Returns the dynamically loaded `Excalidraw` component and props for the registry element. |\n| `TExcalidrawElement` | `@platejs/excalidraw` | Element shape with optional `data`. |\n| `ExcalidrawDataState` | `@platejs/excalidraw` | Data shape for stored Excalidraw elements and app state. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/excalidraw.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "footnote-docs",
      "title": "Footnote",
      "description": "Documentation for Footnote",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/footnote.mdx",
          "content": "---\ntitle: Footnote\ndocs:\n  - route: /docs/markdown\n    title: Markdown\n  - route: /docs/navigation-feedback\n    title: Navigation Feedback\n---\n\n<ComponentPreview name=\"footnote-demo\" />\n\nFootnote turns GFM footnote markup (`[^1]` references and `[^1]: text` definitions) into dedicated Plate nodes you can insert, repair, and jump between. The reference is an inline void `<sup>`; the definition is a block at the end of the document. Paired with `MarkdownPlugin` and `remark-gfm`, references and definitions round-trip as real footnote markdown instead of fallback text.\n\n<PackageInfo>\n\n## Features\n\n- GFM-compatible footnote references and definitions as dedicated Plate nodes.\n- One-transform insertion with automatic numeric identifier allocation.\n- `[^` inline combobox for insertion from the default UI kit.\n- Recreate a missing definition from an unresolved reference without duplicating.\n- Keep the first duplicate definition canonical; renumber later duplicates on demand.\n- Navigation helpers that jump between reference and definition with a landed-target flash.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add footnote-aware markdown is with the `MarkdownKit`, which includes `MarkdownPlugin`, the footnote plugins wired for the default markdown profile, and works with [Plate UI](/docs/plate/installation/plate-ui).\n\n<ComponentSource name=\"markdown-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownKit } from '@/components/editor/plugins/markdown-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...MarkdownKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/footnote @platejs/markdown remark-gfm\n```\n\n### Add Plugins\n\nThree plugins ship together: the inline reference, the block definition, and the combobox input. Pair them with `MarkdownPlugin` and `remark-gfm` so `[^1]` round-trips correctly.\n\n```tsx\nimport {\n  FootnoteDefinitionPlugin,\n  FootnoteReferencePlugin,\n} from '@platejs/footnote/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { createPlateEditor } from 'platejs/react';\nimport remarkGfm from 'remark-gfm';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    FootnoteReferencePlugin,\n    FootnoteDefinitionPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkGfm],\n      },\n    }),\n  ],\n});\n```\n\n`FootnoteReferencePlugin` pulls in `FootnoteInputPlugin` automatically — that's the inline void rendered while the reader is typing inside the `[^` combobox.\n\n### Insert a Footnote\n\nCall `tf.insert.footnote` at the current selection. It inserts the reference, creates a matching definition at the end of the document, and moves the caret into the definition body so the reader can start writing:\n\n```tsx\neditor.tf.insert.footnote();\n```\n\nWhen the selection is expanded, the expanded fragment seeds the definition body so you can select text and \"footnote-ify\" it in one shot.\n\nPass `focusDefinition: false` when the reference should stay inline (for example, inside a larger template):\n\n```tsx\neditor.tf.insert.footnote({ focusDefinition: false });\n```\n\nPass `identifier` to reuse an existing identifier; the transform skips creating a duplicate definition when one already exists.\n\n### Repair an Unresolved Reference\n\nWhen a reference points at an identifier with no definition (e.g. pasted from elsewhere), use `tf.footnote.createDefinition` to create just the definition — without inserting another reference:\n\n```tsx\neditor.tf.footnote.createDefinition({ identifier: '3' });\n```\n\nPass `focus: false` when you want to leave the caret where it was:\n\n```tsx\neditor.tf.footnote.createDefinition({ focus: false, identifier: '3' });\n```\n\n### Navigate Between Reference and Definition\n\n`tf.footnote.focusDefinition` and `tf.footnote.focusReference` jump the selection, scroll the target into view, and flash it through [Navigation Feedback](/docs/plate/navigation-feedback). No extra wiring needed:\n\n```tsx\neditor.tf.footnote.focusDefinition({ identifier: '3' });\neditor.tf.footnote.focusReference({ identifier: '3' });\n```\n\nWhen a single definition is pointed at by multiple references, pass `index` to pick which one to land on:\n\n```tsx\neditor.tf.footnote.focusReference({ identifier: '3', index: 1 });\n```\n\nBoth transforms return `false` when the identifier doesn't resolve, so you can branch on stale links without throwing.\n\n### Handle Duplicate Definitions\n\nTwo definitions with the same identifier is a resolvable edit state, not an error. The **first** definition in document order stays canonical; later ones are flagged as duplicates. Renumber a later duplicate with:\n\n```tsx\nconst nextIdentifier = editor.tf.footnote.normalizeDuplicateDefinition({\n  path: duplicatePath,\n});\n```\n\nThe transform returns the newly assigned identifier string on success, or `false` when the path isn't a duplicate definition or the requested identifier is already taken. Pass `identifier` to target a specific free identifier instead of the next available one.\n\n### Customize Rendering\n\nSwap in your own React components with `withComponent`:\n\n```tsx\nimport {\n  FootnoteDefinitionPlugin,\n  FootnoteReferencePlugin,\n} from '@platejs/footnote/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    FootnoteReferencePlugin.withComponent(MyFootnoteReference),\n    FootnoteDefinitionPlugin.withComponent(MyFootnoteDefinition),\n  ],\n});\n```\n\nThe package owns node semantics, identifier allocation, and navigation helpers. App-level surfaces — hover previews, the `[^` combobox, slash-command entries, toolbar buttons — are built on top of the transforms and API methods below.\n\n</Steps>\n\n## Plugins\n\n### `FootnoteReferencePlugin`\n\nInline void node rendered as `<sup>`. Owns the `[^` combobox trigger, identifier registry, navigation transforms, and query API. Automatically includes `FootnoteInputPlugin`.\n\n<API name=\"FootnoteReferencePlugin\">\n<APIOptions>\n  <APIItem name=\"trigger\" type=\"RegExp | string[] | string\" optional>\n    Character that opens the footnote combobox.\n    - **Default:** `'^'`\n  </APIItem>\n  <APIItem name=\"triggerPreviousCharPattern\" type=\"RegExp\" optional>\n    Only trigger when the previous character matches. The default requires `[` so bare `^` in prose doesn't open the combobox.\n    - **Default:** `/^\\[$/`\n  </APIItem>\n  <APIItem name=\"createComboboxInput\" type=\"(trigger: string) => TElement\" optional>\n    Factory for the node inserted when the combobox opens. Defaults to a `footnoteInput` element.\n  </APIItem>\n  <APIItem name=\"triggerQuery\" type=\"(editor: SlateEditor) => boolean\" optional>\n    Extra predicate gating the combobox. Return `false` to suppress triggering at the current selection.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `FootnoteDefinitionPlugin`\n\nBlock node for footnote definitions. Lives at the bottom of the document and carries the identifier + body content.\n\n<API name=\"FootnoteDefinitionPlugin\" />\n\n### `FootnoteInputPlugin`\n\nInline void used as the live combobox input while the reader is typing `[^…`. Pulled in automatically by `FootnoteReferencePlugin`; add it directly only if you render the combobox yourself.\n\n<API name=\"FootnoteInputPlugin\" />\n\n## API\n\nAll API methods hang off `editor.api.footnote`. Reads go through a lazy per-editor registry that rebuilds only when a footnote operation invalidates it, so hover previews and navigation stay cheap even when a single definition has many references.\n\n### `api.footnote.definition`\n\nGet the canonical (first-in-document-order) definition entry for an identifier.\n\n<API name=\"definition\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NodeEntry<TElement> | undefined\">\n    Canonical definition entry, or `undefined` when nothing matches.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.definitions`\n\nGet every definition entry that shares an identifier, in document order. When duplicates exist, the first entry is canonical; later entries are duplicates.\n\n<API name=\"definitions\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NodeEntry<TElement>[]\">\n    Definition entries in document order.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.definitionText`\n\nGet the plain-text content of the canonical definition. Ideal for hover previews — reads straight from live definition nodes, no copied state.\n\n<API name=\"definitionText\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"string | undefined\">\n    Definition text, or `undefined` when no definition exists.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.references`\n\nGet every reference entry that points at an identifier, in document order.\n\n<API name=\"references\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NodeEntry<TElement>[]\">\n    Reference entries in document order.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.identifiers`\n\nList every identifier that has at least one definition, in document order.\n\n<API name=\"identifiers\">\n<APIReturns>\n  <APIItem name=\"return\" type=\"string[]\">\n    Defined identifiers.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.nextId`\n\nCompute the next free numeric identifier. Used by `tf.insert.footnote` when the caller doesn't supply one.\n\n<API name=\"nextId\">\n<APIReturns>\n  <APIItem name=\"return\" type=\"string\">\n    Next free identifier (e.g. `'1'`, `'2'`).\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.isResolved`\n\nCheck whether an identifier has at least one definition.\n\n<API name=\"isResolved\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `true` when at least one definition exists for the identifier.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.duplicateDefinitions`\n\nGet every non-canonical definition entry for an identifier — that is, every definition after the first in document order.\n\n<API name=\"duplicateDefinitions\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NodeEntry<TElement>[]\">\n    Definition entries past the canonical one.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.duplicateIdentifiers`\n\nList every identifier that has more than one definition.\n\n<API name=\"duplicateIdentifiers\">\n<APIReturns>\n  <APIItem name=\"return\" type=\"string[]\">\n    Identifiers with duplicate definitions.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.hasDuplicateDefinitions`\n\nCheck whether an identifier has more than one definition.\n\n<API name=\"hasDuplicateDefinitions\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `true` when two or more definitions share the identifier.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.footnote.isDuplicateDefinition`\n\nCheck whether a given definition path is a later duplicate (not the canonical one).\n\n<API name=\"isDuplicateDefinition\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"number[]\">\n    Path of the definition to check.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `true` when the node at `path` is a footnote definition past the canonical one.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Transforms\n\n### `tf.insert.footnote`\n\nInsert a footnote reference at the current selection, create a matching definition if one doesn't already exist, and focus the definition body.\n\nWhen the selection is expanded, the expanded fragment seeds the new definition body so you can convert selected prose into a footnote in one call.\n\n<API name=\"insert.footnote\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\" optional>\n    Reuse an existing identifier. Defaults to `api.footnote.nextId()`.\n  </APIItem>\n  <APIItem name=\"focusDefinition\" type=\"boolean\" optional>\n    Focus the definition body after insertion. Pass `false` to keep the caret inline after the reference.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"...options\" type=\"InsertNodesOptions\" optional>\n    Standard insert-nodes options (`at`, `select`, etc.) forwarded to the reference insert.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `tf.footnote.createDefinition`\n\nCreate the missing definition for an existing identifier without inserting another reference. Returns the path of the definition — the newly created one, or the existing one when the identifier already resolves.\n\n<API name=\"footnote.createDefinition\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Identifier to create a definition for.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"boolean\" optional>\n    Focus the definition body after creation.\n    - **Default:** `true`\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"number[]\">\n    Path of the resolved definition.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.footnote.focusDefinition`\n\nJump the selection into the canonical definition body, scroll it into view, and flash it through Navigation Feedback.\n\n<API name=\"footnote.focusDefinition\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when no definition resolves, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.footnote.focusReference`\n\nJump the selection to the matching reference, scroll it into view, and flash it through Navigation Feedback.\n\n<API name=\"footnote.focusReference\">\n<APIParameters>\n  <APIItem name=\"identifier\" type=\"string\">\n    Footnote identifier.\n  </APIItem>\n  <APIItem name=\"index\" type=\"number\" optional>\n    Pick a specific reference when the definition is pointed at by several. Indexed by document order.\n    - **Default:** `0`\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when the reference doesn't resolve, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.footnote.normalizeDuplicateDefinition`\n\nRenumber a later duplicate definition so the canonical definition stays intact. Pass the path of the duplicate; optionally pass a specific `identifier` to target, otherwise the transform picks `api.footnote.nextId()`.\n\n<API name=\"footnote.normalizeDuplicateDefinition\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"number[]\">\n    Path of the duplicate definition to renumber.\n  </APIItem>\n  <APIItem name=\"identifier\" type=\"string\" optional>\n    Target identifier. Must be free. Defaults to `api.footnote.nextId()`.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"false | string\">\n    The assigned identifier on success, or `false` when the path isn't a duplicate definition or the target identifier is already taken.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Related Docs\n\n- [Markdown](/docs/plate/markdown)\n- [Navigation Feedback](/docs/plate/navigation-feedback)\n- [Editor Methods](/docs/plate/editor-methods)\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/footnote.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "heading-docs",
      "title": "Heading",
      "description": "H1 through H6 block elements with shortcuts, input rules, and static renderers.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/heading.mdx",
          "content": "---\ntitle: Heading\ndescription: H1 through H6 block elements with shortcuts, input rules, and static renderers.\ndocs:\n  - route: /docs/components/heading-node\n    title: Heading Element\n  - route: /docs/basic-blocks\n    title: Basic Blocks\n  - route: https://pro.platejs.org/docs/components/heading-node\n    title: Plus\n---\n\nHeading adds six block element types, `h1` through `h6`, for document structure. Each level is its own plugin with the same block behavior, HTML parser, toggle transform, and optional Markdown shorthand rule. This page covers registry setup, manual setup, value shape, shortcuts, input rules, and Markdown serialization.\n\n<ComponentPreview name=\"basic-blocks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Block `h1` through `h6` elements.\n- Individual `H1Plugin` through `H6Plugin` exports.\n- Grouping `HeadingPlugin` and `BaseHeadingPlugin` with configurable levels.\n- Bound `editor.tf.h1.toggle()` through `editor.tf.h6.toggle()` transforms.\n- `# ` through `###### ` input rules with `HeadingRules.markdown()`.\n- `mod+alt+1` through `mod+alt+6` shortcuts in the registry kit.\n- Editable and static registry heading components.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add Basic Blocks\n\n`BasicBlocksKit` installs paragraph, H1-H6, blockquote, and horizontal rule plugins with Plate UI components. For headings, it configures every level with `HeadingRules.markdown()`, `H*Element`, a reset-on-empty break rule, and `mod+alt+<level>` shortcuts.\n\n<ComponentSource name=\"basic-blocks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BasicBlocksKit,\n});\n```\n\n### Render Headings\n\n`heading-node` exports `H1Element` through `H6Element`. Each component renders a `PlateElement` with the matching heading tag and navigation-highlight styles.\n\n<ComponentSource name=\"heading-node\" />\n\n### Add Static Rendering\n\nUse `BaseBasicBlocksKit` when rendering read-only content with `platejs/static`.\n\n<ComponentSource name=\"basic-blocks-base-kit\" />\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/basic-nodes` | Package | Exports base H1-H6 plugins, `BaseHeadingPlugin`, `HeadingRules`, and shared heading rules. |\n| `@platejs/basic-nodes/react` | Package | Exports `H1Plugin` through `H6Plugin`, plus the grouping `HeadingPlugin`. |\n| `basic-blocks-kit` | Registry | Adds H1-H6 React plugins with components, input rules, shortcuts, and reset-on-empty break behavior. |\n| `basic-blocks-base-kit` | Registry | Adds static H1-H6 components for server/static rendering. |\n| `heading-node` | Registry UI | Renders editable and static heading components. |\n| Toolbar and slash UI | Registry UI | Provides H1-H6 turn-into items, H1-H3 insert items, and H1-H3 slash-command items. |\n| `@platejs/markdown` | Package | Serializes and deserializes `h1` through `h6` as Markdown headings. |\n\n`BasicBlocksPlugin` and `BaseBasicBlocksPlugin` are package grouping plugins. They do not install registry UI components; use the registry kits when you want the Plate UI heading styles.\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\n### Add Individual Levels\n\nUse individual level plugins when you want registry components, shortcuts, or per-level configuration.\n\n```tsx\nimport { HeadingRules } from '@platejs/basic-nodes';\nimport {\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  H4Plugin,\n  H5Plugin,\n  H6Plugin,\n} from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport {\n  H1Element,\n  H2Element,\n  H3Element,\n  H4Element,\n  H5Element,\n  H6Element,\n} from '@/components/ui/heading-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    H1Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H1Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+1' } },\n    }),\n    H2Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H2Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+2' } },\n    }),\n    H3Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H3Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+3' } },\n    }),\n    H4Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H4Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+4' } },\n    }),\n    H5Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H5Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+5' } },\n    }),\n    H6Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n      node: { component: H6Element },\n      rules: { break: { empty: 'reset' } },\n      shortcuts: { toggle: { keys: 'mod+alt+6' } },\n    }),\n  ],\n});\n```\n\n### Use A Headless Group\n\nUse `BaseHeadingPlugin` when you want to install several heading levels without configuring each level by hand.\n\n```tsx\nimport { BaseHeadingPlugin } from '@platejs/basic-nodes';\nimport { createSlateEditor } from 'platejs';\n\nexport const editor = createSlateEditor({\n  plugins: [\n    BaseHeadingPlugin.configure({\n      options: {\n        levels: [1, 2, 3],\n      },\n    }),\n  ],\n});\n```\n\n</Steps>\n\n## Value Shape\n\nHeadings are normal block elements. The heading level is the node `type`.\n\n```tsx\nconst value = [\n  {\n    children: [{ text: 'Installation' }],\n    type: 'h1',\n  },\n  {\n    children: [{ text: 'Install packages' }],\n    type: 'h2',\n  },\n  {\n    children: [{ text: 'Configure editor plugins' }],\n    type: 'h3',\n  },\n];\n```\n\n| Type | HTML Parser | Default Render Tag |\n|------|-------------|--------------------|\n| `h1` | `H1` | `<h1>` |\n| `h2` | `H2` | `<h2>` |\n| `h3` | `H3` | `<h3>` |\n| `h4` | `H4` | `<h4>` |\n| `h5` | `H5` | `<h5>` |\n| `h6` | `H6` | `<h6>` |\n\n`BaseHeadingPlugin.configure({ options: { levels: 3 } })` creates H1 through H3. Passing an array such as `[1, 3, 5]` creates only those levels.\n\n## Editing Behavior\n\nAll heading levels share the same base behavior.\n\n| Behavior | Source |\n|----------|--------|\n| Toggle transform | Each base plugin binds `editor.tf.<level>.toggle()` to `editor.tf.toggleBlock(type)`. |\n| Enter split | Base rules set `break.splitReset: true`; the registry kit also resets an empty heading on break. |\n| Backspace at start | Base rules set `delete.start: 'reset'`. |\n| Empty merge | Base rules set `merge.removeEmpty: true`. |\n| HTML paste | Each level deserializes its matching `H1` through `H6` tag. |\n\n## Input Rules And Shortcuts\n\n`HeadingRules.markdown()` creates a block-start input rule for the configured plugin key.\n\n| Input | Result |\n|-------|--------|\n| `# ` | H1 |\n| `## ` | H2 |\n| `### ` | H3 |\n| `#### ` | H4 |\n| `##### ` | H5 |\n| `###### ` | H6 |\n\nThe registry kit also binds `mod+alt+1` through `mod+alt+6` to the matching toggle transform.\n\n## Registry UI\n\n| Surface | Heading Levels |\n|---------|----------------|\n| Turn Into toolbar | H1-H6 |\n| Insert toolbar | H1-H3 |\n| Slash command | H1-H3 |\n| Editable element | `H1Element` through `H6Element` |\n| Static element | `H1ElementStatic` through `H6ElementStatic` |\n\nStatic heading elements preserve an `id` field by rendering an internal anchor span. That anchor is used by DOCX table-of-contents links.\n\n## Markdown\n\n`@platejs/markdown` maps Markdown heading depth to the matching Plate heading type.\n\n```mdx\n# H1\n## H2\n### H3\n```\n\nSerialization uses the node type to choose Markdown depth. For example, `type: 'h2'` serializes as `##`.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseH1Plugin` through `BaseH6Plugin` | `@platejs/basic-nodes` | Headless heading level plugins. |\n| `BaseHeadingPlugin` | `@platejs/basic-nodes` | Grouping plugin that creates nested heading levels from `options.levels`. |\n| `HeadingRules.markdown()` | `@platejs/basic-nodes` | Creates the `# ` through `###### ` block-start input rules. |\n| `H1Plugin` through `H6Plugin` | `@platejs/basic-nodes/react` | React heading level plugins. |\n| `HeadingPlugin` | `@platejs/basic-nodes/react` | React grouping plugin for heading levels. |\n| `editor.tf.h1.toggle()` through `editor.tf.h6.toggle()` | plugin-bound transforms | Toggle selected blocks to the matching heading level. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/heading.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "horizontal-rule-docs",
      "title": "Horizontal Rule",
      "description": "Void divider blocks rendered as horizontal rules.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/horizontal-rule.mdx",
          "content": "---\ntitle: Horizontal Rule\ndescription: Void divider blocks rendered as horizontal rules.\ndocs:\n  - route: /docs/components/hr-node\n    title: Horizontal Rule Element\n  - route: /docs/basic-blocks\n    title: Basic Blocks\n  - route: https://pro.platejs.org/docs/components/hr-node\n    title: Plus\n---\n\nHorizontal Rule adds a void `hr` block for separating sections. The package owns the element type, HTML parser, default `<hr>` render tag, and Markdown-style input rules. The registry kit adds the Plate UI divider component, static renderer, and insert toolbar item.\n\n<ComponentPreview name=\"basic-blocks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Void block `hr` element.\n- HTML deserialization from `<hr>`.\n- Default render tag of `<hr>`.\n- `---` and `___ ` input rules through `HorizontalRuleRules.markdown()`.\n- Editable and static registry divider components.\n- Insert toolbar item labeled `Divider`.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add Basic Blocks\n\n`BasicBlocksKit` installs `HorizontalRulePlugin` with the registry `HrElement` and both default input rules.\n\n<ComponentSource name=\"basic-blocks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BasicBlocksKit,\n});\n```\n\n### Render The Divider\n\n`hr-node` renders the editable divider with selected/focused ring styles and a static companion component.\n\n<ComponentSource name=\"hr-node\" />\n\n### Add Static Rendering\n\nUse `BaseBasicBlocksKit` when rendering read-only content with `platejs/static`.\n\n<ComponentSource name=\"basic-blocks-base-kit\" />\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/basic-nodes` | Package | Exports `BaseHorizontalRulePlugin` and `HorizontalRuleRules`. |\n| `@platejs/basic-nodes/react` | Package | Exports `HorizontalRulePlugin`. |\n| `basic-blocks-kit` | Registry | Adds `HorizontalRulePlugin` with dash and underscore input rules plus `HrElement`. |\n| `basic-blocks-base-kit` | Registry | Adds `BaseHorizontalRulePlugin.withComponent(HrElementStatic)`. |\n| `hr-node` | Registry UI | Renders editable and static divider components. |\n| Insert toolbar | Registry UI | Inserts `KEYS.hr` through the generic `insertBlock` path. |\n\nThere is no package-specific `insertHorizontalRule` helper. App UI usually inserts `KEYS.hr` through the same block insertion helper used by other registry blocks.\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\n### Add The Plugin\n\nUse the React plugin when the editor renders the divider UI.\n\n```tsx\nimport { HorizontalRuleRules } from '@platejs/basic-nodes';\nimport { HorizontalRulePlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { HrElement } from '@/components/ui/hr-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    HorizontalRulePlugin.configure({\n      inputRules: [\n        HorizontalRuleRules.markdown({ variant: '-' }),\n        HorizontalRuleRules.markdown({ variant: '_' }),\n      ],\n      node: { component: HrElement },\n    }),\n  ],\n});\n```\n\n### Add Static Rendering\n\nUse the base plugin with the static component in static rendering paths.\n\n```tsx\nimport { BaseHorizontalRulePlugin } from '@platejs/basic-nodes';\nimport { createStaticEditor } from 'platejs';\n\nimport { HrElementStatic } from '@/components/ui/hr-node-static';\n\nexport const staticEditor = createStaticEditor({\n  plugins: [BaseHorizontalRulePlugin.withComponent(HrElementStatic)],\n});\n```\n\n</Steps>\n\n## Value Shape\n\nHorizontal rules are void block elements. Keep the empty text child so the node remains a valid Slate element.\n\n```tsx\nconst value = [\n  {\n    children: [{ text: 'Before the divider' }],\n    type: 'p',\n  },\n  {\n    children: [{ text: '' }],\n    type: 'hr',\n  },\n  {\n    children: [{ text: 'After the divider' }],\n    type: 'p',\n  },\n];\n```\n\n| Field | Type | Notes |\n|-------|------|-------|\n| `type` | `'hr'` | Plugin key and node type from `KEYS.hr`. |\n| `children` | `[{ text: '' }]` | Required child for the void element. |\n\n## Input Rules\n\n`HorizontalRuleRules.markdown()` converts a paragraph into an `hr` block and inserts an empty paragraph after it.\n\n| Rule | Trigger | Behavior |\n|------|---------|----------|\n| `HorizontalRuleRules.markdown({ variant: '-' })` | type the third `-` after `--` | Converts `---` to `hr`, then inserts a paragraph. |\n| `HorizontalRuleRules.markdown({ variant: '_' })` | type a space after `___` | Converts `___ ` to `hr`, then inserts a paragraph. |\n\nThe rule uses `editor.tf.setNodes({ type: KEYS.hr })`, so it transforms the current block instead of inserting a second divider elsewhere.\n\n## Registry UI\n\n| Surface | Behavior |\n|---------|----------|\n| Editable element | Renders a non-editable padded wrapper with an `<hr>` inside it. |\n| Selected + focused | Adds a focus ring around the divider. |\n| Read-only | Removes the pointer cursor from the editable component. |\n| Static element | Renders the same divider inside `SlateElement`. |\n| Insert toolbar | Adds a `Divider` item with `MinusIcon` and `KEYS.hr`. |\n\nThe divider component renders `{props.children}` after the non-editable wrapper so Slate can still keep the void child mounted.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseHorizontalRulePlugin` | `@platejs/basic-nodes` | Headless void `hr` block with HTML deserialization and default render tag. |\n| `HorizontalRulePlugin` | `@platejs/basic-nodes/react` | React horizontal rule plugin. |\n| `HorizontalRuleRules.markdown({ variant: '-' })` | `@platejs/basic-nodes` | Creates the dash input rule for `---`. |\n| `HorizontalRuleRules.markdown({ variant: '_' })` | `@platejs/basic-nodes` | Creates the underscore input rule for `___ `. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/horizontal-rule.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "link-docs",
      "title": "Link",
      "description": "Documentation for Link",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/link.mdx",
          "content": "---\ntitle: Link\ndocs:\n  - route: https://pro.platejs.org/docs/examples/link\n    title: Plus\n  - route: /docs/components/link-node\n    title: Link Element\n  - route: /docs/components/link-toolbar\n    title: Link Floating Toolbar\n  - route: /docs/components/link-toolbar-button\n    title: Link Toolbar Button\n---\n\n<ComponentPreview name=\"link-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Insert, edit, and remove hyperlinks.\n- Markdown shortcut: type `[text](url)` and close with `)` to turn it into a link.\n- Plain URL autolink: paste a URL, or type a URL then press space or `Enter`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add link functionality is with the `LinkKit`, which includes pre-configured `LinkPlugin` with floating toolbar, autolink rules, and [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"link-kit\" />\n\n- [`LinkElement`](/docs/plate/components/link-node): Renders link elements.\n- [`LinkFloatingToolbar`](/docs/plate/components/link-toolbar): Provides floating toolbar for link editing.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { LinkKit } from '@/components/editor/plugins/link-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...LinkKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/link\n```\n\n### Add Plugin\n\nInclude `LinkPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { LinkRules } from '@platejs/link';\nimport { LinkPlugin } from '@platejs/link/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    LinkPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure the plugin with floating toolbar and custom components.\n\n```tsx\nimport { LinkPlugin } from '@platejs/link/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { LinkElement } from '@/components/ui/link-node';\nimport { LinkFloatingToolbar } from '@/components/ui/link-toolbar';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    LinkPlugin.configure({\n      render: {\n        node: LinkElement,\n        afterEditable: () => <LinkFloatingToolbar />,\n      },\n    }),\n  ],\n});\n```\n\n- `render.afterEditable`: Renders [`LinkFloatingToolbar`](/docs/plate/components/link-toolbar) after the editable area for link editing.\n- `render.node`: Assigns [`LinkElement`](/docs/plate/components/link-node) to render link elements.\n\n### Input Rules\n\nConfigure `LinkPlugin` to enable the built-in link input presets:\n\n```tsx\nimport { LinkPlugin } from '@platejs/link/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    LinkPlugin.configure({\n      inputRules: [\n        LinkRules.markdown(),\n        LinkRules.autolink({ variant: 'paste' }),\n        LinkRules.autolink({ variant: 'space' }),\n        LinkRules.autolink({ variant: 'break' }),\n      ],\n    }),\n  ],\n});\n```\n\n- `LinkRules.autolink({ variant: 'paste' | 'space' | 'break' })`: Enables plain URL autolink.\n- `LinkRules.markdown()`: Enables `[text](url)` source-entry conversion on the closing `)`.\n\n### Add Toolbar Button\n\nYou can add [`LinkToolbarButton`](/docs/plate/components/link-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to insert and edit links.\n\n</Steps>\n\n## Keyboard Shortcuts\n\n<KeyTable>\n  <KeyTableItem hotkey=\"Cmd + K\">Add a link on the selected text.</KeyTableItem>\n</KeyTable>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"link-pro\" />\n\n## Plugins\n\n### `LinkPlugin`\n\nPlugin for link formatting.\n\n<API name=\"LinkPlugin\">\n<APIOptions type=\"object\">\n<APIItem name=\"forceSubmit\" type=\"boolean\" optional>\nDetermines whether to force the submission of the link form.\n</APIItem>\n<APIItem name=\"rangeBeforeOptions\" type=\"RangeBeforeOptions\" optional>\nAllows custom configurations for rangeBeforeOptions.\n- **Default:**\n```ts\n{\n  matchString: ' ',\n  skipInvalid: true,\n  afterMatch: true,\n}\n```\n</APIItem>\n<APIItem name=\"triggerFloatingLinkHotkeys\" type=\"string | string[]\" optional>\nHotkeys to trigger floating link.\n- **Default:** **`'meta+k, ctrl+k'`**\n</APIItem>\n<APIItem name=\"allowedSchemes\" type=\"string[]\" optional>\nList of allowed URL schemes.\n- **Default:** **`['http', 'https', 'mailto', 'tel']`**\n</APIItem>\n<APIItem name=\"dangerouslySkipSanitization\" type=\"boolean\" optional>\nDetermines whether the sanitation of links should be skipped.\n- **Default:** **`false`**\n</APIItem>\n<APIItem name=\"defaultLinkAttributes\" type=\"AnchorHTMLAttributes&lt;HTMLAnchorElement&gt;\" optional>\nDefault HTML attributes for link elements.\n- **Default:** **`{}`**\n</APIItem>\n<APIItem name=\"keepSelectedTextOnPaste\" type=\"boolean\" optional>\nKeeps selected text on pasting links by default.\n- **Default:** **`true`**\n</APIItem>\n<APIItem name=\"isUrl\" type=\"(text: string) => boolean\" optional>\nCallback function to validate a URL.\n- **Default:** **`isUrl`**\n</APIItem>\n<APIItem name=\"getUrlHref\" type=\"(url: string) => string | undefined\" optional>\nCallback function to optionally get the href for a URL. It returns an optional link that is different from the text content. For example, returns `https://google.com` for `google.com`.\n</APIItem>\n<APIItem name=\"transformInput\" type=\"(url: string | null) => string | undefined\" optional>\nCallback function to optionally transform the submitted URL provided by the user to the URL input before validation.\n</APIItem>\n<APIItem name=\"getLinkUrl\" type=\"(prevUrl: string | null) => Promise<string | null>\" optional>\nOn keyboard shortcut or toolbar mousedown, this function is called to get the link URL. The default behavior is to use the browser's native `prompt`.\n</APIItem>\n</APIOptions>\n</API>\n\n## Transforms\n\n### `tf.insert.link`\n\nInserts a link node into the editor.\n\n<API name=\"insert.link\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"object\">\n    Options for inserting the link.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"InsertLinkOptions\">\n  <APIItem name=\"createLinkNodeOptions\" type=\"CreateLinkNodeOptions\">\n    Options for creating the link node.\n  </APIItem>\n  <APIItem name=\"insertOptions\" type=\"InsertNodesOptions\" optional>\n    Additional options for inserting nodes.\n  </APIItem>\n </APIOptions>\n</API>\n\n## API\n\n### `api.floatingLink.hide`\n\nHides the floating link and resets its state.\n\n### `api.floatingLink.reset`\n\nResets the floating link state without changing the openEditorId.\n\n### `api.floatingLink.show`\n\nShows the floating link for the specified mode and editor ID.\n\n<API name=\"floatingLink.show\">\n<APIParameters>\n<APIItem name=\"mode\" type=\"FloatingLinkMode\">\nThe mode to set for the floating link ('edit' or 'insert').\n</APIItem>\n<APIItem name=\"editorId\" type=\"string\">\nThe ID of the editor where the floating link should be shown.\n</APIItem>\n</APIParameters>\n</API>\n\n### `api.link.getAttributes`\n\nGets the attributes for a link element.\n\n<API name=\"link.getAttributes\">\n<APIParameters>\n<APIItem name=\"element\" type=\"TLinkElement\">\nThe link element for which to get attributes.\n</APIItem>\n</APIParameters>\n\n<APIReturns type=\"React.AnchorHTMLAttributes<HTMLAnchorElement>\">\nThe HTML attributes for the link element.\n</APIReturns>\n</API>\n\n### `api.link.submitFloatingLink`\n\nInserts a link if the URL is valid, closes the floating link, and focuses the editor.\n\n<APIReturns type=\"boolean\">\nReturns `true` if the link was inserted successfully.\n</APIReturns>\n\n### `insertLink`\n\nInserts a link node into the editor.\n\n<API name=\"insertLink\">\n<APIParameters>\n  <APIItem name=\"createLinkNodeOptions\" type=\"CreateLinkNodeOptions\">\n    Options for creating link node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertNodesOptions\" optional>\n    Additional options for node insertion.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `submitFloatingLink`\n\nInserts a link if the URL is valid, closes the floating link, and focuses the editor.\n\n- Insert link if url is valid.\n- Text is url if empty.\n- Close floating link.\n- Focus editor.\n\n<API name=\"submitFloatingLink\">\n<APIReturns type=\"boolean\">\nReturns `true` if the link was inserted.\n</APIReturns>\n</API>\n\n### `triggerFloatingLink`\n\nTriggers the floating link.\n\n<API name=\"triggerFloatingLink\">\n<APIOptions type=\"object\">\n<APIItem name=\"focused\" type=\"boolean\" optional>\n  Whether the floating link should be focused.\n</APIItem>\n</APIOptions>\n</API>\n\n### `triggerFloatingLinkEdit`\n\nTriggers the floating link edit.\n\n<API name=\"triggerFloatingLinkEdit\">\n<APIReturns type=\"boolean\">\nReturns `true` if the link was edited.\n</APIReturns>\n</API>\n\n### `triggerFloatingLinkInsert`\n\nTrigger floating link. Do not trigger when:\n- Selection is across blocks\n- Selection has more than one leaf node\n- Lowest selection is not text\n- Selection has a link node\n\n<API name=\"triggerFloatingLinkInsert\">\n<APIOptions type=\"TriggerFloatingLinkOptions\">\n  <APIItem name=\"focused\" type=\"boolean\" optional>\n    Whether the floating link should be focused.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  Returns `true` if the link was inserted.\n</APIReturns>\n</API>\n\n### `unwrapLink`\n\nUnwraps a link node.\n\n<API name=\"unwrapLink\">\n<APIOptions type=\"UnwrapLinkOptions\">\n  <APIItem name=\"split\" type=\"boolean\" optional>\n    If `true`, split the nodes if the selection is inside the link.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `upsertLink`\n\nInsert or update a link node. The behavior depends on the current selection and options:\n\n- If selection is in a link or not a URL:\n  - With `insertTextInLink: true`, inserts URL as text in link\n  - Otherwise, if `text` is empty, sets it to URL\n  - Validates URL unless `skipValidation: true`\n- If selection is expanded or `update: true` in a link:\n  - Removes link node and gets link text\n- Then:\n  - Inserts link node with updated URL and target\n  - If `text` is provided, replaces link text\n\n<API name=\"upsertLink\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"UpsertLinkOptions\">\n    Options for upserting the link.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"UpsertLinkOptions\">\n  <APIItem name=\"url\" type=\"string\">\n    The URL of the link.\n  </APIItem>\n  <APIItem name=\"text\" type=\"string\" optional>\n    The text content of the link.\n  </APIItem>\n  <APIItem name=\"target\" type=\"string\" optional>\n    The target attribute of the link.\n  </APIItem>\n  <APIItem name=\"insertTextInLink\" type=\"boolean\" optional>\n    If `true`, insert the URL as text in the link.\n  </APIItem>\n  <APIItem name=\"insertNodesOptions\" type=\"InsertNodesOptions\" optional>\n    The options for inserting nodes.\n  </APIItem>\n  <APIItem name=\"skipValidation\" type=\"boolean\" optional>\n    If `true`, skips URL validation.\n    - **Default:** `false`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  Returns `true` if the link was inserted or updated.\n</APIReturns>\n</API>\n\n### `upsertLinkText`\n\nIf the text is different from the link above text, replaces the link children with a new text node. The new text node has the same marks as the first text node in the link.\n\n<API name=\"upsertLinkText\">\n<APIOptions type=\"UpsertLinkTextOptions\">\n      <APIItem name=\"text\" type=\"string\" optional>\n        The new text to replace the link children with.\n      </APIItem>\n</APIOptions>\n</API>\n\n### `validateUrl`\n\nValidates a URL based on the plugin options.\n\n<API name=\"validateUrl\">\n<APIOptions type=\"ValidateUrlOptions\">\n  <APIItem name=\"url\" type=\"string\">\n    The URL to validate.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  Returns `true` if the URL is valid.\n</APIReturns>\n</API>\n\n### `wrapLink`\n\nWrap a link node with split.\n\n<API name=\"wrapLink\">\n<APIOptions type=\"WrapLinkOptions\">\n  <APIItem name=\"url\" type=\"string\">\n    The URL of the link.\n  </APIItem>\n  <APIItem name=\"target\" type=\"string\" optional>\n    The target attribute of the link.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `CreateLinkNodeOptions`\n\nOptions for creating a new link node.\n\n<API name=\"CreateLinkNodeOptions\">\n<APIOptions type=\"object\">\n  <APIItem name=\"url\" type=\"string\">\n    The URL of the link node that is being created.\n  </APIItem>\n  <APIItem name=\"text\" type=\"string\" optional>\n    The text that is displayed for the link node. If not provided, the URL is used as the display text.\n  </APIItem>\n  <APIItem name=\"target\" type=\"string\" optional>\n    Specifies where to open the URL:\n    - `_blank`: new tab\n    - `_self`: same frame\n    - `_parent`: parent frame\n    - `_top`: full window\n  </APIItem>\n  <APIItem name=\"children\" type=\"TText[]\" optional>\n    An array of text nodes that represent the link content.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API Components\n\n### `FloatingLinkNewTabInput`\n\nThe input component for controlling whether a link opens in a new tab.\n\n<API name=\"FloatingLinkNewTabInput\">\n<APIState>\n  <APIItem name=\"checked\" type=\"boolean\">\n    Whether the link should open in a new tab.\n  </APIItem>\n  <APIItem name=\"setChecked\" type=\"React.Dispatch<React.SetStateAction<boolean>>\">\n    Function to update the checked state.\n  </APIItem>\n  <APIItem name=\"ref\" type=\"RefObject<HTMLInputElement>\">\n    Reference to the input element.\n  </APIItem>\n</APIState>\n</API>\n\n### `FloatingLinkUrlInput`\n\nThe input component for entering and editing link URLs.\n\n<API name=\"FloatingLinkUrlInput\">\n<APIState>\n  <APIItem name=\"ref\" type=\"RefObject<HTMLInputElement>\">\n    Reference to the input element.\n  </APIItem>\n</APIState>\n</API>\n\n### `LinkOpenButton`\n\nThe button component for opening the link URL.\n\n<API name=\"LinkOpenButton\">\n<APIState>\n  <APIItem name=\"element\" type=\"TLinkElement\">\n    The link element containing the URL to open.\n  </APIItem>\n</APIState>\n</API>\n\n### `useFloatingLinkEdit`\n\nThe behavior hook for the floating link edit functionality.\n\n<API name=\"useFloatingLinkEdit\">\n<APIState>\n  <APIItem name=\"floating\" type=\"object\" optional>\n    The virtual floating returned object.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"ref\" type=\"function\">\n    The ref callback for the floating element.\n  </APIItem>\n  <APIItem name=\"props\" type=\"object\">\n    Props for the floating element.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"style\" type=\"object\">\n        The style of the floating link.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"editButtonProps\" type=\"object\">\n    Props for the edit button.\n    <APISubList>\n      <APISubListItem parent=\"editButtonProps\" name=\"onClick\" type=\"function\">\n        The function to call when the edit button is clicked.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"unlinkButtonProps\" type=\"object\">\n    Props for the unlink button.\n    <APISubList>\n      <APISubListItem parent=\"unlinkButtonProps\" name=\"onClick\" type=\"function\">\n        The function to call when the unlink button is clicked.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useFloatingLinkEnter`\n\nListens for the Enter key press event and submits the floating link in the editor.\n\n### `useFloatingLinkEscape`\n\nListens for the Escape key press event and handles the behavior of the floating link in the editor.\n\n### `useFloatingLinkInsert`\n\nThe behavior hook for inserting a link.\n\n<API name=\"useFloatingLinkInsert\">\n<APIState>\n  <APIItem name=\"floating\" type=\"ReturnType<typeof useFloatingLinkInsertState>\">\n    The virtual floating returned object.\n  </APIItem>\n  <APIItem name=\"refClickOutside\" type=\"React.Ref\">\n    The ref of the floating element.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"ref\" type=\"function\">\n    The ref callback for the floating element.\n  </APIItem>\n  <APIItem name=\"props\" type=\"object\">\n    Props for the floating element.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"style\" type=\"object\">\n        The style of the floating link.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"textInputProps\" type=\"object\">\n    Props for the text input.\n    <APISubList>\n      <APISubListItem parent=\"textInputProps\" name=\"onChange\" type=\"function\">\n        The function to call when the text input value changes.\n      </APISubListItem>\n      <APISubListItem parent=\"textInputProps\" name=\"defaultValue\" type=\"string\">\n        The default value of the text input.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useLink`\n\nThe behavior hook for the link element.\n\n<API name=\"useLink\">\n<APIOptions type=\"UseLinkOptions\">\n  <APIItem name=\"element\" type=\"TLinkElement\">\n    The link element.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for the link element.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"onMouseOver\" type=\"function\">\n        The function to call when the mouse is over the link.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useLinkToolbarButton`\n\nThe behavior hook for the link toolbar button.\n\n<API name=\"useLinkToolbarButton\">\n<APIState>\n  <APIItem name=\"pressed\" type=\"boolean\">\n    Whether the selection is in a link.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for the toolbar button.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"pressed\" type=\"boolean\">\n        Whether the link is pressed.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n        The function to call when the button is clicked.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useVirtualFloatingLink`\n\nCustom hook for managing virtual floating of a link.\n\n<API name=\"useVirtualFloatingLink\">\n<APIOptions type=\"object\">\n  <APIItem name=\"editorId\" type=\"string\">\n    The ID of the editor to which the link belongs.\n  </APIItem>\n  <APIItem name=\"floatingOptions\" type=\"UseVirtualFloatingOptions\" optional>\n    Options for virtual floating.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"UseVirtualFloatingReturn\">\n  The return value of the `useVirtualFloating` hook.\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/link.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "list-classic-docs",
      "title": "List Classic",
      "description": "Documentation for List Classic",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/list-classic.mdx",
          "content": "---\ntitle: List Classic\ndocs:\n  - route: /docs/components/list-classic-node\n    title: List Nodes\n  - route: /docs/components/list-classic-toolbar-button\n    title: List Toolbar Button\n---\n\n<ComponentPreview name=\"list-classic-demo\" />\n\n<Callout type=\"info\" title=\"Choose Your List Plugin\">\n  Plate offers two approaches for implementing lists:\n\n  1. **This List Classic plugin** - Traditional HTML-spec lists with strict nesting rules:\n     - Follows standard HTML list structure (`ul`/`ol` > `li`)\n     - Maintains consistent list hierarchy\n     - Best for content that may be exported to HTML/markdown\n     - Highest complexity\n\n  2. The [**List plugin**](/docs/plate/list) - Flexible indentation-based lists:\n     - More like Word/Google Docs behavior\n     - Any block can be indented to create list-like structures\n     - Used in the [AI editor](/editors#editor-ai)\n     - Supports nested todo lists\n     - Better for free-form content organization\n\n  Choose based on your needs:\n  - Use the **List Classic plugin** when you need standard HTML list compatibility\n  - Use the **List plugin** when you want more flexible indentation behavior\n\n</Callout>\n\n<PackageInfo>\n\n\n## Features\n\n- **HTML-compliant lists**:\n  - Standard `ul`/`ol` > `li` structure\n  - Proper nesting and hierarchy\n  - SEO-friendly markup\n  - Clean HTML/markdown export\n\n- **List types**:\n  - Unordered (bulleted) lists\n  - Ordered (numbered) lists\n  - Task lists with checkboxes\n  - Nested sublists\n\n- **Drag & drop**:\n  - Currently supports root-level list items only\n  - Sibling and nested items drag & drop not yet supported\n\n- **Shortcuts**:\n  - Register the shipped input rules to use markdown shortcuts (**`-`**, **`*`**, **`1.`**, **`[ ]`**) to create lists\n  - Tab/Shift+Tab for indentation control\n\n- **Limitations (use the [List plugin](/docs/plate/list) for these features)**:\n  - Drag & drop only supports root-level lists\n  - List items cannot be aligned\n\nFor a more flexible, Word-like approach, see the [List plugin](/docs/plate/list).\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add list functionality is with the `ListKit` export from `list-classic-kit`, which includes pre-configured list plugins, the classic markdown entry rules, [Plate UI](/docs/plate/installation/plate-ui) components, and keyboard shortcuts.\n\n<ComponentSource name=\"list-classic-kit\" />\n\n- [`BulletedListElement`](/docs/plate/components/list-classic-node): Renders unordered list elements.\n- [`NumberedListElement`](/docs/plate/components/list-classic-node): Renders ordered list elements.\n- [`TaskListElement`](/docs/plate/components/list-classic-node): Renders task list elements with checkboxes.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { ListKit } from '@/components/editor/plugins/list-classic-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...ListKit,\n  ],\n});\n```\n\n</Steps>\n\nThe shipped `ListKit` also wires the classic markdown entry rules for bulleted, ordered, and task lists. See [Plugin Input Rules](/docs/plate/plugin-input-rules) for the runtime model.\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/list-classic\n```\n\n### Add Plugins\n\nInclude the list plugins in your Plate plugins array when creating the editor.\n\n```tsx\nimport { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ListPlugin,\n    BulletedListPlugin,\n    NumberedListPlugin,\n    TaskListPlugin,\n    ListItemPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the plugins with custom components and keyboard shortcuts.\n\n```tsx\nimport {\n  BulletedListRules,\n  OrderedListRules,\n  TaskListRules,\n} from '@platejs/list-classic';\nimport { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { BulletedListElement, NumberedListElement, TaskListElement } from '@/components/ui/list-classic-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ListPlugin.configure({\n      inputRules: [\n        BulletedListRules.markdown({ variant: '-' }),\n        BulletedListRules.markdown({ variant: '*' }),\n        OrderedListRules.markdown({ variant: '.' }),\n        OrderedListRules.markdown({ variant: ')' }),\n        TaskListRules.markdown({ checked: false }),\n        TaskListRules.markdown({ checked: true }),\n      ],\n    }),\n    BulletedListPlugin.configure({\n      node: { component: BulletedListElement },\n      shortcuts: { toggle: { keys: 'mod+alt+5' } },\n    }),\n    NumberedListPlugin.configure({\n      node: { component: NumberedListElement },\n      shortcuts: { toggle: { keys: 'mod+alt+6' } },\n    }),\n    TaskListPlugin.configure({\n      node: { component: TaskListElement },\n      shortcuts: { toggle: { keys: 'mod+alt+7' } },\n    }),\n    ListItemPlugin,\n  ],\n});\n```\n\n- `inputRules`: Registers the classic markdown entry rules for bulleted, ordered, and task lists.\n- `node.component`: Assigns [`BulletedListElement`](/docs/plate/components/list-classic-node), [`NumberedListElement`](/docs/plate/components/list-classic-node), and [`TaskListElement`](/docs/plate/components/list-classic-node) to render list elements.\n- `shortcuts.toggle`: Defines keyboard [shortcuts](/docs/plate/plugin-shortcuts) to toggle list types (`mod+alt+5` for bulleted, `mod+alt+6` for numbered, `mod+alt+7` for task lists).\n\n### Add Toolbar Button\n\nYou can add [`ListToolbarButton`](/docs/plate/components/list-classic-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to create and manage lists.\n\n### Turn Into Toolbar Button\n\nWhen using the `ListPlugin`, use the [`turn-into-toolbar-classic-button`](/docs/plate/components/turn-into-toolbar-classic-button) which includes all list types (bulleted, numbered, and task lists).\n\n### Insert Toolbar Button\n\nWhen using the `ListPlugin`, use the [`insert-toolbar-classic-button`](/docs/plate/components/insert-toolbar-classic-button) which includes all list types (bulleted, numbered, and task lists).\n\n</Steps>\n\n## Plugins\n\n### `ListPlugin`\n\n<API name=\"ListPlugin\">\nContains the following element plugins:\n- `BulletedListPlugin`\n- `NumberedListPlugin`\n- `TaskListPlugin`\n- `ListItemPlugin`\n- `ListItemContentPlugin`\n\n<APIOptions type=\"object\">\n  <APIItem name=\"validLiChildrenTypes\" type=\"string[]\" optional>\n    Valid child node types for list items (besides `p` and `ul`).\n  </APIItem>\n  <APIItem name=\"enableResetOnShiftTab\" type=\"boolean\" optional>\n    Whether Shift+Tab should reset list indent level.\n  </APIItem>\n  <APIItem name=\"inheritCheckStateOnLineEndBreak\" type=\"boolean\" optional>\n    Whether to inherit the checked state of above node after insert break at the end. Only applies to task lists.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"inheritCheckStateOnLineStartBreak\" type=\"boolean\" optional>\n    Whether to inherit the checked state of below node after insert break at the start. Only applies to task lists.\n    - **Default:** `false`\n  </APIItem>\n</APIOptions>\n</API>\n\n### `BulletedListPlugin`\n\nPlugin for unordered (bulleted) lists.\n\n### `NumberedListPlugin`\n\nPlugin for ordered (numbered) lists.\n\n### `TaskListPlugin`\n\nPlugin for task lists with checkboxes.\n\n### `ListItemPlugin`\n\nPlugin for list items.\n\n### `ListItemContentPlugin`\n\nPlugin for list item content.\n\n## Transforms\n\n### `tf.ul.toggle()`\n\nToggles a bulleted list (ul).\n\nExample Shortcut: `Mod+Alt+5`\n\n### `tf.ol.toggle()`\n\nToggles an ordered list (ol).\n\nExample Shortcut: `Mod+Alt+6`\n\n### `tf.taskList.toggle()`\n\nToggles a task list with checkboxes.\n\nExample Shortcut: `Mod+Alt+7`\n\n## API\n\n### `getHighestEmptyList`\n\nFinds the highest end list that can be deleted. The path of the list should be different from `diffListPath`. If the highest end list has 2 or more items, returns `liPath`. It traverses up the parent lists until:\n- The list has less than 2 items\n- Its path is not equal to `diffListPath`\n\n<API name=\"getHighestEmptyList\">\n<APIOptions type=\"object\">\n  <APIItem name=\"liPath\" type=\"Path\">\n    Path of list item.\n  </APIItem>\n  <APIItem name=\"diffListPath\" type=\"Path\" optional>\n    Path of different list.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Path | undefined\">\n  Path of highest deletable end list.\n</APIReturns>\n</API>\n\n### `getListItemEntry`\n\nReturns the nearest `li` and `ul`/`ol` wrapping node entries for a given path (`default = selection`).\n\n<API name=\"getListItemEntry\">\n<APIOptions type=\"object\">\n  <APIItem name=\"at\" type=\"Location | null\" optional>\n    Location to get entries from.\n    - **Default:** `editor.selection`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"ElementEntry | undefined\">\n  Nearest `li` and `ul`/`ol` wrapping node entries.\n</APIReturns>\n</API>\n\n### `getListRoot`\n\nSearches upward for root list element.\n\n<API name=\"getListRoot\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"Path | TRange | Point | null\" optional>\n    Location to start search from.\n    - **Default:** `editor.selection`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"ElementEntry | undefined\">\n  Root list element entry.\n</APIReturns>\n</API>\n\n### `getListTypes`\n\nGets array of supported list types.\n\n<API name=\"getListTypes\">\n<APIReturns type=\"string[]\">\n  Array of supported list types.\n</APIReturns>\n</API>\n\n### `moveListSiblingsAfterCursor`\n\nMoves list siblings after cursor to specified path.\n\n<API name=\"moveListSiblingsAfterCursor\">\n<APIOptions type=\"options\">\n  <APIItem name=\"at\" type=\"Path\">\n    Cursor position path.\n  </APIItem>\n  <APIItem name=\"to\" type=\"Path\">\n    Destination path.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"number\">\n  Number of siblings moved.\n</APIReturns>\n</API>\n\n### `removeFirstListItem`\n\nRemoves first list item if not nested and not first child.\n\n<API name=\"removeFirstListItem\">\n<APIOptions type=\"options\">\n  <APIItem name=\"list\" type=\"ElementEntry\">\n    List entry containing item.\n  </APIItem>\n  <APIItem name=\"listItem\" type=\"ElementEntry\">\n    List item to remove.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  Whether item was removed.\n</APIReturns>\n</API>\n\n### `removeListItem`\n\nRemoves list item and moves sublist to parent if any.\n\n<API name=\"removeListItem\">\n<APIOptions type=\"RemoveListItemOptions\">\n  <APIItem name=\"list\" type=\"ElementEntry\">\n    List entry containing item.\n  </APIItem>\n  <APIItem name=\"listItem\" type=\"ElementEntry\">\n    List item to remove.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to reverse sublist items.\n    - **Default:** `true`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  Whether item was removed.\n</APIReturns>\n</API>\n\n### `someList`\n\nChecks if selection is inside list of specific type.\n\n<API name=\"someList\">\n<APIParameters>\n  <APIItem name=\"type\" type=\"string\">\n    List type to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  Whether selection is in specified list type.\n</APIReturns>\n</API>\n\n### `unindentListItems`\n\nDecreases indentation level of list items.\n\n<API name=\"unindentListItems\">\n<APIOptions type=\"UnindentListItemsOptions\">\n    Target path for unindenting.\n</APIOptions>\n</API>\n\n### `unwrapList`\n\nRemoves list formatting from selected items.\n\n<API name=\"unwrapList\">\n<APIOptions type=\"options\">\n  <APIItem name=\"at\" type=\"Path\" optional>\n    Target path for unwrapping.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useListToolbarButton`\n\nA behavior hook for a list toolbar button.\n\n<API name=\"useListToolbarButton\">\n<APIState>\n  <APIItem name=\"pressed\" type=\"boolean\">\n    Button pressed state.\n  </APIItem>\n  <APIItem name=\"nodeType\" type=\"string\">\n    List node type.\n    - **Default:** `BulletedListPlugin.key`\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for toolbar button.\n  </APIItem>\n  <APISubList>\n    <APISubListItem parent=\"props\" name=\"pressed\" type=\"boolean\">\n      Button pressed state.\n    </APISubListItem>\n    <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n      Handler to toggle list and focus editor.\n    </APISubListItem>\n  </APISubList>\n</APIReturns>\n</API>\n\n### `useTodoListElementState`\n\nHook to manage task list item state.\n\n<API name=\"useTodoListElementState\">\n<APIState>\n  <APIItem name=\"element\" type=\"TTodoListItemElement\">\n    Task list item element.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"checked\" type=\"boolean\">\n    Whether the task item is checked.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\">\n    Whether the editor is in read-only mode.\n  </APIItem>\n  <APIItem name=\"onCheckedChange\" type=\"function\">\n    Handler to toggle the checked state.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTodoListElement`\n\nHook to get props for task list item checkbox.\n\n<API name=\"useTodoListElement\">\n<APIState>\n  <APIItem name=\"checked\" type=\"boolean\">\n    Whether the task item is checked.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\">\n    Whether the editor is in read-only mode.\n  </APIItem>\n  <APIItem name=\"onCheckedChange\" type=\"function\">\n    Handler to toggle the checked state.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"checkboxProps\" type=\"object\">\n    Props to be spread on the checkbox component.\n  </APIItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/list-classic.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "media-docs",
      "title": "Media",
      "description": "Documentation for Media",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/media.mdx",
          "content": "---\ntitle: Media\ndocs:\n  - route: https://pro.platejs.org/docs/examples/media\n    title: Plus\n  - route: /docs/components/media-image-node\n    title: Image Element\n  - route: /docs/components/media-video-node\n    title: Video Element\n  - route: /docs/components/media-audio-node\n    title: Audio Element\n  - route: /docs/components/media-file-node\n    title: File Element\n  - route: /docs/components/media-embed-node\n    title: Media Embed Element\n  - route: /docs/components/media-toolbar\n    title: Media Popover\n  - route: /docs/components/media-placeholder-node\n    title: Media Placeholder Element\n  - route: /docs/components/media-upload-toast\n    title: Media Upload Toast\n  - route: /docs/components/media-toolbar-button\n    title: Media Toolbar Button\n---\n\n<ComponentPreview name=\"media-demo\" />\n\n<PackageInfo>\n\n## Features\n\n### Media Support\n- **File types**: \n  - Image\n  - Video\n  - Audio\n  - Others (PDF, Word, etc.)\n- **Video providers**:\n  - Local video files\n  - YouTube, Vimeo, Dailymotion, Youku, Coub\n- **Embed providers**: \n  - Tweets\n\n### Media Features\n- Editable captions\n- Resizable elements\n- Embed URLs are normalized into `url`, `provider`, and `id` props, with an optional `sourceUrl` preserved for reversible editing.\n\n### Upload\n- **Multiple upload methods**:\n  - Toolbar button with file picker\n  - Drag and drop from file system\n  - Paste from clipboard (images)\n  - URL embedding for external media\n- **Upload experience**:\n  - Real-time progress tracking\n  - Preview during upload\n  - Automatically converts the placeholder to the appropriate media element (image, video, audio, file) once the upload or embed is submitted\n  - Error handling\n  - File size validation\n  - Type validation\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add comprehensive media support is with the `MediaKit`, which includes pre-configured `ImagePlugin`, `VideoPlugin`, `AudioPlugin`, `FilePlugin`, `MediaEmbedPlugin`, `PlaceholderPlugin`, and `CaptionPlugin` with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"media-kit\" />\n\n- [`ImageElement`](/docs/plate/components/media-image-node): Renders image elements.\n- [`VideoElement`](/docs/plate/components/media-video-node): Renders video elements.\n- [`AudioElement`](/docs/plate/components/media-audio-node): Renders audio elements.\n- [`FileElement`](/docs/plate/components/media-file-node): Renders file elements.\n- [`MediaEmbedElement`](/docs/plate/components/media-embed-node): Renders embedded media.\n- [`PlaceholderElement`](/docs/plate/components/media-placeholder-node): Renders upload placeholders.\n- [`MediaUploadToast`](/docs/plate/components/media-upload-toast): Shows upload progress notifications.\n- [`MediaPreviewDialog`](/docs/plate/components/media-preview-dialog): Provides media preview functionality.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { MediaKit } from '@/components/editor/plugins/media-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...MediaKit,\n  ],\n});\n```\n\n### Add API Routes\n\n<ComponentInstallation name=\"media-uploadthing-api\" inline />\n\n### Environment Setup\n\nGet your secret key from [UploadThing](https://uploadthing.com/dashboard/settings) and add it to `.env`:\n\n```bash title=\".env\"\nUPLOADTHING_TOKEN=xxx\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/media\n```\n\n### Add Plugins\n\nInclude the media plugins in your Plate plugins array when creating the editor.\n\n```tsx\nimport {\n  AudioPlugin,\n  FilePlugin,\n  ImagePlugin,\n  MediaEmbedPlugin,\n  PlaceholderPlugin,\n  VideoPlugin,\n} from '@platejs/media/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ImagePlugin,\n    VideoPlugin,\n    AudioPlugin,\n    FilePlugin,\n    MediaEmbedPlugin,\n    PlaceholderPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the plugins with custom components and upload settings.\n\n```tsx\nimport {\n  AudioPlugin,\n  FilePlugin,\n  ImagePlugin,\n  MediaEmbedPlugin,\n  PlaceholderPlugin,\n  VideoPlugin,\n} from '@platejs/media/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\nimport { \n  AudioElement, \n  FileElement, \n  ImageElement, \n  MediaEmbedElement, \n  PlaceholderElement, \n  VideoElement \n} from '@/components/ui/media-nodes';\nimport { MediaUploadToast } from '@/components/ui/media-upload-toast';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ImagePlugin.withComponent(ImageElement),\n    VideoPlugin.withComponent(VideoElement),\n    AudioPlugin.withComponent(AudioElement),\n    FilePlugin.withComponent(FileElement),\n    MediaEmbedPlugin.withComponent(MediaEmbedElement),\n    PlaceholderPlugin.configure({\n      options: { disableEmptyPlaceholder: true },\n      render: { afterEditable: MediaUploadToast, node: PlaceholderElement },\n    }),\n  ],\n});\n```\n\n- `withComponent`: Assigns custom components to render each media type.\n- `options.disableEmptyPlaceholder`: Prevents showing placeholder when no file is uploading.\n- `render.afterEditable`: Renders upload progress toast outside the editor.\n\n<Callout type=\"info\">\n  **Note:** When serialized to Markdown or MDX, embeds persist the canonical `url`, `provider`, and `id`, plus an optional `sourceUrl` so edits remain reversible. Allowlisted provider snippets (e.g. YouTube, Tweet) are reduced to canonical URLs on paste. Raw `<script>` or custom embed chrome is out of scope — add your own rules if you need to preserve it.\n</Callout>\n\n### Caption Support\n\nTo enable media captions, add the [Caption Plugin](/docs/plate/caption):\n\n```tsx\nimport { CaptionPlugin } from '@platejs/caption/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    // ...media plugins,\n    CaptionPlugin.configure({\n      options: {\n        query: {\n          allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],\n        },\n      },\n    }),\n  ],\n});\n```\n\n### Custom Upload Implementation\n\nFor custom upload implementations, create an upload hook that matches this interface:\n\n```ts\ninterface UseUploadFileProps {\n  onUploadComplete?: (file: UploadedFile) => void;\n  onUploadError?: (error: unknown) => void;\n  headers?: Record<string, string>;\n  onUploadBegin?: (fileName: string) => void;\n  onUploadProgress?: (progress: { progress: number }) => void;\n  skipPolling?: boolean;\n}\n\ninterface UploadedFile {\n  key: string;    // Unique identifier\n  url: string;    // Public URL of the uploaded file\n  name: string;   // Original filename\n  size: number;   // File size in bytes\n  type: string;   // MIME type\n}\n```\n\nExample implementation with S3 presigned URLs:\n\n```ts\nexport function useUploadFile({ \n  onUploadComplete, \n  onUploadError, \n  onUploadProgress \n}: UseUploadFileProps = {}) {\n  const [uploadedFile, setUploadedFile] = useState<UploadedFile>();\n  const [uploadingFile, setUploadingFile] = useState<File>();\n  const [progress, setProgress] = useState(0);\n  const [isUploading, setIsUploading] = useState(false);\n\n  async function uploadFile(file: File) {\n    setIsUploading(true);\n    setUploadingFile(file);\n\n    try {\n      // Get presigned URL and final URL from your backend\n      const { presignedUrl, fileUrl, fileKey } = await fetch('/api/upload', {\n        method: 'POST',\n        body: JSON.stringify({\n          filename: file.name,\n          contentType: file.type,\n        }),\n      }).then(r => r.json());\n\n      // Upload to S3 using presigned URL\n      await axios.put(presignedUrl, file, {\n        headers: { 'Content-Type': file.type },\n        onUploadProgress: (progressEvent) => {\n          const progress = (progressEvent.loaded / progressEvent.total) * 100;\n          setProgress(progress);\n          onUploadProgress?.({ progress });\n        },\n      });\n\n      const uploadedFile = {\n        key: fileKey,\n        url: fileUrl,\n        name: file.name,\n        size: file.size,\n        type: file.type,\n      };\n\n      setUploadedFile(uploadedFile);\n      onUploadComplete?.(uploadedFile);\n      \n      return uploadedFile;\n    } catch (error) {\n      onUploadError?.(error);\n      throw error;\n    } finally {\n      setProgress(0);\n      setIsUploading(false);\n      setUploadingFile(undefined);\n    }\n  }\n\n  return {\n    isUploading,\n    progress,\n    uploadFile,\n    uploadedFile,\n    uploadingFile,\n  };\n}\n```\n\nThen integrate your custom upload hook with the media components:\n\n```tsx\nimport { useUploadFile } from '@/hooks/use-upload-file'; // Your custom hook\n\n// In your PlaceholderElement component\nexport function PlaceholderElement({ className, children, element, ...props }) {\n  const { uploadFile, isUploading, progress } = useUploadFile({\n    onUploadComplete: (uploadedFile) => {\n      // Replace placeholder with actual media element\n      const { url, type } = uploadedFile;\n      \n      // Transform placeholder to appropriate media type\n      editor.tf.replace.placeholder({\n        id: element.id,\n        url,\n        type: getMediaType(type), // image, video, audio, file\n      });\n    },\n    onUploadError: (error) => {\n      console.error('Upload failed:', error);\n      // Handle upload error, maybe show toast\n    },\n  });\n\n  // Use uploadFile when files are dropped or selected\n  // This integrates with the PlaceholderPlugin's file handling\n}\n```\n\n### Add Toolbar Button\n\nYou can add [`MediaToolbarButton`](/docs/plate/components/media-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to upload and insert media.\n\n### Insert Toolbar Button\n\nYou can add these items to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert media elements:\n\n```tsx\n{\n  icon: <ImageIcon />,\n  label: 'Image',\n  value: KEYS.img,\n}\n```\n\n</Steps>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"media-pro\" />\n\n## Plugins\n\n### `ImagePlugin`\n\nPlugin for void image elements.\n\n<API name=\"ImagePlugin\">\n<APIOptions type=\"ImagePluginOptions\">\n  <APIItem name=\"uploadImage\" type=\"(dataUrl: string | ArrayBuffer) => Promise<string | ArrayBuffer> | string | ArrayBuffer\" optional>\n    Function to upload image to a server. Receives:\n    - Data URL (string) from `FileReader.readAsDataURL`\n    - ArrayBuffer from clipboard data\n    Returns:\n    - URL string to uploaded image\n    - Original data URL/ArrayBuffer if no upload needed\n    - **Default:** Returns original input\n  </APIItem>\n  <APIItem name=\"disableUploadInsert\" type=\"boolean\" optional>\n    Disables file upload on data insertion.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"disableEmbedInsert\" type=\"boolean\" optional>\n    Disables URL embed on data insertion.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"isUrl\" type=\"function\" optional>\n    A function to check whether a text string is a URL.\n  </APIItem>\n  <APIItem name=\"transformUrl\" type=\"function\" optional>\n    A function to transform the URL.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `VideoPlugin`\n\nPlugin for void video elements. Extends `MediaPluginOptions`.\n\n### `AudioPlugin`\n\nPlugin for void audio elements. Extends `MediaPluginOptions`.\n\n### `FilePlugin`\n\nPlugin for void file elements. Extends `MediaPluginOptions`.\n\n### `MediaEmbedPlugin`\n\nPlugin for void media embed elements. Extends `MediaPluginOptions`.\n\n### `PlaceholderPlugin`\n\nPlugin for managing media placeholders during upload. Handles file uploads, drag & drop, and clipboard paste events.\n\n<API name=\"PlaceholderPlugin\">\n<APIOptions type=\"object\">\n  <APIItem name=\"uploadConfig\" type=\"Partial<Record<AllowedFileType, MediaItemConfig>>\" optional>\nConfiguration for different file types. Default configuration:\n```ts\n{\n  audio: {\n    maxFileCount: 1,\n    maxFileSize: '8MB',\n    mediaType: KEYS.audio,\n    minFileCount: 1,\n  },\n  blob: {\n    maxFileCount: 1,\n    maxFileSize: '8MB',\n    mediaType: KEYS.file,\n    minFileCount: 1,\n  },\n  image: {\n    maxFileCount: 3,\n    maxFileSize: '4MB',\n    mediaType: KEYS.image,\n    minFileCount: 1,\n  },\n  pdf: {\n    maxFileCount: 1,\n    maxFileSize: '4MB',\n    mediaType: KEYS.file,\n    minFileCount: 1,\n  },\n  text: {\n    maxFileCount: 1,\n    maxFileSize: '64KB',\n    mediaType: KEYS.file,\n    minFileCount: 1,\n  },\n  video: {\n    maxFileCount: 1,\n    maxFileSize: '16MB',\n    mediaType: KEYS.video,\n    minFileCount: 1,\n  },\n}\n```\nSupported file types: `'image' | 'video' | 'audio' | 'pdf' | 'text' | 'blob'`\n<APISubList>\n  <APISubListItem parent=\"uploadConfig\" name=\"mediaType\" type=\"MediaKeys\">\n    The media plugin keys that this config is for: `'audio' | 'file' | 'image' | 'video'`\n  </APISubListItem>\n  <APISubListItem parent=\"uploadConfig\" name=\"maxFileCount\" type=\"number\" optional>\n    The maximum number of files of this type that can be uploaded.\n  </APISubListItem>\n  <APISubListItem parent=\"uploadConfig\" name=\"maxFileSize\" type=\"FileSize\" optional>\n    The maximum file size for a file of this type. Format: `${1|2|4|8|16|32|64|128|256|512|1024}${B|KB|MB|GB}`\n  </APISubListItem>\n  <APISubListItem parent=\"uploadConfig\" name=\"minFileCount\" type=\"number\" optional>\n    The minimum number of files of this type that must be uploaded.\n  </APISubListItem>\n</APISubList>\n</APIItem>\n<APIItem name=\"disableEmptyPlaceholder\" type=\"boolean\" optional>\nDisable empty placeholder when no file is uploading.\n- **Default:** `false`\n</APIItem>\n<APIItem name=\"disableFileDrop\" type=\"boolean\" optional>\nDisable drag and drop file upload functionality.\n- **Default:** `false`\n</APIItem>\n<APIItem name=\"maxFileCount\" type=\"number\" optional>\nMaximum number of files that can be uploaded at once, if not specified by `uploadConfig`.\n- **Default:** `5`\n</APIItem>\n<APIItem name=\"multiple\" type=\"boolean\" optional>\nAllow multiple files of the same type to be uploaded.\n- **Default:** `true`\n</APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.placeholder.addUploadingFile`\n\nTracks a file that is currently being uploaded.\n\n<API name=\"addUploadingFile\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the placeholder element.\n  </APIItem>\n  <APIItem name=\"file\" type=\"File\">\n    The file being uploaded.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `api.placeholder.getUploadingFile`\n\nGets a file that is currently being uploaded.\n\n<API name=\"getUploadingFile\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the placeholder element.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"File | undefined\">\n    The uploading file if found, undefined otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.placeholder.removeUploadingFile`\n\nRemoves a file from the uploading tracking state after upload completes or fails.\n\n<API name=\"removeUploadingFile\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    Unique identifier for the placeholder element to remove.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Transforms\n\n### `tf.insert.media`\n\nInserts media files into the editor with upload placeholders.\n\n<API name=\"insertMedia\">\n<APIParameters>\n  <APIItem name=\"files\" type=\"FileList\">\n    Files to upload. Validates against configured file types and limits.\n  </APIItem>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Options for the insert nodes transform.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"at\" type=\"Path\" optional>\n    Location to insert the media. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"nextBlock\" type=\"boolean\" optional>\n    Whether to insert a new block after the media.\n    - **Default:** `true`\n  </APIItem>\n</APIOptions>\n</API>\n\nValidates files against configured limits (size, count, type), creates placeholder elements for each file, handles multiple file uploads sequentially, maintains upload history for undo/redo operations, and triggers error handling if validation fails.\n\nError codes:\n```ts\nenum UploadErrorCode {\n  INVALID_FILE_TYPE = 400,\n  TOO_MANY_FILES = 402,\n  INVALID_FILE_SIZE = 403,\n  TOO_LESS_FILES = 405,\n  TOO_LARGE = 413,\n}\n```\n\n### `tf.insert.imagePlaceholder`\n\nInserts a placeholder that converts to an image element when completed.\n\n### `tf.insert.videoPlaceholder`\n\nInserts a placeholder that converts to a video element when completed.\n\n### `tf.insert.audioPlaceholder`\n\nInserts a placeholder that converts to an audio element when completed.\n\n### `tf.insert.filePlaceholder`\n\nInserts a placeholder that converts to a file element when completed.\n\n### `tf.insert.image`\n\nInserts an image element into the editor.\n\n<API name=\"insertImage\">\n<APIParameters>\n  <APIItem name=\"url\" type=\"string | ArrayBuffer\">\n    The URL or ArrayBuffer of the image.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertNodesOptions\" optional>\n    Additional options for inserting the image element.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"InsertImageOptions\">\n  <APIItem name=\"nextBlock\" type=\"boolean\" optional>\n    If true, the image will be inserted in the next block.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `tf.insert.mediaEmbed`\n\nInserts a media embed element at the current selection.\n\n<API name=\"insertMediaEmbed\">\n<APIOptions type=\"InsertMediaEmbedOptions\">\n  <APIItem name=\"url\" type=\"string\" optional>\n    The URL of the media embed.\n    - **Default:** `''`\n  </APIItem>\n  <APIItem name=\"key\" type=\"string\" optional>\n    The key of the media embed element.\n    - **Default:** `KEYS.mediaEmbed`\n  </APIItem>\n  <APIItem name=\"insertNodesOptions\" type=\"InsertNodesOptions\" optional>\n    Additional options for inserting nodes.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useResizable`\n\nHandles the resizable properties of a media element.\n\n<API name=\"useResizable\">\n<APIState>\n  <APIItem name=\"align\" type=\"'left' | 'center' | 'right'\">\n    The alignment of the content within the resizable element.\n  </APIItem>\n  <APIItem name=\"minWidth\" type=\"ResizeLength\">\n    The minimum width that the resizable element can be adjusted to.\n  </APIItem>\n  <APIItem name=\"maxWidth\" type=\"ResizeLength\">\n    The maximum width that the resizable element can be adjusted to.\n  </APIItem>\n  <APIItem name=\"setNodeWidth\" type=\"(width: number | string) => void\">\n    Function to set the width of the node when resizing.\n  </APIItem>\n  <APIItem name=\"setWidth\" type=\"(width: number | string) => void\">\n    Function to set the width of the resizable element directly.\n  </APIItem>\n  <APIItem name=\"width\" type=\"Property.Width<string | number> | undefined\">\n    The current width of the resizable element (percentage, 'auto', or pixels).\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"wrapperRef\" type=\"React.RefObject<HTMLDivElement>\">\n    React reference to the outermost wrapper div.\n  </APIItem>\n  <APIItem name=\"wrapperProps.style\" type=\"CSSProperties\">\n    CSS styles for the wrapper div.\n  </APIItem>\n  <APIItem name=\"props.style\" type=\"CSSProperties\">\n    CSS styles for the resizable element.\n  </APIItem>\n  <APIItem name=\"context.onResize\" type=\"() => void\">\n    Callback function called when the element is resized.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useMediaState`\n\nA state hook for a media element.\n\n<API name=\"useMediaState\">\n<APIParameters>\n  <APIItem name=\"options.urlParsers\" type=\"EmbedUrlParser[]\" optional>\n    Array of URL parsers to parse the media element URL.\n  \n    - **`EmbedUrlParser`:** `(url: string) => EmbedUrlData | undefined`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"align\" type=\"string\">\n    The alignment of the media element.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"boolean\">\n    Whether the media element is currently focused.\n  </APIItem>\n  <APIItem name=\"selected\" type=\"boolean\">\n    Whether the media element is currently selected.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\">\n    Whether the editor is in read-only mode.\n  </APIItem>\n  <APIItem name=\"embed\" type=\"EmbedUrlData\">\n    The parsed embed data of the media element.\n  </APIItem>\n  <APIItem name=\"isTweet\" type=\"boolean\">\n    Whether the media element is a tweet.\n  </APIItem>\n  <APIItem name=\"isVideo\" type=\"boolean\">\n    Whether the media element is a video.\n  </APIItem>\n  <APIItem name=\"isYoutube\" type=\"boolean\">\n    Whether the media element is a YouTube video.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useMediaToolbarButton`\n\nA behavior hook for a media toolbar button.\n\n<API name=\"useMediaToolbarButton\">\n<APIParameters>\n  <APIItem name=\"options.nodeType\" type=\"string\" optional>\n    The type of media node to insert.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props.onClick\" type=\"() => void\">\n    Callback function that inserts the media node and focuses the editor.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useFloatingMediaEditButton`\n\nHandles the floating media edit button.\n\n<API name=\"useFloatingMediaEditButton\">\n<APIReturns type=\"object\">\n  <APIItem name=\"props.onClick\" type=\"() => void\">\n    Callback function to handle the button click.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useFloatingMediaUrlInput`\n\nHandles the URL input field for media elements.\n\n<API name=\"useFloatingMediaUrlInput\">\n<APIProps>\n  <APIItem name=\"defaultValue\" type=\"string\">\n    The default value for the URL input field.\n  </APIItem>\n</APIProps>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props.onChange\" type=\"() => void\">\n    Callback function to handle input changes.\n  </APIItem>\n  <APIItem name=\"props.autoFocus\" type=\"boolean\">\n    Whether the URL input field should be focused on mount.\n  </APIItem>\n  <APIItem name=\"props.defaultValue\" type=\"string\">\n    The default value for the URL input field.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useImage`\n\nA hook for image elements.\n\n<API name=\"useImage\">\n<APIReturns type=\"object\">\n  <APIItem name=\"props.src\" type=\"string\">\n    The URL of the media element.\n  </APIItem>\n  <APIItem name=\"props.alt\" type=\"string\">\n    The caption string for the image.\n  </APIItem>\n  <APIItem name=\"props.draggable\" type=\"boolean\">\n    Whether the image is draggable.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Utilities\n\n### `parseMediaUrl`\n\nParses a media URL for plugin-specific handling.\n\n<API name=\"parseMediaUrl\">\n<APIParameters>\n  <APIItem name=\"options.pluginKey\" type=\"string\">\n    The key of the media plugin.\n  </APIItem>\n  <APIItem name=\"options.url\" type=\"string\" optional>\n    The URL of the media to be parsed.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `parseVideoUrl`\n\nParses a video URL and extracts the video ID and provider-specific embed URL.\n\n<API name=\"parseVideoUrl\">\n<APIParameters>\n  <APIItem name=\"url\" type=\"string\">\n    The video URL to parse.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"EmbedUrlData | undefined\">\n  An object containing the video ID and provider if parsing is successful, undefined if URL is invalid or unsupported.\n</APIReturns>\n</API>\n\n### `parseTwitterUrl`\n\nParses a Twitter URL and extracts the tweet ID.\n\n<API name=\"parseTwitterUrl\">\n<APIParameters>\n  <APIItem name=\"url\" type=\"string\">\n    The Twitter URL.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"EmbedUrlData | undefined\">\n    An object containing the tweet ID and provider if the parsing is successful.\n    Returns undefined if the URL is not valid or does not match any supported video providers.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `parseIframeUrl`\n\nParses the URL of an iframe embed.\n\n<API name=\"parseIframeUrl\">\n<APIParameters>\n  <APIItem name=\"url\" type=\"string\">\n    The URL or embed code of the iframe.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `isImageUrl`\n\nChecks if a URL is a valid image URL.\n\n<API name=\"isImageUrl\">\n<APIParameters>\n  <APIItem name=\"url\" type=\"string\">\n    The URL to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  Whether the URL is a valid image URL.\n</APIReturns>\n</API>\n\n### `submitFloatingMedia`\n\nSubmits a floating media element.\n\n<API name=\"submitFloatingMedia\">\n<APIParameters>\n  <APIItem name=\"options.element\" type=\"TMediaElement\">\n    The floating media element to be submitted.\n  </APIItem>\n  <APIItem name=\"options.pluginKey\" type=\"string\" optional>\n    The key of the media plugin.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withImageUpload`\n\nEnhances the editor instance with image upload functionality.\n\n<API name=\"withImageUpload\">\n<APIParameters>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n    The plate plugin.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withImageEmbed`\n\nEnhances the editor instance with image-related functionality.\n\n<API name=\"withImageEmbed\">\n<APIParameters>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n    The plate plugin.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Types\n\n### `TMediaElement`\n\n```tsx\nexport interface TMediaElement extends TElement {\n  url: string;\n  id?: string;\n  align?: 'center' | 'left' | 'right';\n  isUpload?: boolean;\n  name?: string;\n  placeholderId?: string;\n}\n```\n\n### `TPlaceholderElement`\n\n```tsx\nexport interface TPlaceholderElement extends TElement {\n  mediaType: string;\n}\n```\n\n### `EmbedUrlData`\n\n```tsx\nexport interface EmbedUrlData {\n  url?: string;\n  provider?: string;\n  id?: string;\n  component?: React.FC<EmbedUrlData>;\n}\n```\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/media.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "mention-docs",
      "title": "Mention",
      "description": "Inline markable void mentions backed by trigger combobox input.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/mention.mdx",
          "content": "---\ntitle: Mention\ndescription: Inline markable void mentions backed by trigger combobox input.\ndocs:\n  - route: /docs/combobox\n    title: Combobox\n  - route: /docs/components/mention-node\n    title: Mention Nodes\n  - route: /docs/components/inline-combobox\n    title: Inline Combobox\n---\n\nMention turns trigger text such as `@` into an inline combobox input and inserts a markable void `mention` node when the user selects an item. The package owns trigger detection, input node creation, mention insertion, selection movement, and Markdown mention serialization. The registry owns the demo item list and the inline combobox UI.\n\n<ComponentPreview name=\"mention-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Inline void `mention` nodes with `value` and optional `key`.\n- Inline void `mention_input` nodes created by trigger text.\n- Trigger combobox support from `@platejs/combobox`.\n- Configurable trigger strings, regexes, and trigger queries.\n- Markable void rendering so bold, italic, and underline can style a mention.\n- Optional trailing space after selected mention items.\n- Markdown format through `[display text](mention:id)` plus bare `@name` deserialization.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`MentionKit` installs `MentionPlugin`, `MentionInputPlugin`, the registry mention nodes, and a trigger rule that allows `@` at the start of a line, after whitespace, or after quotes.\n\n<ComponentSource name=\"mention-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { MentionKit } from '@/components/editor/plugins/mention-kit';\n\nexport const editor = createPlateEditor({\n  plugins: MentionKit,\n});\n```\n\n### Render Mentions\n\n`mention-node` renders both the selected mention and the temporary combobox input. The demo data lives in that registry UI file, so replace it with your app users, pages, or records.\n\n<ComponentSource name=\"mention-node\" />\n\n### Add Static Rendering\n\n`mention-base-kit` uses `BaseMentionPlugin` with the static mention node for read-only output.\n\n<ComponentSource name=\"mention-base-kit\" />\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/mention` | Package | Exports `BaseMentionPlugin`, `BaseMentionInputPlugin`, and `getMentionOnSelectItem`. |\n| `@platejs/mention/react` | Package | Exports `MentionPlugin` and `MentionInputPlugin`. |\n| `@platejs/combobox` | Package | Provides `withTriggerCombobox`, trigger options, and combobox input hooks. |\n| `mention-kit` | Registry | Adds mention plugins with editable mention and input components. |\n| `mention-base-kit` | Registry | Adds `BaseMentionPlugin.withComponent(MentionElementStatic)`. |\n| `mention-node` | Registry UI | Renders mention atoms, the inline combobox input, and demo items. |\n| `inline-combobox` | Registry UI | Renders the Ariakit-backed popover, input, groups, items, and empty state. |\n| `@platejs/markdown` | Package | Serializes mentions as `mention:` links and deserializes mention links or bare mentions. |\n\n## Manual Setup\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/mention\n```\n\n### Add Plugins\n\nConfigure the mention plugin with the trigger behavior and render both node types.\n\n```tsx\nimport { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport {\n  MentionElement,\n  MentionInputElement,\n} from '@/components/ui/mention-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    MentionPlugin.configure({\n      options: {\n        triggerPreviousCharPattern: /^$|^[\\s\"']$/,\n      },\n    }).withComponent(MentionElement),\n    MentionInputPlugin.withComponent(MentionInputElement),\n  ],\n});\n```\n\n### Select An Item\n\nUse `getMentionOnSelectItem` from your combobox item renderer. It inserts the mention, moves the cursor after it, and inserts a trailing space only when `insertSpaceAfterMention` is enabled and the mention lands at the end of the block.\n\n```tsx\nimport { getMentionOnSelectItem } from '@platejs/mention';\n\nconst onSelectItem = getMentionOnSelectItem();\n\n<InlineComboboxItem\n  value={item.text}\n  onClick={() => onSelectItem(editor, item, search)}\n>\n  {item.text}\n</InlineComboboxItem>;\n```\n\n</Steps>\n\n## Value Shape\n\n`BaseMentionPlugin` uses `KEYS.mention`, which resolves to the `mention` node type. The input plugin uses `KEYS.mentionInput`, which resolves to `mention_input`.\n\n```tsx\nconst value = [\n  {\n    children: [\n      { text: 'Assigned to ' },\n      {\n        children: [{ text: '' }],\n        key: 'user_123',\n        type: 'mention',\n        value: 'Jane Smith',\n      },\n      { text: '.' },\n    ],\n    type: 'p',\n  },\n];\n```\n\n| Field | Type | Notes |\n|-------|------|-------|\n| `type` | `'mention'` | Inline void mention node. |\n| `value` | `string` | Display text rendered by the registry node. |\n| `key` | `unknown` | Optional stable id used by selection handlers and Markdown serialization. |\n| `children` | `[{ text: '' }]` | Empty child required for Slate inline void nodes. |\n\nThe mention node is `isMarkableVoid`, so marks on its empty child can style the rendered mention.\n\n## Trigger Flow\n\n`withTriggerCombobox` overrides `insertText`. It creates a combobox input only when every gate passes.\n\n| Gate | Source |\n|------|--------|\n| Inserted text matches `trigger` | `string`, `string[]`, or `RegExp`. |\n| Insert is not using `options.at` | Programmatic text insertion bypasses the trigger. |\n| Editor has a selection | No selection means no inline input target. |\n| `triggerQuery(editor)` returns true | Optional app veto for custom contexts. |\n| Previous character matches `triggerPreviousCharPattern` | Defaults to `/^\\s?$/`; registry kit uses `/^$\\|^[\\s\"']$/`. |\n\nThe default `createComboboxInput` creates:\n\n```tsx\n{\n  children: [{ text: '' }],\n  trigger: '@',\n  type: KEYS.mentionInput,\n}\n```\n\nIf `editor.meta.userId` exists, the combobox input stores that `userId` so only the creator sees the transient input in collaborative editors.\n\n## Markdown\n\n`@platejs/markdown` serializes mentions as link-style `mention:` URLs. It uses `key` for the URL when present and `value` for the visible text.\n\n```md\nHello [Jane Smith](mention:user_123).\n```\n\nDeserialization supports link-style mentions and bare `@alice` text. Normal links such as `[@docs](/docs/plate/mention)` stay links.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseMentionPlugin` | `@platejs/mention` | Headless inline markable void mention plugin with trigger-combobox behavior. |\n| `BaseMentionInputPlugin` | `@platejs/mention` | Inline void input node inserted while the combobox is active. |\n| `MentionPlugin` | `@platejs/mention/react` | React mention plugin. |\n| `MentionInputPlugin` | `@platejs/mention/react` | React mention input plugin. |\n| `editor.tf.insert.mention({ key, value })` | `BaseMentionPlugin` transform | Inserts the mention node at the current selection. |\n| `getMentionOnSelectItem({ key? })` | `@platejs/mention` | Returns an item handler for mention combobox selection. |\n| `withTriggerCombobox` | `@platejs/combobox` | Creates temporary combobox input nodes from trigger text. |\n| `useComboboxInput` | `@platejs/combobox/react` | Handles focus, cancellation, arrow/backspace/escape behavior, and undo/redo forwarding for custom input UI. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/mention.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "table-docs",
      "title": "Table",
      "description": "Documentation for Table",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/table.mdx",
          "content": "---\ntitle: Table\ndocs:\n  - route: https://pro.platejs.org/docs/examples/table\n    title: Plus\n  - route: /docs/components/table-toolbar-button\n    title: Table Toolbar Button\n  - route: /docs/components/table-node\n    title: Table Nodes\n---\n\n<ComponentPreview name=\"table-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Enables creation and editing of tables with advanced behaviors.\n- Arrow navigation (up/down).\n- Grid cell selection.\n- Cell selection expansion with `Shift+Arrow` keys.\n- Copying and pasting cells.\n- Row drag-and-drop reordering\n- Row selection via drag handle\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add table functionality is with the `TableKit`, which includes pre-configured `TablePlugin`, `TableRowPlugin`, `TableCellPlugin`, and `TableCellHeaderPlugin` with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"table-kit\" />\n\n- [`TableElement`](/docs/plate/components/table-node): Renders table containers.\n- [`TableRowElement`](/docs/plate/components/table-node): Renders table rows.\n- [`TableCellElement`](/docs/plate/components/table-node): Renders table cells.\n- [`TableCellHeaderElement`](/docs/plate/components/table-node): Renders table header cells.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { TableKit } from '@/components/editor/plugins/table-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...TableKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/table\n```\n\n### Add Plugin\n\nInclude `TablePlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { TablePlugin } from '@platejs/table/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    TablePlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the table plugins with custom components and options.\n\n```tsx\nimport {\n  TableCellHeaderPlugin,\n  TableCellPlugin,\n  TablePlugin,\n  TableRowPlugin,\n} from '@platejs/table/react';\nimport { createPlateEditor } from 'platejs/react';\nimport {\n  TableCellElement,\n  TableCellHeaderElement,\n  TableElement,\n  TableRowElement,\n} from '@/components/ui/table-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    TablePlugin.configure({\n      node: { component: TableElement },\n      options: {\n        initialTableWidth: 600,\n        disableMerge: false,\n        minColumnWidth: 48,\n      },\n    }),\n    TableRowPlugin.withComponent(TableRowElement),\n    TableCellPlugin.withComponent(TableCellElement),\n    TableCellHeaderPlugin.withComponent(TableCellHeaderElement),\n  ],\n});\n```\n\n- `node.component`: Assigns [`TableElement`](/docs/plate/components/table-node) to render table containers.\n- `withComponent`: Assigns components for table rows, cells, and header cells.\n- `options.initialTableWidth`: Sets the initial width for new tables.\n- `options.disableMerge`: Disables cell merging functionality.\n- `options.minColumnWidth`: Sets the minimum width for table columns.\n\n### Add Toolbar Button\n\nYou can add [`TableToolbarButton`](/docs/plate/components/table-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to insert tables.\n\n### Insert Toolbar Button\n\nYou can add this item to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert table elements:\n\n```tsx\n{\n  icon: <TableIcon />,\n  label: 'Table',\n  value: KEYS.table,\n}\n```\n\n### Disable Merging Example\n\n<ComponentPreview name=\"table-nomerge-demo\" />\n\n</Steps>\n\n## Plugins\n\n### TablePlugin\n\n<API name=\"TablePlugin\">\n<APIOptions>\n  <APIItem name=\"disableMerge\" type=\"boolean\" optional>\n    Disables the merging behavior of cells.\n  </APIItem>\n  <APIItem name=\"disableExpandOnInsert\" type=\"boolean\" optional>\n    Disables the expansion of the table when inserting cells.\n  </APIItem>\n  <APIItem name=\"disableMarginLeft\" type=\"boolean\" optional>\n    Disables the left resizer of the first column in the table.\n  </APIItem>\n  <APIItem name=\"enableUnsetSingleColSize\" type=\"boolean\" optional>\n    Disables unsetting the width of the first column when the table has only one column. Set this to `true` if you want to resize the table width when there's only one column. Leave it `false` if you have a full-width table.\n  </APIItem>\n  <APIItem name=\"initialTableWidth\" type=\"number\" optional>\n    If defined, a normalizer will set each undefined table `colSizes` to this value divided by the number of columns. Note that merged cells are not yet supported.\n  </APIItem>\n  <APIItem name=\"minColumnWidth\" type=\"number\" optional>\n    The minimum width of a column in the table.\n    - **Default:** `48`\n  </APIItem>\n</APIOptions>\n</API>\n\n### TableRowPlugin\n\nPlugin for table rows.\n\n### TableCellPlugin\n\nPlugin for table cells.\n\n### TableCellHeaderPlugin\n\nPlugin for table header cells.\n\n## API\n\n### editor.api.create.table\n\n<API name=\"create.table\">\n<APIParameters>\n<APIItem name=\"options\" type=\"GetEmptyTableNodeOptions\" optional>\nExtends `GetEmptyRowNodeOptions`.\n</APIItem>\n</APIParameters>\n\n<APIOptions>\n<APIItem name=\"header\" type=\"boolean\" optional>\nSpecify `true` if the table has a header row.\n</APIItem>\n<APIItem name=\"rowCount\" type=\"number\" optional>\nThe number of rows in the table.\n\n- **Default:** `0`\n\n</APIItem>\n<APIItem name=\"colCount\" type=\"number | undefined\" optional>\nThe number of columns in the table.\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n<APIItem type=\"TElement\">\n\nThe table node.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### editor.api.create.tableCell\n\nCreates an empty cell node for a table.\n\n<API name=\"create.tableCell\">\n<APIOptions>\n<APIItem name=\"header\" type=\"boolean\" optional>\nSpecify `true` if the cell is a header cell.\n</APIItem>\n<APIItem name=\"row\" type=\"TTableRowElement\" optional>\nThe row element. If `header` is not specified, it will determine if the cell is a header cell based on the row's children.\n</APIItem>\n<APIItem name=\"children\" type=\"Descendant[]\" optional>\nThe children of the new cell node.\n\n- **Default:** `[editor.api.create.block()]`\n\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n<APIItem type=\"TElement\">\n\nThe cell node.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### editor.api.create.tableRow\n\nCreates an empty row node with the specified number of columns.\n\n<API name=\"create.tableRow\">\n<APIOptions>\n<APIItem name=\"header\" type=\"boolean\" optional>\nSpecify `true` if the row is a header row.\n</APIItem>\n<APIItem name=\"colCount\" type=\"number\" optional>\nThe number of columns in the row.\n\n- **Default:** `1`\n\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n<APIItem type=\"TElement\">\n\nThe row node.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### editor.api.table.getCellBorders\n\nGets the border styles for a table cell, handling special cases for first row and first column cells.\n\n<API name=\"getCellBorders\">\n<APIOptions>\n<APIItem name=\"element\" type=\"TTableCellElement\">\nThe table cell element to get the border styles for.\n</APIItem>\n<APIItem name=\"defaultBorder\" type=\"Required<TTableCellElementBorder>\" optional>\nThe default border style to use when cell borders are not defined.\n<APISubList>\n<APISubListItem parent=\"defaultBorder\" name=\"color\" type=\"string\">\n  The border color.\n  - **Default:** `'rgb(209 213 219)'`\n</APISubListItem>\n<APISubListItem parent=\"defaultBorder\" name=\"size\" type=\"number\">\n  The border size.\n  - **Default:** `1`\n</APISubListItem>\n<APISubListItem parent=\"defaultBorder\" name=\"style\" type=\"string\">\n  The border style.\n  - **Default:** `'solid'`\n</APISubListItem>\n</APISubList>\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem type=\"BorderStylesDefault\">\n    An object containing:\n    <APISubList>\n      <APISubListItem parent=\"return\" name=\"bottom\" type=\"Required<TTableCellElementBorder>\">\n        The bottom border style.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"right\" type=\"Required<TTableCellElementBorder>\">\n        The right border style.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"left\" type=\"Required<TTableCellElementBorder>\" optional>\n        The left border style. Only present for cells in the first column.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"top\" type=\"Required<TTableCellElementBorder>\" optional>\n        The top border style. Only present for cells in the first row.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### editor.api.table.getCellChildren\n\nGets the children of a table cell.\n\n<API name=\"getCellChildren\">\n<APIParameters>\n<APIItem name=\"cell\" type=\"TElement\">\nThe table cell element.\n</APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"Descendant[]\">\n\nThe children of the table cell.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### editor.api.table.getCellSize\n\nGets the width and minimum height of a table cell, taking into account column spans and column sizes.\n\n<API name=\"getCellSize\">\n<APIOptions>\n<APIItem name=\"element\" type=\"TTableCellElement\">\nThe table cell element to get the size for.\n</APIItem>\n<APIItem name=\"colSizes\" type=\"number[]\" optional>\nOptional array of column sizes. If not provided, will use the table's overridden column sizes.\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n<APIItem name=\"width\" type=\"number\">\nThe total width of the cell, calculated by summing the widths of all columns it spans.\n</APIItem>\n<APIItem name=\"minHeight\" type=\"number | undefined\">\nThe minimum height of the cell, derived from the row's size property.\n</APIItem>\n</APIReturns>\n</API>\n\n### editor.api.table.getColSpan\n\nGets the column span of a table cell.\n\n<API name=\"getColSpan\">\n<APIParameters>\n<APIItem name=\"element\" type=\"TTableCellElement\">\nThe table cell element to get the column span for.\n</APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"number\">\n    The number of columns this cell spans.\n    - **Default:** `1`\n  </APIItem>\n</APIReturns>\n</API>  \n\n### editor.api.table.getRowSpan\n\nGets the row span of a table cell.\n\n<API name=\"getRowSpan\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"TTableCellElement\">\n    The table cell element to get the row span for.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"number\">\n    The number of rows this cell spans.\n    - **Default:** `1`\n  </APIItem>\n</APIReturns>\n</API>\n\n### getCellType\n\nGet the plugin cell types.\n\n<API name=\"getCellType\">\n<APIReturns>\n\n<APIItem type=\"string[]\">\n  An array of element types for table cells (td and th) in the editor.\n</APIItem>\n\n</APIReturns>\n</API>\n\n### getNextTableCell\n\nGets the next cell in the table.\n  \n<API name=\"getNextTableCell\">\n<APIParameters>\n  <APIItem name=\"currentCell\" type=\"NodeEntry\">\n    The entry of the current cell.\n  </APIItem>\n  <APIItem name=\"currentPath\" type=\"Path\">\n    The path of the current cell.\n  </APIItem>\n  <APIItem name=\"currentRow\" type=\"NodeEntry\">\n    The entry of the current row.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"NodeEntry | undefined\">\n    The node entry of the cell in the next row, or `undefined` if the current\n    row is the last row.\n  </APIItem>\n</APIReturns>\n</API>\n\n### getPreviousTableCell\n\nGets the previous cell in the table.\n\n<API name=\"getPreviousTableCell\">\n<APIParameters>\n  <APIItem name=\"currentCell\" type=\"NodeEntry\">\n    The entry of the current cell.\n  </APIItem>\n  <APIItem name=\"currentPath\" type=\"Path\">\n    The path of the current cell.\n  </APIItem>\n  <APIItem name=\"currentRow\" type=\"NodeEntry\">\n    The entry of the current row.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"NodeEntry | undefined\">\n    The node entry of the cell in the previous row, or `undefined` if the\n    current row is the first row.\n  </APIItem>\n</APIReturns>\n</API>\n\n### getTableColumnCount\n\nGets the number of columns in a table.\n\n<API name=\"getTableColumnCount\">\n<APIParameters>\n  <APIItem name=\"tableNode\" type=\"TElement\">\n    The table node for which to retrieve the column count.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"number\">\n\nThe number of columns in the table.\n\n</APIItem>\n</APIReturns>\n</API>\n### getTableColumnIndex\n\nGets the column index of a cell node within a table.\n\n<API name=\"getTableColumnIndex\">\n<APIParameters>\n  <APIItem name=\"cellNode\" type=\"TElement\">\n    The cell node for which to retrieve the column index.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"number\">\n\nThe column index of the cell node.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### getTableEntries\n\nGets the table, row, and cell node entries based on the current selection or a specified location.\n\n<API name=\"getTableEntries\">\n<APIOptions>\n<APIItem name=\"at\" type=\"Location | null\" optional>\nThe location where the table cell is located.\n\n- **Default:** `editor.selection`\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem name=\"table\" type=\"NodeEntry | undefined\">\n    The table node entry.\n  </APIItem>\n  <APIItem name=\"row\" type=\"NodeEntry | undefined\">\n    The row node entry.\n  </APIItem>\n  <APIItem name=\"cell\" type=\"NodeEntry | undefined\">\n    The cell node entry.\n  </APIItem>\n</APIReturns>\n</API>\n\n\n### getTableGridAbove\n\nGets the sub table above the anchor and focus positions based on the specified format (tables or cells).\n\n<API name=\"getTableGridAbove\">\n<APIOptions>\n<APIItem name=\"format\" type=\"string\" optional>\nThe format of the sub table to retrieve.\n\n- **Default:** `'table'`\n\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem type=\"ElementEntry[]\">The sub table entries.</APIItem>\n</APIReturns>\n</API>\n### getTableGridByRange\n\nGets the sub table between two cell paths within a given range.\n\n<API name=\"getTableGridByRange\">\n<APIOptions>\n<APIItem name=\"at\" type=\"TRange\">\nThe range specifying the start and end cell paths.\n</APIItem>\n<APIItem name=\"format\" type=\"'table' | 'cell'\" optional>\nThe format of the output.\n\n- **Default:** `'table'`\n\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem type=\"ElementEntry[]\">The sub table entries.</APIItem>\n</APIReturns>\n</API>\n\n### getTableRowIndex\n\nGets the row index of a cell node within a table.\n\n<API name=\"getTableRowIndex\">\n<APIParameters>\n  <APIItem name=\"cellNode\" type=\"TElement\">\n    The cell node for which to retrieve the row index.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"number\">\n\nThe row index of the cell node.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### getTopTableCell\n\nGets the cell above the current cell in the table.\n\n<API name=\"getTopTableCell\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"Path\" optional>\n    The path to the current cell. If not provided, the function will search for\n    the current cell in the editor.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"ElementEntry | undefined\">\n\nThe cell node entry.\n\n</APIItem>\n</APIReturns>\n</API>\n### isTableBorderHidden\n\nChecks if the border of a table cell or the table itself is hidden based on the specified border direction.\n\n<API name=\"isTableBorderHidden\">\n<APIParameters>\n  <APIItem name=\"border\" type=\"'top' | 'left' | 'bottom' | 'right'\">\n    The border direction to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n<APIItem type=\"boolean\">\n\n`true` if the border is hidden, `false` otherwise.\n\n</APIItem>\n</APIReturns>\n</API>\n\n## Editing Behavior\n\nTable editing behavior is split across table transforms and plugin extensions. Use `tf.table.merge` and `tf.table.split` for merge controls, `tf.moveSelectionFromCell` for keyboard navigation out of cells, and the table extension hooks below for deletion, paste, normalization, and fragment handling.\n\n## Transforms\n\n### `tf.insert.table`\n\nInserts a table at the current selection if there is no existing table in the editor. Selects the start of the inserted table.\n\n<API name=\"insert.table\">\n<APIParameters>\n<APIItem name=\"getEmptyTableNodeOptions\" type=\"GetEmptyTableNodeOptions\" optional>\nExtends `GetEmptyRowNodeOptions`.\n<APISubList>\n<APISubListItem parent=\"getEmptyTableNodeOptions\" name=\"rowCount\" type=\"number\" optional>\nThe number of rows in the table.\n\n- **Default:** `2`\n\n</APISubListItem>\n<APISubListItem parent=\"getEmptyTableNodeOptions\" name=\"colCount\" type=\"number\" optional>\nThe number of columns in the table.\n\n- **Default:** `2`\n\n</APISubListItem>\n<APISubListItem parent=\"getEmptyTableNodeOptions\" name=\"header\" type=\"boolean\" optional>\nIf `true`, the first row of the table will be treated as a header row.\n</APISubListItem>\n</APISubList>\n\n</APIItem>\n\n<APIItem name=\"options\" type=\"InsertNodesOptions\" optional>\nThe options for inserting the table nodes.\n</APIItem>\n</APIParameters>\n</API>\n\n### `tf.insert.tableColumn`\n\nInserts a column into the table at the current selection or a specified cell path.\n\n<API name=\"insert.tableColumn\">\n<APIOptions>\n<APIItem name=\"at\" type=\"Path\" optional>\nThe exact path of the cell to insert the column at. This overrules the\n`fromCell` option.\n</APIItem>\n<APIItem name=\"before\" type=\"boolean\" optional>\nIf true, insert the column before the current column instead of after.\n</APIItem>\n<APIItem name=\"fromCell\" type=\"Path\" optional>\nThe path of the cell to insert the column from.\n</APIItem>\n<APIItem name=\"header\" type=\"boolean\" optional>\nIf true, the inserted column will be treated as a header column.\n</APIItem>\n<APIItem name=\"select\" type=\"boolean\" optional>\nIf true, the inserted column will be selected after insertion.\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.insert.tableRow`\n\nInserts a row into the table at the current selection or a specified row path.\n\n<API name=\"insert.tableRow\">\n<APIOptions>\n<APIItem name=\"at\" type=\"Path\" optional>\nExact path of the row to insert the column at. Pass the table path to\ninsert at the end of the table. Will overrule `fromRow`.\n</APIItem>\n<APIItem name=\"before\" type=\"boolean\" optional>\nIf true, insert the row before the current row instead of after.\n</APIItem>\n<APIItem name=\"fromRow\" type=\"Path\" optional>\nThe path of the row to insert the new row from.\n</APIItem>\n<APIItem name=\"header\" type=\"boolean\" optional>\nIf true, the inserted row will be treated as a header row.\n</APIItem>\n<APIItem name=\"select\" type=\"boolean\" optional>\nIf true, the inserted row will be selected after insertion.\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.remove.tableColumn`\n\nDeletes the column containing the selected cell in a table.\n\n### `tf.remove.tableRow`\n\nDeletes the row containing the selected cell in a table.\n\n### `tf.remove.table`\n\nDeletes the entire table.\n\n### `tf.table.merge`\n\nMerges multiple selected cells into one.\n\nThe merged cell will:\n- Have a colSpan equal to the number of columns spanned by the selected cells\n- Have a rowSpan equal to the number of rows spanned by the selected cells\n- Contain the combined content of all merged cells (non-empty cells only)\n- Inherit the header status from the first selected cell\n\n### `tf.table.split`\n\nSplits a merged cell back into individual cells.\n\nThe split operation will:\n- Create new cells for each column and row that was spanned\n- Copy the header status from the original merged cell\n- Place the original cell's content in the first cell\n- Create empty cells for the remaining spaces\n\n### `tf.moveSelectionFromCell`\n\nMoves the selection by cell unit within a table.\n\n<API name=\"moveSelectionFromCell\">\n<APIOptions>\n<APIItem name=\"at\" type=\"Location\" optional>\nThe location to move the selection from.\n</APIItem>\n<APIItem name=\"reverse\" type=\"boolean\" optional>\n        Set to `true` to move the selection to the cell above, `false` to move\nthe selection to the cell below.\n</APIItem>\n<APIItem name=\"edge\" type=\"'top' | 'left' | 'right' | 'bottom'\" optional>\nThe edge to expand the cell selection to.\n</APIItem>\n<APIItem name=\"fromOneCell\" type=\"boolean\" optional>\nSet to `true` to move the selection from only one selected cell.\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.setBorderSize`\n\nSets the size of the specified border in a table cell.\n\n<API name=\"setBorderSize\">\n<APIParameters>\n<APIItem name=\"size\" type=\"number\">\nThe size of the border.\n</APIItem>\n<APIItem name=\"options\" type=\"object\" optional>\nOptions for setting the border size.\n</APIItem>\n</APIParameters>\n\n<APIOptions>\n<APIItem name=\"at\" type=\"Location\" optional>\nThe location of the cell to set the border size.\n</APIItem>\n<APIItem name=\"border\" type=\"'all' | 'top' | 'left' | 'bottom' | 'right'\" optional>\nThe border direction to set the size.\n\n- **Default:** `'all'`\n\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.setTableColSize`\n\nSets the width of a specific column in a table.\n\n<API name=\"setTableColSize\">\n<APIOptions>\n<APIItem name=\"colIndex\" type=\"number\" optional>\nThe index of the column to set the width.\n</APIItem>\n<APIItem name=\"width\" type=\"number\" optional>\nThe desired width of the column.\n</APIItem>\n<APIItem name=\"getAboveNodeOptions\" type=\"EditorAboveOptions\" optional>\nAdditional options for finding the table node.\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.setTableMarginLeft`\n\nSets the margin left of a table.\n\n<API name=\"setTableMarginLeft\">\n<APIOptions>\n<APIItem name=\"marginLeft\" type=\"number\">\nAn object containing the desired margin left value.\n</APIItem>\n<APIItem name=\"getAboveNodeOptions\" type=\"EditorAboveOptions\" optional>\nAdditional options for finding the table node.\n</APIItem>\n</APIOptions>\n</API>\n\n### `tf.setTableRowSize`\n\nSets the size (height) of a table row.\n\n<API name=\"setTableRowSize\">\n<APIOptions>\n<APIItem name=\"rowIndex\" type=\"number\">\nThe index of the row to set the size.\n</APIItem>\n<APIItem name=\"height\" type=\"number\">\nThe desired height of the row.\n</APIItem>\n<APIItem name=\"getAboveNodeOptions\" type=\"EditorAboveOptions\" optional>\nAdditional options for finding the table node.\n</APIItem>\n</APIOptions>\n</API>\n\n## Plugin Extensions\n\n### `onKeyDownTable`\n\nHandles the keyboard events for tables.\n\n<API name=\"onKeyDownTable\">\n<APIParameters>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n    The plate plugin.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem type=\"KeyboardHandlerReturnType\">\n    The keyboard handler return type.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `withDeleteTable`\n\nPrevents the deletion of cells in tables.\n\n### `withGetFragmentTable`\n\nIf the selection is inside a table, it retrieves the subtable above the selection as the fragment. This is useful when copying and pasting table cells.\n\n### `withInsertFragmentTable`\n\nIf inserting a table:\n\n- If the block above the anchor of the selection is a table, replace each cell above with the inserted table until out of bounds. Select the inserted cells.\n- If there is no table above the anchor, check if the selection is inside a table. If it is, find the cell at the anchor position and replace its children with the inserted fragment.\n\n### `withInsertTextTable`\n\nIf the selection is expanded:\n\n- Check if the selection is inside a table. If it is, collapse the selection to the focus edge.\n\n### `withNormalizeTable`\n\nNormalize table structure by performing the following actions:\n\n- Wrap cell children in a paragraph if they are texts.\n- Unwrap nodes that are not valid table elements.\n- Set initial column sizes for tables if specified.\n\n### `withSelectionTable`\n\nHandle table selections by performing the following actions:\n\n- Adjust the focus of the selection when the anchor is inside a table and the focus is in a block before or after the table.\n- Adjust the focus of the selection when the focus is inside a table and the anchor is in a block before or after the table.\n- Override the selection from a cell if the previous and new selections are in different cells.\n\n### `withSetFragmentDataTable`\n\nHandle setting data to the clipboard when copying or cutting table data by performing the following actions:\n\n- Check if a table entry and selected cell entries exist.\n- Handle single-cell copy or cut operations by copying the cell content instead of the table structure.\n- Create a table structure with the selected cells' content.\n- Set the text, HTML, CSV, TSV, and Slate fragment data to the clipboard.\n\n### `withTable`\n\nEnhance the editor instance with table-related functionality by applying the following higher-order functions:\n\n- `withNormalizeTable`: Normalize table structure and content.\n- `withDeleteTable`: Prevent cell deletion within a table.\n- `withGetFragmentTable`: Handle getting the fragment data when copying or cutting table cells.\n- `withInsertFragmentTable`: Handle inserting table fragments.\n- `withInsertTextTable`: Handle inserting text within a table.\n- `withSelectionTable`: Handle adjusting the selection within a table.\n- `withSetFragmentDataTable`: Handle setting the fragment data when copying or cutting table data.\n\n## Hooks\n\n### `useTableCellElementResizable`\n\nA hook that provides resizing functionality for table cell elements.\n\n<API name=\"useTableCellElementResizable\">\n<APIOptions type=\"TableCellElementResizableOptions\">\n<APIItem name=\"colIndex\" type=\"number\">\nThe index of the column.\n</APIItem>\n<APIItem name=\"colSpan\" type=\"number\">\nThe number of columns this cell spans.\n</APIItem>\n<APIItem name=\"rowIndex\" type=\"number\">\n        The index of the row.\n</APIItem>\n<APIItem name=\"step\" type=\"number\" optional>\nResize by step instead of by pixel.\n</APIItem>\n<APIItem name=\"stepX\" type=\"number\" optional>\nStep size for horizontal resizing.\n</APIItem>\n<APIItem name=\"stepY\" type=\"number\" optional>\nStep size for vertical resizing.\n\n- **Default:** `step`\n\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem name=\"bottomProps\" type=\"ResizeHandleProps\">\n    Props for the bottom resize handle, including resize direction and handler.\n  </APIItem>\n  <APIItem name=\"hiddenLeft\" type=\"boolean\">\n    Whether the left resize handle should be hidden. True if not the first column or margin left is disabled.\n  </APIItem>\n  <APIItem name=\"leftProps\" type=\"ResizeHandleProps\">\n    Props for the left resize handle, including resize direction and handler.\n  </APIItem>\n  <APIItem name=\"rightProps\" type=\"ResizeHandleProps\">\n    Props for the right resize handle, including resize direction, initial size, and handler.\n  </APIItem>\n</APIReturns>\n</API>\n  \n\n### `useTableStore`\n\nThe table store stores the state of the table plugin.\n\n<API name=\"useTableStore\">\n<APIAttributes>\n  <APIItem name=\"colSizeOverrides\" type=\"TableStoreSizeOverrides\">\n    The column size overrides.\n  </APIItem>\n  <APIItem name=\"rowSizeOverrides\" type=\"TableStoreSizeOverrides\">\n    The row size overrides.\n  </APIItem>\n  <APIItem name=\"marginLeftOverride\" type=\"number | null\">\n    The margin left override.\n  </APIItem>\n  <APIItem name=\"selectedCells\" type=\"TElement[] | null\">\n    The selected cells.\n  </APIItem>\n  <APIItem name=\"selectedTables\" type=\"TElement[] | null\">\n    The selected tables.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `useIsCellSelected`\n\nCustom hook that checks if a table cell is selected.\n\n<API name=\"useIsCellSelected\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"TElement\">\n    The table cell element to check.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `useSelectedCells`\n\nA hook that manages the selection of cells in a table.\n\nIt keeps track of the currently selected cells and updates them based on changes in editor selection.\n\n### `useTableBordersDropdownMenuContentState`\n\nA state hook for the table borders dropdown menu content.\n\n<API name=\"useTableBordersDropdownMenuContentState\">\n<APIReturns>\nAn object with the following properties:\n<APIItem name=\"hasBottomBorder\" type=\"boolean\">\nIndicates whether the selected table cells have a bottom border.\n</APIItem>\n<APIItem name=\"hasTopBorder\" type=\"boolean\">\nIndicates whether the selected table cells have a top border.\n</APIItem>\n<APIItem name=\"hasLeftBorder\" type=\"boolean\">\nIndicates whether the selected table cells have a left border.\n</APIItem>\n<APIItem name=\"hasRightBorder\" type=\"boolean\">\nIndicates whether the selected table cells have a right border.\n</APIItem>\n<APIItem name=\"hasNoBorders\" type=\"boolean\">\nIndicates whether the selected table cells have no borders.\n</APIItem>\n<APIItem name=\"hasOuterBorders\" type=\"boolean\">\nIndicates whether the selected table cells have outer borders (i.e.,\nborders on all sides).\n</APIItem>\n<APIItem\n  name=\"getOnSelectTableBorder\"\n  type=\"function\"\n>\nA factory function that returns the `onSelectTableBorder` handler for a\nspecific border type.\n\n- The `onSelectTableBorder` handler is responsible for setting the border style for the selected table cells.\n\n</APIItem>\n</APIReturns>\n</API>\n\n### `useTableColSizes`\n\nCustom hook that returns the column sizes of a table with overrides applied. If the `colCount` of the table updates to 1 and the `enableUnsetSingleColSize` option is enabled, it unsets the `colSizes` node.\n\n<API name=\"useTableColSizes\">\n<APIOptions>\n<APIItem name=\"disableOverrides\" type=\"boolean\" optional>\nIf `true`, disables applying overrides to the column sizes.\n- **Default:** `false`\n</APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem name=\"overriddenColSizes\" type=\"number[]\">\n    The column sizes of the table with overrides applied.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTableElement`\n\nA hook for a table element that handles cell selection and margin left calculations.\n\n<API name=\"useTableElement\">\n<APIReturns>\n  <APIItem name=\"isSelectingCell\" type=\"boolean\">\n    Whether cells are currently being selected.\n  </APIItem>\n  <APIItem name=\"marginLeft\" type=\"number\">\n    The margin left of the table, considering overrides and plugin options.\n  </APIItem>\n  <APIItem name=\"props\" type=\"object\">\n    Props for the table element:\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"onMouseDown\" type=\"function\">\n        Handler that collapses selection when clicking on the table while cells are selected.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n  \n### `useTableCellElement`\n\nA hook for a table cell element that provides state and functionality for table cells.\n\n<API name=\"useTableCellElement\">\n<APIReturns>\n  <APIItem name=\"borders\" type=\"BorderStylesDefault\">\n    The border styles of the table cell.\n  </APIItem>\n  <APIItem name=\"colIndex\" type=\"number\">\n    The ending column index (considering colSpan).\n  </APIItem>\n  <APIItem name=\"colSpan\" type=\"number\">\n    The number of columns this cell spans.\n  </APIItem>\n  <APIItem name=\"isSelectingCell\" type=\"boolean\">\n    Whether cells are currently being selected.\n  </APIItem>\n  <APIItem name=\"minHeight\" type=\"number | undefined\">\n    The minimum height of the cell.\n  </APIItem>\n  <APIItem name=\"rowIndex\" type=\"number\">\n    The ending row index (considering rowSpan).\n  </APIItem>\n  <APIItem name=\"selected\" type=\"boolean\">\n    Whether this cell is currently selected.\n  </APIItem>\n  <APIItem name=\"width\" type=\"number | string\">\n    The width of the cell.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTableCellBorders`\n\nA hook that returns the border styles for a table cell.\n\n<API name=\"useTableCellBorders\">\n<APIReturns>\n  <APIItem type=\"BorderStylesDefault\">\n    An object containing the border styles for the cell:\n    <APISubList>\n      <APISubListItem parent=\"return\" name=\"bottom\" type=\"Required<TTableCellElementBorder>\">\n        The bottom border style.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"right\" type=\"Required<TTableCellElementBorder>\">\n        The right border style.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"left\" type=\"Required<TTableCellElementBorder>\" optional>\n        The left border style. Only present for cells in the first column.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"top\" type=\"Required<TTableCellElementBorder>\" optional>\n        The top border style. Only present for cells in the first row.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTableCellSize`\n\nA hook that returns the size (width and minimum height) of a table cell.\n\n<API name=\"useTableCellSize\">\n<APIReturns>\n  <APIItem type=\"object\">\n    An object containing:\n    <APISubList>\n      <APISubListItem parent=\"return\" name=\"width\" type=\"number\">\n        The total width of the cell, calculated by summing the widths of all columns it spans.\n      </APISubListItem>\n      <APISubListItem parent=\"return\" name=\"minHeight\" type=\"number | undefined\">\n        The minimum height of the cell, derived from the row's size property.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/table.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "toc-docs",
      "title": "Table of Contents",
      "description": "Documentation for Table of Contents",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/toc.mdx",
          "content": "---\ntitle: Table of Contents\ndocs:\n  - route: https://pro.platejs.org/docs/examples/toc\n    title: Plus\n  - route: components/toc-node\n    title: Toc Element\n---\n\n<ComponentPreview name=\"toc-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Automatically generates a table of contents from document headings\n- Smooth scrolling to headings\n- Active section tracking for the current heading while you scroll\n- Keyboard-accessible heading navigation with `Enter` and `Space`\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add table of contents functionality is with the `TocKit`, which includes pre-configured `TocPlugin` with the [Plate UI](/docs/plate/installation/plate-ui) component.\n\n<ComponentSource name=\"toc-kit\" />\n\n- [`TocElement`](/docs/plate/components/toc-node): Renders table of contents elements.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { TocKit } from '@/components/editor/plugins/toc-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...TocKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/basic-nodes @platejs/toc\n```\n\n### Add Plugins\n\nInclude `TocPlugin` and `HnPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { TocPlugin } from '@platejs/toc/react';\nimport { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    H1Plugin,\n    H2Plugin,\n    H3Plugin,\n    TocPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure the `TocPlugin` with custom component and scroll options.\n\n```tsx\nimport { TocPlugin } from '@platejs/toc/react';\nimport { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { TocElement } from '@/components/ui/toc-node';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    H1Plugin.withComponent(H1Element),\n    H2Plugin.withComponent(H2Element),\n    H3Plugin.withComponent(H3Element),\n    TocPlugin.configure({\n      node: { component: TocElement },\n      options: {\n        topOffset: 80,\n        isScroll: true,\n      },\n    }),\n  ],\n});\n```\n\n- `node.component`: Assigns [`TocElement`](/docs/plate/components/toc-node) to render table of contents elements.\n- `options.topOffset`: Sets the top offset when scrolling to headings.\n- `options.isScroll`: Enables scrolling behavior to headings.\n\n### Insert Toolbar Button\n\nYou can add this item to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert table of contents elements:\n\n```tsx\n{\n  icon: <TableOfContentsIcon />,\n  label: 'Table of contents',\n  value: KEYS.toc,\n}\n```\n\n### Scroll Container Setup\n\n- If your scrolling element is [EditorContainer](/docs/plate/components/editor), you can skip this step.\n- If your scrolling element is the editor container, pass `useEditorContainerRef()` as the `ref` prop. For example:\n\n```tsx\n// Below <Plate> component\nfunction EditorContainer({ children }: { children: React.ReactNode }) {\n  const containerRef = useEditorContainerRef();\n\n  return <div ref={containerRef}>{children}</div>;\n}\n```\n\n- If your scrolling element is an ancestor of the editor container, pass `useEditorScrollRef()` as the `ref` prop. For example:\n\n```tsx\n// Below <Plate> component\nfunction Layout() {\n  const scrollRef = useEditorScrollRef();\n\n  return (\n    <main ref={scrollRef}>\n      <EditorContainer>\n        <PlateContent />\n      </EditorContainer>\n    </main>\n  );\n}\n```\n\n</Steps>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"toc-pro\" />\n\n## Plugins\n\n### `TocPlugin`\n\nPlugin for generating table of contents.\n\n<API name=\"TocPlugin\">\n<APIOptions>\n  <APIItem name=\"isScroll\" type=\"boolean\" optional>\n    Enable scrolling behavior.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"topOffset\" type=\"number\" optional>\n    Top offset when scrolling to heading.\n    - **Default:** `80`\n  </APIItem>\n  <APIItem name=\"queryHeading\" type=\"(editor: SlateEditor) => Heading[]\" optional>\n    Custom function to query headings.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Transforms\n\n### `tf.insertToc`\n\nInsert table of contents element.\n\n<API name=\"insertToc\">\n<APIOptions type=\"InsertNodesOptions<SlateEditor>\">\n    Node insertion options.\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useTocElementState`\n\nManage TOC element state.\n\n<API name=\"useTocElementState\">\n<APIReturns>\n  <APIItem name=\"activeContentId\" type=\"string\">\n    Active heading ID for the current document position.\n  </APIItem>\n  <APIItem name=\"headingList\" type=\"Heading[]\">\n    Document headings array.\n  </APIItem>\n  <APIItem name=\"onContentScroll\" type=\"(el: HTMLElement, id: string, behavior: ScrollBehavior) => void\">\n    Heading scroll handler.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTocElement`\n\nHandle TOC element interactions.\n\n<API name=\"useTocElement\">\n\n<APIParameters>\n  <APIItem name=\"onContentScroll\" type=\"(el: HTMLElement, id: string, behavior: ScrollBehavior) => void\">\n    Scroll handler from useTocElementState.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem name=\"props\" type=\"object\">\n    Props for TOC element.\n  </APIItem>\n  <APISubList>\n    <APISubListItem parent=\"props\" name=\"onClick\" type=\"(e: React.MouseEvent, item: Heading, behavior: ScrollBehavior) => void\">\n      TOC item click handler.\n    </APISubListItem>\n  </APISubList>\n</APIReturns>\n</API>\n\n### `useTocSideBarState`\n\nManage TOC sidebar state.\n\n<API name=\"useTocSideBarState\">\n<APIParameters>\n  <APIItem name=\"open\" type=\"boolean\" optional>\n    Initial open state.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"rootMargin\" type=\"string\" optional>\n    Intersection Observer root margin.\n    - **Default:** `'0px 0px 0px 0px'`\n  </APIItem>\n  <APIItem name=\"topOffset\" type=\"number\" optional>\n    Scroll top offset.\n    - **Default:** `0`\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem name=\"activeContentId\" type=\"string\">\n    Active section ID.\n  </APIItem>\n  <APIItem name=\"headingList\" type=\"Heading[]\">\n    Document headings.\n  </APIItem>\n  <APIItem name=\"mouseInToc\" type=\"boolean\">\n    Mouse over TOC state.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\">\n    Sidebar open state.\n  </APIItem>\n  <APIItem name=\"setIsObserve\" type=\"React.Dispatch<React.SetStateAction<boolean>>\">\n    Set observation state.\n  </APIItem>\n  <APIItem name=\"setMouseInToc\" type=\"React.Dispatch<React.SetStateAction<boolean>>\">\n    Set mouse over state.\n  </APIItem>\n  <APIItem name=\"tocRef\" type=\"React.RefObject<HTMLElement>\">\n    TOC element ref.\n  </APIItem>\n  <APIItem name=\"onContentScroll\" type=\"(options: { id: string; behavior?: ScrollBehavior; el: HTMLElement }) => void\">\n    Content scroll handler.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useTocSideBar`\n\nThis hook provides props and handlers for the TOC sidebar component.\n\n<API name=\"useTocSideBar\">\n<APIParameters>\n  <APIItem name=\"mouseInToc\" type=\"boolean\">\n    Mouse over TOC state.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\">\n    Sidebar open state.\n  </APIItem>\n  <APIItem name=\"setIsObserve\" type=\"React.Dispatch<React.SetStateAction<boolean>>\">\n    Set observation state.\n  </APIItem>\n  <APIItem name=\"setMouseInToc\" type=\"React.Dispatch<React.SetStateAction<boolean>>\">\n    Set mouse over state.\n  </APIItem>\n  <APIItem name=\"tocRef\" type=\"React.RefObject<HTMLElement>\">\n    TOC element ref.\n  </APIItem>\n  <APIItem name=\"onContentScroll\" type=\"(options: { id: string; behavior?: ScrollBehavior; el: HTMLElement }) => void\">\n    Content scroll handler.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"navProps\" type=\"object\">\n    Navigation element props.\n  </APIItem>\n  <APISubList type=\"navProps\">\n    <APISubListItem parent=\"navProps\" name=\"ref\" type=\"React.RefObject<HTMLElement>\">\n      TOC element ref.\n    </APISubListItem>\n    <APISubListItem parent=\"navProps\" name=\"onMouseEnter\" type=\"() => void\">\n      Mouse enter handler.\n    </APISubListItem>\n    <APISubListItem parent=\"navProps\" name=\"onMouseLeave\" type=\"(e: React.MouseEvent<HTMLElement, MouseEvent>) => void\">\n      Mouse leave handler.\n    </APISubListItem>\n  </APISubList>\n  <APISubListItem parent=\"navProps\" name=\"onContentClick\" type=\"(e: React.MouseEvent<HTMLElement, MouseEvent>, item: Heading, behavior?: ScrollBehavior) => void\">\n    TOC item click handler.\n  </APISubListItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/toc.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "toggle-docs",
      "title": "Toggle",
      "description": "Documentation for Toggle",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(elements)/toggle.mdx",
          "content": "---\ntitle: Toggle\ndocs:\n  - route: /docs/components/toggle-node\n    title: Toggle Element\n  - route: /docs/components/toggle-toolbar-button\n    title: Toggle Button\n---\n\n<ComponentPreview name=\"toggle-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Add toggles to your document\n- Toggles are closed by default, and can be opened to reveal the content inside\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add toggle functionality is with the `ToggleKit`, which includes pre-configured `TogglePlugin` with indent support and their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"toggle-kit\" />\n\n- [`IndentKit`](/docs/plate/indent): Provides indent support for toggle elements.\n- [`ToggleElement`](/docs/plate/components/toggle-node): Renders toggle elements.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { ToggleKit } from '@/components/editor/plugins/toggle-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...ToggleKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/indent @platejs/toggle\n```\n\n### Add Plugins\n\nInclude `TogglePlugin` and `IndentPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { TogglePlugin } from '@platejs/toggle/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin,\n    TogglePlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nConfigure the `IndentPlugin` and `TogglePlugin` with proper targeting and component assignment.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { TogglePlugin } from '@platejs/toggle/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { ToggleElement } from '@/components/ui/toggle-node';\nimport { KEYS } from 'platejs';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin.configure({\n      inject: {\n        targetPlugins: [...KEYS.heading, KEYS.p, KEYS.toggle],\n      },\n    }),\n    TogglePlugin.withComponent(ToggleElement),\n  ],\n});\n```\n\n- `withComponent`: Assigns [`ToggleElement`](/docs/plate/components/toggle-node) to render toggle elements.\n- `IndentPlugin.inject.targetPlugins`: Configures which element types support indentation, including toggles.\n\n### Add Toolbar Button\n\nYou can add [`ToggleToolbarButton`](/docs/plate/components/toggle-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to insert toggle elements.\n\n### Turn Into Toolbar Button\n\nYou can add this item to the [Turn Into Toolbar Button](/docs/plate/toolbar#turn-into-toolbar-button) to convert blocks into toggles:\n\n```tsx\n{\n  icon: <ChevronRightIcon />,\n  label: 'Toggle list',\n  value: KEYS.toggle,\n}\n```\n\n### Insert Toolbar Button\n\nYou can add this item to the [Insert Toolbar Button](/docs/plate/toolbar#insert-toolbar-button) to insert toggle elements:\n\n```tsx\n{\n  icon: <ChevronRightIcon />,\n  label: 'Toggle list',\n  value: KEYS.toggle,\n}\n```\n\n</Steps>\n\n## Plugins\n\n### `TogglePlugin`\n\nPlugin for managing toggle functionality.\n\n<API name=\"TogglePlugin\">\n<APIOptions type=\"object\">\n  <APIItem name=\"openIds\" type=\"Set<string>\" optional>\n    Set of open toggle IDs.\n    - **Default:** `new Set()`\n  </APIItem>\n  <APIItem name=\"isOpen\" type=\"(toggleId: string) => boolean\" optional>\n    Function to check if toggle is open.\n    - **Default:** `(id) => openIds.has(id)`\n  </APIItem>\n  <APIItem name=\"someClosed\" type=\"(toggleIds: string[]) => boolean\" optional>\n    Function to check if any toggles are closed.\n    - **Default:** `(ids) => ids.some(id => !isOpen(id))`\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.toggle.toggleIds`\n\nToggles the open state of specified toggles.\n\n<API name=\"editor.api.toggle.toggleIds\">\nToggle open state of toggles.\n\n<APIParameters>\n  <APIItem name=\"ids\" type=\"string[]\">\n    Array of element IDs to toggle.\n  </APIItem>\n  <APIItem name=\"force\" type=\"boolean | null\" optional>\n    Force toggle state:\n    - `true`: expand all toggles\n    - `false`: collapse all toggles\n    - `null`: toggle current state\n  </APIItem>\n</APIParameters>\n</API>\n\n### `openNextToggles`\n\nMark block at current selection as open toggle.\n\n## Hooks\n\n### `useToggleToolbarButtonState`\n\nHook for getting toggle toolbar button state.\n\n<API name=\"useToggleToolbarButtonState\">\n<APIReturns type=\"object\">\n  <APIItem name=\"pressed\" type=\"boolean\">\n    Whether button is pressed.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useToggleToolbarButton`\n\nHook for handling toggle toolbar button behavior.\n\n<API name=\"useToggleToolbarButton\">\n<APIState>\n  <APIItem name=\"pressed\" type=\"boolean\">\n    Whether button is pressed.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for toggle button.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"pressed\" type=\"boolean\">\n        Whether button is pressed.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n        Toggle node type and focus editor.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"onMouseDown\" type=\"function\">\n        Prevent focus loss on click.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useToggleButtonState`\n\nHook for getting toggle button state.\n\n<API name=\"useToggleButtonState\">\nGet toggle button state.\n\n<APIParameters>\n  <APIItem name=\"toggleId\" type=\"string\">\n    Toggle element ID.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"toggleId\" type=\"string\">\n    Toggle element ID.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\">\n    Whether toggle is expanded.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useToggleButton`\n\nHook for handling toggle button behavior.\n\n<API name=\"useToggleButton\">\nHandle toggle button behavior.\n\n<APIParameters>\n  <APIItem name=\"toggleId\" type=\"string\">\n    Toggle element ID.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\">\n    Whether toggle is expanded.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"toggleId\" type=\"string\">\n    Toggle element ID.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\">\n    Whether toggle is expanded.\n  </APIItem>\n  <APIItem name=\"buttonProps\" type=\"object\">\n    Props for toggle button.\n    <APISubList>\n      <APISubListItem parent=\"buttonProps\" name=\"onClick\" type=\"function\">\n        Toggle open state.\n      </APISubListItem>\n      <APISubListItem parent=\"buttonProps\" name=\"onMouseDown\" type=\"function\">\n        Prevent focus loss on click.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(elements)/toggle.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "combobox-docs",
      "title": "Combobox",
      "description": "Documentation for Combobox",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(combobox)/combobox.mdx",
          "content": "---\ntitle: Combobox\ndocs:\n  - route: /docs/components/inline-combobox\n    title: Inline Combobox\n---\n\n<Cards>\n\n<Card icon=\"mention\" title=\"Mention\" href=\"/docs/plate/mention\">\nInsert mentions for users, pages, or any reference with `@`\n</Card>\n\n<Card icon=\"slash-command\" title=\"Slash Command\" href=\"/docs/plate/slash-command\">\nQuick access to editor commands and blocks with `/`\n</Card>\n\n<Card icon=\"emoji\" title=\"Emoji\" href=\"/docs/plate/emoji\">\nInsert emojis with autocomplete using `:`\n</Card>\n\n</Cards>\n\n<PackageInfo>\n\n## Features\n\n- Utilities for creating trigger-based combobox functionality\n- Configurable trigger characters and patterns\n- Keyboard navigation and selection handling\n\n</PackageInfo>\n\n## Create a Combobox Plugin\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/combobox\n```\n\n### Create Input Plugin\n\nFirst, create an input plugin that will be inserted when the trigger is activated:\n\n```tsx\nimport { createSlatePlugin } from 'platejs';\n\nconst TagInputPlugin = createSlatePlugin({\n  key: 'tag_input',\n  editOnly: true,\n  node: {\n    isElement: true,\n    isInline: true,\n    isVoid: true,\n  },\n});\n```\n\n### Create Main Plugin\n\nCreate your main plugin using `withTriggerCombobox`:\n\n```tsx\nimport { createTSlatePlugin, type PluginConfig } from 'platejs';\nimport { \n  type TriggerComboboxPluginOptions, \n  withTriggerCombobox \n} from '@platejs/combobox';\n\ntype TagConfig = PluginConfig<'tag', TriggerComboboxPluginOptions>;\n\nexport const TagPlugin = createTSlatePlugin<TagConfig>({\n  key: 'tag',\n  node: { isElement: true, isInline: true, isVoid: true },\n  options: {\n    trigger: '#',\n    triggerPreviousCharPattern: /^\\s?$/,\n    createComboboxInput: () => ({\n      children: [{ text: '' }],\n      type: 'tag_input',\n    }),\n  },\n  plugins: [TagInputPlugin],\n}).overrideEditor(withTriggerCombobox);\n```\n\n- `node.isElement`: Defines this as an element node (not text)\n- `node.isInline`: Makes the tag element inline (not block)\n- `node.isVoid`: Prevents editing inside the tag element\n- `options.trigger`: Character that triggers the combobox (in this case `#`)\n- `options.triggerPreviousCharPattern`: RegExp pattern that must match the character before the trigger. `/^\\s?$/` allows the trigger at the start of a line or after whitespace\n- `options.createComboboxInput`: Function that creates the input element node when the trigger is activated\n\n### Create Component\n\nCreate the input element component using `InlineCombobox`:\n\n```tsx\nimport { PlateElement, useFocused, useReadOnly, useSelected } from 'platejs/react';\nimport {\n  InlineCombobox,\n  InlineComboboxContent,\n  InlineComboboxEmpty,\n  InlineComboboxInput,\n  InlineComboboxItem,\n} from '@/components/ui/inline-combobox';\nimport { cn } from '@/lib/utils';\n\nconst tags = [\n  { id: 'frontend', name: 'Frontend', color: 'blue' },\n  { id: 'backend', name: 'Backend', color: 'green' },\n  { id: 'design', name: 'Design', color: 'purple' },\n  { id: 'urgent', name: 'Urgent', color: 'red' },\n];\n\nexport function TagInputElement({ element, ...props }) {\n  return (\n    <PlateElement as=\"span\" {...props}>\n      <InlineCombobox element={element} trigger=\"#\">\n        <InlineComboboxInput />\n        \n        <InlineComboboxContent>\n          <InlineComboboxEmpty>No tags found</InlineComboboxEmpty>\n          \n          {tags.map((tag) => (\n            <InlineComboboxItem\n              key={tag.id}\n              value={tag.name}\n              onClick={() => {\n                // Insert actual tag element\n                editor.tf.insertNodes({\n                  type: 'tag',\n                  tagId: tag.id,\n                  children: [{ text: tag.name }],\n                });\n              }}\n            >\n              <span \n                className={`w-3 h-3 rounded-full bg-${tag.color}-500 mr-2`}\n              />\n              #{tag.name}\n            </InlineComboboxItem>\n          ))}\n        </InlineComboboxContent>\n      </InlineCombobox>\n      \n      {props.children}\n    </PlateElement>\n  );\n}\n\nexport function TagElement({ element, ...props }) {\n  const selected = useSelected();\n  const focused = useFocused();\n  const readOnly = useReadOnly();\n\n  return (\n    <PlateElement\n      {...props}\n      className={cn(\n        'inline-block rounded-md bg-primary/10 px-1.5 py-0.5 align-baseline text-sm font-medium text-primary',\n        !readOnly && 'cursor-pointer',\n        selected && focused && 'ring-2 ring-ring'\n      )}\n      attributes={{\n        ...props.attributes,\n        contentEditable: false,\n        'data-slate-value': element.value,\n      }}\n    >\n      #{element.value}\n      {props.children}\n    </PlateElement>\n  );\n}\n```\n\n### Add to Editor\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { TagPlugin, TagInputPlugin } from './tag-plugin';\nimport { TagElement, TagInputElement } from './tag-components';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    TagPlugin.configure({\n      options: {\n        triggerQuery: (editor) => {\n          // Disable in code blocks\n          return !editor.api.some({ match: { type: 'code_block' } });\n        },\n      },\n    }).withComponent(TagElement),\n    TagInputPlugin.withComponent(TagInputElement),\n  ],\n});\n```\n\n- `options.triggerQuery`: Optional function to conditionally enable/disable the trigger based on editor state\n\n</Steps>\n\n## Examples\n\n<ComponentPreview name=\"mention-demo\" />\n<ComponentPreview name=\"slash-command-demo\" />\n<ComponentPreview name=\"emoji-demo\" />\n\n## Options\n\n### TriggerComboboxPluginOptions\n\nConfiguration options for trigger-based combobox plugins.\n\n<API name=\"TriggerComboboxPluginOptions\">\n<APIOptions>\n  <APIItem name=\"createComboboxInput\" type=\"(trigger: string) => TElement\">\n    Function to create the input node when trigger is activated.\n  </APIItem>\n  <APIItem name=\"trigger\" type=\"RegExp | string[] | string\">\n    Character(s) that trigger the combobox. Can be:\n    - A single character (e.g. '@')\n    - An array of characters\n    - A regular expression\n  </APIItem>\n  <APIItem name=\"triggerPreviousCharPattern\" type=\"RegExp\" optional>\n    Pattern to match the character before trigger.\n    - **Example:** `/^\\s?$/` matches start of line or space\n  </APIItem>\n  <APIItem name=\"triggerQuery\" type=\"(editor: SlateEditor) => boolean\" optional>\n    Custom query function to control when trigger is active.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### useComboboxInput\n\nHook for managing combobox input behavior and keyboard interactions.\n\n<API name=\"useComboboxInput\">\n<APIOptions>\n  <APIItem name=\"ref\" type=\"RefObject<HTMLElement>\">\n    Reference to the input element.\n  </APIItem>\n  <APIItem name=\"autoFocus\" type=\"boolean\" optional>\n    Auto focus the input when mounted.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cancelInputOnArrowLeftRight\" type=\"boolean\" optional>\n    Cancel on arrow keys.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cancelInputOnBackspace\" type=\"boolean\" optional>\n    Cancel on backspace at start.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cancelInputOnBlur\" type=\"boolean\" optional>\n    Cancel on blur.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cancelInputOnDeselect\" type=\"boolean\" optional>\n    Cancel when deselected.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cancelInputOnEscape\" type=\"boolean\" optional>\n    Cancel on escape key.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"cursorState\" type=\"ComboboxInputCursorState\" optional>\n    Current cursor position state.\n  </APIItem>\n  <APIItem name=\"forwardUndoRedoToEditor\" type=\"boolean\" optional>\n    Forward undo/redo to editor.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"onCancelInput\" type=\"(cause: CancelComboboxInputCause) => void\" optional>\n    Callback when input is cancelled.\n  </APIItem>\n</APIOptions>\n\n<APIReturns>\n  <APIItem name=\"cancelInput\" type=\"(cause?: CancelComboboxInputCause, focusEditor?: boolean) => void\">\n    Function to cancel the input.\n  </APIItem>\n  <APIItem name=\"props\" type=\"object\">\n    Props for the input element.\n  </APIItem>\n  <APIItem name=\"removeInput\" type=\"(focusEditor?: boolean) => void\">\n    Function to remove the input node.\n  </APIItem>\n</APIReturns>\n</API>\n\n### useHTMLInputCursorState\n\nHook for tracking cursor position in an HTML input element.\n\n<API name=\"useHTMLInputCursorState\">\n<APIParameters>\n  <APIItem name=\"ref\" type=\"RefObject<HTMLInputElement>\">\n    Reference to the input element to track.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  <APIItem name=\"atStart\" type=\"boolean\">\n    Whether cursor is at the start of input.\n  </APIItem>\n  <APIItem name=\"atEnd\" type=\"boolean\">\n    Whether cursor is at the end of input.\n  </APIItem>\n</APIReturns>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(combobox)/combobox.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "emoji-docs",
      "title": "Emoji",
      "description": "Inline emoji search and toolbar emoji picking.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(combobox)/emoji.mdx",
          "content": "---\ntitle: Emoji\ndescription: Inline emoji search and toolbar emoji picking.\ndocs:\n  - route: /docs/combobox\n    title: Combobox\n  - route: /docs/components/emoji-node\n    title: Emoji Input Element\n  - route: /docs/components/emoji-toolbar-button\n    title: Emoji Toolbar Button\n---\n\nEmoji adds two insertion paths: a `:` trigger that opens an inline combobox, and a toolbar popover for browsing categories. The package owns emoji data, trigger behavior, index search, picker state, frequent emoji storage, and insertion. The registry owns the inline input element and toolbar picker UI.\n\n<ComponentPreview name=\"emoji-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `:` trigger powered by `@platejs/combobox`.\n- Edit-only inline void `emoji_input` node.\n- Emoji search from `@emoji-mart/data`.\n- Default insertion as Unicode text.\n- Custom inserted node support through `createEmojiNode`.\n- Toolbar popover with categories, search, preview, and frequent emoji tracking.\n- Markdown shortcode deserialization to Unicode text.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`EmojiKit` installs `EmojiPlugin` with `@emoji-mart/data` and renders `EmojiInputPlugin` with the registry inline combobox.\n\n<ComponentSource name=\"emoji-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { EmojiKit } from '@/components/editor/plugins/emoji-kit';\n\nexport const editor = createPlateEditor({\n  plugins: EmojiKit,\n});\n```\n\n### Render The Inline Input\n\n`emoji-node` debounces the search text, queries `EmojiInlineIndexSearch`, and inserts the selected emoji.\n\n<ComponentSource name=\"emoji-node\" />\n\n### Add Toolbar Picking\n\n`emoji-toolbar-button` renders the Radix popover picker backed by `useEmojiDropdownMenuState`.\n\n<ComponentSource name=\"emoji-toolbar-button\" />\n\n</Steps>\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `@platejs/emoji` | Package | Exports `BaseEmojiPlugin`, `BaseEmojiInputPlugin`, `insertEmoji`, emoji search libraries, settings, categories, and types. |\n| `@platejs/emoji/react` | Package | Exports `EmojiPlugin`, `EmojiInputPlugin`, picker hooks, and frequent emoji storage. |\n| `@platejs/combobox` | Package | Provides trigger detection and transient input cleanup for the `:` flow. |\n| `@emoji-mart/data` | Dependency | Provides the emoji dataset used by the registry kit. |\n| `emoji-kit` | Registry | Adds `EmojiPlugin` with emoji-mart data and `EmojiInputPlugin.withComponent(EmojiInputElement)`. |\n| `emoji-node` | Registry UI | Renders inline emoji search with `InlineCombobox`. |\n| `emoji-toolbar-button` | Registry UI | Renders the toolbar popover, category grid, search bar, and preview. |\n| `inline-combobox` | Registry UI | Renders the inline trigger input and popover primitives. |\n| `@platejs/markdown` | Package | Deserializes emoji shortcodes such as `:fire:` to Unicode text. |\n\nThe emoji plugin is edit-only. It inserts text or your custom node, then the transient `emoji_input` disappears.\n\n## Manual Setup\n\n<Steps>\n\n### Install Packages\n\n```bash\nnpm install @platejs/emoji @emoji-mart/data\n```\n\n### Add Plugins\n\nUse `@emoji-mart/data` when you want the full emoji dataset instead of the package default library.\n\n```tsx\nimport emojiMartData from '@emoji-mart/data';\nimport { EmojiInputPlugin, EmojiPlugin } from '@platejs/emoji/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { EmojiInputElement } from '@/components/ui/emoji-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    EmojiPlugin.configure({\n      options: {\n        data: emojiMartData as any,\n      },\n    }),\n    EmojiInputPlugin.withComponent(EmojiInputElement),\n  ],\n});\n```\n\n### Add A Toolbar Button\n\nRender `EmojiToolbarButton` in your toolbar when users should browse emoji without typing `:`.\n\n```tsx\nimport { EmojiToolbarButton } from '@/components/ui/emoji-toolbar-button';\n\nexport function FixedToolbarButtons() {\n  return <EmojiToolbarButton />;\n}\n```\n\n</Steps>\n\n## Inline Flow\n\nThe inline path is a combobox flow.\n\n| Step | Source |\n|------|--------|\n| Type `:` | `BaseEmojiPlugin` trigger. |\n| Previous character must match `/^\\s?$/` | Start of block or whitespace by default. |\n| Insert transient input | `createComboboxInput` creates `{ type: KEYS.emojiInput, children: [{ text: '' }] }`. |\n| Search emoji data | `EmojiInputElement` calls `EmojiInlineIndexSearch.getInstance(data).search(query).get()`. |\n| Select a result | `InlineComboboxItem` removes the input and calls `insertEmoji(editor, emoji)`. |\n| Insert final content | `insertEmoji` calls `createEmojiNode(emoji)` and inserts that node. |\n\n`EmojiInputElement` sets `filter={false}` because emoji search already returns filtered results. It also uses `hideWhenNoValue`, so the popover stays closed until the user types a search value after `:`.\n\n## Inserted Value\n\nBy default, selecting an emoji inserts the first native skin as text.\n\n```tsx\nEmojiPlugin.configure({\n  options: {\n    createEmojiNode: ({ skins }) => ({ text: skins[0].native }),\n  },\n});\n```\n\nUse `createEmojiNode` when your app stores emoji as structured inline nodes instead of text.\n\n```tsx\nEmojiPlugin.configure({\n  options: {\n    createEmojiNode: (emoji) => ({\n      children: [{ text: emoji.id }],\n      emojiId: emoji.id,\n      type: 'emoji-chip',\n    }),\n  },\n});\n```\n\n## Toolbar Picker\n\n`EmojiToolbarButton` calls `useEmojiDropdownMenuState`, then passes the picker state into `EmojiPicker`.\n\n| Option | Default | Use |\n|--------|---------|-----|\n| `closeOnSelect` | `true` | Close the toolbar popover after insertion. |\n| `settings` | `EmojiSettings` | Configure categories, per-line count, button size, and frequent emoji behavior. |\n| `settings.showFrequent.limit` | Package setting | Limits the frequent emoji category. |\n\nFrequent emoji counts are stored in `window.localStorage` through `FrequentEmojiStorage`. On the server, the storage class returns the default frequent set.\n\n## Markdown\n\n`@platejs/markdown` deserializes emoji shortcodes to Unicode text.\n\n```md\nLaunch :fire: soon\n```\n\nSerializing the resulting value writes the Unicode emoji:\n\n```md\nLaunch 🔥 soon\n```\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseEmojiPlugin` | `@platejs/emoji` | Edit-only trigger plugin with default `:`, emoji data, input creation, and text insertion. |\n| `BaseEmojiInputPlugin` | `@platejs/emoji` | Edit-only inline void `emoji_input` plugin. |\n| `EmojiPlugin` | `@platejs/emoji/react` | React emoji plugin with nested `EmojiInputPlugin`. |\n| `EmojiInputPlugin` | `@platejs/emoji/react` | React input plugin for `EmojiInputElement`. |\n| `insertEmoji(editor, emoji)` | `@platejs/emoji` | Inserts `createEmojiNode(emoji)`. |\n| `EmojiInlineIndexSearch` | `@platejs/emoji` | Inline search index used by `emoji-node`. |\n| `useEmojiDropdownMenuState(options?)` | `@platejs/emoji/react` | Builds toolbar popover state and picker state. |\n| `useEmojiPicker(options)` | `@platejs/emoji/react` | Handles search, category focus, preview, selection, and frequent emoji updates. |\n| `FrequentEmojiStorage` | `@platejs/emoji/react` | Reads and writes frequent emoji counts through localStorage. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(combobox)/emoji.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "slash-command-docs",
      "title": "Slash Command",
      "description": "Documentation for Slash Command",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(combobox)/slash-command.mdx",
          "content": "---\ntitle: Slash Command\ndocs:\n  - route: https://pro.platejs.org/docs/examples/slash-command\n    title: Plus\n  - route: /docs/combobox\n    title: Combobox\n  - route: /docs/components/slash-node\n    title: Slash Nodes\n---\n\n<ComponentPreview name=\"slash-command-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Quick access to various editor commands\n- Triggered by `/` character\n- Keyboard navigation and filtering\n- Customizable command groups and options\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add slash command functionality is with the `SlashKit`, which includes pre-configured `SlashPlugin` and `SlashInputPlugin` along with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"slash-kit\" />\n\n- [`SlashInputElement`](/docs/plate/components/slash-node): Renders the slash command combobox with predefined options\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { SlashKit } from '@/components/editor/plugins/slash-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...SlashKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/slash-command\n```\n\n### Add Plugins\n\n```tsx\nimport { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    SlashPlugin,\n    SlashInputPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\n```tsx\nimport { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { SlashInputElement } from '@/components/ui/slash-node';\nimport { KEYS } from 'platejs';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    SlashPlugin.configure({\n      options: {\n        trigger: '/',\n        triggerPreviousCharPattern: /^\\s?$/,\n        triggerQuery: (editor) =>\n          !editor.api.some({\n            match: { type: editor.getType(KEYS.codeBlock) },\n          }),\n      },\n    }),\n    SlashInputPlugin.withComponent(SlashInputElement),\n  ],\n});\n```\n\n- `options.trigger`: Character that triggers the slash command combobox (default: `/`)\n- `options.triggerPreviousCharPattern`: RegExp pattern for character before trigger\n- `options.triggerQuery`: Function to determine when slash commands should be enabled\n- `withComponent`: Assigns the UI component for the slash command interface\n\n</Steps>\n\n## Usage\n\nHow to use slash commands:\n\n1. Type `/` anywhere in your document to open the slash menu\n2. Start typing to filter options or use arrow keys to navigate\n3. Press Enter or click to select an option\n4. Press Escape to close the menu without selecting\n\nAvailable options include:\n- Text blocks (paragraphs, headings)\n- Lists (bulleted, numbered, to-do)\n- Advanced blocks (tables, code blocks, callouts)\n- Inline elements (dates, equations)\n\n<Callout type=\"info\">\n  Use keywords to quickly find options. For example, type '/ul' for Bulleted List or '/h1' for Heading 1.\n</Callout>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"slash-command-pro\" />\n\n## Plugins\n\n### SlashPlugin\n\nPlugin for slash command functionality. Extends [TriggerComboboxPluginOptions](/docs/plate/combobox#triggercomboboxpluginoptions).\n\n<API name=\"SlashPlugin\">\n<APIOptions>\n  <APIItem name=\"trigger\" type=\"string\" optional>\n    Character that triggers slash command combobox.\n    - **Default:** `'/'`\n  </APIItem>\n  <APIItem name=\"triggerPreviousCharPattern\" type=\"RegExp\" optional>\n    RegExp to match character before trigger.\n    - **Default:** `/^\\s?$/`\n  </APIItem>\n  <APIItem name=\"createComboboxInput\" type=\"() => TComboboxInputElement\" optional>\n    Function to create combobox input element.\n    - **Default:** \n    ```tsx\n    () => ({\n      children: [{ text: '' }],\n      type: KEYS.slashInput,\n    });\n    ```\n  </APIItem>\n  <APIItem name=\"triggerQuery\" type=\"(editor: PlateEditor) => boolean\" optional>\n    Function to determine when slash commands should be enabled. Useful for disabling in certain contexts like code blocks.\n  </APIItem>\n</APIOptions>\n</API>\n\n### SlashInputPlugin\n\nHandles the input behavior for slash command insertion.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(combobox)/slash-command.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "exit-break-docs",
      "title": "Exit Break",
      "description": "Documentation for Exit Break",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(utils)/exit-break.mdx",
          "content": "---\ntitle: Exit Break\n---\n\n<ComponentPreview name=\"exit-break-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Exit from nested block structures (like code blocks, tables, columns) using keyboard shortcuts.\n- Automatically determines the appropriate exit point based on block hierarchy.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add exit break functionality is with the `ExitBreakKit`, which includes pre-configured `ExitBreakPlugin` with keyboard shortcuts.\n\n<ComponentSource name=\"exit-break-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { ExitBreakKit } from '@/components/editor/plugins/exit-break-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...ExitBreakKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Add Plugin\n\n```tsx\nimport { ExitBreakPlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ExitBreakPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\n```tsx\nimport { ExitBreakPlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ExitBreakPlugin.configure({\n      shortcuts: {\n        insert: { keys: 'mod+enter' },\n        insertBefore: { keys: 'mod+shift+enter' },\n      },\n    }),\n  ],\n});\n```\n\n- `shortcuts.insert`: Defines a keyboard [shortcut](/docs/plate/plugin-shortcuts) to exit and insert block after.\n- `shortcuts.insertBefore`: Defines a keyboard [shortcut](/docs/plate/plugin-shortcuts) to exit and insert block before.\n\n</Steps>\n\n## Keyboard Shortcuts\n\n<KeyTable>\n  <KeyTableItem hotkey=\"Cmd + Enter\">\n    Exit the current block structure and insert a new block after.\n  </KeyTableItem>\n  <KeyTableItem hotkey=\"Cmd + Shift + Enter\">\n    Exit the current block structure and insert a new block before.\n  </KeyTableItem>\n</KeyTable>\n\n## Plugins\n\n### `ExitBreakPlugin`\n\nProvides transforms to exit nested block structures automatically. The plugin determines the appropriate exit point by finding the first ancestor that allows standard block siblings.\n\n<API name=\"ExitBreakPlugin\">\n<APIOptions>\n  <APIItem name=\"shortcuts\" type=\"object\" optional>\n    Keyboard shortcuts configuration.\n    <APISubList>\n      <APISubListItem parent=\"shortcuts\" name=\"insert\" type=\"Shortcut\" optional>\n        Shortcut to exit and insert block after.\n        - **Example:** `{ keys: 'mod+enter' }`\n      </APISubListItem>\n      <APISubListItem parent=\"shortcuts\" name=\"insertBefore\" type=\"Shortcut\" optional>\n        Shortcut to exit and insert block before.\n        - **Example:** `{ keys: 'mod+shift+enter' }`\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIOptions>\n</API>\n\n## How Exit Break Works\n\nExit break uses the [`isStrictSiblings`](/docs/plate/api/core/plate-plugin#isstrictsiblings) property to determine where to insert new blocks when exiting nested structures.\n\n### Exit Point Determination\n\nWhen you trigger exit break:\n\n1. Starts from the current text block (e.g., paragraph inside a table cell)\n2. Traverses up the document tree looking at each ancestor\n3. Finds the first ancestor with `isStrictSiblings: false`\n4. Inserts a new paragraph as a sibling to that ancestor\n\n### Examples\n\n**Code Block:**\n```tsx\n<codeblock>                              // ← Exit point (top-level block)\n  <codeline>code|</codeline>             // ← Starting position\n</codeblock>\n<p>|</p>                                 // ← New paragraph inserted here\n```\n\n**Table in Column (exits at table level):**\n```tsx\n// First exit\n<column_group>                           \n  <column>                               \n    <table>                              // ← Exit point (isStrictSiblings: false)\n      <tr>                               // isStrictSiblings: true\n        <td>                             // isStrictSiblings: true\n          <p>content|</p>                // ← Starting position\n        </td>\n      </tr>\n    </table>\n    <p>|</p>                             // ← New paragraph inserted here\n  </column>\n</column_group>\n\n// Second exit\n<column_group>                           // ← Exit point (isStrictSiblings: false)\n  <column>                               // isStrictSiblings: true\n    <table>                              \n      <tr>                               \n        <td>                             \n          <p>content</p>\n        </td>\n      </tr>\n    </table>\n    <p>|</p>                             // ← Starting position\n  </column>\n</column_group>\n<p>|</p>                                 // ← New paragraph inserted here\n```\n\n### Custom Plugin Configuration\n\nConfigure [`isStrictSiblings`](/docs/plate/api/core/plate-plugin#isstrictsiblings) for custom plugins:\n\n```tsx\n// Table structure\nconst CustomTablePlugin = createSlatePlugin({\n  key: 'table',\n  node: {\n    isElement: true,\n    // isStrictSiblings: false (default) - allows paragraph siblings\n  },\n});\n\nconst CustomTableRowPlugin = createSlatePlugin({\n  key: 'tr',\n  node: {\n    isElement: true,\n    isStrictSiblings: true, // Only allows tr siblings\n  },\n});\n\nconst CustomTableCellPlugin = createSlatePlugin({\n  key: 'td',\n  node: {\n    isElement: true,\n    isStrictSiblings: true, // Only allows td/th siblings\n  },\n});\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(utils)/exit-break.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "forced-layout-docs",
      "title": "Forced Layout",
      "description": "Path-based normalization for required document structure.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(utils)/forced-layout.mdx",
          "content": "---\ntitle: Forced Layout\ndescription: Path-based normalization for required document structure.\ndocs:\n  - route: /docs/trailing-block\n    title: Trailing Block\n  - route: /docs/single-block\n    title: Single Block\n  - route: /docs/plugin-rules\n    title: Plugin Rules\n---\n\nForced Layout is the `NormalizeTypesPlugin` pattern for pinning document positions to required node types. Use it for fixed slots such as \"the first block is an H1.\" Use [Trailing Block](/docs/plate/trailing-block) when the requirement is \"the document always ends with a paragraph.\"\n\n<PackageInfo>\n\n## Features\n\n- Path-indexed normalization rules.\n- `strictType` for rewriting an existing node to a required type.\n- `type` for inserting a missing node without rewriting an existing node.\n- Root-only normalization pass.\n- Automatic block creation through `editor.api.create.block`.\n- `onError` callback when insertion fails.\n- Conditional enabling through normal plugin `enabled` configuration.\n\n</PackageInfo>\n\n## Fast Path\n\nAdd `NormalizeTypesPlugin` when specific paths must exist or hold a specific block type.\n\n```tsx\nimport { KEYS, NormalizeTypesPlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    NormalizeTypesPlugin.configure({\n      options: {\n        rules: [\n          { path: [0], strictType: KEYS.h1 },\n          { path: [1], type: KEYS.p },\n        ],\n      },\n    }),\n  ],\n});\n```\n\nThis keeps the first block as an H1 and inserts a paragraph at path `[1]` when that node is missing.\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `NormalizeTypesPlugin` | `platejs` / `@platejs/utils` | Stores `rules` and `onError`, then overrides normalization. |\n| `withNormalizeTypes` | `@platejs/utils` | Runs the path rules during root normalization. |\n| `NodeApi.get(editor, path)` | `@platejs/slate` | Reads the node at the configured path. |\n| `editor.api.create.block` | Core editor API | Creates inserted or replacement block props. |\n| Playground demo | Registry example | Enables a first-block H1 rule when the playground id is `forced-layout`. |\n\nThere is no `ForcedLayoutPlugin` and no `forced-layout-kit`. The public plugin is `NormalizeTypesPlugin`.\n\n## Rule Semantics\n\n`NormalizeTypesPlugin` runs only when the root editor node normalizes. It checks rules in order and stops the current normalization pass after the first rule that changes the document.\n\n| Rule Shape | Existing Node | Missing Node |\n|------------|---------------|--------------|\n| `{ path, strictType }` | If the node is an element with a different type, Plate sets its block props to `strictType` and preserves children. | Plate inserts `editor.api.create.block({ type: strictType })`. |\n| `{ path, type }` | Plate leaves the node alone. | Plate inserts `editor.api.create.block({ type })`. |\n\nUse `strictType` for required slots. Use `type` for optional slots that should be filled only when empty.\n\n## Error Handling\n\nIf inserting a missing node fails, `withNormalizeTypes` calls `onError(error)` and falls through to the editor's normal `normalizeNode`.\n\n```tsx\nimport { NormalizeTypesPlugin } from 'platejs';\n\nexport const requiredTitle = NormalizeTypesPlugin.configure({\n  options: {\n    onError: (error) => {\n      console.error(error);\n    },\n    rules: [{ path: [0], strictType: 'h1' }],\n  },\n});\n```\n\nKeep `onError` small. A normalization callback should report or collect the failure, not mutate the same path again.\n\n## Choosing The Right Utility\n\n| Need | Use |\n|------|-----|\n| First block must be a title | `NormalizeTypesPlugin` with `strictType`. |\n| A missing slot should be inserted | `NormalizeTypesPlugin` with `type`. |\n| Editor may only contain one root block | [Single Block](/docs/plate/single-block). |\n| Editor must end with a paragraph | [Trailing Block](/docs/plate/trailing-block). |\n| Pressing Enter should exit or reset a block | [Plugin Rules](/docs/plate/plugin-rules). |\n\nForced layout is for absolute paths. It is not a schema engine for every possible nested node shape.\n\n## Playground Toggle\n\nThe registry playground demonstrates this pattern by enabling the plugin only for the `forced-layout` example id.\n\n```tsx\nNormalizeTypesPlugin.configure({\n  enabled: id === 'forced-layout',\n  options: {\n    rules: [{ path: [0], strictType: 'h1' }],\n  },\n});\n```\n\nThat example keeps the first playground block as an H1 while leaving the rest of the editor to normal Plate behavior.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `NormalizeTypesPlugin` | `platejs` / `@platejs/utils` | Path-based type normalization plugin. |\n| `NormalizeTypesConfig.options.rules` | `@platejs/utils` | Ordered list of path rules. Defaults to `[]`. |\n| `Rule.path` | `@platejs/utils` | Slate `Path` where the rule applies. |\n| `Rule.strictType` | `@platejs/utils` | Required type for an existing or missing node. |\n| `Rule.type` | `@platejs/utils` | Type for a missing node only. |\n| `NormalizeTypesConfig.options.onError` | `@platejs/utils` | Called when inserting a missing node throws. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(utils)/forced-layout.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "single-block-docs",
      "title": "Single Block",
      "description": "Restrict an editor to one root block or one line of text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/(utils)/single-block.mdx",
          "content": "---\ntitle: Single Block\ndescription: Restrict an editor to one root block or one line of text.\ndocs:\n  - route: /docs/examples/single-block\n    title: Demo\n  - route: /docs/trailing-block\n    title: Trailing Block\n---\n\nSingle Block provides two input constraints: one root block with soft breaks, or one line with no breaks. Both plugins disable `TrailingBlockPlugin` while enabled and collapse extra root blocks during normalization.\n\n<ComponentPreview name=\"single-block-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `SingleBlockPlugin` keeps one root block and converts Enter to a soft break.\n- `SingleLinePlugin` keeps one root block and removes all line break characters.\n- Root normalization merges extra blocks into the first block.\n- Both plugins disable `TrailingBlockPlugin`.\n- Demo toggle for switching between single-block and single-line behavior.\n\n</PackageInfo>\n\n## Fast Path\n\nUse `SingleBlockPlugin` when the field may contain line breaks.\n\n```tsx\nimport { SingleBlockPlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [SingleBlockPlugin],\n});\n```\n\nUse `SingleLinePlugin` when the field must be plain one-line text.\n\n```tsx\nimport { SingleLinePlugin } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [SingleLinePlugin],\n});\n```\n\n## Ownership\n\n| Layer | Owner | What It Does |\n|-------|-------|--------------|\n| `SingleBlockPlugin` | `platejs` / `@platejs/utils` | Keeps one root block and preserves line breaks as text. |\n| `SingleLinePlugin` | `platejs` / `@platejs/utils` | Keeps one root block and strips line break characters. |\n| `TrailingBlockPlugin` | `@platejs/utils` | Disabled by both plugins through `override.enabled`. |\n| `single-block-demo` | Registry example | Lets users toggle between single-block and single-line mode. |\n\nThese plugins are editor constraints, not schema validation. They rewrite editor content during normalization.\n\n## Behavior\n\n| Plugin | Enter | Soft Break | Extra Root Blocks | Existing Text Breaks |\n|--------|-------|------------|-------------------|----------------------|\n| `SingleBlockPlugin` | Calls `editor.tf.insertSoftBreak()`. | Preserved as `\\n`. | Merged into the first block with `\\n` separators. | Preserved. |\n| `SingleLinePlugin` | No-op. | No-op. | Merged into the first block with no separator. | Removes `\\r`, `\\n`, `\\r\\n`, `\\u2028`, and `\\u2029`. |\n\nUse single-block mode for descriptions, comments, or titles that may wrap across lines. Use single-line mode for labels, slugs, short titles, and command inputs.\n\n## Normalization\n\nBoth plugins override `normalizeNode`.\n\n| Case | Result |\n|------|--------|\n| Root has one block | Leaves the value alone. |\n| Root has multiple blocks with `SingleBlockPlugin` | Inserts `\\n` at the start of each next block, then merges it into the first block. |\n| Root has multiple blocks with `SingleLinePlugin` | Merges each next block into the first block without a separator. |\n| Text node contains line separators with `SingleLinePlugin` | Replaces the text with the filtered one-line string. |\n\nThe merge happens inside `editor.tf.withoutNormalizing`, so the root collapse finishes as one normalization pass.\n\n## Demo Toggle\n\nThe registry example switches plugins from a checkbox.\n\n```tsx\nimport { SingleBlockPlugin, SingleLinePlugin } from 'platejs';\n\nconst plugins = [\n  isSingleBlock ? SingleBlockPlugin : SingleLinePlugin,\n];\n```\n\n`Single Block Mode` keeps pasted lines as `\\n`. Turning it off switches to single-line mode and removes line breaks.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `SingleBlockPlugin` | `platejs` / `@platejs/utils` | One root block with soft breaks preserved. |\n| `SingleLinePlugin` | `platejs` / `@platejs/utils` | One root block with all line breaks removed. |\n| `KEYS.singleBlock` | `platejs` / `@platejs/utils` | Plugin key for `SingleBlockPlugin`. |\n| `KEYS.singleLine` | `platejs` / `@platejs/utils` | Plugin key for `SingleLinePlugin`. |\n| `override.enabled.trailingBlock` | Plugin override | Set to `false` by both plugins. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/(utils)/single-block.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "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"
    },
    {
      "name": "autoformat-docs",
      "title": "Autoformat",
      "description": "Markdown shortcuts and text substitutions powered by input rules.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/autoformat.mdx",
          "content": "---\ntitle: Autoformat\ndescription: Markdown shortcuts and text substitutions powered by input rules.\ndocs:\n  - route: /docs/plugin-input-rules\n    title: Plugin Input Rules\n  - route: /docs/basic-blocks\n    title: Basic Elements\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/code-block\n    title: Code Block\n  - route: /docs/list\n    title: List\n---\n\nFor the full runtime — dispatch, ownership, selection context, and custom rule authoring — see the [Plugin Input Rules](/docs/plate/plugin-input-rules) guide. This page is the kit-first entry for markdown shortcuts and text substitutions.\n\n<ComponentPreview name=\"autoformat-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Markdown shortcuts owned by the feature plugins that understand them.\n- Text substitutions powered by local `createTextSubstitutionInputRule(...)` definitions.\n- Explicit activation through `inputRules`.\n- No hidden default shortcut behavior.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add common text substitutions is with `AutoformatKit`.\n\n<ComponentSource name=\"autoformat-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...AutoformatKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Feature-Owned Markdown Shortcuts\n\nMarkdown shortcuts live on the plugins that own those features:\n\n```tsx\nimport {\n  BlockquoteRules,\n  HeadingRules,\n  HorizontalRuleRules,\n} from '@platejs/basic-nodes';\nimport {\n  BlockquotePlugin,\n  H1Plugin,\n  HorizontalRulePlugin,\n} from '@platejs/basic-nodes/react';\nimport { CodeBlockRules } from '@platejs/code-block';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\nimport { LinkRules } from '@platejs/link';\nimport { LinkPlugin } from '@platejs/link/react';\nimport {\n  BulletedListRules,\n  OrderedListRules,\n  TaskListRules,\n} from '@platejs/list';\nimport { ListPlugin } from '@platejs/list/react';\nimport { MathRules } from '@platejs/math';\nimport { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    H1Plugin.configure({\n      inputRules: [HeadingRules.markdown()],\n    }),\n    BlockquotePlugin.configure({\n      inputRules: [BlockquoteRules.markdown()],\n    }),\n    HorizontalRulePlugin.configure({\n      inputRules: [HorizontalRuleRules.markdown({ variant: '-' })],\n    }),\n    CodeBlockPlugin.configure({\n      inputRules: [CodeBlockRules.markdown({ on: 'match' })],\n    }),\n    ListPlugin.configure({\n      inputRules: [\n        BulletedListRules.markdown({ variant: '-' }),\n        OrderedListRules.markdown({ variant: '.' }),\n        TaskListRules.markdown({ checked: false }),\n      ],\n    }),\n    InlineEquationPlugin.configure({\n      inputRules: [MathRules.markdown({ variant: '$' })],\n    }),\n    EquationPlugin.configure({\n      inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })],\n    }),\n    LinkPlugin.configure({\n      inputRules: [\n        LinkRules.markdown(),\n        LinkRules.autolink({ variant: 'paste' }),\n        LinkRules.autolink({ variant: 'space' }),\n        LinkRules.autolink({ variant: 'break' }),\n      ],\n    }),\n  ],\n});\n```\n\n- Package rule families own the markdown semantics. Kits register explicit rule instances.\n\n### Local Text Substitutions\n\nGeneric substitutions such as smart quotes, arrows, and fractions are best kept\nas local copied plugin code:\n\n```tsx\nimport {\n  createSlatePlugin,\n  createTextSubstitutionInputRule,\n} from 'platejs';\n\nconst ShortcutsPlugin = createSlatePlugin({\n  key: 'shortcuts',\n  inputRules: [\n    createTextSubstitutionInputRule({\n      patterns: [{ format: '—', match: '--' }],\n    }),\n  ],\n});\n\nconst editor = createPlateEditor({\n  plugins: [ShortcutsPlugin],\n});\n```\n\n### Fine-Grained Overrides\n\nRule names are the override unit:\n\n```tsx\nimport { ItalicRules } from '@platejs/basic-nodes';\n\nItalicPlugin.configure({\n  inputRules: [ItalicRules.markdown({ variant: '_' })],\n});\n```\n\n</Steps>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/autoformat.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "block-menu-docs",
      "title": "Block Menu",
      "description": "Context-menu actions for selected editor blocks.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/block-menu.mdx",
          "content": "---\ntitle: Block Menu\ndescription: Context-menu actions for selected editor blocks.\ndocs:\n  - route: /docs/block-selection\n    title: Block Selection\n  - route: /docs/components/block-context-menu\n    title: Block Context Menu\n  - route: /docs/examples/block-menu\n    title: Demo\n  - route: https://pro.platejs.org/docs/examples/block-menu\n    title: Plus\n---\n\nBlock Menu adds a right-click menu on top of block selection. `BlockMenuPlugin` owns the open state and pointer position; `BlockSelectionPlugin` decides which blocks the menu edits. The registry `BlockContextMenu` renders the menu actions.\n\n<ComponentPreview name=\"block-menu-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Right-click block selection.\n- Context menu open state through `openId` and `position`.\n- Delete, duplicate, turn-into, indent, outdent, align, and Ask AI actions.\n- Touch-device and read-only guards.\n- Element-level opt in/out with `data-plate-open-context-menu`.\n- Plus menu with drag-handle entry, combobox filtering, nested actions, colors, comments, and AI.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`BlockMenuKit` spreads `BlockSelectionKit` and renders `BlockContextMenu` above the editable.\n\n<ComponentSource name=\"block-menu-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BlockMenuKit } from '@/components/editor/plugins/block-menu-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BlockMenuKit,\n});\n```\n\n### Render The Menu\n\n`block-context-menu` is the registry UI used by the kit.\n\n<ComponentSource name=\"block-context-menu\" />\n\n### Try The Plus Menu\n\n<ComponentPreviewPro name=\"block-menu-pro\" />\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BlockMenuPlugin` | `@platejs/selection/react` | Stores `openId` and pointer `position`, then exposes menu show/hide APIs. |\n| `BlockSelectionPlugin` | `@platejs/selection/react` | Selects the block under the context-menu event and applies actions to selected blocks. |\n| `BlockSelectionKit` | Registry | Enables context-menu selection and filters non-selectable blocks such as columns, code lines, and table cells. |\n| `BlockMenuKit` | Registry | Combines `BlockSelectionKit` with `BlockMenuPlugin.render.aboveEditable`. |\n| `BlockContextMenu` | Registry UI | Renders Radix context-menu items and calls block-selection transforms. |\n| `block-menu-demo` | Registry example | Shows the default menu in the full editor. |\n| `block-menu-pro` | Plus example | Adds drag-handle entry, nested filtering, colors, comments, and AI actions. |\n\nThe menu is UI state, not document state. The document stores block ids and node properties; it does not store whether a menu is open.\n\n## Manual Setup\n\n<Steps>\n\n### Install Packages\n\n```bash\nnpm install @platejs/selection @platejs/ai\n```\n\n`@platejs/ai` is needed only when you keep the `Ask AI` item from the registry menu.\n\n### Add Plugins\n\nUse `BlockSelectionKit` when you want to keep the registry selection behavior but replace the menu wiring.\n\n```tsx\nimport { BlockMenuPlugin } from '@platejs/selection/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';\nimport { BlockContextMenu } from '@/components/ui/block-context-menu';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    ...BlockSelectionKit,\n    BlockMenuPlugin.configure({\n      render: { aboveEditable: BlockContextMenu },\n    }),\n  ],\n});\n```\n\nUse this lower-level shape only when you are replacing both registry kits.\n\n```tsx\nimport {\n  BlockMenuPlugin,\n  BlockSelectionPlugin,\n} from '@platejs/selection/react';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BlockContextMenu } from '@/components/ui/block-context-menu';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    BlockSelectionPlugin.configure({\n      options: {\n        enableContextMenu: true,\n      },\n    }),\n    BlockMenuPlugin.configure({\n      render: { aboveEditable: BlockContextMenu },\n    }),\n  ],\n});\n```\n\n</Steps>\n\n## Context Menu Rules\n\n| Case | Behavior |\n|------|----------|\n| Right-click on a selectable block | Selects that block, then opens the context menu at the pointer position. |\n| Shift + right-click | Adds the block to the current block selection. |\n| Right-click inside a focused text selection | Leaves the browser context menu unless the block is already selected, void, or explicitly opted in. |\n| Left-click while the menu is open | Prevents the click and hides the menu. |\n| Touch device | Renders children without the context-menu wrapper. |\n| Read-only editor | Prevents the context menu. |\n\nDisable the Plate context menu for a specific surface with `data-plate-open-context-menu={false}`.\n\n```tsx\n<PlateElement data-plate-open-context-menu={false} {...props}>\n  {children}\n</PlateElement>\n```\n\nForce it open from a focused block with `data-plate-open-context-menu=\"true\"` when the block should bypass the focused-selection guard.\n\n## Menu Actions\n\n`BlockContextMenu` acts on the current block selection.\n\n| Action | Source |\n|--------|--------|\n| Ask AI | Opens `AIChatPlugin` after the menu closes. |\n| Delete | Calls `editor.getTransforms(BlockSelectionPlugin).blockSelection.removeNodes()`. |\n| Duplicate | Calls `editor.getTransforms(BlockSelectionPlugin).blockSelection.duplicate()`. |\n| Turn into | Calls the registry `setBlockType` helper for paragraph, headings, blockquote, and code drawing. |\n| Indent / Outdent | Calls `blockSelection.setIndent(1)` or `blockSelection.setIndent(-1)`. |\n| Align | Calls `blockSelection.setNodes({ align })`. |\n\nThe menu focuses block selection after close so keyboard selection remains active.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BLOCK_CONTEXT_MENU_ID` | `@platejs/selection/react` | Built-in open id for the registry context menu. |\n| `BlockMenuPlugin` | `@platejs/selection/react` | Menu state plugin with `openId` and `position` options. |\n| `api.blockMenu.hide()` | `@platejs/selection/react` | Closes the menu and moves its stored position offscreen. |\n| `api.blockMenu.show(id, position?)` | `@platejs/selection/react` | Opens a menu by id and optionally sets pointer coordinates. |\n| `api.blockMenu.showContextMenu(blockId, position)` | `@platejs/selection/react` | Selects one block by id, then opens the context menu at the pointer coordinates. |\n| `BlockSelectionPlugin.options.enableContextMenu` | `@platejs/selection/react` | Enables block selection from right-click events. |\n| `api.blockSelection.addOnContextMenu` | `@platejs/selection/react` | Shared right-click handler used by selectable block node props. |\n| `BlockContextMenu` | Registry UI | Default context menu component used by `BlockMenuKit`. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/block-menu.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "block-placeholder-docs",
      "title": "Block Placeholder",
      "description": "Placeholder text for the active empty block.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/block-placeholder.mdx",
          "content": "---\ntitle: Block Placeholder\ndescription: Placeholder text for the active empty block.\ndocs:\n  - route: /docs/examples/block-placeholder\n    title: Demo\n---\n\nBlock Placeholder injects a `placeholder` prop into the active empty block. It is block-level UI state, not stored document content. Use the editor-level `placeholder` prop for the globally empty editor state.\n\n<ComponentPreview name=\"block-placeholder-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Active-block placeholder text.\n- Per-type placeholder map through `placeholders`.\n- Root-level filtering through `query`.\n- Custom placeholder styling through `className`.\n- Focus, read-only, composition, selection, and empty-editor guards.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`BlockPlaceholderKit` configures `BlockPlaceholderPlugin` for paragraph blocks.\n\n<ComponentSource name=\"block-placeholder-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BlockPlaceholderKit } from '@/components/editor/plugins/block-placeholder-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BlockPlaceholderKit,\n});\n```\n\n### Style The Placeholder\n\nThe registry kit uses a `before:` pseudo-element that reads the injected `placeholder` attribute.\n\n```tsx\nBlockPlaceholderPlugin.configure({\n  options: {\n    className:\n      'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',\n  },\n});\n```\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BlockPlaceholderPlugin` | `platejs/react` / `@platejs/utils/react` | Tracks the current placeholder target and injects block node props. |\n| `BlockPlaceholderKit` | Registry | Configures the default paragraph placeholder and styling. |\n| `block-placeholder-demo` | Registry example | Shows the placeholder on an empty paragraph inside a non-empty editor. |\n| `Editor` `placeholder` prop | `platejs/react` | Covers the globally empty editor state. |\n\nThe plugin stores its current target in `_target`. That option is runtime state for rendering; do not serialize it.\n\n## Manual Setup\n\n<Steps>\n\n### Add The Plugin\n\n`BlockPlaceholderPlugin` is available from `platejs/react`.\n\n```tsx\nimport { KEYS } from 'platejs';\nimport { BlockPlaceholderPlugin, createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    BlockPlaceholderPlugin.configure({\n      options: {\n        className:\n          'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',\n        placeholders: {\n          [KEYS.p]: 'Type something...',\n        },\n        query: ({ path }) => path.length === 1,\n      },\n    }),\n  ],\n});\n```\n\n### Add Type-Specific Copy\n\nKeys in `placeholders` are plugin keys. The plugin resolves each key with `editor.getType(key)` before matching the active block type.\n\n```tsx\nBlockPlaceholderPlugin.configure({\n  options: {\n    placeholders: {\n      [KEYS.p]: 'Type something...',\n      [KEYS.h1]: 'Untitled',\n      [KEYS.blockquote]: 'Quote',\n      [KEYS.codeBlock]: 'Code',\n    },\n  },\n});\n```\n\n</Steps>\n\n## Visibility Rules\n\nThe plugin shows a placeholder only when every gate passes.\n\n| Gate | Requirement |\n|------|-------------|\n| Editor mode | Not read-only and not composing. |\n| Focus | Editor is focused and has a selection. |\n| Selection | Selection is collapsed. |\n| Active block | `editor.api.block()` returns an empty block. |\n| Whole editor | The editor is not in its pristine single-empty-block state. Empty blocks with visible structural state, such as list metadata, still qualify. |\n| Placeholder map | The block type matches one entry in `placeholders`. |\n| Query | `query({ editor, node, path, ...ctx })` returns `true`. |\n\nThe default `query` returns `true` for root blocks only. The whole-editor guard uses `editor.api.isElementStateEmpty`, so only `type` and props claimed by plugins through `node.isMetadataProp` are treated as pristine metadata.\n\n```tsx\nquery: ({ path }) => path.length === 1\n```\n\nUse `query` when placeholders should skip nested content, tables, columns, or app-specific containers.\n\n## Styling\n\nThe plugin injects two props on the target block:\n\n| Prop | Source |\n|------|--------|\n| `placeholder` | Resolved string from `placeholders`. |\n| `className` | `options.className`. |\n\nUse CSS that reads `attr(placeholder)`. Tailwind arbitrary content works well for this because the placeholder text stays in the DOM attribute instead of document data.\n\n```tsx\nclassName:\n  'before:absolute before:pointer-events-none before:text-muted-foreground/80 before:content-[attr(placeholder)]'\n```\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BlockPlaceholderPlugin` | `platejs/react` / `@platejs/utils/react` | Adds block placeholders through injected node props. |\n| `options.placeholders` | `Record<string, string>` | Maps plugin keys to placeholder text. Default: `{ [KEYS.p]: 'Type something...' }`. |\n| `options.query` | `(context) => boolean` | Filters eligible blocks. Default: `({ path }) => path.length === 1`. |\n| `options.className` | `string` | Class applied to the block only while its placeholder is active. |\n| `options._target` | Internal runtime state | Stores the current target node and placeholder string. |\n| `selectors.placeholder(node)` | Plugin selector | Returns the placeholder string for the current target node. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/block-placeholder.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "block-selection-docs",
      "title": "Block Selection",
      "description": "Documentation for Block Selection",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/block-selection.mdx",
          "content": "---\ntitle: Block Selection\ndocs:\n  - route: /docs/components/block-selection\n    title: Block Selection\n---\n\n<ComponentPreview name=\"block-selection-demo\" />\n\n<PackageInfo>\n\nThe Block Selection feature allows users to select and manipulate entire text blocks, as opposed to individual words or characters.\n\n## Features\n\n- Select entire blocks with a single action.\n- Multi-block selection using mouse drag or keyboard shortcuts.\n- Copy, cut, and delete operations on selected blocks.\n- Keyboard shortcuts for quick selection:\n  - `Cmd+A`: Select all blocks.\n  - Arrow keys: Select the block above or below.\n- Customizable styling for selected blocks.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add Block Selection is with the `BlockSelectionKit`, which includes the pre-configured `BlockSelectionPlugin` and the [`BlockSelection`](/docs/plate/components/block-selection) UI component.\n\n<ComponentSource name=\"block-selection-kit\" />\n\n- [`BlockSelection`](/docs/plate/components/block-selection): Renders the selection rectangle around selected blocks.\n\n### Add Kit\n\nThe `BlockSelectionKit` enables the context menu by default and provides a default `isSelectable` logic to exclude common non-selectable blocks like code lines and table cells.\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...BlockSelectionKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/selection\n```\n\n### Add Plugin\n\n```tsx\nimport { BlockSelectionPlugin } from '@platejs/selection/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    BlockSelectionPlugin,\n  ],\n});\n```\n\nPut this plugin before any other plugins overriding `selectAll` – `Cmd+A` (code block, table, column, etc.) to avoid any conflicts.\n\n#### Excluding Blocks from Selection\n\nYou can control which blocks are selectable using `options.isSelectable`. This function receives an element and its path, and should return `true` if the block is selectable.\n\nFor example, to exclude code lines, columns, and table cells:\n\n```tsx\nimport { BlockSelectionPlugin } from '@platejs/selection/react';\n\nBlockSelectionPlugin.configure({\n  options: {\n    isSelectable: (element, path) => {\n      if (['code_line', 'column', 'td'].includes(element.type)) {\n        return false;\n      }\n      // Exclude blocks inside table rows\n      if (editor.api.block({ above: true, at: path, match: { type: 'tr' } })) {\n        return false;\n      }\n      return true;\n    },\n  },\n});\n```\n\n#### Customizing Scroll Behavior\n\nIf your editor is inside a scrollable container, you may need to configure the selection area's boundaries and scroll speed.\n\n1.  Add an `id` to your scroll container, e.g., `id={editor.meta.uid}`.\n2.  Set `position: relative` on the container.\n3.  Use the `areaOptions` to configure the boundaries and scrolling behavior.\n\n```ts\nBlockSelectionPlugin.configure({\n  options: {\n    areaOptions: {\n      boundaries: `#${editor.meta.uid}`,\n      container: `#${editor.meta.uid}`,\n      behaviour: {\n        scrolling: {\n          // Recommended speed, close to native\n          speedDivider: 0.8,\n        },\n        // Threshold to start selection area\n        startThreshold: 4,\n      },\n    },\n  },\n});\n```\n\n#### Full Page Selection\n\nYou can enable block selection for elements outside the `<Editor />` component by adding the `data-plate-selectable` attribute.\n\n```tsx\n<Cover data-plate-selectable />\n<Sidebar data-plate-selectable />\n```\n\nTo prevent unselecting blocks when clicking on certain elements (e.g., a toolbar button), add the `data-plate-prevent-unselect` attribute.\n\n```tsx\n<YourToolbarButton data-plate-prevent-unselect />\n```\n\nTo reset the selection when clicking outside selectable areas, you can use a click handler or call the API directly:\n\n```tsx\n// 1. Direct API call\neditor.api.blockSelection.deselect();\n\n// 2. Click outside handler\nconst handleClickOutside = (event: MouseEvent) => {\n  if (!(event.target as HTMLElement).closest('[data-plate-selectable]')) {\n    editor.api.blockSelection.deselect();\n  }\n};\n```\n\n</Steps>\n\n## Styling\n\n### Selection Area\n\nStyle the selection area by targeting the `.slate-selection-area` class, which is added to the editor container.\n\n```css\n/* Example using Tailwind CSS utility classes */\n'[&_.slate-selection-area]:border [&_.slate-selection-area]:border-primary [&_.slate-selection-area]:bg-primary/10'\n```\n\n### Selected Element\n\nUse the `useBlockSelected` hook to determine if a block is selected. You can render a visual indicator, like the [`BlockSelection`](/docs/plate/components/block-selection) component, which is designed for this purpose.\n\nPlate UI renders this component for all selectable blocks using `render.belowRootNodes`:\n\n```tsx\nrender: {\n  belowRootNodes: (props) => {\n    if (!props.className?.includes('slate-selectable')) return null;\n\n    return <BlockSelection />;\n  },\n},\n```\n\n## Plugins\n\n### `BlockSelectionPlugin`\n\nPlugin for block selection functionality.\n\n<API name=\"BlockSelectionPlugin\">\n<APIOptions>\n  <APIItem name=\"areaOptions\" type=\"PartialSelectionOptions\" optional>\n    Options for the selection area. See [SelectionJS docs](https://github.com/Simonwep/selection-js) for all available options.\n    \n```ts\n{\n  boundaries: [`#${editor.meta.uid}`],\n  container: [`#${editor.meta.uid}`],\n  selectables: [`#${editor.meta.uid} .slate-selectable`],\n  selectionAreaClass: 'slate-selection-area',\n}\n```\n    \n  </APIItem>\n  <APIItem name=\"enableContextMenu\" type=\"boolean\" optional>\n    Enables or disables the context menu for block selection.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"isSelecting\" type=\"boolean\" optional>\n    Indicates whether block selection is currently active.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"onKeyDownSelecting\" type=\"(e: KeyboardEvent) => void\" optional>\n    A function to handle the keydown event when selecting.\n  </APIItem>\n  <APIItem name=\"query\" type=\"QueryNodeOptions\" optional>\n    Options for querying nodes during block selection.\n    - **Default:** `{ maxLevel: 1 }`\n  </APIItem>\n  <APIItem name=\"selectedIds\" type=\"Set<string>\" optional>\n    A set of IDs for the currently selected blocks.\n    - **Default:** `new Set()`\n  </APIItem>\n  <APIItem name=\"anchorId\" type=\"string | null\" optional>\n    (Internal) The ID of the anchor block in the current selection. Used for shift-based selection.\n    - **Default:** `null`\n  </APIItem>\n  <APIItem name=\"isSelectable\" type=\"(element: TElement, path: Path) => boolean\" optional>\n    Function to determine if a block element is selectable.\n    - **Default:** `() => true`\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.blockSelection.add`\n\nAdds one or more blocks to the selection.\n\n<API name=\"add\">\n  <APIParameters>\n    <APIItem name=\"id\" type=\"string | string[]\">\n      The ID(s) of the block(s) to be selected.\n    </APIItem>\n  </APIParameters>\n</API>\n\n### `api.blockSelection.clear`\n\nResets the set of selected IDs to an empty set.\n\n### `api.blockSelection.delete`\n\nRemoves one or more blocks from the selection.\n\n<API name=\"delete\">\n  <APIParameters>\n    <APIItem name=\"id\" type=\"string | string[]\">\n      The ID(s) of the block(s) to remove from selection.\n    </APIItem>\n  </APIParameters>\n</API>\n\n### `api.blockSelection.deselect`\n\nDeselects all blocks and sets the `isSelecting` flag to false.\n\n### `api.blockSelection.focus`\n\nFocuses the block selection shadow input. This input handles copy, delete, and paste events for selected blocks.\n\n### `api.blockSelection.getNodes`\n\nGets the selected blocks in the editor.\n\n<API name=\"getNodes\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ selectionFallback?: boolean }\" optional>\n    Options for getting nodes.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"selectionFallback\" type=\"boolean\" optional>\n    If true, and no blocks are selected by block selection, the method will use\n    the editor's original selection to retrieve blocks. - **Default:** `false`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry[]\">\n  Array of selected block entries.\n</APIReturns>\n</API>\n\n### `api.blockSelection.has`\n\nChecks if one or more blocks are selected.\n\n<API name=\"has\">\n  <APIParameters>\n    <APIItem name=\"id\" type=\"string | string[]\">\n      The ID(s) of the block(s) to check.\n    </APIItem>\n  </APIParameters>\n  <APIReturns>\n    <APIItem type=\"boolean\">Whether the block(s) are selected.</APIItem>\n  </APIReturns>\n</API>\n\n### `api.blockSelection.isSelectable`\n\nChecks if a block at a given path is selectable based on the `isSelectable` plugin option.\n\n<API name=\"isSelectable\">\n  <APIParameters>\n    <APIItem name=\"element\" type=\"TElement\">\n      Block element to check.\n    </APIItem>\n    <APIItem name=\"path\" type=\"Path\">\n      Path to the block element.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"boolean\">Whether the block is selectable.</APIReturns>\n</API>\n\n### `api.blockSelection.moveSelection`\n\nMoves the selection up or down to the next selectable block.\n\nWhen moving up:\n\n- Gets the previous selectable block from the top-most selected block\n- Sets it as the new anchor\n- Clears previous selection and selects only this block\n  When moving down:\n- Gets the next selectable block from the bottom-most selected block\n- Sets it as the new anchor\n- Clears previous selection and selects only this block\n\n<API name=\"moveSelection\">\n  <APIParameters>\n    <APIItem name=\"direction\" type=\"'up' | 'down'\">\n      Direction to move selection.\n    </APIItem>\n  </APIParameters>\n</API>\n\n### `api.blockSelection.selectAll`\n\nSelects all selectable blocks in the editor.\n\n### `api.blockSelection.set`\n\nSets the selection to one or more blocks, clearing any existing selection.\n\n<API name=\"set\">\n  <APIParameters>\n    <APIItem name=\"id\" type=\"string | string[]\">\n      The ID(s) of the block(s) to be selected.\n    </APIItem>\n  </APIParameters>\n</API>\n\n### `api.blockSelection.shiftSelection`\n\nExpands or shrinks the selection based on the anchor block.\n\nFor `Shift+ArrowDown`:\n\n- If anchor is top-most: Expands down by adding block below bottom-most\n- Otherwise: Shrinks from top-most (unless top-most is the anchor)\n  For `Shift+ArrowUp`:\n- If anchor is bottom-most: Expands up by adding block above top-most\n- Otherwise: Shrinks from bottom-most (unless bottom-most is the anchor)\n  The anchor block always remains selected. If no anchor is set, it defaults to:\n- Bottom-most block for `Shift+ArrowUp`\n- Top-most block for `Shift+ArrowDown`\n\n<API name=\"shiftSelection\">\n  <APIParameters>\n    <APIItem name=\"direction\" type=\"'up' | 'down'\">\n      Direction to expand/shrink selection.\n    </APIItem>\n  </APIParameters>\n</API>\n\n## Transforms\n\n### `tf.blockSelection.duplicate`\n\nDuplicates the selected blocks.\n\n### `tf.blockSelection.removeNodes`\n\nRemoves the selected nodes from the editor.\n\n### `tf.blockSelection.select`\n\nSelects the nodes returned by `getNodes()` in the editor and resets selected IDs.\n\n### `tf.blockSelection.setNodes`\n\nSets properties on the selected nodes.\n\n<API name=\"setNodes\">\n  <APIParameters>\n    <APIItem name=\"props\" type=\"Partial<NodeProps<TElement>>\">\n      Properties to set on selected nodes.\n    </APIItem>\n    <APIItem name=\"options\" type=\"SetNodesOptions\" optional>\n      Options for setting nodes.\n    </APIItem>\n  </APIParameters>\n</API>\n\n### `tf.blockSelection.setTexts`\n\nSets text properties on the selected nodes.\n\n<API name=\"setTexts\">\n  <APIParameters>\n    <APIItem name=\"props\" type=\"Partial<NodeProps<TText>>\">\n      Text properties to set on selected nodes.\n    </APIItem>\n    <APIItem name=\"options\" type=\"Omit<SetNodesOptions, 'at'>\" optional>\n      Options for setting text nodes, excluding the 'at' property.\n    </APIItem>\n  </APIParameters>\n</API>\n\n## Hooks\n\n### `useBlockSelectable`\n\nA hook that provides props for making a block element selectable, including context menu behavior.\n\n<API name=\"useBlockSelectable\">\n  <APIReturns type=\"object\">\n    <APIItem name=\"props\" type=\"object\">\n      Props to be spread on the block element.\n      <APISubList>\n        <APISubListItem parent=\"props\" name=\"className\" type=\"string\">\n          Required class for selection functionality. - **Default:**\n          `'slate-selectable'`\n        </APISubListItem>\n        <APISubListItem\n          parent=\"props\"\n          name=\"onContextMenu\"\n          type=\"(event: React.MouseEvent) => void\"\n        >\n          Handles right-click context menu behavior: - Opens context menu for\n          selected blocks - Opens for void elements - Opens for elements with\n          `data-plate-open-context-menu=\"true\"` - Adds block to selection with\n          Shift key for multi-select\n        </APISubListItem>\n      </APISubList>\n    </APIItem>\n  </APIReturns>\n</API>\n\n### `useBlockSelected`\n\n<API name=\"useBlockSelected\">\n  <APIReturns type=\"boolean\">Whether the context block is selected.</APIReturns>\n</API>\n\n### `useBlockSelectionNodes`\n\n<API name=\"useBlockSelectionNodes\">\n  <APIReturns type=\"NodeEntry[]\">Array of selected block entries.</APIReturns>\n</API>\n\n### `useBlockSelectionFragment`\n\n<API name=\"useBlockSelectionFragment\">\n  <APIReturns type=\"Node[]\">Array of selected block nodes.</APIReturns>\n</API>\n\n### `useBlockSelectionFragmentProp`\n\n<API name=\"useBlockSelectionFragmentProp\">\n  <APIReturns type=\"Node[]\">Fragment prop for selected blocks.</APIReturns>\n</API>\n\n### `useSelectionArea`\n\nInitialize and manage selection area functionality.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/block-selection.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "caption-docs",
      "title": "Caption",
      "description": "Add captions to media elements like images, videos, and files.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/caption.mdx",
          "content": "---\ntitle: Caption\ndescription: Add captions to media elements like images, videos, and files.\ndocs:\n  - route: /docs/components/caption\n    title: Caption\n---\n\n<ComponentPreview name=\"media-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Add captions to images, videos, audio files, and other media elements.\n- Arrow navigation selects caption within a block.\n- Inline caption editing with textarea component.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add caption functionality is with the `MediaKit`, which includes the pre-configured `CaptionPlugin` along with media plugins and their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"media-kit\" />\n\n- [`Caption`](/docs/plate/components/caption): Renders caption components for media elements.\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { MediaKit } from '@/components/editor/plugins/media-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...MediaKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/caption\n```\n\n### Add Plugin\n\n```tsx\nimport { CaptionPlugin } from '@platejs/caption/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CaptionPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure which media plugins should support captions:\n\n```tsx\nimport { KEYS } from 'platejs';\nimport { CaptionPlugin } from '@platejs/caption/react';\nimport {\n  AudioPlugin,\n  FilePlugin,\n  ImagePlugin,\n  MediaEmbedPlugin,\n  VideoPlugin,\n} from '@platejs/media/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ImagePlugin,\n    VideoPlugin,\n    AudioPlugin,\n    FilePlugin,\n    MediaEmbedPlugin,\n    CaptionPlugin.configure({\n      options: {\n        query: {\n          allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],\n        },\n      },\n    }),\n  ],\n});\n```\n\n- `query.allow`: Array of plugin keys that support captions.\n\n</Steps>\n\n## Plugins\n\n### `CaptionPlugin`\n\nPlugin for adding caption functionality to media elements.\n\n<API name=\"CaptionPlugin\">\n<APIOptions>\n  <APIItem name=\"query\" type=\"{ allow: string[] }\" required>\n    Configuration for which plugins support captions.\n    <APISubList>\n      <APISubListItem parent=\"query\" name=\"allow\" type=\"string[]\">\n        Plugin keys of the blocks that can have captions.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"focusEndPath\" type=\"Path\" optional>\n    Path to focus at the end of the caption.\n    - **Default:** `null`\n  </APIItem>\n  <APIItem name=\"focusStartPath\" type=\"Path\" optional>\n    Path to focus at the start of the caption.\n    - **Default:** `null`\n  </APIItem>\n  <APIItem name=\"visibleId\" type=\"string\" optional>\n    ID of the currently visible caption.\n    - **Default:** `null`\n  </APIItem>\n</APIOptions>\n</API>\n\n## Types\n\n### `TCaptionElement`\n\nExtends `TElement`.\n\n<API name=\"TCaptionElement\">\n<APIAttributes>\n  <APIItem name=\"caption\" type=\"Descendant[]\" optional>\n    Caption value as an array of descendant nodes.\n  </APIItem>\n</APIAttributes>\n</API>\n\n## Components\n\n### `<Caption>`\n\n<API name=\"Caption\">\n<APIProps>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Options for the caption component.\n  </APIItem>\n  <APIItem name=\"state\" type=\"object\" optional>\n    State for the caption component.\n    <APISubList>\n      <APISubListItem parent=\"state\" name=\"captionString\" type=\"string\" optional>\n        The string representing the caption.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"selected\" type=\"boolean\" optional>\n        Whether the caption component is selected.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"readOnly\" type=\"boolean\" optional>\n        Whether the caption component is in read-only mode.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  \n  <APIOptions type=\"object\">\n  <APIItem name=\"readOnly\" type=\"boolean\" optional>\n    Whether the caption component is in read-only mode.\n  </APIItem>\n</APIOptions>\n</APIProps>\n</API>\n\n### `<CaptionTextarea>`\n\n<API name=\"CaptionTextarea\">\n<APIProps>\n  <APIItem name=\"state\" type=\"object\">\n    State for the caption textarea.\n    <APISubList>\n      <APISubListItem parent=\"state\" name=\"textareaRef\" type=\"Ref\">\n        Reference to the textarea element.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"captionValue\" type=\"TextareaAutosizeProps['value']\">\n        The value of the caption displayed in the textarea.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"setCaptionValue\" type=\"(value: TextareaAutosizeProps['value']) => void\">\n        Function to update the caption value.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"readOnly\" type=\"boolean\">\n        Whether the caption component is in read-only mode.\n      </APISubListItem>\n      <APISubListItem parent=\"state\" name=\"element\" type=\"TCaptionElement\">\n        The caption element.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIProps>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/caption.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "cursor-overlay-docs",
      "title": "Cursor Overlay",
      "description": "Visual feedback for selections and cursor positions when editor loses focus.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/cursor-overlay.mdx",
          "content": "---\ntitle: Cursor Overlay\ndescription: Visual feedback for selections and cursor positions when editor loses focus.\ndocs:\n  - route: /docs/components/cursor-overlay\n    title: Cursor Overlay\n---\n\n<ComponentPreview name=\"cursor-overlay-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Maintains selection highlight when another element is focused.\n- Dynamic selection display (e.g., during AI streaming).\n- Shows cursor position during drag operations.\n- Customizable styling for cursors and selections.\n- Essential for external UI interactions (e.g., link toolbar, AI combobox).\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add cursor overlay functionality is with the `CursorOverlayKit`, which includes the pre-configured `CursorOverlayPlugin` and the [`CursorOverlay`](/docs/plate/components/cursor-overlay) UI component.\n\n<ComponentSource name=\"cursor-overlay-kit\" />\n\n- [`CursorOverlay`](/docs/plate/components/cursor-overlay): Renders cursor and selection overlays.\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { CursorOverlayKit } from '@/components/editor/plugins/cursor-overlay-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...CursorOverlayKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/selection\n```\n\n### Add Plugin\n\n```tsx\nimport { CursorOverlayPlugin } from '@platejs/selection/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CursorOverlayPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure the cursor overlay with a component to render overlays:\n\n```tsx\nimport { CursorOverlayPlugin } from '@platejs/selection/react';\nimport { CursorOverlay } from '@/components/ui/cursor-overlay';\n\nCursorOverlayPlugin.configure({\n  render: {\n    afterEditable: () => <CursorOverlay />,\n  },\n});\n```\n\n- `render.afterEditable`: Assigns [`CursorOverlay`](/docs/plate/components/cursor-overlay) to render after the editable content.\n\n### Editor Container Setup\n\nThe cursor overlay requires a container component to ensure correct positioning. If you're using the [Editor](/docs/plate/components/editor) component, this is handled automatically through `EditorContainer`.\n\nFor custom setups, ensure your editor is wrapped with a container that has the editor's unique ID:\n\n```tsx\nimport { PlateContainer } from 'platejs/react';\n\nexport function EditorContainer(props: React.HTMLAttributes<HTMLDivElement>) {\n  return <PlateContainer {...props} />;\n}\n```\n\n### Preserving Selection Focus\n\nTo maintain the editor's selection state when focusing UI elements, add the `data-plate-focus=\"true\"` attribute to those elements:\n\n```tsx\n<ToolbarButton data-plate-focus=\"true\">\n  {/* toolbar content */}\n</ToolbarButton>\n```\n\nThis prevents the cursor overlay from disappearing when interacting with toolbar buttons or other UI elements.\n\n</Steps>\n\n## Plugins\n\n### `CursorOverlayPlugin`\n\nPlugin that manages cursor and selection overlays for visual feedback.\n\n<API name=\"CursorOverlayPlugin\">\n<APIOptions>\n  <APIItem name=\"cursors\" type=\"Record<string, CursorState<CursorData>>\">\n    Object containing cursor states with their unique identifiers.\n    - **Default:** `{}`\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.cursorOverlay.addCursor`\n\nAdds a cursor overlay with the specified key and state.\n\n<API name=\"addCursor\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"string\">\n    Unique identifier for the cursor (e.g., 'blur', 'drag', 'custom').\n  </APIItem>\n  <APIItem name=\"cursor\" type=\"CursorState<CursorData>\">\n    The cursor state including selection and optional styling data.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `api.cursorOverlay.removeCursor`\n\nRemoves a cursor overlay by its key.\n\n<API name=\"removeCursor\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"string\">\n    The key of the cursor to remove.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Hooks\n\n### `useCursorOverlay`\n\nA hook that manages cursor and selection overlay states, providing real-time cursor positions and selection rectangles.\n\n<API name=\"useCursorOverlay\">\n<APIOptions type=\"object\">\n  <APIItem name=\"minSelectionWidth\" type=\"number\" optional>\n    Minimum width in pixels for a selection rectangle. Useful for making cursor carets more visible.\n    - **Default:** `1`\n  </APIItem>\n  <APIItem name=\"refreshOnResize\" type=\"boolean\" optional>\n    Whether to recalculate cursor positions when the container is resized.\n    - **Default:** `true`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"cursors\" type=\"CursorOverlayState<TCursorData>[]\">\n    Array of cursor states with their corresponding selection rectangles and styling data.\n  </APIItem>\n  <APIItem name=\"refresh\" type=\"() => void\">\n    Function to manually trigger a recalculation of cursor positions.\n  </APIItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/cursor-overlay.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "dnd-docs",
      "title": "Drag & Drop",
      "description": "Drag and drop blocks to reorganize content within the editor.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/dnd.mdx",
          "content": "---\ntitle: Drag & Drop\ndescription: Drag and drop blocks to reorganize content within the editor.\ndocs:\n  - route: https://pro.platejs.org/docs/examples/dnd\n    title: Plus\n  - route: /docs/components/block-draggable\n    title: Block Draggable\n---\n\n<ComponentPreview name=\"dnd-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Drag and drop blocks for content movement and insertion within the editor.\n- Automatic scrolling when dragging blocks near viewport edges.\n- File drop support for inserting media blocks.\n- Visual drop indicators and drag handles.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add drag and drop functionality is with the `DndKit`, which includes the pre-configured `DndPlugin` along with React DnD setup and the [`BlockDraggable`](/docs/plate/components/block-draggable) UI component.\n\n<ComponentSource name=\"dnd-kit\" />\n\n- [`BlockDraggable`](/docs/plate/components/block-draggable): Renders drag handles and drop indicators for blocks.\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { DndKit } from '@/components/editor/plugins/dnd-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...DndKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/dnd react-dnd react-dnd-html5-backend\n```\n\n### Add Plugin\n\n```tsx\nimport { DndPlugin } from '@platejs/dnd';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    DndPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfigure drag and drop with a draggable component and DnD provider:\n\n```tsx\nimport { DndProvider } from 'react-dnd';\nimport { HTML5Backend } from 'react-dnd-html5-backend';\nimport { DndPlugin } from '@platejs/dnd';\n\nDndPlugin.configure({\n  render: {\n    aboveSlate: ({ children }) => (\n      <DndProvider backend={HTML5Backend}>{children}</DndProvider>\n    ),\n  },\n});\n```\n\n- `render.aboveNodes`: Assigns [`BlockDraggable`](/docs/plate/components/block-draggable) to render drag handles above blocks.\n- `render.aboveSlate`: Wraps the editor with `DndProvider` and `HTML5Backend`. Remove this if you already have `DndProvider` in your React tree.\n\n### Advanced Configuration\n\nAdd automatic scrolling and file drop handling:\n\n```tsx\nimport { DndPlugin } from '@platejs/dnd';\nimport { PlaceholderPlugin } from '@platejs/media/react';\n\nDndPlugin.configure({\n  options: {\n    enableScroller: true,\n    onDropFiles: ({ dragItem, editor, target }) => {\n      editor\n        .getTransforms(PlaceholderPlugin)\n        .insert.media(dragItem.files, { at: target, nextBlock: false });\n    },\n  },\n  render: {\n    aboveNodes: BlockDraggable,\n    aboveSlate: ({ children }) => (\n      <DndProvider backend={HTML5Backend}>{children}</DndProvider>\n    ),\n  },\n});\n```\n\n- `enableScroller`: Enables automatic scrolling when dragging blocks near the viewport edges.\n- `onDropFiles`: Handles file drops by inserting them as media blocks at the target location.\n\n</Steps>\n\n## Plugins\n\n### `DndPlugin`\n\nPlugin for drag and drop functionality within the editor.\n\n<API name=\"DndPlugin\">\n<APIOptions>\n  <APIItem name=\"enableScroller\" type=\"boolean\" optional>\n    Enables automatic scrolling when dragging blocks near viewport edges.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"scrollerProps\" type=\"Partial<ScrollerProps>\" optional>\n    Props for the `Scroller` component when `enableScroller` is true.\n  </APIItem>\n  <APIItem name=\"onDropFiles\" type=\"function\" optional>\n    Handler for file drop events.\n    <APISubList>\n      <APISubListItem parent=\"onDropFiles\" name=\"id\" type=\"string\">\n        ID of the target element.\n      </APISubListItem>\n      <APISubListItem parent=\"onDropFiles\" name=\"dragItem\" type=\"FileDragItemNode\">\n        Object containing the dropped files.\n      </APISubListItem>\n      <APISubListItem parent=\"onDropFiles\" name=\"editor\" type=\"PlateEditor\">\n        The editor instance.\n      </APISubListItem>\n      <APISubListItem parent=\"onDropFiles\" name=\"target\" type=\"Path\">\n        Path where files should be inserted.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"dropTarget\" type=\"{ id: string | null; line: DropLineDirection }\" optional>\n    The current drop target state containing both the target element id and drop line direction.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `focusBlockStartById`\n\nSelects the start of a block by ID and focuses the editor.\n\n<API name=\"focusBlockStartById\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    The ID of the block to be focused.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `getBlocksWithId`\n\nReturns an array of blocks that have an ID.\n\n<API name=\"getBlocksWithId\">\n<APIOptions type=\"EditorNodesOptions\">\n  The options for getting node entries.\n</APIOptions>\n\n<APIReturns type=\"NodeEntry[]\">\n  Array of blocks that have an ID.\n</APIReturns>\n</API>\n\n### `selectBlockById`\n\nSelects a block by its ID.\n\n<API name=\"selectBlockById\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\">\n    The ID of the block to select.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Hooks\n\n### `useDndNode`\n\nA custom hook that combines the `useDragNode` and `useDropNode` hooks to enable dragging and dropping of a node from the editor. It provides a default preview for the dragged node, which can be customized or disabled.\n\n<API name=\"useDndNode\">\n<APIOptions type=\"UseDndNodeOptions\">\n  <APIItem name=\"element\" type=\"TElement\">\n    The node to be dragged.\n  </APIItem>\n  <APIItem name=\"type\" type=\"string\" optional>\n    The type of drag item.\n    - **Default:** `'block'`\n  </APIItem>\n  <APIItem name=\"nodeRef\" type=\"any\">\n    The ref of the node to be dragged.\n  </APIItem>\n  <APIItem name=\"orientation\" type=\"'horizontal' | 'vertical'\" optional>\n    The orientation of drag and drop.\n    - **Default:** `'vertical'`\n  </APIItem>\n  <APIItem name=\"canDropNode\" type=\"(args: { dragEntry: NodeEntry; dragItem: DragItemNode; dropEntry: NodeEntry; editor: PlateEditor }) => boolean\" optional>\n    Callback to determine if a node can be dropped at the current location.\n  </APIItem>\n  <APIItem name=\"preview\" type=\"previewOptions\" optional>\n    The preview options for the dragged node.\n  </APIItem>\n  <APIItem name=\"drag\" type=\"dragOptions\" optional>\n    The drag options for the dragged node.\n  </APIItem>\n  <APIItem name=\"drop\" type=\"dropOptions\" optional>\n    The drop options for the dragged node.\n  </APIItem>\n  <APIItem name=\"onDropHandler\" type=\"string\" optional>\n    Handler called when the node is dropped.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"isDragging\" type=\"boolean\">\n    Whether the node is currently being dragged.\n  </APIItem>\n  <APIItem name=\"isOver\" type=\"boolean\">\n    Whether the dragged node is over a drop target.\n  </APIItem>\n  <APIItem name=\"dragRef\" type=\"ConnectDragSource\">\n    Drag reference for the draggable element.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useDragNode`\n\nA custom hook that enables dragging of a node from the editor using the `useDrag` hook from `react-dnd`.\n\n<API name=\"useDragNode\">\n<APIOptions type=\"UseDragNodeOptions\">\n  <APIItem name=\"element\" type=\"TElement\">\n    The node to be dragged.\n  </APIItem>\n  <APIItem name=\"item\" type=\"DragObject | DragObjectFactory<DragObject>\" optional>\n    Drag object or factory function that returns it.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `useDraggable`\n\nA custom hook that enables draggable behavior for a given element. It provides a preview for the element, which can be customized or disabled.\n\n<API name=\"useDraggable\">\n<APIOptions type=\"object\">\n  <APIItem name=\"element\" type=\"TElement\">\n    The element to make draggable.\n  </APIItem>\n  <APIItem name=\"orientation\" type=\"'horizontal' | 'vertical'\" optional>\n    Orientation of drag and drop.\n    - **Default:** `'vertical'`\n  </APIItem>\n  <APIItem name=\"type\" type=\"string\" optional>\n    Type of drag item.\n    - **Default:** `'block'`\n  </APIItem>\n  <APIItem name=\"onDropHandler\" type=\"function\" optional>\n    Handler called when element is dropped.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"handleRef\" type=\"(element: Element | React.ReactElement | React.RefObject<any> | null) => void\">\n    Drag source connector function.\n  </APIItem>\n  <APIItem name=\"isDragging\" type=\"boolean\">\n    Whether element is being dragged.\n  </APIItem>\n  <APIItem name=\"previewRef\" type=\"React.RefObject<HTMLDivElement>\">\n    Reference to draggable element.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useDropNode`\n\nA custom hook that enables dropping a node on the editor. It uses the `useDrop` hook from `react-dnd` to handle the drop behavior.\n\n<API name=\"useDropNode\">\n<APIOptions type=\"UseDropNodeOptions\">\n  <APIItem name=\"nodeRef\" type=\"any\">\n    Reference to the node being dragged.\n  </APIItem>\n  <APIItem name=\"element\" type=\"TElement\">\n    The node to which the drop line is attached.\n  </APIItem>\n  <APIItem name=\"dropLine\" type=\"string\">\n    Current value of the drop line.\n  </APIItem>\n  <APIItem name=\"onChangeDropLine\" type=\"function\">\n    Callback when drop line changes.\n  </APIItem>\n  <APIItem name=\"onDropHandler\" type=\"object\">\n    Callback that intercepts drop handling.\n    - Returns `true` to prevent default behavior\n    - Returns `false` to run default behavior after\n  </APIItem>\n</APIOptions>\n</API>\n\n### `useDropLine`\n\nReturns the current drop line state for an element.\n\n<API name=\"useDropLine\">\n<APIOptions type=\"object\">\n  <APIItem name=\"id\" type=\"string\" optional>\n    Element ID to show drop line for.\n    - **Default:** Current element ID\n  </APIItem>\n  <APIItem name=\"orientation\" type=\"'horizontal' | 'vertical'\" optional>\n    Filter drop lines by orientation.\n    - **Default:** `'vertical'`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"dropLine\" type=\"'top' | 'bottom' | 'left' | 'right' | ''\">\n    Current drop line direction.\n  </APIItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/dnd.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "find-replace-docs",
      "title": "Find",
      "description": "Highlight search matches in editable text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/find-replace.mdx",
          "content": "---\ntitle: Find\ndescription: Highlight search matches in editable text.\ndocs:\n  - route: /docs/components/search-highlight-node\n    title: Search Highlight Leaf\n  - route: /docs/examples/find-replace\n    title: Find Replace Demo\n---\n\nFind highlights matching text in editor blocks. `FindReplacePlugin` owns the search option and decorations; your UI owns the search input and any replace command.\n\n<ComponentPreview name=\"find-replace-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Case-insensitive search highlighting.\n- Multiple matches inside the same block.\n- Matches split across adjacent text leaves.\n- App-controlled search state through plugin options.\n- Registry highlight leaf for visible matches.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Plugin\n\nConfigure `FindReplacePlugin` with a leaf renderer for highlighted ranges.\n\n```tsx\nimport { FindReplacePlugin } from '@platejs/find-replace';\nimport { createPlateEditor } from 'platejs/react';\n\nimport { SearchHighlightLeaf } from '@/components/ui/search-highlight-node';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    FindReplacePlugin.configure({\n      render: { node: SearchHighlightLeaf },\n    }),\n  ],\n});\n```\n\n### Control The Search\n\nUpdate the `search` option from your toolbar and ask Plate to redecorate the editor.\n\n```tsx title=\"components/find-toolbar.tsx\" showLineNumbers\nimport { FindReplacePlugin } from '@platejs/find-replace';\nimport { useEditorPlugin, usePluginOption } from 'platejs/react';\n\nexport function FindToolbar() {\n  const { editor, setOption } = useEditorPlugin(FindReplacePlugin);\n  const search = usePluginOption(FindReplacePlugin, 'search');\n\n  return (\n    <input\n      type=\"search\"\n      value={search}\n      onChange={(event) => {\n        setOption('search', event.target.value);\n        editor.api.redecorate();\n      }}\n    />\n  );\n}\n```\n\n### Use The Registry Example\n\nThe demo combines `FindReplacePlugin`, `FixedToolbar`, `Input`, and `SearchHighlightLeaf`.\n\n<ComponentSource name=\"find-replace-demo\" />\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `FindReplacePlugin` | `@platejs/find-replace` | Stores `options.search`, registers a leaf node, and wires `decorateFindReplace`. |\n| `decorateFindReplace` | `@platejs/find-replace` | Finds matches in one element's text children and returns Slate ranges. |\n| `SearchHighlightLeaf` | Registry UI | Renders decorated ranges with a yellow background. |\n| `FindToolbar` | App or registry example | Updates `options.search` and calls `editor.api.redecorate()`. |\n| Replace actions | App code | Perform text replacement with editor transforms. The package does not replace content for you. |\n\nThe package is headless. The registry gives you a visible highlight leaf and a demo toolbar.\n\n## Search Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Empty `search` | Returns no ranges. |\n| Case handling | Text and query are lowercased before matching. |\n| Match scope | Only element nodes whose direct children are all text nodes are decorated. |\n| Multiple matches | Each match becomes one or more ranges. |\n| Split text leaves | A match crossing adjacent leaves is split into per-leaf ranges. |\n| Range payload | Each range includes `[FindReplacePlugin.key]: true` and the matched `search` slice. |\n\n`decorateFindReplace` searches the concatenated text for one element, then maps each match back to the original child text paths.\n\n## Search Highlight Leaf\n\nInstall the registry leaf when you want the default yellow highlight.\n\n```bash\nnpx shadcn@latest add @plate/search-highlight-node\n```\n\nThe copied component is intentionally small:\n\n```tsx title=\"components/ui/search-highlight-node.tsx\"\nimport { type PlateLeafProps, PlateLeaf } from 'platejs/react';\n\nexport function SearchHighlightLeaf(props: PlateLeafProps) {\n  return <PlateLeaf {...props} className=\"bg-yellow-100\" />;\n}\n```\n\nChange the class name when your design system needs a different search color.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `FindReplacePlugin` | `@platejs/find-replace` | Main plugin for search highlighting. |\n| `options.search` | `string` | Query text to highlight. Defaults to `''`. |\n| `decorateFindReplace` | `@platejs/find-replace` | Decoration function used by the plugin. |\n| `SearchRange.search` | `string` | The query slice represented by that range. |\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/find-replace.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "multi-select-docs",
      "title": "Multi Select",
      "description": "Documentation for Multi Select",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/multi-select.mdx",
          "content": "---\ntitle: Multi Select\ndocs:\n  - route: /docs/components/tag-node\n  - route: /docs/components/select-editor\n---\n\n<ComponentPreview name=\"select-editor-demo\" />\n\n<PackageInfo>\n\n## Features\n\nUnlike traditional input-based multi-selects, this component is built on top of Plate editor, providing:\n\n- Full history support (undo/redo)\n- Native cursor navigation between and within tags\n- Select one to many tags\n- Copy/paste tags\n- Drag and drop to reorder tags\n- Read-only mode\n- Duplicate tags prevention\n- Create new tags with case insensitive matching\n- Search text cleanup and whitespace trimming\n- Fuzzy search powered by [cmdk](https://github.com/pacocoursey/cmdk)\n\n</PackageInfo>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/tag\n```\n\n### Add Plugins\n\n```tsx\nimport { MultiSelectPlugin } from '@platejs/tag/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    MultiSelectPlugin, // Multi-select editor with tag functionality\n  ],\n});\n```\n\n### Configure Plugins\n\n```tsx\nimport { MultiSelectPlugin } from '@platejs/tag/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { TagElement } from '@/components/ui/tag-node';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    MultiSelectPlugin.withComponent(TagElement),\n  ],\n});\n```\n\n- `MultiSelectPlugin`: Extends TagPlugin and constrains the editor to only contain tag elements\n- `withComponent`: Assigns [`TagElement`](/docs/plate/components/tag-node) to render tag components\n\n### Add SelectEditor\n\n<ComponentInstallation name=\"select-editor\" inline />\n\n### Basic Example\n\n```tsx\nimport { MultiSelectPlugin } from '@platejs/tag/react';\nimport { TagElement } from '@/components/ui/tag-node';\nimport {\n  SelectEditor,\n  SelectEditorContent,\n  SelectEditorInput,\n  SelectEditorCombobox,\n  type SelectItem,\n} from '@/components/ui/select-editor';\n\n// Define your items\nconst ITEMS: SelectItem[] = [\n  { value: 'React' },\n  { value: 'TypeScript' },\n  { value: 'JavaScript' },\n];\n\nexport default function MySelectEditor() {\n  const [value, setValue] = React.useState<SelectItem[]>([ITEMS[0]]);\n\n  return (\n    <SelectEditor\n      value={value}\n      onValueChange={setValue}\n      items={ITEMS}\n    >\n      <SelectEditorContent>\n        <SelectEditorInput placeholder=\"Select items...\" />\n        <SelectEditorCombobox />\n      </SelectEditorContent>\n    </SelectEditor>\n  );\n}\n```\n\n### Form Example\n\n<ComponentSource name=\"select-editor-demo\" />\n\n</Steps>\n\n## Plugins\n\n### TagPlugin\n\nInline void element plugin for individual tag functionality.\n\n### MultiSelectPlugin\n\nExtension of `TagPlugin` that constrains the editor to only contain tag elements, enabling multi-select behavior with automatic text cleanup and duplicate prevention.\n\n## API\n\n### tf.insert.tag\n\nInserts new multi-select element at current selection.\n\n<API name=\"tf.insert.tag\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"TTagProps\">\n    Properties for multi-select element.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"TTagProps\">\n  <APIItem name=\"value\" type=\"string\">\n    Unique value of multi-select element.\n  </APIItem>\n</APIOptions>\n</API>\n\n### getSelectedItems\n\nGets all tag items in the editor.\n\n<API name=\"getSelectedItems\">\n<APIReturns type=\"TTagProps[]\">\n  Array of tag items in editor.\n</APIReturns>\n</API>\n\n### isEqualTags\n\nUtility function to compare two sets of tags for equality, ignoring order.\n\n<API name=\"isEqualTags\">\n<APIParameters>\n  <APIItem name=\"newTags\" type=\"TTagProps[]\" optional>\n    New tags to compare against current editor tags.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  Whether both sets contain same values.\n</APIReturns>\n</API>\n\n## Hooks\n\n### useSelectedItems\n\nHook to get the currently selected tag items in the editor.\n\n<API name=\"useSelectedItems\">\n<APIReturns type=\"TTagProps[]\">\n  Array of selected tag items with values and properties.\n</APIReturns>\n</API>\n\n### useSelectableItems\n\nHook to get the available items that can be selected, filtered by search and excluding already selected items.\n\n<API name=\"useSelectableItems\">\n<APIOptions type=\"options\">\n  <APIItem name=\"allowNew\" type=\"boolean\" optional>\n    Whether to allow creating new items.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"filter\" type=\"(value: string, search: string) => boolean\" optional>\n    Custom filter function for items.\n  </APIItem>\n  <APIItem name=\"items\" type=\"T[]\" optional>\n    Array of available items.\n  </APIItem>\n  <APIItem name=\"newItemFilter\" type=\"(search: string) => boolean\" optional>\n    Filter function for new items.\n  </APIItem>\n  <APIItem name=\"newItemPosition\" type=\"'end' | 'start'\" optional>\n    Position of new items in list.\n    - **Default:** `'end'`\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"T[]\">\n  Filtered array of selectable items.\n</APIReturns>\n</API>\n\n### useSelectEditorCombobox\n\nHook to handle combobox behavior in the editor, including text cleanup and item selection.\n\n<API name=\"useSelectEditorCombobox\">\n<APIOptions type=\"options\">\n  <APIItem name=\"open\" type=\"boolean\">\n    Whether combobox is open.\n  </APIItem>\n  <APIItem name=\"selectFirstItem\" type=\"() => void\">\n    Function to select first combobox item.\n  </APIItem>\n  <APIItem name=\"onValueChange\" type=\"(items: TTagProps[]) => void\" optional>\n    Callback when selected items change.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Types\n\n### TTagElement\n\n```ts\ntype TTagElement = TElement & {\n  value: string;\n  [key: string]: unknown;\n};\n```\n\n### TTagProps\n\n```ts\ntype TTagProps = {\n  value: string;\n  [key: string]: unknown;\n};\n```",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/multi-select.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "navigation-feedback-docs",
      "title": "Navigation Feedback",
      "description": "Flash a landed target after TOC, footnote, search, or custom navigation jumps.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/navigation-feedback.mdx",
          "content": "---\ntitle: Navigation Feedback\ndescription: Flash a landed target after TOC, footnote, search, or custom navigation jumps.\n---\n\n<ComponentPreview name=\"toc-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Briefly highlight the landed node after a navigation jump.\n- Replace any previous flash deterministically — no stacked timers, no doubled animations.\n- Expose transforms for flash-only and full select-focus-scroll-flash flows.\n- Inject `data-nav-*` attributes so any render can style the active target.\n\n</PackageInfo>\n\nNavigation Feedback is a small core plugin for \"you landed here\" UX. It doesn't own navigation itself — it flashes the landed node so the reader can see where the editor just moved. Reach for it in TOC jumps, footnote navigation, search results, and custom outline surfaces.\n\n## Usage\n\n<Steps>\n\n### Core Plugin\n\n`NavigationFeedbackPlugin` is part of Plate core and is included by `createPlateEditor` automatically. You don't need to add it to the `plugins` array for the defaults to work.\n\n### Configure Duration\n\nThe default flash lasts 1.6 seconds. If you want it tighter or longer, use the top-level `navigationFeedback` editor option:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  navigationFeedback: {\n    duration: 1200,\n  },\n});\n```\n\n- `navigationFeedback.duration`: Default flash duration in milliseconds. **Default:** `1600`.\n\nDisable the plugin entirely if you don't want any landed-target flash:\n\n```tsx\nconst editor = createPlateEditor({\n  navigationFeedback: false,\n});\n```\n\n### Flash a Target\n\nWhen another action already handled scroll, focus, and selection, and you only want the visual confirmation, call `editor.tf.navigation.flashTarget`:\n\n```tsx\neditor.tf.navigation.flashTarget({\n  target: {\n    path: [12],\n    type: 'node',\n  },\n});\n```\n\nThe call returns `false` when the path doesn't resolve to a node, so you can branch on stale targets without throwing.\n\nOverride the duration or variant per call when this jump deserves its own look:\n\n```tsx\neditor.tf.navigation.flashTarget({\n  duration: 1500,\n  target: {\n    path: [12],\n    type: 'node',\n  },\n  variant: 'mention',\n});\n```\n\nThe `variant` string is written straight to `data-nav-highlight`, so you can key distinct CSS animations off it — `'navigated'`, `'mention'`, `'found'`, whatever you need.\n\n### Navigate and Flash\n\nMost navigation actions should do four things at once: move the selection, focus the editor, scroll the target into view, and flash the landed node. `editor.tf.navigation.navigate` does all four, and each step is independent — skip any of them with the flags below.\n\nHere's how the footnote plugin jumps from a reference to its definition:\n\n```tsx\neditor.tf.navigation.navigate({\n  focus: true,\n  scroll: true,\n  scrollTarget: point,\n  select: {\n    anchor: { offset: 0, path: firstTextPath },\n    focus: { offset: 0, path: firstTextPath },\n  },\n  target: {\n    path: definition[1],\n    type: 'node',\n  },\n});\n```\n\nSkip the flash when the jump should be silent:\n\n```tsx\neditor.tf.navigation.navigate({\n  flash: false,\n  select: point,\n  target: { path: [12], type: 'node' },\n});\n```\n\nOr tune the flash per call:\n\n```tsx\neditor.tf.navigation.navigate({\n  flash: { duration: 1200, variant: 'mention' },\n  select: point,\n  target: { path: [12], type: 'node' },\n});\n```\n\nIf you don't pass `scrollTarget`, Plate picks a scroll point in this order: `select.focus`, `select.anchor`, `select` (when it's a `Point`), then `editor.api.start(target.path)`.\n\n### Style the Landed Target\n\nWhenever a target is active, the plugin injects transient attributes onto that node's DOM and a CSS variable with the current duration:\n\n| Attribute | Value |\n| --- | --- |\n| `data-nav-target` | `\"true\"` on the active node. |\n| `data-nav-highlight` | Current `variant` (e.g. `\"navigated\"`). |\n| `data-nav-cycle` | `\"0\"` or `\"1\"` — alternates per flash so CSS animations restart cleanly. |\n| `data-nav-pulse` | Monotonic pulse counter. Useful for debugging repeat triggers. |\n| `--plate-nav-feedback-duration` | Inline CSS variable set to `${duration}ms`. |\n\nStyle them anywhere your editor styles live:\n\n```css\n.slate-editor [data-nav-highlight] {\n  border-radius: 0.375rem;\n}\n\n.slate-editor [data-nav-highlight][data-nav-cycle='0'] {\n  animation: plate-nav-highlight-a var(--plate-nav-feedback-duration, 900ms)\n    ease-out;\n}\n\n.slate-editor [data-nav-highlight][data-nav-cycle='1'] {\n  animation: plate-nav-highlight-b var(--plate-nav-feedback-duration, 900ms)\n    ease-out;\n}\n```\n\n<Callout type=\"info\">\n  **Why two animations?** Flashing the same node twice in a row on the same keyframe name wouldn't restart the animation. The plugin alternates `data-nav-cycle` between `0` and `1` so adjacent flashes run different animation names and the browser replays cleanly.\n</Callout>\n\n### Highlight Custom Renders\n\nAttribute injection fires through the plugin's `nodeProps` inject, so any standard `PlateElement` render that spreads its `attributes` onto the root DOM node picks up `data-nav-*` for free.\n\nFor atoms, inline voids, or components that need the highlight state inside JSX (e.g. on a nested button), read it with `useNavigationHighlight`:\n\n```tsx\nimport { useNavigationHighlight, usePath } from 'platejs/react';\n\nexport function FootnoteReferenceElement(props) {\n  const path = usePath();\n  const highlight = useNavigationHighlight(path);\n\n  return (\n    <PlateElement\n      {...props}\n      attributes={{\n        ...props.attributes,\n        'data-nav-cycle': highlight ? String(highlight.cycle) : undefined,\n        'data-nav-highlight': highlight?.variant,\n        'data-nav-pulse': highlight ? String(highlight.pulse) : undefined,\n        'data-nav-target': highlight ? 'true' : undefined,\n        style: {\n          ...props.attributes.style,\n          '--plate-nav-feedback-duration': highlight\n            ? `${highlight.duration}ms`\n            : undefined,\n        },\n      }}\n    >\n      {props.children}\n    </PlateElement>\n  );\n}\n```\n\nThe hook returns `null` when this node isn't the active target and the full target (`{ cycle, duration, path, pulse, type, variant }`) when it is. Pass a `Path`, a `TElement`, or a `TText` — paths go through, nodes get resolved via `editor.api.findPath`.\n\nDone. You now have a deterministic flash on every jump and the wiring to style or extend it.\n\n</Steps>\n\n## Plugins\n\n### `NavigationFeedbackPlugin`\n\nCore plugin for transient \"landed target\" feedback after successful navigation.\n\n<API name=\"NavigationFeedbackPlugin\">\n<APIOptions>\n  <APIItem name=\"duration\" type=\"number\">\n    Default flash duration in milliseconds.\n    - **Default:** `1600`\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.navigation.activeTarget`\n\nGet the current flashed target, or `null` when none is active. The returned target carries a resolved `path`, so later edits that shift the target keep the highlight on the right node.\n\n<API name=\"activeTarget\">\n<APIReturns>\n  <APIItem name=\"return\" type=\"NavigationFeedbackActiveTarget | null\">\n    Active target `{ cycle, duration, path, pulse, type, variant }`, or `null`.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.navigation.clear`\n\nClear the current feedback target immediately. Safe to call when nothing is active.\n\n<API name=\"clear\" />\n\n### `api.navigation.isTarget`\n\nCheck whether a given path is the current flashed target.\n\n<API name=\"isTarget\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to compare against the active target.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `true` when there is an active target and its path equals `path`.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Transforms\n\n### `tf.navigation.flashTarget`\n\nFlash a target node without changing selection, focus, or scroll. Replaces any active flash on the same editor.\n\n<API name=\"flashTarget\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"{ path: Path; type: 'node' }\">\n    Node target to flash.\n  </APIItem>\n  <APIItem name=\"duration\" type=\"number\" optional>\n    Override the default duration for this call.\n  </APIItem>\n  <APIItem name=\"variant\" type=\"string\" optional>\n    Highlight variant stored in `data-nav-highlight`.\n    - **Default:** `navigated`\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when the path doesn't resolve to a node, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.navigation.navigate`\n\nSelect, focus, scroll, and flash a target in one call. Each step is independent — skip any of them with the flags below.\n\n<API name=\"navigate\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"{ path: Path; type: 'node' }\">\n    Node target to navigate to.\n  </APIItem>\n  <APIItem name=\"select\" type=\"Point | TRange\" optional>\n    Point (collapsed) or range to apply before scrolling and flashing.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"boolean\" optional>\n    Focus the editor after selection.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"scroll\" type=\"boolean\" optional>\n    Scroll the resolved point into view.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"scrollTarget\" type=\"Point\" optional>\n    Explicit point to scroll into view. Falls back to `select.focus`, `select.anchor`, `select`, then `editor.api.start(target.path)`.\n  </APIItem>\n  <APIItem name=\"flash\" type=\"false | { duration?: number; variant?: string }\" optional>\n    Per-call flash config. Pass `false` to navigate without flashing.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when the path doesn't resolve to a node, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.navigation.clear`\n\nClear the current flashed target immediately. Same effect as `api.navigation.clear`.\n\n<API name=\"clear\" />\n\n## Hooks\n\n### `useNavigationHighlight`\n\nSubscribe a custom render to the active navigation target. Returns the target metadata when the given path/node matches, `null` otherwise.\n\n<API name=\"useNavigationHighlight\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"Path | TElement | TText | null | undefined\">\n    Path to compare, or a node the hook resolves via `editor.api.findPath`.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NavigationFeedbackActiveTarget | null\">\n    Active target metadata when this node is the current flashed target, `null` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Related Docs\n\n- [Table of Contents](/docs/plate/toc)\n- [Footnote](/docs/plate/footnote)\n- [Editor Methods](/docs/plate/editor-methods)\n- [Plugin Configuration](/docs/plate/plugin)\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/navigation-feedback.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "tabbable-docs",
      "title": "Tabbable",
      "description": "Documentation for Tabbable",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/tabbable.mdx",
          "content": "---\ntitle: Tabbable\n---\n\n<ComponentPreview name=\"tabbable-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Ensures consistent tab order between tabbable elements in the editor\n- Manages focus transitions between void elements and external DOM elements\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add the tabbable plugin is with the `TabbableKit`, which includes pre-configured `TabbablePlugin` with smart query logic to avoid conflicts with other plugins.\n\n<ComponentSource name=\"tabbable-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { TabbableKit } from '@/components/editor/plugins/tabbable-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...TabbableKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/tabbable\n```\n\n### Add Plugin\n\n```tsx\nimport { TabbablePlugin } from '@platejs/tabbable/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    TabbablePlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\n```tsx\nimport { TabbablePlugin } from '@platejs/tabbable/react';\nimport { createPlateEditor } from 'platejs/react';\nimport { KEYS } from 'platejs';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    TabbablePlugin.configure({\n      options: {\n        query: (event) => {\n          // Disable when in lists or code blocks\n          const inList = editor.api.some({ match: { type: KEYS.li } });\n          const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });\n          return !inList && !inCodeBlock;\n        },\n        globalEventListener: true,\n        isTabbable: (tabbableEntry) => \n          editor.api.isVoid(tabbableEntry.slateNode),\n      },\n    }),\n  ],\n});\n```\n\n- `options.query`: Function to dynamically enable/disable the plugin based on editor state.\n- `options.globalEventListener`: When `true`, adds event listener to document instead of editor.\n- `options.isTabbable`: Function to determine which elements should be included in tab order.\n\n</Steps>\n\n## Advanced Usage\n\n### Conflicts with Other Plugins\n\nThe Tabbable plugin may cause issues with other plugins that handle the `Tab` key, such as:\n\n- Lists\n- Code blocks\n- Indent plugin\n\nUse the `query` option to disable the Tabbable plugin when the `Tab` key should be handled by another plugin:\n\n```tsx\nquery: (event) => {\n  const inList = editor.api.some({ match: { type: KEYS.li } });\n  const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });\n  return !inList && !inCodeBlock;\n},\n```\n\nAlternatively, if you're using the Indent plugin, you can enable the Tabbable plugin only when a specific type of node is selected, such as voids:\n\n```tsx\nquery: (event) => !!editor.api.some({\n  match: (node) => editor.api.isVoid(node),\n}),\n```\n\n### Non-void Slate Nodes\n\nOne `TabbableEntry` will be created for each tabbable DOM element in the editor, as determined using the [tabbable](https://www.npmjs.com/package/tabbable) NPM package. The list of tabbables is then filtered using `isTabbable`.\n\nBy default, `isTabbable` only returns true for entries inside void Slate nodes. You can override `isTabbable` to add support for DOM elements contained in other types of Slate node:\n\n```tsx\n// Enable tabbable DOM elements inside CUSTOM_ELEMENT\nisTabbable: (tabbableEntry) => (\n  tabbableEntry.slateNode.type === CUSTOM_ELEMENT ||\n  editor.api.isVoid(tabbableEntry.slateNode)\n),\n```\n\n### DOM Elements Outside the Editor\n\nIn some circumstances, you may want to allow users to tab from the editor to a DOM element rendered outside the editor, such as an interactive popover.\n\nTo do this, override `insertTabbableEntries` to return an array of `TabbableEntry` objects, one for each DOM element outside the editor that you want to include in the tabbable list. The `slateNode` and `path` of the `TabbableEntry` should refer to the Slate node the user's cursor will be inside when the DOM element should be tabbable to.\n\nSet the `globalEventListener` option to `true` to make sure the Tabbable plugin is able to return the user's focus to the editor.\n\nFor example, if the DOM element appears when a link is selected, the `slateNode` and `path` should be that of the link.\n\n```tsx\n// Add buttons inside .my-popover to the list of tabbables\nglobalEventListener: true,\ninsertTabbableEntries: (event) => {\n  const [selectedNode, selectedNodePath] = editor.api.node(editor.selection);\n\n  return [\n    ...document.querySelectorAll('.my-popover > button'),\n  ].map((domNode) => ({\n    domNode,\n    slateNode: selectedNode,\n    path: selectedNodePath,\n  }));\n},\n```\n\n## Plugins\n\n### TabbablePlugin\n\nPlugin for managing tab order between tabbable elements.\n\n<API name=\"TabbablePlugin\">\n<APIOptions>\n  <APIItem name=\"query\" type=\"(event: KeyboardEvent) => boolean\" optional>\n    Enable/disable plugin dynamically.\n    - **Default:** `() => true`\n  </APIItem>\n  <APIItem name=\"globalEventListener\" type=\"boolean\" optional>\n    Add event listener to document instead of editor.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"insertTabbableEntries\" type=\"(event: KeyboardEvent) => TabbableEntry[]\" optional>\n    Add additional tabbable entries outside editor.\n    - **Default:** `() => []`\n  </APIItem>\n  <APIItem name=\"isTabbable\" type=\"(tabbableEntry: TabbableEntry) => boolean\" optional>\n    Determine if element should be tabbable.\n    - **Default:** `(tabbableEntry) => editor.api.isVoid(tabbableEntry.slateNode)`\n  </APIItem>\n</APIOptions>\n</API>\n\n## Types\n\n### TabbableEntry\n\nDefines the properties of a tabbable entry.\n\n<API name=\"TabbableEntry\">\n<APIAttributes>\n  <APIItem name=\"domNode\" type=\"HTMLElement\">\n    HTML element representing tabbable entry.\n  </APIItem>\n  <APIItem name=\"slateNode\" type=\"TNode\">\n    Corresponding Slate node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to Slate node in document.\n  </APIItem>\n</APIAttributes>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/tabbable.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "toolbar-docs",
      "title": "Toolbar",
      "description": "Fixed and floating toolbars for your editor.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(functionality)/toolbar.mdx",
          "content": "---\ntitle: Toolbar\ndescription: Fixed and floating toolbars for your editor.\ndocs:\n  - route: https://pro.platejs.org/docs/examples/floating-toolbar\n    title: Plus\n  - route: /docs/components/fixed-toolbar\n    title: Fixed Toolbar\n  - route: /docs/components/floating-toolbar\n    title: Floating Toolbar\n---\n\n<ComponentPreview name=\"basic-nodes-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Fixed Toolbar**: Persistent toolbar that sticks to the top of the editor\n- **Floating Toolbar**: Contextual toolbar that appears on text selection\n- **Customizable Buttons**: Easily add, remove, and reorder toolbar buttons\n- **Responsive Design**: Adapts to different screen sizes and content\n- **Plugin Integration**: Seamless integration with Plate plugins and UI components\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add toolbar functionality is with the `FixedToolbarKit` and `FloatingToolbarKit`, which include pre-configured toolbar plugins along with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"fixed-toolbar-kit\" />\n<ComponentSource name=\"floating-toolbar-kit\" />\n\n- [`FixedToolbar`](/docs/plate/components/fixed-toolbar): Renders a persistent toolbar above the editor\n- [`FixedToolbarButtons`](/docs/plate/components/fixed-toolbar-buttons): Pre-configured button set for the fixed toolbar\n- [`FloatingToolbar`](/docs/plate/components/floating-toolbar): Renders a contextual toolbar on text selection\n- [`FloatingToolbarButtons`](/docs/plate/components/floating-toolbar-buttons): Pre-configured button set for the floating toolbar\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { FixedToolbarKit } from '@/components/editor/plugins/fixed-toolbar-kit';\nimport { FloatingToolbarKit } from '@/components/editor/plugins/floating-toolbar-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...FixedToolbarKit,\n    ...FloatingToolbarKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Create Plugins\n\n```tsx\nimport { createPlatePlugin } from 'platejs/react';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons';\nimport { FloatingToolbar } from '@/components/ui/floating-toolbar';\nimport { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';\n\nconst fixedToolbarPlugin = createPlatePlugin({\n  key: 'fixed-toolbar',\n  render: {\n    beforeEditable: () => (\n      <FixedToolbar>\n        <FixedToolbarButtons />\n      </FixedToolbar>\n    ),\n  },\n});\n\nconst floatingToolbarPlugin = createPlatePlugin({\n  key: 'floating-toolbar',\n  render: {\n    afterEditable: () => (\n      <FloatingToolbar>\n        <FloatingToolbarButtons />\n      </FloatingToolbar>\n    ),\n  },\n});\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    fixedToolbarPlugin,\n    floatingToolbarPlugin,\n  ],\n});\n```\n\n- `render.beforeEditable`: Renders [`FixedToolbar`](/docs/plate/components/fixed-toolbar) above the editor content\n- `render.afterEditable`: Renders [`FloatingToolbar`](/docs/plate/components/floating-toolbar) as an overlay after the editor\n\n### Customizing Fixed Toolbar Buttons\n\nThe `FixedToolbarButtons` component contains the default set of buttons for the fixed toolbar.\n\n<ComponentSource name=\"fixed-toolbar-buttons\" />\n\nTo customize it, you can edit `components/ui/fixed-toolbar-buttons.tsx`.\n\n### Customizing Floating Toolbar Buttons\n\nSimilarly, you can customize the floating toolbar by editing `components/ui/floating-toolbar-buttons.tsx`.\n\n<ComponentSource name=\"floating-toolbar-buttons\" />\n\n\n### Creating Custom Button\n\nThis example shows a button that inserts custom text into the editor.\n\n```tsx\nimport { useEditorRef } from 'platejs/react';\nimport { CustomIcon } from 'lucide-react';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nexport function CustomToolbarButton() {\n  const editor = useEditorRef();\n\n  return (\n    <ToolbarButton\n      onClick={() => {\n        // Custom action\n        editor.tf.insertText('Custom text');\n      }}\n      tooltip=\"Custom Action\"\n    >\n      <CustomIcon />\n    </ToolbarButton>\n  );\n}\n```\n\n### Creating Mark Button\n\nFor toggling marks like bold or italic, you can use the [`MarkToolbarButton`](/docs/plate/components/mark-toolbar-button) component. It simplifies the process by handling the toggle state and action automatically.\n\nThis example creates a \"Bold\" button.\n\n```tsx\nimport { BoldIcon } from 'lucide-react';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function BoldToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">\n      <BoldIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n- `nodeType`: Specifies the mark to toggle (e.g., `bold`, `italic`).\n- `tooltip`: Provides a helpful tooltip for the button.\n- The `MarkToolbarButton` uses `useMarkToolbarButtonState` to get the toggle state and `useMarkToolbarButton` to get the `onClick` handler and other props.\n\n### Turn Into Toolbar Button\n\nThe [`TurnIntoToolbarButton`](/docs/plate/components/turn-into-toolbar-button) provides a dropdown menu to convert the current block into different types (headings, lists, quotes, etc.).\n\n<ComponentSource name=\"turn-into-toolbar-button\" />\n\nTo add a new block type to the turn-into options, edit the `turnIntoItems` array:\n\n```tsx\nconst turnIntoItems = [\n  // ... existing items\n  {\n    icon: <CustomIcon />,\n    keywords: ['custom', 'special'],\n    label: 'Custom Block',\n    value: 'custom-block',\n  },\n];\n```\n\n### Insert Toolbar Button\n\nThe [`InsertToolbarButton`](/docs/plate/components/insert-toolbar-button) provides a dropdown menu to insert various elements (blocks, lists, media, inline elements).\n\n<ComponentSource name=\"insert-toolbar-button\" />\n\nTo add a new insertable item, add it to the appropriate group in the `groups` array:\n\n```tsx\n{\n  group: 'Basic blocks',\n  items: [\n    // ... existing items\n    {\n      icon: <CustomIcon />,\n      label: 'Custom Block',\n      value: 'custom-block',\n    },\n  ].map((item) => ({\n    ...item,\n    onSelect: (editor, value) => {\n      insertBlock(editor, value);\n    },\n  })),\n}\n```\n\n</Steps>\n\n## Plate Plus\n\n<ComponentPreviewPro name=\"floating-toolbar-pro\" />\n\n## Plugins\n\n### `FixedToolbarKit`\n\nPlugin that renders a fixed toolbar above the editor content.\n\n<API name=\"FixedToolbarKit\">\n<APIOptions>\n  <APIItem name=\"render.beforeEditable\" type=\"() => ReactNode\">\n    Renders the fixed toolbar before the editor content. Contains FixedToolbarButtons by default.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `FloatingToolbarKit`\n\nPlugin that renders a floating toolbar that appears on text selection.\n\n<API name=\"FloatingToolbarKit\">\n<APIOptions>\n  <APIItem name=\"render.afterEditable\" type=\"() => ReactNode\">\n    Renders the floating toolbar as an overlay after the editor. Contains FloatingToolbarButtons by default.\n  </APIItem>\n</APIOptions>\n</API> ",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(functionality)/toolbar.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "basic-marks-docs",
      "title": "Basic Marks",
      "description": "Common inline text formatting marks.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/basic-marks.mdx",
          "content": "---\ntitle: Basic Marks\ndescription: Common inline text formatting marks.\ndocs:\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/toolbar\n    title: Toolbar\n---\n\nBasic Marks covers the common inline formatting marks: bold, italic, underline, strikethrough, code, subscript, superscript, highlight, and kbd. The package owns mark plugins and transforms; the registry kit adds leaf components, toolbar wiring, and Markdown-style input rules.\n\n<Cards>\n\n<Card icon=\"bold\" title=\"Bold\" href=\"/docs/plate/bold\">\nUse `bold` for strong emphasis.\n</Card>\n\n<Card icon=\"italic\" title=\"Italic\" href=\"/docs/plate/italic\">\nUse `italic` for emphasis or stylistic text.\n</Card>\n\n<Card icon=\"underline\" title=\"Underline\" href=\"/docs/plate/underline\">\nUse `underline` for underlined text.\n</Card>\n\n<Card icon=\"strikethrough\" title=\"Strikethrough\" href=\"/docs/plate/strikethrough\">\nUse `strikethrough` for deleted or replaced text.\n</Card>\n\n<Card icon=\"code\" title=\"Code\" href=\"/docs/plate/code\">\nUse `code` for inline code and technical terms.\n</Card>\n\n<Card icon=\"subscript\" title=\"Subscript\" href=\"/docs/plate/subscript\">\nUse `sub` for subscript text.\n</Card>\n\n<Card icon=\"superscript\" title=\"Superscript\" href=\"/docs/plate/superscript\">\nUse `sup` for superscript text.\n</Card>\n\n<Card icon=\"kbd\" title=\"Kbd\" href=\"/docs/plate/kbd\">\nUse `kbd` for keyboard shortcuts.\n</Card>\n\n<Card icon=\"highlight\" title=\"Highlight\" href=\"/docs/plate/highlight\">\nUse `highlight` for marked text.\n</Card>\n\n</Cards>\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Leaf plugins for common mark keys.\n- Toggle transforms through each mark plugin.\n- HTML deserialization for semantic tags and styles.\n- Markdown-style input rules in the registry kit.\n- Registry leaves for code, highlight, and keyboard text.\n- Toolbar support through `MarkToolbarButton`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add The Kit\n\n`BasicMarksKit` is the full client-side registry kit. It includes mark plugins, input rules, `CodeLeaf`, `HighlightLeaf`, and `KbdLeaf`.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add Toolbar Buttons\n\nUse `MarkToolbarButton` for direct mark toggles.\n\n```tsx\nimport { BoldIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function BoldToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.bold} tooltip=\"Bold (⌘+B)\">\n      <BoldIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BasicMarksPlugin` | `@platejs/basic-nodes/react` | Groups bold, code, italic, strikethrough, subscript, superscript, and underline. |\n| `BaseBasicMarksPlugin` | `@platejs/basic-nodes` | Headless grouping plugin for the same seven marks. |\n| `BasicMarksKit` | Registry | Adds the full mark set, input rules, and client leaf components. |\n| `BaseBasicMarksKit` | Registry | Adds static/base mark plugins with static code, highlight, and kbd leaves. |\n| Individual mark plugins | `@platejs/basic-nodes` | Own mark keys, HTML parsing, render tags, and `toggle` transforms. |\n| `MarkToolbarButton` | Registry UI | Reads mark state and calls the mark toolbar hook. |\n\n`BasicMarksPlugin` is smaller than `BasicMarksKit`: highlight and kbd are separate plugins that the registry kit includes.\n\n## Mark Set\n\n| Mark | Plugin | Key | Render | Notes |\n|------|--------|-----|--------|-------|\n| Bold | `BoldPlugin` | `KEYS.bold` | `strong` | Deserializes `strong`, `b`, and bold font weight. |\n| Italic | `ItalicPlugin` | `KEYS.italic` | `em` | Deserializes `em`, `i`, and italic font style. |\n| Underline | `UnderlinePlugin` | `KEYS.underline` | `u` | Deserializes `u` and underline text decoration. |\n| Strikethrough | `StrikethroughPlugin` | `KEYS.strikethrough` | `s` | Uses directional selection affinity. |\n| Code | `CodePlugin` | `KEYS.code` | `code` | Uses hard selection affinity and skips `pre` HTML parents. |\n| Subscript | `SubscriptPlugin` | `KEYS.sub` | `sub` | Toggle removes superscript. |\n| Superscript | `SuperscriptPlugin` | `KEYS.sup` | `sup` | Toggle removes subscript. |\n| Highlight | `HighlightPlugin` | `KEYS.highlight` | `mark` | Uses directional selection affinity. |\n| Kbd | `KbdPlugin` | `KEYS.kbd` | `kbd` | Uses hard selection affinity. |\n\nEach base mark plugin extends `editor.tf.<mark>.toggle()` by calling `editor.tf.toggleMark(type)`.\n\n## Input Rules\n\n`BasicMarksKit` registers input rules explicitly. The package plugins do not enable them by default.\n\n| Rule Family | Kit Registration |\n|-------------|------------------|\n| `BoldRules.markdown` | `variant: '*'` and `variant: '_'` |\n| `ItalicRules.markdown` | `variant: '*'` and `variant: '_'` |\n| `UnderlineRules.markdown` | Default underline rule |\n| `CodeRules.markdown` | Default inline code rule |\n| `StrikethroughRules.markdown` | Default strikethrough rule |\n| `SubscriptRules.markdown` | Default subscript rule |\n| `SuperscriptRules.markdown` | Default superscript rule |\n| `HighlightRules.markdown` | `variant: '=='` and `variant: '≡'` |\n| `MarkComboRules.markdown` | Bold/italic/underline combinations |\n\nSee [Plugin Input Rules](/docs/plate/plugin-input-rules) for the runtime model.\n\n## Manual Usage\n\nInstall the package when you want to compose marks yourself.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd only the marks you need.\n\n```tsx title=\"components/editor/mark-plugins.tsx\"\nimport {\n  BoldPlugin,\n  CodePlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\n\nexport const markPlugins = [\n  BoldPlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n  CodePlugin,\n];\n```\n\nUse the individual pages for mark-specific setup and component details.\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BasicMarksPlugin` | `@platejs/basic-nodes/react` | React grouping plugin for seven common marks. |\n| `BaseBasicMarksPlugin` | `@platejs/basic-nodes` | Headless grouping plugin for seven common marks. |\n| `BasicMarksKit` | Registry | Full client kit with highlight, kbd, input rules, and leaves. |\n| `BaseBasicMarksKit` | Registry | Static/base kit for server or static rendering. |\n| `MarkToolbarButton` | Registry UI | Toolbar control for one mark key. |\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/basic-marks.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "bold-docs",
      "title": "Bold",
      "description": "Add strong inline text formatting.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/bold.mdx",
          "content": "---\ntitle: Bold\ndescription: Add strong inline text formatting.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nBold applies the `bold` leaf mark to selected text. `BoldPlugin` owns the mark key, shortcut, HTML parsing, render tag, and toggle transform.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.bold` leaf mark.\n- Default `⌘B` / `Ctrl+B` shortcut.\n- HTML deserialization from `strong`, `b`, and bold font weight.\n- `<strong>` rendering by default.\n- Optional Markdown-style input rules through `BoldRules`.\n- Toolbar support through `MarkToolbarButton`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `BoldPlugin`, bold input rules, and the standard toolbar buttons that use `KEYS.bold`.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.bold`.\n\n```tsx\nimport { BoldIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function BoldToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.bold} tooltip=\"Bold (⌘+B)\">\n      <BoldIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `BoldPlugin` directly when you do not want the whole kit.\n\n```tsx\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [BoldPlugin],\n});\n```\n\nRegister Markdown-style input rules when users should type bold delimiters.\n\n```tsx\nimport { BoldRules } from '@platejs/basic-nodes';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\n\nexport const boldPlugin = BoldPlugin.configure({\n  inputRules: [\n    BoldRules.markdown({ variant: '*' }),\n    BoldRules.markdown({ variant: '_' }),\n  ],\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseBoldPlugin` | `@platejs/basic-nodes` | Headless bold mark, HTML parser, render tag, and `toggle` transform. |\n| `BoldPlugin` | `@platejs/basic-nodes/react` | React wrapper with default `mod+b` shortcut. |\n| `BoldRules.markdown` | `@platejs/basic-nodes` | Optional mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `BoldPlugin` with `*` and `_` Markdown-style input rules. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns the mark. The registry owns toolbar placement and icon choice.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.bold` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.bold.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Shortcut | `BoldPlugin` registers `mod+b`. |\n| HTML tags | `strong`, `b` |\n| HTML styles | `font-weight: 600`, `700`, or `bold` |\n| HTML guard | Ignores descendants where `fontWeight` is `normal`. |\n| Render output | `strong` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseBoldPlugin` | `@platejs/basic-nodes` | Headless bold plugin. |\n| `BoldPlugin` | `@platejs/basic-nodes/react` | React bold plugin with shortcut defaults. |\n| `BoldRules.markdown(options)` | `@platejs/basic-nodes` | Creates a bold mark input rule. |\n| `tf.bold.toggle()` | `@platejs/basic-nodes` | Toggles the bold mark at the selection. |\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/bold.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "code-docs",
      "title": "Code",
      "description": "Add inline code formatting.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/code.mdx",
          "content": "---\ntitle: Code\ndescription: Add inline code formatting.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/code-node\n    title: Code Leaf\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/code-block\n    title: Code Block\n---\n\nCode applies the `code` leaf mark to inline text. Use [Code Block](/docs/plate/code-block) for fenced or multiline code; this page is only for inline code spans.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.code` leaf mark.\n- Hard selection affinity.\n- HTML deserialization from `code` tags and Consolas font styles.\n- `<code>` rendering by default.\n- Registry `CodeLeaf` for styled inline code.\n- Optional Markdown-style input rule through `CodeRules`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `CodePlugin`, `CodeLeaf`, the backtick input rule, and a `mod+e` shortcut.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.code`.\n\n```tsx\nimport { Code2Icon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function CodeToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.code} tooltip=\"Code (⌘+E)\">\n      <Code2Icon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `CodePlugin` directly when you want the default `<code>` render.\n\n```tsx\nimport { CodePlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [CodePlugin],\n});\n```\n\nConfigure the registry leaf, input rule, and shortcut when you want the same behavior as the kit.\n\n```tsx\nimport { CodeRules } from '@platejs/basic-nodes';\nimport { CodePlugin } from '@platejs/basic-nodes/react';\n\nimport { CodeLeaf } from '@/components/ui/code-node';\n\nexport const codePlugin = CodePlugin.configure({\n  inputRules: [CodeRules.markdown()],\n  node: { component: CodeLeaf },\n  shortcuts: { toggle: { keys: 'mod+e' } },\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseCodePlugin` | `@platejs/basic-nodes` | Headless inline code mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `CodePlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless code mark. |\n| `CodeRules.markdown` | `@platejs/basic-nodes` | Optional backtick input rule. |\n| `CodeLeaf` | Registry UI | Styled inline code leaf using `PlateLeaf`. |\n| `CodeLeafStatic` | Registry UI | Static rendering version using `SlateLeaf`. |\n| `BasicMarksKit` | Registry | Adds `CodePlugin`, `CodeLeaf`, `CodeRules.markdown()`, and `mod+e`. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns inline code semantics. The registry owns visual styling and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.code` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.code.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Selection affinity | `hard` |\n| HTML tags | `code` |\n| HTML styles | `font-family: Consolas` |\n| HTML guard | Skips code inside `pre` and paragraph-level Consolas blocks. |\n| Render output | `code` |\n| Kit shortcut | `mod+e` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseCodePlugin` | `@platejs/basic-nodes` | Headless inline code plugin. |\n| `CodePlugin` | `@platejs/basic-nodes/react` | React inline code plugin. |\n| `CodeRules.markdown()` | `@platejs/basic-nodes` | Creates the backtick mark input rule. |\n| `tf.code.toggle()` | `@platejs/basic-nodes` | Toggles the inline code mark at the selection. |\n| `CodeLeaf` | Registry UI | Styled client inline code leaf. |\n| `CodeLeafStatic` | Registry UI | Styled static inline code leaf. |\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/code.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "highlight-docs",
      "title": "Highlight",
      "description": "Add marked inline text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/highlight.mdx",
          "content": "---\ntitle: Highlight\ndescription: Add marked inline text.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/highlight-node\n    title: Highlight Leaf\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/examples/find-replace\n    title: Find Replace Demo\n---\n\nHighlight applies the `highlight` leaf mark to selected text. It is the authoring mark; [Find Replace](/docs/plate/examples/find-replace) uses a separate search highlight leaf for transient search results.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.highlight` leaf mark.\n- Directional selection affinity.\n- HTML deserialization from `mark`.\n- `<mark>` rendering by default.\n- Registry `HighlightLeaf` for styled marked text.\n- Optional input rules through `HighlightRules`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `HighlightPlugin`, `HighlightLeaf`, `==` and `≡` input rules, and a `mod+shift+h` shortcut.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.highlight`.\n\n```tsx\nimport { HighlighterIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function HighlightToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.highlight} tooltip=\"Highlight\">\n      <HighlighterIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `HighlightPlugin` directly when you want the default `<mark>` render.\n\n```tsx\nimport { HighlightPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [HighlightPlugin],\n});\n```\n\nConfigure the registry leaf, input rules, and shortcut when you want the same behavior as the kit.\n\n```tsx\nimport { HighlightRules } from '@platejs/basic-nodes';\nimport { HighlightPlugin } from '@platejs/basic-nodes/react';\n\nimport { HighlightLeaf } from '@/components/ui/highlight-node';\n\nexport const highlightPlugin = HighlightPlugin.configure({\n  inputRules: [\n    HighlightRules.markdown({ variant: '==' }),\n    HighlightRules.markdown({ variant: '≡' }),\n  ],\n  node: { component: HighlightLeaf },\n  shortcuts: { toggle: { keys: 'mod+shift+h' } },\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseHighlightPlugin` | `@platejs/basic-nodes` | Headless highlight mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `HighlightPlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless highlight mark. |\n| `HighlightRules.markdown` | `@platejs/basic-nodes` | Optional mark input rule factory. |\n| `HighlightLeaf` | Registry UI | Styled client highlight leaf using `PlateLeaf`. |\n| `HighlightLeafStatic` | Registry UI | Static rendering version using `SlateLeaf`. |\n| `BasicMarksKit` | Registry | Adds `HighlightPlugin`, `HighlightLeaf`, two input-rule variants, and `mod+shift+h`. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns the mark. The registry owns highlight color and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.highlight` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.highlight.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Selection affinity | `directional` |\n| HTML tags | `mark` |\n| Render output | `mark` |\n| Kit input rules | `==` and `≡` variants |\n| Kit shortcut | `mod+shift+h` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseHighlightPlugin` | `@platejs/basic-nodes` | Headless highlight plugin. |\n| `HighlightPlugin` | `@platejs/basic-nodes/react` | React highlight plugin. |\n| `HighlightRules.markdown(options)` | `@platejs/basic-nodes` | Creates a highlight mark input rule. |\n| `tf.highlight.toggle()` | `@platejs/basic-nodes` | Toggles the highlight mark at the selection. |\n| `HighlightLeaf` | Registry UI | Styled client highlight leaf. |\n| `HighlightLeafStatic` | Registry UI | Styled static highlight leaf. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/highlight.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "italic-docs",
      "title": "Italic",
      "description": "Add emphasized inline text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/italic.mdx",
          "content": "---\ntitle: Italic\ndescription: Add emphasized inline text.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nItalic applies the `italic` leaf mark to selected text. `ItalicPlugin` owns the mark key, shortcut, HTML parsing, render tag, and toggle transform.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.italic` leaf mark.\n- Default `⌘I` / `Ctrl+I` shortcut.\n- HTML deserialization from `em`, `i`, and italic font style.\n- `<em>` rendering by default.\n- Optional Markdown-style input rules through `ItalicRules`.\n- Toolbar support through `MarkToolbarButton`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `ItalicPlugin`, `*` and `_` Markdown-style input rules, and the standard toolbar buttons that use `KEYS.italic`.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.italic`.\n\n```tsx\nimport { ItalicIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function ItalicToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.italic} tooltip=\"Italic (⌘+I)\">\n      <ItalicIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `ItalicPlugin` directly when you do not want the whole kit.\n\n```tsx\nimport { ItalicPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [ItalicPlugin],\n});\n```\n\nRegister Markdown-style input rules when users should type italic delimiters.\n\n```tsx\nimport { ItalicRules } from '@platejs/basic-nodes';\nimport { ItalicPlugin } from '@platejs/basic-nodes/react';\n\nexport const italicPlugin = ItalicPlugin.configure({\n  inputRules: [\n    ItalicRules.markdown({ variant: '*' }),\n    ItalicRules.markdown({ variant: '_' }),\n  ],\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseItalicPlugin` | `@platejs/basic-nodes` | Headless italic mark, HTML parser, render tag, and `toggle` transform. |\n| `ItalicPlugin` | `@platejs/basic-nodes/react` | React wrapper with default `mod+i` shortcut. |\n| `ItalicRules.markdown` | `@platejs/basic-nodes` | Optional mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `ItalicPlugin` with `*` and `_` Markdown-style input rules. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns the mark. The registry owns toolbar placement and icon choice.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.italic` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.italic.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Shortcut | `ItalicPlugin` registers `mod+i`. |\n| HTML tags | `em`, `i` |\n| HTML styles | `font-style: italic` |\n| HTML guard | Ignores descendants where `fontStyle` is `normal`. |\n| Render output | `em` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseItalicPlugin` | `@platejs/basic-nodes` | Headless italic plugin. |\n| `ItalicPlugin` | `@platejs/basic-nodes/react` | React italic plugin with shortcut defaults. |\n| `ItalicRules.markdown(options)` | `@platejs/basic-nodes` | Creates an italic mark input rule. |\n| `tf.italic.toggle()` | `@platejs/basic-nodes` | Toggles the italic mark at the selection. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/italic.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "kbd-docs",
      "title": "Keyboard Input",
      "description": "Style keyboard shortcuts and key names.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/kbd.mdx",
          "content": "---\ntitle: Keyboard Input\ndescription: Style keyboard shortcuts and key names.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/kbd-node\n    title: Keyboard Input Leaf\n  - route: /docs/components/more-toolbar-button\n    title: More Toolbar Button\n---\n\nKeyboard Input applies the `kbd` leaf mark to selected text. Use it for shortcuts, key names, and command hints that should render as `<kbd>`.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.kbd` leaf mark.\n- Hard selection affinity.\n- HTML deserialization from `kbd`.\n- `<kbd>` rendering by default.\n- Registry `KbdLeaf` for styled keyboard input.\n- Static rendering support through `KbdLeafStatic`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `KbdPlugin.withComponent(KbdLeaf)`. It does not add a shortcut or input rule for keyboard input.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add the Leaf\n\n`KbdLeaf` provides the registry styling for the `<kbd>` element.\n\n<ComponentSource name=\"kbd-node\" />\n\n### Add a Toolbar Control\n\nThe registry `MoreToolbarButton` toggles keyboard input from the More menu.\n\n```tsx\nimport { MoreToolbarButton } from '@/components/ui/more-toolbar-button';\n\nexport function FormattingMoreMenu() {\n  return <MoreToolbarButton />;\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `KbdPlugin` directly when you want the default `<kbd>` render.\n\n```tsx\nimport { KbdPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [KbdPlugin],\n});\n```\n\nAttach the registry leaf when you want the same visual treatment as the kit.\n\n```tsx\nimport { KbdPlugin } from '@platejs/basic-nodes/react';\n\nimport { KbdLeaf } from '@/components/ui/kbd-node';\n\nexport const kbdPlugin = KbdPlugin.withComponent(KbdLeaf);\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseKbdPlugin` | `@platejs/basic-nodes` | Headless keyboard-input mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `KbdPlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless keyboard-input mark. |\n| `KbdLeaf` | Registry UI | Styled client `<kbd>` leaf using `PlateLeaf`. |\n| `KbdLeafStatic` | Registry UI | Static rendering version using `SlateLeaf`. |\n| `BasicMarksKit` | Registry | Adds `KbdPlugin.withComponent(KbdLeaf)`. |\n| `MoreToolbarButton` | Registry UI | Calls `editor.tf.toggleMark(KEYS.kbd)` from the More menu. |\n\nThe package owns the mark. The registry owns keycap styling and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.kbd` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.kbd.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Selection affinity | `hard` |\n| HTML tags | `kbd` |\n| Render output | `kbd` |\n| Kit component | `KbdLeaf` |\n| Registry toolbar | `MoreToolbarButton` toggles `KEYS.kbd`. |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseKbdPlugin` | `@platejs/basic-nodes` | Headless keyboard-input plugin. |\n| `KbdPlugin` | `@platejs/basic-nodes/react` | React keyboard-input plugin. |\n| `tf.kbd.toggle()` | `@platejs/basic-nodes` | Toggles the keyboard-input mark at the selection. |\n| `KbdLeaf` | Registry UI | Styled client keyboard-input leaf. |\n| `KbdLeafStatic` | Registry UI | Styled static keyboard-input leaf. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/kbd.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "strikethrough-docs",
      "title": "Strikethrough",
      "description": "Strike through inline text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/strikethrough.mdx",
          "content": "---\ntitle: Strikethrough\ndescription: Strike through inline text.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nStrikethrough applies the `strikethrough` leaf mark to selected text. The package owns the mark semantics; `BasicMarksKit` adds the Markdown-style input rule and shortcut.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.strikethrough` leaf mark.\n- Directional selection affinity.\n- HTML deserialization from `s`, `del`, `strike`, and line-through text decoration.\n- `<s>` rendering by default.\n- Optional Markdown-style input rule through `StrikethroughRules`.\n- Toolbar support through `MarkToolbarButton`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `StrikethroughPlugin`, `~~` input rules, and a `mod+shift+x` shortcut.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.strikethrough`.\n\n```tsx\nimport { StrikethroughIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function StrikethroughToolbarButton() {\n  return (\n    <MarkToolbarButton\n      nodeType={KEYS.strikethrough}\n      tooltip=\"Strikethrough\"\n    >\n      <StrikethroughIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `StrikethroughPlugin` directly when you want the default `<s>` render.\n\n```tsx\nimport { StrikethroughPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [StrikethroughPlugin],\n});\n```\n\nConfigure the input rule and shortcut when you want the same behavior as the kit.\n\n```tsx\nimport { StrikethroughRules } from '@platejs/basic-nodes';\nimport { StrikethroughPlugin } from '@platejs/basic-nodes/react';\n\nexport const strikethroughPlugin = StrikethroughPlugin.configure({\n  inputRules: [StrikethroughRules.markdown()],\n  shortcuts: { toggle: { keys: 'mod+shift+x' } },\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseStrikethroughPlugin` | `@platejs/basic-nodes` | Headless strikethrough mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `StrikethroughPlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless strikethrough mark. |\n| `StrikethroughRules.markdown` | `@platejs/basic-nodes` | Optional `~~` mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `StrikethroughPlugin`, the input rule, and `mod+shift+x`. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns the mark. The registry owns shortcut configuration and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.strikethrough` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.strikethrough.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Selection affinity | `directional` |\n| HTML tags | `s`, `del`, `strike` |\n| HTML styles | `text-decoration: line-through` |\n| HTML guard | Ignores descendants where `textDecoration` is `none`. |\n| Render output | `s` |\n| Kit input rule | `StrikethroughRules.markdown()` |\n| Kit shortcut | `mod+shift+x` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseStrikethroughPlugin` | `@platejs/basic-nodes` | Headless strikethrough plugin. |\n| `StrikethroughPlugin` | `@platejs/basic-nodes/react` | React strikethrough plugin. |\n| `StrikethroughRules.markdown()` | `@platejs/basic-nodes` | Creates the `~~` mark input rule. |\n| `tf.strikethrough.toggle()` | `@platejs/basic-nodes` | Toggles the strikethrough mark at the selection. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/strikethrough.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "subscript-docs",
      "title": "Subscript",
      "description": "Format inline text below the baseline.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/subscript.mdx",
          "content": "---\ntitle: Subscript\ndescription: Format inline text below the baseline.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/superscript\n    title: Superscript\n  - route: /docs/components/more-toolbar-button\n    title: More Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nSubscript applies the `subscript` leaf mark to selected text. Its toggle transform removes `superscript`, so the same text cannot keep both vertical-position marks through the plugin API.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.sub` leaf mark.\n- Directional selection affinity.\n- HTML deserialization from `sub` and `vertical-align: sub`.\n- `<sub>` rendering by default.\n- Optional Markdown-style input rule through `SubscriptRules`.\n- Mutual exclusion with `KEYS.sup`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `SubscriptPlugin`, the `~` input rule, and a `mod+comma` shortcut.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add a Toolbar Control\n\nThe registry `MoreToolbarButton` toggles subscript from the More menu and removes superscript.\n\n```tsx\nimport { MoreToolbarButton } from '@/components/ui/more-toolbar-button';\n\nexport function FormattingMoreMenu() {\n  return <MoreToolbarButton />;\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `SubscriptPlugin` directly when you want the default `<sub>` render.\n\n```tsx\nimport { SubscriptPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [SubscriptPlugin],\n});\n```\n\nConfigure the input rule and shortcut when you want the same behavior as the kit.\n\n```tsx\nimport { SubscriptRules } from '@platejs/basic-nodes';\nimport { SubscriptPlugin } from '@platejs/basic-nodes/react';\n\nexport const subscriptPlugin = SubscriptPlugin.configure({\n  inputRules: [SubscriptRules.markdown()],\n  shortcuts: { toggle: { keys: 'mod+comma' } },\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseSubscriptPlugin` | `@platejs/basic-nodes` | Headless subscript mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `SubscriptPlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless subscript mark. |\n| `SubscriptRules.markdown` | `@platejs/basic-nodes` | Optional `~` mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `SubscriptPlugin`, the input rule, and `mod+comma`. |\n| `MoreToolbarButton` | Registry UI | Calls `editor.tf.toggleMark(KEYS.sub, { remove: KEYS.sup })`. |\n\nThe package owns subscript semantics. The registry owns shortcut configuration and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.sub` (`subscript`) |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.subscript.toggle()` calls `editor.tf.toggleMark(type, { remove: editor.getType(KEYS.sup) })`. |\n| Selection affinity | `directional` |\n| HTML tags | `sub` |\n| HTML styles | `vertical-align: sub` |\n| Render output | `sub` |\n| Kit input rule | `SubscriptRules.markdown()` |\n| Kit shortcut | `mod+comma` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseSubscriptPlugin` | `@platejs/basic-nodes` | Headless subscript plugin. |\n| `SubscriptPlugin` | `@platejs/basic-nodes/react` | React subscript plugin. |\n| `SubscriptRules.markdown()` | `@platejs/basic-nodes` | Creates the `~` mark input rule. |\n| `tf.subscript.toggle()` | `@platejs/basic-nodes` | Toggles subscript and removes superscript. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/subscript.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "superscript-docs",
      "title": "Superscript",
      "description": "Format inline text above the baseline.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/superscript.mdx",
          "content": "---\ntitle: Superscript\ndescription: Format inline text above the baseline.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/subscript\n    title: Subscript\n  - route: /docs/components/more-toolbar-button\n    title: More Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nSuperscript applies the `superscript` leaf mark to selected text. Its toggle transform removes `subscript`, so the same text cannot keep both vertical-position marks through the plugin API.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.sup` leaf mark.\n- Directional selection affinity.\n- HTML deserialization from `sup` and `vertical-align: super`.\n- `<sup>` rendering by default.\n- Optional Markdown-style input rule through `SuperscriptRules`.\n- Mutual exclusion with `KEYS.sub`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `SuperscriptPlugin`, the `^` input rule, and a `mod+period` shortcut.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add a Toolbar Control\n\nThe registry `MoreToolbarButton` toggles superscript from the More menu and removes subscript.\n\n```tsx\nimport { MoreToolbarButton } from '@/components/ui/more-toolbar-button';\n\nexport function FormattingMoreMenu() {\n  return <MoreToolbarButton />;\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `SuperscriptPlugin` directly when you want the default `<sup>` render.\n\n```tsx\nimport { SuperscriptPlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [SuperscriptPlugin],\n});\n```\n\nConfigure the input rule and shortcut when you want the same behavior as the kit.\n\n```tsx\nimport { SuperscriptRules } from '@platejs/basic-nodes';\nimport { SuperscriptPlugin } from '@platejs/basic-nodes/react';\n\nexport const superscriptPlugin = SuperscriptPlugin.configure({\n  inputRules: [SuperscriptRules.markdown()],\n  shortcuts: { toggle: { keys: 'mod+period' } },\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseSuperscriptPlugin` | `@platejs/basic-nodes` | Headless superscript mark, HTML parser, render tag, selection rule, and `toggle` transform. |\n| `SuperscriptPlugin` | `@platejs/basic-nodes/react` | React wrapper for the headless superscript mark. |\n| `SuperscriptRules.markdown` | `@platejs/basic-nodes` | Optional `^` mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `SuperscriptPlugin`, the input rule, and `mod+period`. |\n| `MoreToolbarButton` | Registry UI | Calls `editor.tf.toggleMark(KEYS.sup, { remove: KEYS.sub })`. |\n\nThe package owns superscript semantics. The registry owns shortcut configuration and toolbar placement.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.sup` (`superscript`) |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.superscript.toggle()` calls `editor.tf.toggleMark(type, { remove: editor.getType(KEYS.sub) })`. |\n| Selection affinity | `directional` |\n| HTML tags | `sup` |\n| HTML styles | `vertical-align: super` |\n| Render output | `sup` |\n| Kit input rule | `SuperscriptRules.markdown()` |\n| Kit shortcut | `mod+period` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseSuperscriptPlugin` | `@platejs/basic-nodes` | Headless superscript plugin. |\n| `SuperscriptPlugin` | `@platejs/basic-nodes/react` | React superscript plugin. |\n| `SuperscriptRules.markdown()` | `@platejs/basic-nodes` | Creates the `^` mark input rule. |\n| `tf.superscript.toggle()` | `@platejs/basic-nodes` | Toggles superscript and removes subscript. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/superscript.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "underline-docs",
      "title": "Underline",
      "description": "Add underlined inline text.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(marks)/underline.mdx",
          "content": "---\ntitle: Underline\ndescription: Add underlined inline text.\ndocs:\n  - route: /docs/basic-marks\n    title: Basic Marks\n  - route: /docs/components/mark-toolbar-button\n    title: Mark Toolbar Button\n  - route: /docs/plugin-shortcuts\n    title: Plugin Shortcuts\n---\n\nUnderline applies the `underline` leaf mark to selected text. `UnderlinePlugin` owns the mark key, shortcut, HTML parsing, render tag, and toggle transform.\n\n<ComponentPreview name=\"basic-marks-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- `KEYS.underline` leaf mark.\n- Default `⌘U` / `Ctrl+U` shortcut.\n- HTML deserialization from `u` and underline text decoration.\n- `<u>` rendering by default.\n- Optional Markdown-style input rule through `UnderlineRules`.\n- Toolbar support through `MarkToolbarButton`.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add Basic Marks\n\n`BasicMarksKit` includes `UnderlinePlugin`, the underscore input rule, and the standard toolbar buttons that use `KEYS.underline`.\n\n<ComponentSource name=\"basic-marks-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...BasicMarksKit],\n});\n```\n\n### Add A Toolbar Button\n\nUse `MarkToolbarButton` with `KEYS.underline`.\n\n```tsx\nimport { UnderlineIcon } from 'lucide-react';\nimport { KEYS } from 'platejs';\n\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nexport function UnderlineToolbarButton() {\n  return (\n    <MarkToolbarButton nodeType={KEYS.underline} tooltip=\"Underline (⌘+U)\">\n      <UnderlineIcon />\n    </MarkToolbarButton>\n  );\n}\n```\n\n</Steps>\n\n## Manual Usage\n\nInstall the mark package.\n\n```bash\nnpm install @platejs/basic-nodes\n```\n\nAdd `UnderlinePlugin` directly when you do not want the whole kit.\n\n```tsx\nimport { UnderlinePlugin } from '@platejs/basic-nodes/react';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [UnderlinePlugin],\n});\n```\n\nRegister the Markdown-style input rule when users should type underline delimiters.\n\n```tsx\nimport { UnderlineRules } from '@platejs/basic-nodes';\nimport { UnderlinePlugin } from '@platejs/basic-nodes/react';\n\nexport const underlinePlugin = UnderlinePlugin.configure({\n  inputRules: [UnderlineRules.markdown()],\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseUnderlinePlugin` | `@platejs/basic-nodes` | Headless underline mark, HTML parser, render tag, and `toggle` transform. |\n| `UnderlinePlugin` | `@platejs/basic-nodes/react` | React wrapper with default `mod+u` shortcut. |\n| `UnderlineRules.markdown` | `@platejs/basic-nodes` | Optional mark input rule factory. |\n| `BasicMarksKit` | Registry | Adds `UnderlinePlugin` with the underscore input rule. |\n| `MarkToolbarButton` | Registry UI | Reads active mark state and calls the mark toggle hook. |\n\nThe package owns the mark. The registry owns toolbar placement and icon choice.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Mark key | `KEYS.underline` |\n| Leaf behavior | `node.isLeaf: true` |\n| Toggle transform | `editor.tf.underline.toggle()` calls `editor.tf.toggleMark(type)`. |\n| Shortcut | `UnderlinePlugin` registers `mod+u`. |\n| HTML tags | `u` |\n| HTML styles | `text-decoration: underline` |\n| HTML guard | Ignores descendants where `textDecoration` is `none`. |\n| Render output | `u` |\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BaseUnderlinePlugin` | `@platejs/basic-nodes` | Headless underline plugin. |\n| `UnderlinePlugin` | `@platejs/basic-nodes/react` | React underline plugin with shortcut defaults. |\n| `UnderlineRules.markdown()` | `@platejs/basic-nodes` | Creates the underline mark input rule. |\n| `tf.underline.toggle()` | `@platejs/basic-nodes` | Toggles the underline mark at the selection. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(marks)/underline.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "csv-docs",
      "title": "Serializing CSV",
      "description": "Documentation for Serializing CSV",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(serializing)/csv.mdx",
          "content": "---\ntitle: Serializing CSV\n---\n\n<ComponentPreview name=\"csv-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Convert CSV content to Plate table format\n- Configurable error tolerance for parsing malformed CSV data\n\n<Callout className=\"my-4\" type=\"note\">\n  Converting a Plate value to CSV is not yet supported.\n</Callout>\n\n</PackageInfo>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/csv\n```\n\n### Add Plugin\n\n```tsx\nimport { CsvPlugin } from '@platejs/csv';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CsvPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\n```tsx\nimport { CsvPlugin } from '@platejs/csv';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    CsvPlugin.configure({\n      options: {\n        errorTolerance: 0.1,\n        parseOptions: {\n          header: true,\n          skipEmptyLines: true,\n          delimiter: ',',\n        },\n      },\n    }),\n  ],\n});\n```\n\n- `options.errorTolerance`: Percentage of rows that can contain errors (default: `0.25` for 25%)\n- `options.parseOptions`: Configuration passed to PapaParse library for CSV parsing\n- `options.parseOptions.header`: Treat first row as headers (default: `true`)\n\n</Steps>\n\n## Usage\n\n### CSV to Plate\n\nUse the API method to deserialize CSV data:\n\n```tsx\n// Deserialize CSV string to Plate format\nconst csvData = `Name,Age,City\nJohn,30,New York\nJane,25,Boston`;\n\nconst nodes = editor.api.csv.deserialize({ data: csvData });\n```\n\n## Plugins\n\n### CsvPlugin\n\nPlugin for CSV deserialization functionality.\n\n<API name=\"CsvPlugin\">\n<APIOptions>\n  <APIItem name=\"errorTolerance\" type=\"number\" optional>\n    The tolerance for errors in the CSV data, represented as a percentage in decimal form. This value is calculated as the ratio of errors to the total number of rows.\n\n    - **Default:** `0.25` (This indicates that up to 25% of the rows can contain errors.)\n  </APIItem>\n  <APIItem name=\"parseOptions\" type=\"ParseConfig\" optional>\n    Options to be passed to the PapaParse library for parsing CSV data.\n\n    - **Default:** `{ header: true }` (Indicating that the first row of the CSV data should be treated as a header.)\n    - See [PapaParse documentation](https://www.papaparse.com/docs#config) for more details about these options.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### api.csv.deserialize\n\nTakes a CSV (Comma Separated Values) string and converts it into a Plate compatible format. This function uses the `papaparse` library to parse the CSV data.\n\n<API name=\"deserialize\">\n<APIParameters>\n  <APIItem name=\"data\" type=\"string\">\n    The CSV data string to be deserialized.\n  </APIItem>\n  <APIItem name=\"errorTolerance\" type=\"number\" optional>\n    Percentage in decimal form, from 0 to ∞, 0 for no errors allowed. Percentage is based on number of errors compared to number of rows.\n    - **Default:** `0.25`\n  </APIItem>\n  <APIItem name=\"parseOptions\" type=\"ParseConfig\">\n    Options to be passed to the PapaParse library for parsing CSV data.\n    - **Default:** `{ header: true }`\n    - See [PapaParse documentation](https://www.papaparse.com/docs#config)\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Descendant[]\">\n  Array of `Descendant` nodes representing the CSV data in Plate format. Returns `undefined` if parsing fails.\n</APIReturns>\n</API>\n\nCreates a table representation of the CSV data:\n- Headers (if present) become the first row\n- Each CSV row becomes a table row\n- Uses plugins: `TablePlugin`, `TableCellHeaderPlugin`, `TableRowPlugin`, and `TableCellPlugin`\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(serializing)/csv.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "docx-io-docs",
      "title": "DOCX Import/Export",
      "description": "Import DOCX files and export Plate content to Word documents.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(serializing)/docx-io.mdx",
          "content": "---\ntitle: DOCX Import/Export\ndescription: Import DOCX files and export Plate content to Word documents.\n---\n\n<PackageInfo>\n\n## Features\n\n- **Import DOCX files** to Plate format with full content and comment extraction\n- **Export to DOCX** with support for all common formatting, tables, lists, and images\n- Support for headers, footers, page orientation, and margins\n- Configurable CSS styles and fonts for export\n\n</PackageInfo>\n\n<Callout type=\"info\">\n  Looking for paste from Word support? See [DOCX Paste](/docs/plate/docx).\n</Callout>\n\n## Installation\n\n```bash\nnpm install @platejs/docx-io\n```\n\n## Import DOCX\n\n<Steps>\n\n### Import DOCX File\n\nUse `importDocx` to convert a DOCX file to Plate nodes:\n\n```tsx\nimport { importDocx } from '@platejs/docx-io';\n\nconst handleFileUpload = async (file: File) => {\n  const arrayBuffer = await file.arrayBuffer();\n  const result = await importDocx(editor, arrayBuffer);\n\n  // Insert nodes into editor\n  editor.tf.insertNodes(result.nodes);\n\n  // Handle comments if needed\n  for (const comment of result.comments) {\n    console.log(`Comment ${comment.id}: ${comment.text}`);\n  }\n\n  // Check for conversion warnings\n  if (result.warnings.length > 0) {\n    console.warn('Conversion warnings:', result.warnings);\n  }\n};\n```\n\n</Steps>\n\n## Export DOCX\n\n<Steps>\n\n### Basic Export\n\nUse `exportToDocx` to convert Plate content to a DOCX file:\n\n```tsx\nimport { exportToDocx, downloadDocx } from '@platejs/docx-io';\n\nconst handleExport = async () => {\n  const blob = await exportToDocx(editor.children, {\n    orientation: 'portrait',\n    margins: { top: 1440, bottom: 1440, left: 1440, right: 1440 },\n    fontFamily: 'Calibri',\n  });\n\n  downloadDocx(blob, 'document.docx');\n};\n```\n\nOr use the combined function:\n\n```tsx\nimport { exportEditorToDocx } from '@platejs/docx-io';\n\nawait exportEditorToDocx(editor.children, 'document', {\n  orientation: 'portrait',\n});\n```\n\n### With Editor Plugins\n\nFor accurate serialization, provide your editor plugins:\n\n```tsx\nimport { exportToDocx } from '@platejs/docx-io';\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\nimport { DocxExportKit } from '@/components/editor/plugins/docx-export-kit';\n\nconst blob = await exportToDocx(editor.children, {\n  editorPlugins: [...BaseEditorKit, ...DocxExportKit],\n});\n```\n\n### Custom Styles\n\nCustomize the export styles:\n\n```tsx\nimport { exportToDocx, DOCX_EXPORT_STYLES } from '@platejs/docx-io';\n\nconst blob = await exportToDocx(editor.children, {\n  customStyles: `\n    .custom-highlight { background-color: #ffeb3b; }\n    h1 { color: #1a1a1a; }\n  `,\n  fontFamily: 'Times New Roman',\n});\n```\n\n### Using DocxExportPlugin\n\nFor plugin-based API access:\n\n```tsx\nimport { DocxExportPlugin } from '@platejs/docx-io';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    DocxExportPlugin.configure({\n      options: {\n        editorPlugins: myPlugins,\n        editorStaticComponent: MyEditorStatic,\n      },\n    }),\n  ],\n});\n\n// Export using plugin API\nconst blob = await editor.api.docxExport.exportToBlob({\n  orientation: 'landscape',\n});\n\neditor.api.docxExport.download(blob, 'document');\n\n// Or use transform for export + download\nawait editor.tf.docxExport.exportAndDownload('document', {\n  orientation: 'portrait',\n});\n```\n\n</Steps>\n\n## DOCX Export Kit\n\nThe `DocxExportKit` provides DOCX-optimized static components for elements that require special handling:\n\n<ComponentSource name=\"docx-export-kit\" />\n\nComponents included:\n- **Code blocks**: Inline syntax highlighting with line breaks\n- **Columns**: Table layout instead of flexbox\n- **Equations**: Inline font styling (KaTeX doesn't work in DOCX)\n- **Callouts**: Table layout for icon + content\n- **TOC**: Anchor links with proper paragraph breaks\n\n## Plugins\n\n### DocxExportPlugin\n\nPlugin providing DOCX export functionality with typed API methods.\n\n<API name=\"DocxExportPlugin\">\n<APIOptions>\n<APIItem name=\"editorPlugins\" type=\"SlatePlugin[]\" optional>\nPlugins to use for HTML serialization. If not provided, uses the editor's current plugins.\n</APIItem>\n<APIItem name=\"editorStaticComponent\" type=\"React.ComponentType<PlateStaticProps>\" optional>\nReact component to use for static rendering.\n</APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `importDocx`\n\nImport a DOCX file and convert it to Plate nodes.\n\n<API name=\"importDocx\">\n<APIParameters>\n<APIItem name=\"editor\" type=\"SlateEditor\">\nThe Plate editor instance.\n</APIItem>\n<APIItem name=\"arrayBuffer\" type=\"ArrayBuffer\">\nThe DOCX file as ArrayBuffer.\n</APIItem>\n<APIItem name=\"options\" type=\"ImportDocxOptions\" optional>\nImport options.\n</APIItem>\n</APIParameters>\n\n<APIOptions type=\"ImportDocxOptions\">\n<APIItem name=\"rtf\" type=\"string\" optional>\nRTF data for image extraction.\n</APIItem>\n</APIOptions>\n\n<APIReturns type=\"Promise<ImportDocxResult>\">\n<APIItem name=\"nodes\" type=\"TNode[]\">\nDeserialized editor nodes.\n</APIItem>\n<APIItem name=\"comments\" type=\"DocxComment[]\">\nComments extracted from the DOCX file.\n</APIItem>\n<APIItem name=\"warnings\" type=\"string[]\">\nWarnings from mammoth conversion.\n</APIItem>\n</APIReturns>\n</API>\n\n### `exportToDocx`\n\nConvert Plate content to a DOCX blob.\n\n<API name=\"exportToDocx\">\n<APIParameters>\n<APIItem name=\"value\" type=\"Value\">\nThe Plate editor value (array of nodes).\n</APIItem>\n<APIItem name=\"options\" type=\"DocxExportOptions\" optional>\nExport options.\n</APIItem>\n</APIParameters>\n\n<APIOptions type=\"DocxExportOptions\">\n<APIItem name=\"orientation\" type=\"'portrait' | 'landscape'\" optional>\nPage orientation.\n\n- **Default:** `'portrait'`\n</APIItem>\n<APIItem name=\"margins\" type=\"DocxExportMargins\" optional>\nPage margins in twentieths of a point (1 inch = 1440).\n\n- **Default:** `{ top: 1440, bottom: 1440, left: 1440, right: 1440, header: 720, footer: 720, gutter: 0 }`\n</APIItem>\n<APIItem name=\"fontFamily\" type=\"string\" optional>\nFont family for the document body. Overrides default Calibri font.\n</APIItem>\n<APIItem name=\"customStyles\" type=\"string\" optional>\nAdditional CSS styles to include. Appended after default DOCX_EXPORT_STYLES.\n</APIItem>\n<APIItem name=\"title\" type=\"string\" optional>\nDocument title for metadata.\n</APIItem>\n<APIItem name=\"editorPlugins\" type=\"SlatePlugin[]\" optional>\nPlugins for HTML serialization.\n</APIItem>\n<APIItem name=\"editorStaticComponent\" type=\"React.ComponentType\" optional>\nComponent for static rendering.\n</APIItem>\n</APIOptions>\n\n<APIReturns type=\"Promise<Blob>\">\nA Blob containing the DOCX file.\n</APIReturns>\n</API>\n\n### `downloadDocx`\n\nDownload a DOCX blob as a file.\n\n<API name=\"downloadDocx\">\n<APIParameters>\n<APIItem name=\"blob\" type=\"Blob\">\nThe DOCX blob to download.\n</APIItem>\n<APIItem name=\"filename\" type=\"string\">\nThe filename (with or without .docx extension).\n</APIItem>\n</APIParameters>\n</API>\n\n### `exportEditorToDocx`\n\nExport and download editor content as a DOCX file in one call.\n\n<API name=\"exportEditorToDocx\">\n<APIParameters>\n<APIItem name=\"value\" type=\"Value\">\nThe Plate editor value.\n</APIItem>\n<APIItem name=\"filename\" type=\"string\">\nThe filename for download.\n</APIItem>\n<APIItem name=\"options\" type=\"DocxExportOptions\" optional>\nExport options (same as `exportToDocx`).\n</APIItem>\n</APIParameters>\n</API>\n\n### api.docxExport.exportToBlob\n\nConvert editor content to a DOCX blob using the plugin API.\n\n<API name=\"api.docxExport.exportToBlob\">\n<APIOptions type=\"DocxExportOperationOptions\">\n<APIItem name=\"orientation\" type=\"'portrait' | 'landscape'\" optional>\nPage orientation.\n</APIItem>\n<APIItem name=\"margins\" type=\"DocxExportMargins\" optional>\nPage margins.\n</APIItem>\n<APIItem name=\"fontFamily\" type=\"string\" optional>\nFont family.\n</APIItem>\n<APIItem name=\"customStyles\" type=\"string\" optional>\nAdditional CSS styles.\n</APIItem>\n<APIItem name=\"title\" type=\"string\" optional>\nDocument title.\n</APIItem>\n</APIOptions>\n\n<APIReturns type=\"Promise<Blob>\">\nA Blob containing the DOCX file.\n</APIReturns>\n</API>\n\n### api.docxExport.download\n\nDownload a DOCX blob as a file.\n\n<API name=\"api.docxExport.download\">\n<APIParameters>\n<APIItem name=\"blob\" type=\"Blob\">\nThe DOCX blob.\n</APIItem>\n<APIItem name=\"filename\" type=\"string\">\nThe filename.\n</APIItem>\n</APIParameters>\n</API>\n\n## Transforms\n\n### tf.docxExport.exportAndDownload\n\nExport and download editor content as a DOCX file.\n\n<API name=\"tf.docxExport.exportAndDownload\">\n<APIParameters>\n<APIItem name=\"filename\" type=\"string\">\nThe filename for download.\n</APIItem>\n<APIItem name=\"options\" type=\"DocxExportOperationOptions\" optional>\nExport options.\n</APIItem>\n</APIParameters>\n</API>\n\n## Types\n\n### DocxComment\n\n```ts\ntype DocxComment = {\n  id: string;\n  text: string;\n};\n```\n\n### DocxExportMargins\n\n```ts\ntype DocxExportMargins = {\n  top?: number;\n  bottom?: number;\n  left?: number;\n  right?: number;\n  header?: number;\n  footer?: number;\n  gutter?: number;\n};\n```\n\n## Constants\n\n### DOCX_EXPORT_STYLES\n\nDefault CSS styles optimized for Microsoft Word HTML rendering:\n\n- Calibri font (Microsoft Office default)\n- 11pt font size with 1.5 line height\n- Heading hierarchy (24pt to 10pt)\n- Table styles with borders\n- Code block styling with Courier New\n- Blockquote styling with left border\n\n## Known Limitations\n\n- **Mobile browsers**: Export may not work reliably on mobile browsers due to limitations with blob handling and downloads.\n- **Complex layouts**: Some complex CSS layouts (flexbox, grid) are converted to table-based layouts for Word compatibility.\n- **Custom fonts**: Only system fonts available in Word will render correctly.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(serializing)/docx-io.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "docx-docs",
      "title": "DOCX Paste",
      "description": "Paste content from Microsoft Word with automatic formatting.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(serializing)/docx.mdx",
          "content": "---\ntitle: DOCX Paste\ndescription: Paste content from Microsoft Word with automatic formatting.\n---\n\n<ComponentPreview name=\"docx-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Convert pasted DOCX content to Plate format\n- Clean and normalize DOCX HTML content for Plate compatibility\n- Support for list styles and nested indentation from Word documents\n\n</PackageInfo>\n\n<Callout type=\"info\">\n  Looking for DOCX file import/export? See [DOCX Import/Export](/docs/plate/docx-io).\n</Callout>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add DOCX import functionality is with the `DocxKit`, which includes pre-configured `DocxPlugin` and `JuicePlugin` for handling DOCX content and CSS processing.\n\n<ComponentSource name=\"docx-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { DocxKit } from '@/components/editor/plugins/docx-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...DocxKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/docx @platejs/juice\n```\n\n### Add Plugins\n\n```tsx\nimport { DocxPlugin } from '@platejs/docx';\nimport { JuicePlugin } from '@platejs/juice';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    DocxPlugin,\n    JuicePlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\n```tsx\nimport { DocxPlugin } from '@platejs/docx';\nimport { JuicePlugin } from '@platejs/juice';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    DocxPlugin, // Handles DOCX content transformation\n    JuicePlugin, // Inlines CSS properties into style attributes\n  ],\n});\n```\n\n- `DocxPlugin`: Processes pasted DOCX content and converts it to Plate format\n- `JuicePlugin`: Inlines CSS properties into the `style` attribute for better compatibility\n\n</Steps>\n\n## Usage\n\n### DOCX to Plate\n\nWhen users paste content from Microsoft Word, the DOCX plugin automatically:\n\n1. Detects DOCX content in the clipboard\n2. Cleans and normalizes the HTML structure\n3. Preserves indentation and list formatting\n4. Converts DOCX-specific elements to Plate format\n\nThe plugin works seamlessly with the paste functionality - no additional code is needed once installed.\n\n## Plugins\n\n### DocxPlugin\n\nPlugin for processing DOCX content during paste operations.\n\n### JuicePlugin\n\nPlugin for inlining CSS properties into HTML elements. Converts external CSS styles to inline `style` attributes. This is essential for DOCX processing as it ensures styling information is preserved when content is pasted from Word documents.\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(serializing)/docx.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "html-docs",
      "title": "HTML",
      "description": "Convert Plate content to HTML and vice-versa.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(serializing)/html.mdx",
          "content": "---\ntitle: HTML\ndescription: Convert Plate content to HTML and vice-versa.\ntoc: true\n---\n\nThis guide covers converting Plate editor content to HTML (`serializeHtml`) and parsing HTML back into Plate's format (`editor.api.html.deserialize`).\n\n<ComponentPreview name=\"html-demo\" />\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to enable HTML serialization is with the `BaseEditorKit`, which includes pre-configured base plugins that support HTML conversion for most common elements and marks.\n\n<ComponentSource name=\"editor-base-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createSlateEditor } from 'platejs';\nimport { serializeHtml } from 'platejs/static';\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\n\nconst editor = createSlateEditor({\n  plugins: BaseEditorKit,\n  value: [\n    { type: 'h1', children: [{ text: 'Hello World' }] },\n    { type: 'p', children: [{ text: 'This content will be serialized to HTML.' }] },\n  ],\n});\n\n// Serialize to HTML\nconst html = await serializeHtml(editor);\n```\n\n### Example\n\nSee a complete server-side HTML generation example:\n\n<ComponentSource name=\"slate-to-html\" />\n\n</Steps>\n\n## Plate to HTML\n\nConvert Plate editor content (Plate nodes) into an HTML string. This is often done server-side.\n\n[View Server-Side Example](/docs/plate/examples/slate-to-html)\n\n<Callout type=\"warning\" title=\"Key Server-Side Constraint\">\n  When using `serializeHtml` or other Plate utilities in a server environment (Node.js, RSC), you **must not** import from `/react` subpaths of any `platejs*` package. Always use the base imports (e.g., `@platejs/basic-nodes` instead of `@platejs/basic-nodes/react`).\n\n  This means you should use `createSlateEditor` from `platejs` for server-side editor instances, not `usePlateEditor` or `createPlateEditor` from `platejs/react`.\n</Callout>\n\n<Steps>\n\n### Basic Usage\n\nProvide a server-side editor instance and configure your Plate components during editor creation.\n\n```tsx title=\"lib/generate-html.ts\"\nimport { createSlateEditor } from 'platejs';\nimport { serializeHtml } from 'platejs/static'; // Static import\n// Import base plugins (NOT from /react paths)\nimport { BaseHeadingPlugin } from '@platejs/basic-nodes';\n// Import your STATIC components for rendering\nimport { ParagraphElementStatic } from '@/components/ui/paragraph-node-static';\nimport { HeadingElementStatic } from '@/components/ui/heading-node-static';\n// For a styled static output, you might use a wrapper like EditorStatic\nimport { EditorStatic } from '@/components/ui/editor-static';\n\n// Map plugin keys to their STATIC rendering components\nconst components = {\n  p: ParagraphElementStatic, // 'p' is the default key for paragraphs\n  h1: HeadingElementStatic,\n  // ... add mappings for all your elements and marks\n};\n\n// Create a server-side editor instance with components\nconst editor = createSlateEditor({\n  plugins: [\n    BaseHeadingPlugin,   // Base plugin for headings\n    // ... add all other base plugins relevant to your content\n  ],\n  components,\n});\n\nasync function getMyHtml() {\n  // Example: set some content on the server-side editor\n  editor.children = [\n    { type: 'h1', children: [{text: 'My Title'}] },\n    { type: 'p', children: [{text: 'My content.'}] }\n  ];\n\n  const html = await serializeHtml(editor, {\n    // Optional: Use a custom wrapper like EditorStatic for styling\n    // editorComponent: EditorStatic,\n    // props: { variant: 'none', className: 'p-4 m-4 border' },\n  });\n\n  return html;\n}\n```\n\n### Styling Serialized HTML\n\n`serializeHtml` returns only the HTML for the editor content itself. If you use styled components (like `EditorStatic` or custom static components with specific classes), you must ensure the necessary CSS is available in the final context where the HTML will be displayed.\n\nThis often means wrapping the serialized HTML in a full HTML document that includes your stylesheets:\n\n```tsx title=\"lib/generate-full-html-document.ts\"\n// ... (previous setup from generate-html.ts)\n\nasync function getFullHtmlDocument() {\n  const editorHtmlContent = await getMyHtml(); // From previous example\n\n  const fullHtml = `<!DOCTYPE html>\n  <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <link rel=\"stylesheet\" href=\"/path/to/your-global-styles.css\" />\n      <link rel=\"stylesheet\" href=\"/path/to/tailwind-or-component-styles.css\" />\n      <title>Serialized Content</title>\n    </head>\n    <body>\n      <div class=\"my-document-wrapper prose dark:prose-invert\">\n        ${editorHtmlContent}\n      </div>\n    </body>\n  </html>`;\n  return fullHtml;\n}\n```\n\n<Callout type=\"info\" title=\"Static Output Only\">\n  The serialization process converts Plate nodes to static HTML. Interactive features (React event handlers, client-side hooks) or components relying on browser APIs will not function in the serialized output.\n</Callout>\n\n### Using Static Components\n\nFor server-side serialization, you **must** use static versions of your components (no client-only code, no React hooks like `useEffect` or `useState`).\n\nRefer to the [Static Rendering Guide](/docs/plate/static) for detailed instructions on creating server-safe static components for your Plate elements and marks.\n\n```tsx title=\"components/ui/paragraph-node-static.tsx\"\nimport React from 'react';\nimport type { SlateElementProps } from 'platejs/static';\n\n// Example static paragraph component\nexport function ParagraphElementStatic(props: SlateElementProps) {\n  return (\n    <SlateElement {...props} className={cn('m-0 px-0 py-1')}>\n      {props.children}\n    </SlateElement>\n  );\n}\n```\n\n</Steps>\n\n---\n\n## HTML to Plate\n\nThe HTML deserializer allows you to convert HTML content (strings or DOM elements) back into Plate format. This supports round-trip conversion, preserving structure, formatting, and attributes where corresponding plugin rules exist.\n\n<Steps>\n\n### Basic Usage\n\nUse `editor.api.html.deserialize` within a client-side Plate editor context.\n\n```tsx title=\"components/my-html-importer.tsx\"\nimport { PlateEditor, usePlateEditor } from 'platejs/react'; // React-specific imports for client-side\n// Import ALL Plate plugins needed to represent the HTML content\nimport { HeadingPlugin } from '@platejs/basic-nodes/react';\n// ... and so on for bold, italic, tables, lists, etc.\n\nfunction MyHtmlImporter({ htmlString }: { htmlString: string }) {\n  const editor = usePlateEditor({\n    plugins: [\n      HeadingPlugin,     // For <h1>, <h2>, etc.\n      // ... include all plugins corresponding to the HTML you expect to parse\n    ],\n  });\n\n  const handleImport = () => {\n    const slateValue = editor.api.html.deserialize({ element: htmlString });\n    editor.tf.setValue(slateValue);\n  };\n\n  // ... render your editor and a button to trigger handleImport ...\n  return <button onClick={handleImport}>Import HTML</button>;\n}\n```\n\n<Callout type=\"warning\" title=\"Client-Side Operation\">\n  HTML deserialization using `editor.api.html.deserialize` is typically a client-side operation as it interacts with a live Plate editor instance configured with React components and plugins.\n</Callout>\n\n### Plugin Deserialization Rules Overview\n\nEach Plate plugin can define rules for how it interprets specific HTML tags, styles, and attributes during deserialization. Below is a summary of common HTML structures and the Plate plugins typically responsible for them.\n\n| HTML Element / Style                                       | Plate Plugin (Typical)  | Notes                                                                    |\n| :--------------------------------------------------------- | :---------------------- | :----------------------------------------------------------------------- |\n| `<strong>`, `<b>`, `font-weight: 600,700,bold`          | [`BoldPlugin`](/docs/plate/bold)            | Converts to `bold: true` mark.                                           |\n| `<em>`, `<i>`, `font-style: italic`                      | [`ItalicPlugin`](/docs/plate/italic)          | Converts to `italic: true` mark.                                         |\n| `<u>`, `text-decoration: underline`                       | [`UnderlinePlugin`](/docs/plate/underline)       | Converts to `underline: true` mark.                                      |\n| `<s>`, `<del>`, `<strike>`, `text-decoration: line-through` | [`StrikethroughPlugin`](/docs/plate/strikethrough)   | Converts to `strikethrough: true` mark.                                  |\n| `<sub>`, `vertical-align: sub`                            | [`SubscriptPlugin`](/docs/plate/subscript)       | Converts to `subscript: true` mark.                                      |\n| `<sup>`, `vertical-align: super`                           | [`SuperscriptPlugin`](/docs/plate/superscript)     | Converts to `superscript: true` mark.                                    |\n| `<code>` (not in `<pre>`), `font-family: Consolas`         | [`CodePlugin`](/docs/plate/code)            | Converts to `code: true` mark (inline code).                             |\n| `<kbd>`                                                    | [`KbdPlugin`](/docs/plate/kbd)             | Converts to `kbd: true` mark.                                            |\n| `<p>`                                                      | [`ParagraphPlugin`](/docs/plate/basic-blocks)       | Converts to paragraph element.                                           |\n| `<h1>` - `<h6>`                                            | [`HeadingPlugin`](/docs/plate/heading)         | Converts to corresponding heading elements (`h1` - `h6`).                |\n| `<ul>`                                                     | [`ListPlugin` (classic)](/docs/plate/list-classic)            | Converts to unordered list (`ul` type). Items become `li`.               |\n| `<ol>`                                                     | [`ListPlugin` (classic)](/docs/plate/list-classic)            | Converts to ordered list (`ol` type). Items become `li`.                 |\n| `<li>` (within `<ul>` or `<ol>`)                           | [`ListPlugin` (classic)](/docs/plate/list-classic)            | Converts to list item (`li` type), with `lic` (list item content) child. |\n| `<li>` (with `aria-level` for indent)                      | [`ListPlugin`](/docs/plate/list)      | Converts to paragraph with `indent` and `listStyleType` props.           |\n| `<blockquote>`                                             | [`BlockquotePlugin`](/docs/plate/blockquote)      | Converts to blockquote element.                                          |\n| `<pre>` (often with `<code>` inside)                       | [`CodeBlockPlugin`](/docs/plate/code-block)       | Converts to `code_block` element. Content split into `code_line`.        |\n| `<hr>`                                                     | [`HorizontalRulePlugin`](/docs/plate/horizontal-rule)  | Converts to horizontal rule element.                                     |\n| `<a>`                                                      | [`LinkPlugin`](/docs/plate/link)            | Converts to link element (`a` type) with `url` property.                 |\n| `<img>`                                                    | [`ImagePlugin`](/docs/plate/media)           | Converts to image element (`img` type) with `url` property.              |\n| `<iframe>`                                                 | [`MediaEmbedPlugin`](/docs/plate/media)      | Converts to media embed element, attempting to parse URL.                |\n| `<table>`                                                  | [`TablePlugin`](/docs/plate/table)           | Converts to `table` element.                                             |\n| `<tr>`                                                     | [`TablePlugin`](/docs/plate/table)           | Converts to `tr` (table row) element.                                    |\n| `<td>`                                                     | [`TablePlugin`](/docs/plate/table)           | Converts to `td` (table cell) element.                                   |\n| `<th>`                                                     | [`TablePlugin`](/docs/plate/table)           | Converts to `th` (table header cell) element.                            |\n| `style=\"background-color: ...\"`                          | [`FontColorPlugin`](/docs/plate/font)    | Converts to `backgroundColor` mark. (Plugin name might seem inverse) |\n| `style=\"color: ...\"`                                     | [`FontColorPlugin`](/docs/plate/font)       | Converts to `color` mark.                                                |\n| `style=\"font-family: ...\"`                               | [`FontFamilyPlugin`](/docs/plate/font)      | Converts to `fontFamily` mark.                                           |\n| `style=\"font-size: ...\"`                                 | [`FontSizePlugin`](/docs/plate/font)        | Converts to `fontSize` mark.                                             |\n| `style=\"font-weight: ...\"` (other than bold values)      | [`FontWeightPlugin`](/docs/plate/font)      | Converts to `fontWeight` mark for non-standard bold values.              |\n| `<mark>`                                                   | [`HighlightPlugin`](/docs/plate/highlight)       | Converts to `highlight: true` mark.                                      |\n| `style=\"text-align: ...\"`                                | [`TextAlignPlugin`](/docs/plate/text-align)           | Sets `align` property on block elements.                                 |\n| `style=\"line-height: ...\"`                               | [`LineHeightPlugin`](/docs/plate/line-height)      | Sets `lineHeight` property on block elements.                            |\n\n<Callout type=\"note\" title=\"Plugin Configuration\">\n  The exact Plate type (e.g., `ParagraphPlugin.key` vs. `'p'`) depends on how plugins are configured. The table shows typical associations. Ensure the corresponding Plate plugins are included in your editor for these rules to apply.\n</Callout>\n\n### Deserialization Properties in Plugins\n\nPlugins can define how they handle HTML deserialization using properties within their `parsers.html.deserializer` configuration:\n\n-   **`parse`**: A function `({ editor, element, getOptions, ... }) => Partial<SlateNode>` that takes an HTML element and returns a partial Plate node. This is where the main conversion logic resides.\n-   **`query`**: An optional function `({ element, getOptions }) => boolean` that determines if the deserializer rule should even be considered for the current HTML element.\n-   **`rules`**: An array of rule objects, each defining conditions for matching an HTML element:\n    -   `validNodeName`: String or array of strings for matching HTML tag names (e.g., `'P'`, `['STRONG', 'B']`).\n    -   `validAttribute`: Object or array of objects specifying required attribute names and/or values (e.g., `{ align: ['left', 'center'] }`).\n    -   `validClassName`: String or array of strings for matching CSS class names.\n    -   `validStyle`: Object or array of objects specifying required CSS style properties and/or values (e.g., `{ fontWeight: ['600', '700', 'bold'] }`).\n-   **`isElement`**: Boolean, `true` if the plugin deserializes an HTML element into a Plate Element node.\n-   **`isLeaf`**: Boolean, `true` if the plugin deserializes an HTML element or style into a Plate Leaf (mark) on a Text node.\n-   **`attributeNames`**: Array of HTML attribute names whose values should be preserved on the `node.attributes` property of the resulting Plate node.\n-   **`withoutChildren`**: Boolean, if `true`, child nodes of the HTML element are not processed by `convertHtmlAttributes`.\n\n### Customizing Deserialization Behavior\n\nYou can extend a plugin to modify its HTML parsing logic. This is useful for supporting non-standard HTML attributes or structures.\n\n```tsx title=\"lib/custom-code-block-plugin.ts\"\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\nimport { CodeLinePlugin } from '@platejs/code-block'; // Base if needed\n\nconst MyCustomCodeBlockPlugin = CodeBlockPlugin.configure({\n  parsers: {\n    html: {\n      deserializer: {\n        // Inherit most rules and properties, then override or add\n        ...CodeBlockPlugin.parsers.html.deserializer,\n        parse: ({ element, editor }) => { // editor might be needed for getType\n          const language = element.getAttribute('data-custom-lang') || element.className.match(/language-(?<lang>[^\\s]+)/)?.groups?.lang;\n          const textContent = element.textContent || '';\n          const lines = textContent.split('\\n');\n\n          return {\n            type: CodeBlockPlugin.key, // Or editor.getType(CodeBlockPlugin.key)\n            lang: language,\n            code: textContent, // Example: store full code string\n            children: lines.map((line) => ({\n              type: editor.getType(CodeLinePlugin.key),\n              children: [{ text: line }],\n            })),\n          };\n        },\n        rules: [\n          // Inherit existing rules if desired\n          ...(CodeBlockPlugin.parsers.html.deserializer.rules || []),\n          // Add a new rule to match based on a custom attribute\n          { validAttribute: { 'data-custom-lang': true } },\n        ],\n      },\n    },\n  },\n});\n\n// Then use MyCustomCodeBlockPlugin in your editor configuration.\n```\nThis example customizes `CodeBlockPlugin` to look for a `data-custom-lang` attribute or a `language-*` class for determining the code language.\n\n### Advanced Deserialization Example (`ListPlugin`)\n\nThe `ListPlugin` demonstrates a more complex deserialization scenario where it transforms HTML list structures (`<li>` elements) into indented paragraphs within Plate, using `aria-level` to determine indentation.\n\nHere's a conceptual look at its deserialization logic:\n\n```ts\n// Simplified concept from ListPlugin\nexport const ListPluginConfig = {\n  // ... other configurations ...\n  parsers: {\n    html: {\n      deserializer: {\n        isElement: true,\n        // query: ({ element }) => hasListAncestor(element), // Example condition\n        parse: ({ editor, element }) => ({\n          type: 'p', // Converts <li> to <p>\n          indent: Number(element.getAttribute('aria-level') || '1'),\n          listStyleType: element.style.listStyleType || undefined,\n          // Children are processed by Plate's default deserializer after this node is created\n        }),\n        rules: [\n          { validNodeName: 'LI' }, // Only applies to <li> elements\n        ],\n      },\n    },\n  },\n};\n```\nThis illustrates how a plugin can completely reinterpret HTML structures into a different Plate representation.\n\n</Steps>\n\n## API Reference\n\n### `serializeHtml(editor, options)`\n\nConverts Plate nodes from `editor.children` (or a provided `value`) into an HTML string. This function is typically used server-side.\n\n<API name=\"serializeHtml\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"PlateEditor\">\n    A server-side Plate editor instance, created via `createSlateEditor` with components configured.\n  </APIItem>\n  <APIItem name=\"options\" type=\"SerializeHtmlOptions\">\n    Options for serialization.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"SerializeHtmlOptions<P = PlateStaticProps>\">\n  <APIItem name=\"editorComponent\" type=\"React.ComponentType<P>\" optional>\n    A React component to wrap the entire editor content during static rendering. Defaults to `PlateStatic`.\n    The component receives `editor`, `value`, and any `props` passed here.\n  </APIItem>\n  <APIItem name=\"props\" type=\"Partial<P>\" optional>\n    Props to pass to the `editorComponent`. `P` defaults to `PlateStaticProps`.\n  </APIItem>\n  <APIItem name=\"value\" type=\"Descendant[]\" optional>\n    Plate nodes to serialize. If not provided, `editor.children` will be used.\n  </APIItem>\n  <APIItem name=\"preserveClassNames\" type=\"string[]\" optional>\n    Class name prefixes to preserve when `stripClassNames` is true. Default preserve list in the stripping helper: `['slate-']`.\n  </APIItem>\n  <APIItem name=\"stripClassNames\" type=\"boolean\" optional>\n    If `true`, removes all class names from the output HTML except those whose prefixes are listed in `preserveClassNames`. Default: `false`.\n  </APIItem>\n  <APIItem name=\"stripDataAttributes\" type=\"boolean\" optional>\n    If `true`, removes all `data-*` attributes from the output HTML. Default: `false`.\n  </APIItem>\n</APIOptions>\n<APIReturns>\n  <APIItem type=\"Promise<string>\">\n    A promise that resolves to the serialized HTML string.\n  </APIItem>\n</APIReturns>\n</API>\n\n---\n\n### `api.html.deserialize(options)`\n\nParses an HTML string or `HTMLElement` into a Plate `Value` (an array of `Descendant` nodes). This is typically used on the client-side with a fully configured Plate editor.\n\n<API name=\"deserializeHtml\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"PlateEditor\">\n    The client-side Plate editor instance.\n  </APIItem>\n  <APIItem name=\"options\" type=\"DeserializeHtmlOptions\">\n    Options for deserialization.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"DeserializeHtmlOptions\">\n  <APIItem name=\"element\" type=\"HTMLElement | string\">\n    The HTML string or `HTMLElement` to deserialize.\n  </APIItem>\n  <APIItem name=\"collapseWhiteSpace\" type=\"boolean\" optional>\n    If `true` (default), collapses whitespace from text nodes similarly to how browsers treat whitespace in HTML. Set to `false` to preserve all whitespace. Default: `true`.\n  </APIItem>\n  <APIItem name=\"stripWhitespace\" type=\"boolean\" optional>\n    **Deprecated.** Use `collapseWhiteSpace`. If `true`, leading/trailing whitespace is trimmed and sequences of whitespace are collapsed. Default: `true`.\n  </APIItem>\n</APIOptions>\n<APIReturns>\n  <APIItem type=\"Descendant[]\">\n    The deserialized Plate `Value`.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Next Steps\n\n-   Explore the [Static Rendering guide](/docs/plate/static) for creating server-safe components.\n-   Review individual plugin documentation for specific HTML serialization/deserialization capabilities and default rules.\n-   See the [Server-Side HTML Generation Example](/docs/plate/examples/slate-to-html).\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(serializing)/html.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "markdown-docs",
      "title": "Markdown",
      "description": "Convert Plate content to Markdown and vice-versa.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(serializing)/markdown.mdx",
          "content": "---\ntitle: Markdown\ndescription: Convert Plate content to Markdown and vice-versa.\ntoc: true\n---\n\nThe `@platejs/markdown` package provides robust, two-way conversion between Markdown and Plate's content structure.\n\n<ComponentPreview name=\"markdown-to-slate-demo\" />\n\n<ComponentPreview name=\"markdown-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Markdown to Plate JSON:** Convert Markdown strings to Plate's editable format (`deserialize`).\n- **Plate JSON to Markdown:** Convert Plate content back to Markdown strings (`serialize`).\n- **Safe by Default:** Handles Markdown conversion without `dangerouslySetInnerHTML`.\n- **Customizable Rules:** Define how specific Markdown syntax or custom Plate elements are converted using `rules`. Supports MDX.\n- **Extensible:** Utilize [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) via the `remarkPlugins` option.\n- **Compliant:** Supports CommonMark, with GFM (GitHub Flavored Markdown) available via [`remark-gfm`](https://github.com/remarkjs/remark-gfm).\n- **Round-Trip Serialization:** Preserves custom elements through MDX syntax during conversion cycles.\n\n</PackageInfo>\n\n## Why Use Plate Markdown?\n\nWhile libraries like `react-markdown` render Markdown to React elements, `@platejs/markdown` offers deeper integration with the Plate ecosystem:\n\n- **Rich Text Editing:** Enables advanced editing features by converting Markdown to Plate's structured format.\n- **WYSIWYG Experience:** Edit content in a rich text view and serialize it back to Markdown.\n- **Custom Elements & Data:** Handles complex custom Plate elements (mentions, embeds) by converting them to/from MDX.\n- **Extensibility:** Leverages Plate's plugin system and the unified/remark ecosystem for powerful customization.\n\n<Callout type=\"note\">\n  If you only need to display Markdown as HTML without editing or custom\n  elements, `react-markdown` might be sufficient. For a rich text editor with\n  Markdown import/export and custom content, `@platejs/markdown` is the\n  integrated solution.\n</Callout>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add Markdown functionality is with the `MarkdownKit`, which includes pre-configured `MarkdownPlugin` with essential remark plugins for [Plate UI](/docs/plate/installation/plate-ui) compatibility.\n\n<ComponentSource name=\"markdown-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownKit } from '@/components/editor/plugins/markdown-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...MarkdownKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install platejs @platejs/markdown\n```\n\n### Add Plugin\n\n```tsx\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    MarkdownPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfiguring `MarkdownPlugin` is recommended to enable Markdown paste handling and set default conversion `rules`.\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport {\n  MarkdownPlugin,\n  remarkMention,\n  remarkMdx,\n} from '@platejs/markdown';\nimport remarkEmoji from 'remark-emoji';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other Plate plugins\n    MarkdownPlugin.configure({\n      options: {\n        // Add remark plugins for syntax extensions (GFM, emoji shortcodes, Math, MDX)\n        remarkPlugins: [remarkMath, remarkGfm, remarkEmoji, remarkMdx, remarkMention],\n        // Define custom rules if needed\n        rules: {\n          // date: { /* ... rule implementation ... */ },\n        },\n      },\n    }),\n  ],\n});\n\n// To disable Markdown paste handling:\nconst editorWithoutPaste = createPlateEditor({\n  plugins: [\n    // ...other Plate plugins\n    MarkdownPlugin.configure(() => ({ parser: null })),\n  ],\n});\n```\n\n<Callout type=\"info\">\n  If you don't use `MarkdownPlugin` with `configure`, you can still use\n  `editor.api.markdown.deserialize` and `editor.api.markdown.serialize`\n  directly, but without plugin-configured default rules or paste handling.\n</Callout>\n\n### Markdown to Plate (Deserialization)\n\nUse `editor.api.markdown.deserialize` to convert a Markdown string into a Plate `Value` (an array of nodes). This is often used for the editor's initial content.\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\n// ... import other necessary Plate plugins for rendering elements\n\nconst markdownString = '# Hello, *Plate*!';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // MarkdownPlugin must be included\n    MarkdownPlugin,\n    // ... other plugins needed to render the deserialized elements (e.g., HeadingPlugin, ItalicPlugin)\n  ],\n  // Use deserialize in the value factory for initial content\n  value: (editor) =>\n    editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),\n});\n```\n\n<Callout type=\"warning\" title=\"Plugin Requirements\">\n  Ensure all Plate plugins required to render the deserialized Markdown (e.g.,\n  `HeadingPlugin` for `#`, `TablePlugin` for tables) are included in your\n  editor's `plugins` array.\n</Callout>\n\n### Plate to Markdown (Serialization)\n\nUse `editor.api.markdown.serialize` to convert the current editor content (or a specific array of nodes) into a Markdown string.\n\n**Serializing Current Editor Content:**\n\n```tsx\n// Assuming `editor` is your Plate editor instance with content\nconst markdownOutput = editor.api.markdown.serialize();\nconsole.info(markdownOutput);\n```\n\n**Serializing Specific Nodes:**\n\n```tsx\nconst specificNodes = [\n  { type: 'p', children: [{ text: 'Serialize just this paragraph.' }] },\n  { type: 'h1', children: [{ text: 'And this heading.' }] },\n];\n\n// Assuming `editor` is your Plate editor instance\nconst partialMarkdownOutput = editor.api.markdown.serialize({\n  value: specificNodes,\n});\nconsole.info(partialMarkdownOutput);\n```\n\n### Round-Trip Serialization with Custom Elements (MDX)\n\nA key feature is handling custom Plate elements that lack standard Markdown representation (e.g., underline, mentions). `@platejs/markdown` converts these to [MDX][github-mdx] elements during serialization and parses them back during deserialization.\n\n**Example:** Handling a custom `date` element.\n\n**Plate Node Structure:**\n\n```ts\n{\n  type: 'p',\n  children: [\n    { text: 'Today is ' },\n    { type: 'date', date: '2025-03-31', children: [{ text: '' }] } // Leaf elements need a text child\n  ],\n}\n```\n\n**Plugin Configuration with `rules`:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport type { MdMdxJsxTextElement } from '@platejs/markdown';\nimport { MarkdownPlugin, remarkMdx } from '@platejs/markdown';\n// ... other imports\n\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Key matches:\n      // 1. Plate element's plugin 'key' or 'type'.\n      // 2. mdast node type.\n      // 3. MDX tag name.\n      date: {\n        // Markdown -> Plate\n        deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {\n          const dateValue = (mdastNode.children?.[0] as any)?.value || '';\n          return {\n            type: 'date', // Your Plate element type\n            date: dateValue,\n            children: [{ text: '' }], // Valid Plate structure\n          };\n        },\n        // Plate -> Markdown (MDX)\n        serialize: (slateNode): MdMdxJsxTextElement => {\n          return {\n            type: 'mdxJsxTextElement',\n            name: 'date', // MDX tag name\n            attributes: [], // Optional: [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]\n            children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],\n          };\n        },\n      },\n      // ... rules for other custom elements\n    },\n    remarkPlugins: [remarkMdx /*, ... other remark plugins like remarkGfm */],\n  },\n});\n```\n\n**Conversion Process:**\n\n1.  **Serialization (Plate → Markdown):** The Plate `date` node writes as `<date value=\"2025-03-31\" />`.\n2.  **Deserialization (Markdown → Plate):** Both `<date value=\"2025-03-31\" />` and `<date>2025-03-31</date>` convert back to the Plate `date` node.\n\n</Steps>\n\n## API Reference\n\n### `MarkdownPlugin`\n\nThe core plugin configuration object. Use `MarkdownPlugin.configure({ options: {} })` to set global options for Markdown processing.\n\n<API name=\"MarkdownPlugin\">\n  <APIOptions>\n    <APIItem name=\"allowedNodes\" type=\"PlateType | null\">\n      Whitelist specific node types (Plate types and Markdown AST types like\n      `strong`). Cannot be used with `disallowedNodes`. If set, only listed\n      types are processed. Default: `null` (all allowed).\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType | null\">\n      Blacklist specific node types. Cannot be used with `allowedNodes`. Listed\n      types are filtered out. Default: `null`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\">\n      Fine-grained node filtering with custom functions, applied *after*\n      `allowedNodes`/`disallowedNodes`. - `deserialize?: (mdastNode: any) =>\n      boolean`: Filter for Markdown → Plate. Return `true` to keep. -\n      `serialize?: (slateNode: any) => boolean`: Filter for Plate → Markdown.\n      Return `true` to keep. Default: `null`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\">\n      Custom conversion rules between Markdown AST and Plate elements. See\n      [Round-Trip\n      Serialization](#round-trip-serialization-with-custom-elements-mdx) and\n      [Customizing Conversion Rules](#appendix-b-customizing-conversion-rules).\n      For marks/leaves, ensure the rule object has `mark: true`. Default: `null`\n      (uses internal `defaultRules`).\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\">\n      Array of [remark\n      plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)\n      (e.g., `remark-gfm`, `remark-math`, `remark-mdx`). Operates on Markdown\n      AST (`mdast`). Default: `[]`.\n    </APIItem>\n  </APIOptions>\n  <APIAttributes>\n    <APIItem name=\"parser\" type=\"Parser | null\">\n      Configuration for pasted content. Set to `null` to disable Markdown paste\n      handling. Default enables pasting `text/plain` as Markdown. See\n      [PlatePlugin API > parser](/docs/plate/api/core/plate-plugin#parser).\n    </APIItem>\n  </APIAttributes>\n</API>\n\n---\n\n### `api.markdown.deserialize`\n\nConverts a Markdown string into a Plate `Value` (`Descendant[]`).\n\n<API name=\"deserialize\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      The Markdown string to deserialize.\n    </APIItem>\n    <APIItem name=\"options\" type=\"DeserializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"DeserializeMdOptions\">\n    <APIItem name=\"allowedNodes\" type=\"PlateType\" optional>\n      Override plugin `allowedNodes`.\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType\" optional>\n      Override plugin `disallowedNodes`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\" optional>\n      Override plugin `allowNode`.\n    </APIItem>\n    <APIItem name=\"memoize\" type=\"boolean\" optional>\n      Adds `_memo` property with raw Markdown to top-level blocks for\n      memoization (e.g., with `PlateStatic`). Default: `false`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\" optional>\n      Override plugin `rules`.\n    </APIItem>\n    <APIItem name=\"parser\" type=\"ParseMarkdownBlocksOptions\" optional>\n      Options for the underlying Markdown block parser (`parseMarkdownBlocks`).\n      See below.\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\" optional>\n      Override plugin `remarkPlugins`.\n    </APIItem>\n    <APIItem name=\"splitLineBreaks\" type=\"boolean\" optional>\n      If `true`, single line breaks (`\\\\n`) in paragraphs become paragraph\n      breaks. Default: `false`.\n    </APIItem>\n    <APIItem name=\"withoutMdx\" type=\"boolean\" optional>\n      If `true`, skips the MDX preprocessing pass and filters `remarkMdx` out\n      of the plugin list. Default: `false`.\n    </APIItem>\n    <APIItem name=\"preserveEmptyParagraphs\" type=\"boolean\" optional>\n      Preserves empty paragraph nodes during deserialization.\n    </APIItem>\n    <APIItem name=\"onError\" type=\"(error: Error) => void\" optional>\n      Receives parser errors before the safe fallback path runs.\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"Descendant[]\">An array of Plate nodes.</APIReturns>\n</API>\n\n---\n\n### `api.markdown.deserializeInline`\n\nConverts inline Markdown text into Slate children.\n\n<API name=\"deserializeInline\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      Inline Markdown text to deserialize.\n    </APIItem>\n    <APIItem name=\"options\" type=\"DeserializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"Descendant[]\">Slate children for inline content.</APIReturns>\n</API>\n\n---\n\n### `api.markdown.serialize`\n\nConverts a Plate `Value` (`Descendant[]`) into a Markdown string.\n\n<API name=\"serialize\">\n  <APIParameters>\n    <APIItem name=\"options\" type=\"SerializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"SerializeMdOptions\">\n    <APIItem name=\"value\" type=\"Descendant[]\" optional>\n      Plate nodes to serialize. Defaults to `editor.children`.\n    </APIItem>\n    <APIItem name=\"allowedNodes\" type=\"PlateType\" optional>\n      Override plugin `allowedNodes`.\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType\" optional>\n      Override plugin `disallowedNodes`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\" optional>\n      Override plugin `allowNode`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\" optional>\n      Override plugin `rules`.\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\" optional>\n      Override plugin `remarkPlugins` (affects stringification).\n    </APIItem>\n    <APIItem name=\"remarkStringifyOptions\" type=\"RemarkStringifyOptions | null\" optional>\n      Options passed to `remark-stringify`. Defaults to plugin options, with\n      Plate setting emphasis to `_` and resource links to `false`.\n    </APIItem>\n    <APIItem name=\"plainMarks\" type=\"PlateType[] | null\" optional>\n      Marks to serialize as plain text instead of Markdown formatting.\n    </APIItem>\n    <APIItem name=\"spread\" type=\"boolean\" optional>\n      Controls spread formatting for list output. Default: `false`.\n    </APIItem>\n    <APIItem name=\"preserveEmptyParagraphs\" type=\"boolean\" optional>\n      Preserves empty paragraph nodes during serialization.\n    </APIItem>\n    <APIItem name=\"withBlockId\" type=\"boolean\" optional>\n      When true, preserves block IDs in markdown serialization to enable AI\n      comment tracking. Wraps blocks with `<block id=\"...\">content</block>`\n      syntax. - **Default:** `false`\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"string\">A Markdown string.</APIReturns>\n</API>\n\n---\n\n### `parseMarkdownBlocks`\n\nUtility to parse a Markdown string into block-level tokens (used by `deserialize`, useful with `memoize`).\n\n<API name=\"parseMarkdownBlocks\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      The Markdown string.\n    </APIItem>\n    <APIItem name=\"options\" type=\"ParseMarkdownBlocksOptions\" optional>\n      Parsing options.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"ParseMarkdownBlocksOptions\">\n    <APIItem name=\"exclude\" type=\"string[]\" optional>\n      Marked token types (e.g., `'space'`) to exclude. Default: `['space']`.\n    </APIItem>\n    <APIItem name=\"trim\" type=\"boolean\" optional>\n      Trim trailing whitespace from input. Default: `true`.\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"Token[]\">\n    Array of marked `Token` objects with raw Markdown.\n  </APIReturns>\n</API>\n\n## Examples\n\n<Steps>\n\n### Using a Remark Plugin (GFM)\n\nAdd support for GitHub Flavored Markdown (tables, strikethrough, task lists, autolinks).\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport remarkGfm from 'remark-gfm';\n// Import Plate plugins for GFM elements\nimport { TablePlugin } from '@platejs/table/react';\nimport { TodoListPlugin } from '@platejs/list-classic/react'; // Ensure this is the correct List plugin for tasks\nimport { StrikethroughPlugin } from '@platejs/basic-nodes/react';\nimport { LinkPlugin } from '@platejs/link/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    TablePlugin,\n    TodoListPlugin, // Or your specific task list plugin\n    StrikethroughPlugin,\n    LinkPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkGfm],\n      },\n    }),\n  ],\n});\n```\n\n**Usage:**\n\n```tsx\nconst markdown = `\nA table:\n\n| a | b |\n| - | - |\n\n~~Strikethrough~~\n\n- [x] Task list item\n\nVisit https://platejs.org\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// editor.tf.setValue(slateValue); // To set editor content\n\nconst markdownOutput = editor.api.markdown.serialize();\n// markdownOutput will contain GFM syntax\n```\n\n### Customizing Rendering (Syntax Highlighting)\n\nThis example shows two approaches: customizing the rendering component (common for UI changes) and customizing the conversion rule (advanced, for changing Plate structure).\n\n**Background:**\n\n- `@platejs/markdown` converts Markdown fenced code blocks (e.g., \\`\\`\\`js ... \\`\\`\\`) to Plate `code_block` elements with `code_line` children.\n- The Plate `CodeBlockElement` (often from `@platejs/code-block/react`) renders this structure.\n- Syntax highlighting typically occurs within `CodeBlockElement` using a library like `lowlight` (via `CodeBlockPlugin`). See [Code Block Plugin](/docs/plate/code-block) for details.\n\n**Approach 1: Customizing Rendering Component (Recommended for UI)**\n\nTo change how code blocks appear, customize the component for the `code_block` plugin key.\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor } from 'platejs/react';\nimport {\n  CodeBlockPlugin,\n  CodeLinePlugin,\n  CodeSyntaxPlugin,\n} from '@platejs/code-block/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component\n\nconst editor = createPlateEditor({\n  plugins: [\n    CodeBlockPlugin.withComponent(MyCustomCodeBlockElement), // Base plugin for structure/logic\n    CodeLinePlugin.withComponent(MyCustomCodeLineElement),\n    CodeSyntaxPlugin.withComponent(MyCustomCodeSyntaxElement),\n    MarkdownPlugin, // For Markdown conversion\n    // ... other plugins\n  ],\n});\n\n// MyCustomCodeBlockElement.tsx would then implement the desired rendering\n// (e.g., using react-syntax-highlighter), consuming props from PlateElement.\n```\n\nRefer to the [Code Block Plugin documentation](/docs/plate/code-block) for complete examples.\n\n**Approach 2: Customizing Conversion Rule (Advanced - Changing Plate Structure)**\n\nTo fundamentally alter the Plate JSON for code blocks (e.g., storing code as a single string prop), override the `deserialize` rule.\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\n\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Override deserialization for mdast 'code' type\n      code: {\n        deserialize: (mdastNode, deco, options) => {\n          return {\n            type: KEYS.codeBlock, // Use Plate's type\n            lang: mdastNode.lang ?? undefined,\n            rawCode: mdastNode.value || '', // Store raw code directly\n            children: [{ text: '' }], // Plate Element needs a dummy text child\n          };\n        },\n      },\n      // A custom `serialize` rule for `code_block` would also be needed\n      // to convert `rawCode` back to an mdast 'code' node.\n      [KEYS.codeBlock]: {\n        serialize: (slateNode, options) => {\n          return {\n            // mdast 'code' node\n            type: 'code',\n            lang: slateNode.lang,\n            value: slateNode.rawCode,\n          };\n        },\n      },\n    },\n    // remarkPlugins: [...]\n  },\n});\n\n// Your custom rendering component (MyCustomCodeBlockElement) would then\n// need to read the code from the `rawCode` property.\n```\n\nChoose based on whether you're changing UI (Approach 1) or data structure (Approach 2).\n\n### Using Remark Plugins for Math (`remark-math`)\n\nEnable TeX math syntax (`$inline$`, `$$block$$`).\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport remarkMath from 'remark-math';\n// Import Plate math plugins for rendering\nimport { MathPlugin } from '@platejs/math/react'; // Main Math plugin\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    MathPlugin, // Renders block and inline equations\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMath],\n        // Default rules handle 'math' and 'inlineMath' mdast types from remark-math,\n        // converting them to Plate's 'equation' and 'inline_equation' types.\n      },\n    }),\n  ],\n});\n```\n\n**Usage:**\n\n```tsx\nconst markdown = `\nInline math: $E=mc^2$\n\nBlock math:\n$$\n\\\\int_a^b f(x) dx = F(b) - F(a)\n$$\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// slateValue will contain 'inline_equation' and 'equation' nodes.\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// markdownOutput will contain $...$ and $$...$$ syntax.\n```\n\n### Using Mentions (`remarkMention`)\n\nEnable mention syntax using the link format for consistency and special character support.\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin, remarkMention } from '@platejs/markdown';\nimport { MentionPlugin } from '@platejs/mention/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    MentionPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMention],\n      },\n    }),\n  ],\n});\n```\n\n**Supported Format:**\n\n```tsx\nconst markdown = `\nMention: [Alice](mention:alice)\nMention with spaces: [John Doe](mention:john_doe)\nFull name with ID: [Jane Smith](mention:user_123)\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// Creates mention nodes with appropriate values and display text\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// All mentions use the link format: [Alice](mention:alice), [John Doe](mention:john_doe), etc.\n```\n\nThe `remarkMention` plugin uses the **[display text](mention:id)** format - a Markdown link-style format that supports spaces and custom display text.\n\nWhen serializing, all mentions use the link format to ensure consistency and support for special characters.\n\n### Using Columns\n\nEnable column layouts with MDX support for multi-column documents.\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin, remarkMdx } from '@platejs/markdown';\nimport { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    ColumnPlugin,\n    ColumnItemPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMdx], // Required for column MDX syntax\n      },\n    }),\n  ],\n});\n```\n\n**Supported Format:**\n\n```tsx\nconst markdown = `\n<column_group>\n  <column width=\"50%\">\n    Left column content with 50% width\n  </column>\n  <column width=\"50%\">\n    Right column content with 50% width\n  </column>\n</column_group>\n\n<column_group>\n  <column width=\"33%\">First</column>\n  <column width=\"33%\">Second</column>\n  <column width=\"34%\">Third</column>\n</column_group>\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// Creates column_group with nested column elements\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// Preserves column structure with width attributes\n```\n\n**Column Features:**\n\n- Supports arbitrary number of columns\n- Width attributes are optional (defaults to equal distribution)\n- Nested content fully supported within columns\n- Width normalization ensures columns always sum to 100%\n\n</Steps>\n\n## Remark Plugins\n\n`@platejs/markdown` leverages the [unified][github-unified] / [remark][github-remark] ecosystem. Extend its capabilities by adding remark plugins via the `remarkPlugins` option in `MarkdownPlugin.configure`. These plugins operate on the [mdast (Markdown Abstract Syntax Tree)][github-mdast].\n\n**Finding Plugins:**\n\n- [List of remark plugins][github-remark-plugins] (Official)\n- [`remark-plugin` topic on GitHub][github-topic-remark-plugin]\n- [Awesome Remark][github-awesome-remark]\n\n**Common Uses:**\n\n- **Syntax Extensions:** `remark-gfm` (tables, etc.), `remark-math` (TeX), `remark-frontmatter`, `remark-mdx`.\n- **Linting/Formatting:** `remark-lint` (often separate tooling).\n- **Custom Transformations:** Custom plugins to modify mdast.\n\n<Callout type=\"info\" title=\"Remark vs. Rehype\">\n  Plate components (e.g., `TableElement`, `CodeBlockElement`) render Plate JSON.\n  `remarkPlugins` modify the Markdown AST. Unlike some renderers,\n  `rehypePlugins` (for HTML AST) are not part of `MarkdownPlugin`'s conversion\n  pipeline. Run HTML transforms before Plate, or model controlled HTML-like\n  content as MDX plus explicit `rules`.\n</Callout>\n\n## Syntax Support\n\n`@platejs/markdown` uses [`remark-parse`][github-remark-parse], adhering to [CommonMark][commonmark-spec]. Enable GFM or other syntaxes via `remarkPlugins`.\n\n- **Learn Markdown:** [CommonMark Help][commonmark-help]\n- **GFM Spec:** [GitHub Flavored Markdown Spec][gfm-spec]\n\n## Architecture Overview\n\n`@platejs/markdown` bridges Markdown strings and Plate's editor format using the unified/remark ecosystem.\n\n```\n                                             @platejs/markdown\n          +--------------------------------------------------------------------------------------------+\n          |                                                                                            |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |  |           |        |                |        |               |      |           |       |\n markdown-+->+ remark    +-mdast->+ remark plugins +-mdast->+ mdast-to-slate+----->+   nodes   +-plate-+->react elements\n          |  |           |        |                |        |               |      |           |       |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |       ^                                                                      |             |\n          |       |                                                                      v             |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |  |           |        |                |        |               |      |           |       |\n          |  | stringify |<-mdast-+ remark plugins |<-mdast-+ slate-to-mdast+<-----+ serialize |       |\n          |  |           |        |                |        |               |      |           |       |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |                                                                                            |\n          +--------------------------------------------------------------------------------------------+\n```\n\n**Key Steps:**\n\n1.  **Parse (Deserialization):**\n    - Markdown string → `remark-parse` → mdast.\n    - `remarkPlugins` transform mdast (e.g., `remark-gfm`).\n    - `mdast-to-slate` converts mdast to Plate nodes using `rules`.\n    - Plate renders nodes via its component system.\n2.  **Stringify (Serialization):**\n    - Plate nodes → `slate-to-mdast` (using `rules`) → mdast.\n    - `remarkPlugins` transform mdast.\n    - `remark-stringify` converts mdast to Markdown string.\n\n<Callout type=\"note\" title=\"Comparison with react-markdown\">\n  - **Direct Node Rendering:** Plate directly renders its nodes via components,\n  unlike `react-markdown` which often uses rehype to convert Markdown to HTML,\n  then to React elements. - **Bidirectional:** Plate's Markdown processor is\n  fully bidirectional. - **Rich Text Integration:** Nodes are integrated with\n  Plate's editing capabilities. - **Plugin System:** Components are managed via\n  Plate's plugin system.\n</Callout>\n\n## Migrating from `react-markdown`\n\nMigrating involves mapping `react-markdown` concepts to Plate's architecture.\n\n**Key Differences:**\n\n1.  **Rendering Pipeline:** `react-markdown` (MD → mdast → hast → React) vs. `@platejs/markdown` (MD ↔ mdast ↔ Plate JSON; Plate components render Plate JSON).\n2.  **Component Customization:**\n    - `react-markdown`: `components` prop replaces HTML tag renderers.\n    - Plate:\n      - `MarkdownPlugin` `rules`: Customize mdast ↔ Plate JSON conversion.\n      - `createPlateEditor` `components`: Customize React components for Plate node types. See [Appendix C](#appendix-c-components).\n3.  **Plugin Ecosystem:** `@platejs/markdown` primarily uses `remarkPlugins`. `rehypePlugins` are less common.\n\n**Mapping Options:**\n\n| `react-markdown` Prop           | `@platejs/markdown` Equivalent/Concept                                                                           | Notes                                                                                  |\n| :------------------------------ | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |\n| `children` (string)             | Pass to `editor.api.markdown.deserialize(string)`                                                                | Input for deserialization; often in `createPlateEditor` `value` option.                |\n| `remarkPlugins`                 | `MarkdownPlugin.configure({ options: { remarkPlugins: [...] }})`                                                 | Direct mapping; operates on mdast.                                                     |\n| `rehypePlugins`                 | Not part of `MarkdownPlugin`'s conversion pipeline.                                                              | Run any HTML pipeline before passing Markdown or Plate nodes to Plate.                 |\n| `components={{ h1: MyH1 }}`     | `createPlateEditor({ components: { h1: MyH1 } })`                                                                | Configures Plate rendering component. Key depends on `HeadingPlugin` config.           |\n| `components={{ code: MyCode }}` | 1. **Conversion**: `MarkdownPlugin > rules > code`. 2. **Rendering**: `components: { [KEYS.codeBlock]: MyCode }` | `rules` for mdast (`code`) to Plate (`code_block`). `components` for Plate rendering.  |\n| `allowedElements`               | `MarkdownPlugin.configure({ options: { allowedNodes: [...] }})`                                                  | Filters nodes during conversion (mdast/Plate types).                                   |\n| `disallowedElements`            | `MarkdownPlugin.configure({ options: { disallowedNodes: [...] }})`                                               | Filters nodes during conversion.                                                       |\n| `unwrapDisallowed`              | No direct equivalent. Filtering removes nodes.                                                                   | Custom `rules` could implement unwrapping.                                             |\n| `skipHtml`                      | Default behavior strips most HTML.                                                                               | Sanitize or convert raw HTML before calling `editor.api.markdown.deserialize`.          |\n| `urlTransform`                  | Customize via `rules` for `link` (deserialize) or plugin type (serialize).                                       | Handle URL transformations in conversion rules.                                        |\n| `allowElement`                  | `MarkdownPlugin.configure({ options: { allowNode: { ... } } })`                                                  | Function-based filtering during conversion.                                            |\n\n## Appendix A: HTML in Markdown\n\nBy default, `@platejs/markdown` does **not** process raw HTML tags. Standard Markdown syntax still becomes Plate nodes, but literal HTML like `<div>` is ignored unless you handle it outside Plate or model it as MDX/custom nodes.\n\n`MarkdownPlugin` runs `remark-parse`, configured `remarkPlugins`, and Plate conversion rules. It does not run a rehype HTML stage, so `rehype-raw` and `rehype-sanitize` are not `MarkdownPlugin` options.\n\nFor raw HTML from a trusted source, convert it in your own content pipeline before calling Plate. For untrusted input, sanitize with a strict element and attribute whitelist before deserializing.\n\n```tsx\nconst safeMarkdown = await sanitizeMarkdownBeforePlate(untrustedMarkdown);\n\nconst value = editor.api.markdown.deserialize(safeMarkdown);\n```\n\nFor HTML-like custom nodes that you control, prefer MDX syntax with `remarkMdx` and explicit `rules`. That keeps the conversion in Plate's supported Markdown pipeline.\n\n<Callout type=\"destructive\" title=\"Security Warning\">\n  Raw HTML can carry XSS payloads. Treat untrusted Markdown as unsafe until your\n  own pipeline sanitizes it with a strict element and attribute whitelist.\n</Callout>\n\n## Appendix B: Customizing Conversion Rules (`rules`)\n\nThe `rules` option in `MarkdownPlugin.configure` offers fine-grained control over mdast ↔ Plate JSON conversion. Keys in the `rules` object match node types.\n\n- **Deserialization (Markdown → Plate):** Keys are `mdast` node types (e.g., `paragraph`, `heading`, `strong`, `link`, MDX types like `mdxJsxTextElement`). The `deserialize` function takes `(mdastNode, deco, options)` and returns a Plate `Descendant` or `Descendant[]`.\n- **Serialization (Plate → Markdown):** Keys are Plate element/text types (e.g., `p`, `h1`, `a`, `code_block`, `bold`). The `serialize` function takes `(slateNode, options)` and returns an `mdast` node.\n\n**Example: Overriding Link Deserialization**\n\n```tsx title=\"lib/plate-editor.ts\"\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Rule for mdast 'link' type\n      link: {\n        deserialize: (mdastNode, deco, options) => {\n          // Default creates { type: 'a', url: ..., children: [...] }\n          // Add a custom property:\n          return {\n            type: 'a', // Plate link element type\n            url: mdastNode.url,\n            title: mdastNode.title,\n            customProp: 'added-during-deserialize',\n            children: convertChildrenDeserialize(\n              mdastNode.children,\n              deco,\n              options\n            ),\n          };\n        },\n      },\n      // Rule for Plate 'a' type (if serialization needs override for customProp)\n      a: {\n        // Assuming 'a' is the Plate type for links\n        serialize: (slateNode, options) => {\n          // Default creates mdast 'link'\n          // Handle customProp if needed in MDX attributes or similar\n          return {\n            type: 'link', // mdast type\n            url: slateNode.url,\n            title: slateNode.title,\n            // customProp: slateNode.customProp, // MDX attribute?\n            children: convertNodesSerialize(slateNode.children, options),\n          };\n        },\n      },\n    },\n    // ... remarkPlugins ...\n  },\n});\n```\n\n**Default Rules Summary:**\nRefer to [`defaultRules.ts`](https://github.com/udecode/plate/blob/main/packages/markdown/src/lib/rules/defaultRules.ts) for the complete list. Key conversions include:\n\n| Markdown (mdast)    | Plate Type             | Notes                                          |\n| :------------------ | :--------------------- | :--------------------------------------------- |\n| `paragraph`         | `p`                    |                                                |\n| `heading` (depth)   | `h1` - `h6`            | Based on depth.                                |\n| `blockquote`        | `blockquote`           |                                                |\n| `list` (ordered)    | `ol` / `p`\\*           | `ol`/`li`/`lic` or `p` with list indent props. |\n| `list` (unordered)  | `ul` / `p`\\*           | `ul`/`li`/`lic` or `p` with list indent props. |\n| `code` (fenced)     | `code_block`           | Contains `code_line` children.                 |\n| `inlineCode`        | `code` (mark)          | Applied to text.                               |\n| `strong`            | `bold` (mark)          | Applied to text.                               |\n| `emphasis`          | `italic` (mark)        | Applied to text.                               |\n| `delete`            | `strikethrough` (mark) | Applied to text.                               |\n| `link`              | `a`                    |                                                |\n| `image`             | `img`                  | Wraps in paragraph during serialization.       |\n| `thematicBreak`     | `hr`                   |                                                |\n| `table`             | `table`                | Contains `tr`.                                 |\n| `math` (block)      | `equation`             | Requires `remark-math`.                        |\n| `inlineMath`        | `inline_equation`      | Requires `remark-math`.                        |\n| `mdxJsxFlowElement` | _Custom_               | Requires `remark-mdx` and custom `rules`.      |\n| `mdxJsxTextElement` | _Custom_               | Requires `remark-mdx` and custom `rules`.      |\n\n\\* List conversion depends on `ListPlugin` detection.\n\n**Emoji shortcodes:** Add [`remark-emoji`](https://github.com/rhysd/remark-emoji) to `remarkPlugins` to turn `:fire:` into unicode `🔥` on deserialization and back to unicode on serialization.\n\n**GFM footnotes:** With `remark-gfm` enabled, footnotes deserialize into `footnoteReference` and `footnoteDefinition` nodes. Add the matching [Footnote plugins](/docs/plate/footnote) to render them as real editor nodes instead of falling back to unknown types.\n\n---\n\n**Default MDX Conversions (with `remark-mdx`):**\n\n| MDX (mdast)                            | Plate Type               | Notes                                       |\n| :------------------------------------- | :----------------------- | :------------------------------------------ |\n| `<del>...</del>`                       | `strikethrough` (mark)   | Alt for `~~strikethrough~~`                 |\n| `<sub>...</sub>`                       | `subscript` (mark)       | H<sub>2</sub>O                              |\n| `<sup>...</sup>`                       | `superscript` (mark)     | E=mc<sup>2</sup>                            |\n| `<u>...</u>`                           | `underline` (mark)       | <u>Underlined</u>                           |\n| `<mark>...</mark>`                     | `highlight` (mark)       | <mark>Highlighted</mark>                    |\n| `<span style=\"font-family: ...\">`      | `fontFamily` (mark)      |                                             |\n| `<span style=\"font-size: ...\">`        | `fontSize` (mark)        |                                             |\n| `<span style=\"font-weight: ...\">`      | `fontWeight` (mark)      |                                             |\n| `<span style=\"color: ...\">`            | `color` (mark)           |                                             |\n| `<span style=\"background-color: ...\">` | `backgroundColor` (mark) |                                             |\n| `<date>...</date>`                     | `date`                   | Custom Date element                         |\n| `[text](mention:id)`                   | `mention`                | Custom Mention element                      |\n| `<file name=\"...\" />`                  | `file`                   | Custom File element                         |\n| `<audio src=\"...\" />`                  | `audio`                  | Custom Audio element                        |\n| `<video src=\"...\" />`                  | `video`                  | Custom Video element                        |\n| `<toc />`                              | `toc`                    | Table of Contents                           |\n| `<callout>...</callout>`               | `callout`                | Callout block                               |\n| `<column_group>...</column_group>`     | `column_group`           | Multi-column layout container               |\n| `<column width=\"50%\">...</column>`     | `column`                 | Single column with optional width attribute |\n\n## Appendix C: Components for Rendering\n\nWhile `rules` handle MD ↔ Plate conversion, Plate uses React components to _render_ Plate nodes. Configure these in `createPlateEditor` via the `components` option or plugin `withComponent` method.\n\n**Example:**\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\nimport { ParagraphElement } from '@/components/ui/paragraph-node'; // Example UI component\nimport { CodeBlockElement } from '@/components/ui/code-block-node'; // Example UI component\n\nconst editor = createPlateEditor({\n  plugins: [\n    ParagraphPlugin.withComponent(ParagraphElement),\n    CodeBlockPlugin.withComponent(CodeBlockElement),\n    BoldPlugin,\n    /* ... */\n  ],\n});\n```\n\nRefer to [Plugin Components](/docs/plate/plugin-components) for more on creating/registering components.\n\n## Appendix D: `PlateMarkdown` Component (Read-Only Display)\n\nFor a `react-markdown`-like component for read-only display:\n\n```tsx title=\"components/plate-markdown.tsx\"\nimport React, { useEffect } from 'react';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\n// Import necessary Plate plugins for common Markdown features\nimport { HeadingPlugin } from '@platejs/basic-nodes/react';\n// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.\n// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.\n\nexport interface PlateMarkdownProps {\n  children: string; // Markdown content\n  remarkPlugins?: any[];\n  components?: Record<string, React.ComponentType<any>>; // Plate component overrides\n  className?: string;\n}\n\nexport function PlateMarkdown({\n  children,\n  remarkPlugins = [],\n  components = {},\n  className,\n}: PlateMarkdownProps) {\n  const editor = usePlateEditor({\n    plugins: [\n      // Include all plugins needed to render your Markdown\n      HeadingPlugin /* ... other plugins ... */,\n      MarkdownPlugin.configure({ options: { remarkPlugins } }),\n    ],\n    components, // Pass through component overrides\n  });\n\n  useEffect(() => {\n    editor.tf.reset(); // Clear previous content\n    editor.tf.setValue(\n      editor.getApi(MarkdownPlugin).markdown.deserialize(children)\n    );\n  }, [children, editor, remarkPlugins]); // Re-deserialize if markdown or plugins change\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent readOnly className={className} />\n    </Plate>\n  );\n}\n\n// Usage Example:\n// const markdownString = \"# Hello\\nThis is *Markdown*.\";\n// <PlateMarkdown className=\"prose dark:prose-invert\">\n//   {markdownString}\n// </PlateMarkdown>\n```\n\n<Callout type=\"info\" title=\"Initial Value\">\n  This `PlateMarkdown` component provides a **read-only** view. For full\n  editing, see the [Installation guides](/docs/plate/installation).\n</Callout>\n\n## Security Considerations\n\n`@platejs/markdown` prioritizes safety by converting Markdown to a structured Plate format, avoiding direct HTML rendering. However, security depends on:\n\n- **Custom `rules`:** Ensure `deserialize` rules don't introduce unsafe data.\n- **`remarkPlugins`:** Vet third-party remark plugins for potential security risks.\n- **Raw HTML Processing:** Sanitize or convert raw HTML before passing Markdown to Plate. Treat untrusted Markdown as unsafe until your own pipeline has applied a strict whitelist.\n- **Plugin Responsibility:** URL validation in `LinkPlugin` ([`isUrl`](/docs/plate/link#linkplugin)) or `MediaEmbedPlugin` ([`parseMediaUrl`](/docs/plate/media#parsemediaurl)) is crucial.\n\n**Recommendation:** Treat untrusted Markdown input cautiously. Sanitize if allowing complex features or raw HTML.\n\n## Related Links\n\n- **[remark][github-remark]:** Markdown processor.\n- **[unified][github-unified]:** Core processing engine.\n- **[MDX][github-mdx]:** JSX in Markdown.\n- **[react-markdown][github-react-markdown]:** Alternative React Markdown component.\n- **[remark-slate-transformer][github-remark-slate-transformer]:** Initial mdast ↔ Plate conversion work by [inokawa](https://github.com/inokawa).\n\n[commonmark-help]: https://commonmark.org/help/\n[commonmark-spec]: https://spec.commonmark.org/\n[gfm-spec]: https://github.github.com/gfm/\n[github-awesome-remark]: https://github.com/remarkjs/awesome-remark\n[github-mdast]: https://github.com/syntax-tree/mdast\n[github-mdx]: https://mdxjs.com/\n[github-react-markdown]: https://github.com/remarkjs/react-markdown\n[github-remark-slate-transformer]: https://github.com/inokawa/remark-slate-transformer\n[github-rehype-raw]: https://github.com/rehypejs/rehype-raw\n[github-rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize\n[github-remark]: https://github.com/remarkjs/remark\n[github-remark-gfm]: https://github.com/remarkjs/remark-gfm\n[github-remark-parse]: https://github.com/remarkjs/remark/tree/main/packages/remark-parse\n[github-remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins\n[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify\n[github-topic-remark-plugin]: https://github.com/topics/remark-plugin\n[github-unified]: https://github.com/unifiedjs/unified\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(serializing)/markdown.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "font-docs",
      "title": "Font",
      "description": "Provide extended formatting options for document content.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(styles)/font.mdx",
          "content": "---\ntitle: Font\ndescription: Provide extended formatting options for document content.\ndocs:\n  - route: /docs/components/font-color-toolbar-button\n    title: Font Color Toolbar Button\n  - route: /docs/components/font-size-toolbar-button\n    title: Font Size Toolbar Button\n---\n\n<ComponentPreview name=\"font-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Apply font styling to selected text including size, family, color, background color, and weight.\n- Supports custom font families, sizes, colors, and weights.\n\n## Plugins\n\n- `FontBackgroundColorPlugin`: Control background color with `background-color` style\n- `FontColorPlugin`: Control font color with `color` style\n- `FontFamilyPlugin`: Change font family using inline elements with `font-family` style\n- `FontSizePlugin`: Control font size with CSS class or `font-size` style\n- `FontWeightPlugin`: Control font weight with `font-weight` style\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add font styling functionality is with the `FontKit`, which includes pre-configured font plugins with their [Plate UI](/docs/plate/installation/plate-ui) components.\n\n<ComponentSource name=\"font-kit\" />\n\n- Includes all font plugins (`FontColorPlugin`, `FontBackgroundColorPlugin`, `FontSizePlugin`, `FontFamilyPlugin`) with sensible defaults.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { FontKit } from '@/components/editor/plugins/font-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...FontKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/basic-styles\n```\n\n### Add Plugins\n\nInclude the font plugins in your Plate plugins array when creating the editor.\n\n```tsx\nimport {\n  FontBackgroundColorPlugin,\n  FontColorPlugin,\n  FontFamilyPlugin,\n  FontSizePlugin,\n} from '@platejs/basic-styles/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    FontColorPlugin,\n    FontBackgroundColorPlugin,\n    FontFamilyPlugin,\n    FontSizePlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nYou can configure individual font plugins with custom options and target elements.\n\n```tsx\nimport {\n  FontColorPlugin,\n  FontBackgroundColorPlugin,\n  FontSizePlugin,\n  FontFamilyPlugin,\n} from '@platejs/basic-styles/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    FontColorPlugin.configure({\n      inject: {\n        nodeProps: {\n          defaultNodeValue: 'black',\n        },\n        targetPlugins: [KEYS.p],\n      },\n    }),\n    FontSizePlugin.configure({\n      inject: {\n        targetPlugins: [KEYS.p],\n      },\n    }),\n    FontBackgroundColorPlugin.configure({\n      inject: {\n        targetPlugins: [KEYS.p],\n      },\n    }),\n    FontFamilyPlugin.configure({\n      inject: {\n        targetPlugins: [KEYS.p],\n      },\n    }),\n  ],\n});\n```\n\n- `inject.nodeProps.defaultNodeValue`: Sets the default font color value.\n- `inject.targetPlugins`: Specifies which element types can receive font styling (affects HTML parsing).\n\n### Add Toolbar Button\n\nYou can add [`FontColorToolbarButton`](/docs/plate/components/font-color-toolbar-button) and [`FontSizeToolbarButton`](/docs/plate/components/font-size-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to control font color and size.\n\n</Steps>\n\n## Plugins\n\n### `FontBackgroundColorPlugin`\n\nPlugin for font background color formatting. Applies `background-color` style to selected text.\n\n### `FontColorPlugin`\n\nPlugin for font color formatting. Applies `color` style to selected text.\n\n### `FontFamilyPlugin`\n\nPlugin for font family formatting. Applies `font-family` style to selected text.\n\n### `FontSizePlugin`\n\nPlugin for font size formatting. Applies `font-size` style to selected text.\n\n### `FontWeightPlugin`\n\nPlugin for font weight formatting. Applies `font-weight` style to selected text.\n\n## Transforms\n\n### `tf.backgroundColor.addMark`\n\nSet the font background color mark on the selected text.\n\n<API name=\"tf.backgroundColor.addMark\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"string\">\n    The background color value to set (e.g., `'#ff0000'`, `'red'`).\n  </APIItem>\n</APIParameters>\n</API>\n\n### `tf.color.addMark`\n\nSet the font color mark on the selected text.\n\n<API name=\"tf.color.addMark\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"string\">\n    The color value to set (e.g., `'#0000ff'`, `'blue'`).\n  </APIItem>\n</APIParameters>\n</API>\n\n### `tf.fontFamily.addMark`\n\nSet the font family mark on the selected text.\n\n<API name=\"tf.fontFamily.addMark\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"string\">\n    The font family value to set (e.g., `'Arial'`, `'Times New Roman'`).\n  </APIItem>\n</APIParameters>\n</API>\n\n### `tf.fontSize.addMark`\n\nSet the font size mark on the selected text.\n\n<API name=\"tf.fontSize.addMark\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"string\">\n    The font size value to set (e.g., `'16px'`, `'1.2em'`).\n  </APIItem>\n</APIParameters>\n</API>\n\n### `tf.fontWeight.addMark`\n\nSet the font weight mark on the selected text.\n\n<API name=\"tf.fontWeight.addMark\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"string\">\n    The font weight value to set (e.g., `'bold'`, `'400'`, `'600'`).\n  </APIItem>\n</APIParameters>\n</API>\n\n## API\n\n### `toUnitLess`\n\nConvert a font size value to a unitless value.\n\n<API name=\"toUnitLess\">\n<APIParameters>\n  <APIItem name=\"fontSize\" type=\"string\">\n    The font size value to convert.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"string\">\n  The font size value without units.\n</APIReturns>\n</API>\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(styles)/font.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "indent-docs",
      "title": "Indent",
      "description": "Documentation for Indent",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(styles)/indent.mdx",
          "content": "---\ntitle: Indent\ndocs:\n  - route: /docs/components/indent-toolbar-button\n    title: Indent Toolbar Buttons\n---\n\n<ComponentPreview name=\"indent-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Add indentation to block elements using Tab/Shift+Tab keyboard shortcuts.\n- Apply consistent indentation with customizable offset and units.\n- Injects an `indent` prop into targeted block elements.\n- Support for maximum indentation depth control.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add indent functionality is with the `IndentKit`, which includes pre-configured `IndentPlugin` targeting paragraph, heading, blockquote, code block, and toggle elements.\n\n<ComponentSource name=\"indent-kit\" />\n\n- Configures `Paragraph`, `Heading`, `Blockquote`, `CodeBlock`, and `Toggle` elements to support the `indent` property.\n- Sets a custom offset of `24px` for indentation spacing.\n- Provides Tab/Shift+Tab keyboard shortcuts for indenting and outdenting.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { IndentKit } from '@/components/editor/plugins/indent-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...IndentKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/indent\n```\n\n### Add Plugin\n\nInclude `IndentPlugin` in your Plate plugins array when creating the editor.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nYou can configure the `IndentPlugin` to target specific elements and customize indentation behavior.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin.configure({\n      inject: {\n        nodeProps: {\n          styleKey: 'marginLeft',\n        },\n        targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote],\n      },\n      options: {\n        offset: 24,\n        unit: 'px',\n        indentMax: 10,\n      },\n    }),\n  ],\n});\n```\n\n- `inject.nodeProps.styleKey`: Maps the injected prop to the CSS `marginLeft` property.\n- `inject.targetPlugins`: An array of plugin keys indicating which element types can be indented.\n- `options.offset`: Indentation offset in pixels (default: `24`).\n- `options.unit`: Unit for indentation values (default: `'px'`).\n- `options.indentMax`: Maximum number of indentations allowed.\n\n### Add Toolbar Button\n\nYou can add [`IndentToolbarButton`](/docs/plate/components/indent-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to control indentation.\n\n</Steps>\n\n## Plugins\n\n### `IndentPlugin`\n\nPlugin for indenting block elements. It injects an `indent` prop into the elements specified by `inject.targetPlugins` and applies `marginLeft` styling.\n\n<API name=\"IndentPlugin\">\n<APIOptions type=\"object\">\n  <APIItem name=\"inject.nodeProps.nodeKey\" type=\"string\" optional>\n    The property name injected into target elements.\n    - **Default:** `'indent'`\n  </APIItem>\n  <APIItem name=\"inject.nodeProps.styleKey\" type=\"string\" optional>\n    CSS property name for styling.\n    - **Default:** `'marginLeft'`\n  </APIItem>\n  <APIItem name=\"inject.targetPlugins\" type=\"string[]\" optional>\n    Array of plugin keys to target for indent injection.\n    - **Default:** `['p']`\n  </APIItem>\n  <APIItem name=\"options.offset\" type=\"number\" optional>\n    Indentation offset used in `(offset * element.indent) + unit`.\n    - **Default:** `24`\n  </APIItem>\n  <APIItem name=\"options.unit\" type=\"string\" optional>\n    Indentation unit used in `(offset * element.indent) + unit`.\n    - **Default:** `'px'`\n  </APIItem>\n  <APIItem name=\"options.indentMax\" type=\"number\" optional>\n    Maximum number of indentations allowed.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `indent`\n\nIndents the selected block(s) in the editor.\n\n<API name=\"indent\">\n  <APIOptions type=\"SetIndentOptions\" optional>\n    Options for indenting blocks.\n  </APIOptions>\n</API>\n\n### `outdent`\n\nDecrease the indentation of the selected blocks.\n\n<API name=\"outdent\">\n  <APIOptions type=\"SetIndentOptions\" optional>\n    Options for outdenting blocks.\n  </APIOptions>\n</API>\n\n### `setIndent`\n\nAdd offset to the indentation of the selected blocks.\n\n<API name=\"setIndent\">\n<APIOptions type=\"SetIndentOptions\">\n  <APIItem name=\"offset\" type=\"number\" optional>\n    Indentation offset used in `(offset * element.indent) + unit`.\n    - **Default:** `1`\n  </APIItem>\n  <APIItem name=\"getNodesOptions\" type=\"EditorNodesOptions\" optional>\n    Options to get nodes to indent.\n  </APIItem>\n  <APIItem name=\"setNodesProps\" type=\"object\" optional>\n    Additional props to set on nodes to indent.\n  </APIItem>\n  <APIItem name=\"unsetNodesProps\" type=\"string[]\" optional>\n    Additional props to unset on nodes to indent.\n    - **Default:** `[]`\n  </APIItem>\n</APIOptions>\n</API>\n\n## Types\n\n### `SetIndentOptions`\n\nUsed to provide options for setting the indentation of a block of text.\n\n<API name=\"SetIndentOptions\">\n<APIOptions>\n  <APIItem name=\"offset\" type=\"number\">\n    Change in indentation (1 to indent, -1 to outdent).\n    - **Default:** `1`\n  </APIItem>\n  <APIItem name=\"getNodesOptions\" type=\"EditorNodesOptions<V>\">\n    Additional `getNodes` options.\n  </APIItem>\n  <APIItem name=\"setNodesProps\" type=\"object\">\n    Additional `setNodes` options.\n  </APIItem>\n  <APIItem name=\"unsetNodesProps\" type=\"string[]\">\n    Properties to unset when indentation is 0.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useIndentButton`\n\nA behavior hook for the indent button component.\n\n<API name=\"useIndentButton\">\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for the indent button.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n        Callback to handle click event. Indents selected content and focuses editor.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useOutdentButton`\n\nA behavior hook for the outdent button component.\n\n<API name=\"useOutdentButton\">\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for the outdent button.\n    <APISubList>\n      <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n        Callback to handle click event. Outdents selected content and focuses editor.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(styles)/indent.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "line-height-docs",
      "title": "Line Height",
      "description": "Set block line height.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(styles)/line-height.mdx",
          "content": "---\ntitle: Line Height\ndescription: Set block line height.\ndocs:\n  - route: /docs/examples/line-height\n    title: Line Height Demo\n  - route: /docs/components/line-height-toolbar-button\n    title: Line Height Toolbar Button\n---\n\n`LineHeightPlugin` stores line height on blocks with a `lineHeight` property. Setting the default value removes the property, so saved values only carry overrides.\n\n<ComponentPreview name=\"line-height-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Block-level `lineHeight` storage.\n- HTML `line-height` style deserialization.\n- Default-value cleanup.\n- Configurable target block types.\n- Shared `setLineHeight` transform.\n- Registry dropdown toolbar driven by valid node values.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add the Kit\n\nUse `LineHeightKit` for the Plate UI setup. It targets paragraphs and all heading levels, with valid values `1`, `1.2`, `1.5`, `2`, and `3`.\n\n<ComponentSource name=\"line-height-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { LineHeightKit } from '@/components/editor/plugins/line-height-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...LineHeightKit],\n});\n```\n\n### Add the Toolbar Control\n\n`LineHeightToolbarButton` reads `defaultNodeValue` and `validNodeValues` from the plugin inject props.\n\n```tsx\nimport { LineHeightToolbarButton } from '@/components/ui/line-height-toolbar-button';\n\nexport function LineHeightControls() {\n  return <LineHeightToolbarButton />;\n}\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/basic-styles\n```\n\n### Add the Plugin\n\nConfigure target block types and the values your UI should expose.\n\n```tsx\nimport { LineHeightPlugin } from '@platejs/basic-styles/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    LineHeightPlugin.configure({\n      inject: {\n        nodeProps: {\n          defaultNodeValue: 1.5,\n          validNodeValues: [1, 1.2, 1.5, 2, 3],\n        },\n        targetPlugins: [...KEYS.heading, KEYS.p],\n      },\n    }),\n  ],\n});\n```\n\n### Set Line Height\n\nUse the bound transform or the headless utility.\n\n```tsx\nimport { setLineHeight } from '@platejs/basic-styles';\n\neditor.tf.lineHeight.setNodes(2);\n\nsetLineHeight(editor, 1.5, { at: [] });\n```\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseLineHeightPlugin` | `@platejs/basic-styles` | Headless plugin that stores `lineHeight`, injects block props, and parses HTML line-height styles. |\n| `LineHeightPlugin` | `@platejs/basic-styles/react` | React wrapper around `BaseLineHeightPlugin`. |\n| `setLineHeight` | `@platejs/basic-styles` | Sets or clears `lineHeight` on matching blocks. |\n| `tf.lineHeight.setNodes` | `@platejs/basic-styles` | Bound transform exposed by the plugin. |\n| `BaseLineHeightKit` | Registry kit | Static/headless setup for paragraphs and headings. |\n| `LineHeightKit` | Registry kit | React setup plus toolbar dependency. |\n| `LineHeightToolbarButton` | Registry UI | Dropdown that writes values through `lineHeight.setNodes`. |\n\nThe package owns line-height storage and parsing. The registry owns the dropdown UI and allowed value list.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Plugin key | `KEYS.lineHeight` |\n| Stored property | `lineHeight` |\n| Default target plugins | `[KEYS.p]` |\n| Registry target plugins | `[...KEYS.heading, KEYS.p]` |\n| Default node value | `1.5` |\n| Registry valid values | `[1, 1.2, 1.5, 2, 3]` |\n| HTML parser | Reads `element.style.lineHeight`. |\n| Setting a custom value | Sets `{ lineHeight: value }` on matching blocks. |\n| Setting the default value | Unsets `lineHeight` on matching blocks. |\n| Non-target blocks | Are ignored by `setLineHeight`. |\n\n## HTML\n\nThe plugin injects an HTML deserializer into each target plugin. When pasted HTML contains an inline `line-height` style on a target block, Plate stores the value on that block.\n\n```html\n<p style=\"line-height: 2\">Readable spacing</p>\n```\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `LineHeightPlugin` | `@platejs/basic-styles/react` | React line-height plugin. |\n| `BaseLineHeightPlugin` | `@platejs/basic-styles` | Headless line-height plugin. |\n| `editor.tf.lineHeight.setNodes(value, options?)` | `@platejs/basic-styles` | Sets or clears line height on matching blocks. |\n| `setLineHeight(editor, value, options?)` | `@platejs/basic-styles` | Headless transform behind the bound API. |\n\n## Options\n\n| Option | Surface | Use |\n|--------|---------|-----|\n| `inject.targetPlugins` | `LineHeightPlugin` | Block types that can keep `lineHeight`. |\n| `inject.nodeProps.nodeKey` | `BaseLineHeightPlugin` | Stored block property; defaults to `lineHeight`. |\n| `inject.nodeProps.defaultNodeValue` | `BaseLineHeightPlugin` | Value that clears the stored property when selected. |\n| `inject.nodeProps.validNodeValues` | Registry toolbar | Dropdown values used by `LineHeightToolbarButton`. |\n| `SetNodesOptions` | `setLineHeight` | Slate node update options passed to `setNodes` or `unsetNodes`. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(styles)/line-height.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "list-docs",
      "title": "List",
      "description": "Documentation for List",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(styles)/list.mdx",
          "content": "---\ntitle: List\ndocs:\n  - route: /docs/components/list-toolbar-button\n    title: List Toolbar Button\n---\n\n<ComponentPreview name=\"list-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Flexible Block Indentation**: Transform any block type (paragraphs, headings, etc.) into list items through indentation.\n- **Simplified Structure**: Flat DOM structure where each indented block is independent, unlike [List Classic plugin](/docs/plate/list-classic).\n- **List Types**: Support for bulleted lists (unordered) and numbered lists (ordered).\n- **Markdown Shortcuts**: Register the shipped list input rules to create lists from markdown triggers like `-`, `*`, `1.`, and `[]`.\n\nFor more information about the underlying indentation system, see the [Indent plugin](/docs/plate/indent).\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add list functionality is with the `ListKit`, which includes pre-configured `ListPlugin`, the shipped list input rules, and the required [Indent plugin](/docs/plate/indent) targeting paragraph, heading, blockquote, code block, and toggle elements.\n\n<ComponentSource name=\"list-kit\" />\n\n- [`BlockList`](/docs/plate/components/block-list): Renders list wrapper elements with support for todo lists.\n- Includes [`IndentKit`](/docs/plate/indent) for the underlying indentation system.\n- Configures `Paragraph`, `Heading`, `Blockquote`, `CodeBlock`, and `Toggle` elements to support list functionality.\n\n### Add Kit\n\nAdd the kit to your plugins:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { ListKit } from '@/components/editor/plugins/list-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...ListKit,\n  ],\n});\n```\n\n### Add Toolbar Button\n\nYou can add [`ListToolbarButton`](/docs/plate/components/list-toolbar-button) to your [Toolbar](/docs/plate/toolbar) to create and manage lists.\n\n</Steps>\n\n## Turn Into Toolbar Button\n\nYou can add these items to the [Turn Into Toolbar Button](/docs/plate/toolbar#turn-into-toolbar-button) to convert blocks into lists:\n\n```tsx\n{\n  icon: <ListIcon />,\n  label: 'Bulleted list',\n  value: KEYS.ul,\n}\n```\n\n```tsx\n{\n  icon: <ListOrderedIcon />,\n  label: 'Numbered list',\n  value: KEYS.ol,\n}\n```\n\n```tsx\n{\n  icon: <SquareIcon />,\n  label: 'To-do list',\n  value: KEYS.listTodo,\n}\n```\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install @platejs/list @platejs/indent\n```\n\n### Add Plugins\n\nInclude both `IndentPlugin` and `ListPlugin` in your Plate plugins array when creating the editor. The List plugin depends on the Indent plugin.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { ListPlugin } from '@platejs/list/react';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin,\n    ListPlugin,\n  ],\n});\n```\n\n### Configure Plugins\n\nYou can configure both plugins to target specific elements, customize list behavior, and register the shipped markdown rules.\n\n```tsx\nimport { IndentPlugin } from '@platejs/indent/react';\nimport {\n  BulletedListRules,\n  OrderedListRules,\n  TaskListRules,\n} from '@platejs/list';\nimport { ListPlugin } from '@platejs/list/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\nimport { BlockList } from '@/components/ui/block-list';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    IndentPlugin.configure({\n      inject: {\n        targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock],\n      },\n    }),\n    ListPlugin.configure({\n      inputRules: [\n        BulletedListRules.markdown({ variant: '-' }),\n        OrderedListRules.markdown({ variant: '.' }),\n        TaskListRules.markdown({ checked: false }),\n      ],\n      inject: {\n        targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock],\n      },\n      render: {\n        belowNodes: BlockList,\n      },\n    }),\n  ],\n});\n```\n\n- `inject.targetPlugins`: An array of plugin keys indicating which element types can become list items.\n- `inputRules`: Registers the feature-owned markdown shortcuts for bulleted, ordered, and task lists.\n- `render.belowNodes`: Assigns [`BlockList`](/docs/plate/components/block-list) to render list wrapper elements.\n\nFor the runtime model and other variants, see [Plugin Input Rules](/docs/plate/plugin-input-rules).\n\n</Steps>\n\n## Plugins\n\n### `ListPlugin`\n\nPlugin for creating and managing lists. It works with the [Indent plugin](/docs/plate/indent) to provide flexible list functionality where any block can be transformed into a list item through indentation.\n\n<API name=\"ListPlugin\">\n<APIOptions>\n  <APIItem name=\"getSiblingListOptions\" type=\"GetSiblingListOptions<TElement>\" optional>\n    Function to determine indent list options for sibling elements.\n  </APIItem>\n  <APIItem name=\"getListStyleType\" type=\"(element: HTMLElement) => ListStyleType\" optional>\n    Function mapping HTML elements to list style types.\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `getNextList`\n\nGets the next sibling entry with an indent list.\n\n<API name=\"getNextList\">\n<APIParameters>\n  <APIItem name=\"entry\" type=\"ElementEntryOf\">\n    Entry of the current element.\n  </APIItem>\n  <APIItem name=\"options\" type=\"Partial<GetSiblingListOptions>\" optional>\n    Options for getting next indent list.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry | undefined\">\n  Entry of the next sibling with an indent list, or `undefined` if not found.\n</APIReturns>\n</API>\n\n### `getPreviousList`\n\nGets the previous sibling entry with an indent list.\n\n<API name=\"getPreviousList\">\n<APIParameters>\n  <APIItem name=\"entry\" type=\"ElementEntryOf\">\n    Entry of the current element.\n  </APIItem>\n  <APIItem name=\"options\" type=\"Partial<GetSiblingListOptions>\" optional>\n    Options for getting previous indent list.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry | undefined\">\n  Entry of the previous sibling with an indent list, or `undefined` if not found.\n</APIReturns>\n</API>\n\n### `indentList`\n\nIncreases the indentation of the selected blocks.\n\n<API name=\"indentList\">\n<APIOptions type=\"ListOptions\">\n  <APIItem name=\"listStyleType\" type=\"ListStyleType | string\" optional>\n    List style type to use.\n    - **Default:** `ListStyleType.Disc`\n  </APIItem>\n</APIOptions>\n</API>\n\n### `outdentList`\n\nDecreases the indentation of the selected blocks.\n\n<API name=\"outdentList\">\n<APIOptions type=\"ListOptions\">\n  <APIItem name=\"listStyleType\" type=\"ListStyleType | string\" optional>\n    List style type to use.\n    - **Default:** `ListStyleType.Disc`\n  </APIItem>\n</APIOptions>\n</API>\n\n### `someList`\n\nChecks if some of the selected blocks have a specific list style type.\n\n<API name=\"someList\">\n<APIParameters>\n  <APIItem name=\"type\" type=\"string | string[]\">\n    List style type to check.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `toggleList`\n\nToggles the indent list.\n\n<API name=\"toggleList\">\n<APIOptions type=\"ListOptions\">\n  <APIItem name=\"listStyleType\" type=\"ListStyleType | string\" optional>\n    List style type to use.\n  </APIItem>\n\n  <APIItem name=\"listRestart\" type=\"number\" optional>\n    Override the number of the list item.\n  </APIItem>\n\n  <APIItem name=\"listRestartPolite\" type=\"number\" optional>\n    Override the number of the list item, only taking effect if the list item is the first in the list.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Types\n\n### `GetSiblingListOptions`\n\nUsed to provide options for getting the sibling indent list in a block of text.\n\n<API name=\"GetSiblingListOptions\">\n<APIOptions>\n  <APIItem name=\"getPreviousEntry\" type=\"function\">\n    This function is used to get the previous sibling entry from a given entry.\n  </APIItem>\n  <APIItem name=\"getNextEntry\" type=\"function\">\n    This function is used to get the next sibling entry from a given entry.\n  </APIItem>\n  <APIItem name=\"query\" type=\"function\">\n    This function is used to validate a sibling node during the lookup process.\n    If it returns false, the next sibling is checked.\n  </APIItem>\n  <APIItem name=\"eqIndent\" type=\"boolean\">\n    Indicates whether to break the lookup when the sibling node has an indent\n    level equal to the current node. If true, the lookup stops when a sibling\n    node with the same indent level is found.\n  </APIItem>\n  <APIItem name=\"breakQuery\" type=\"(node: TNode) => boolean | undefined\">\n    A function that takes a `TNode` and returns a boolean value or undefined.\n    This function is used to specify a condition under which the lookup process\n    should be stopped.\n  </APIItem>\n  <APIItem name=\"breakOnLowerIndent\" type=\"boolean\">\n    Indicates whether to break the lookup when a sibling node with a lower\n    indent level is found. If true, the lookup stops when a sibling node with a\n    lower indent level is found.\n  </APIItem>\n  <APIItem name=\"breakOnEqIndentNeqListStyleType\" type=\"boolean\">\n    Indicates whether to break the lookup when a sibling node with the same\n    indent level but a different list style type is found. If true, the lookup\n    stops when such a sibling node is found.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Hooks\n\n### `useListToolbarButton`\n\nA behavior hook for the indent list toolbar button.\n\n<API name=\"useListToolbarButton\">\n<APIState>\n  <APIItem name=\"nodeType\" type=\"string\">\n    The list style type.\n  </APIItem>\n  <APIItem name=\"pressed\" type=\"boolean\">\n    Whether the button is pressed.\n  </APIItem>\n</APIState>\n\n<APIReturns type=\"object\">\n  <APIItem name=\"props\" type=\"object\">\n    Props for the toolbar button.\n     <APISubList>\n      <APISubListItem parent=\"props\" name=\"pressed\" type=\"boolean\">\n        Whether the button is pressed.\n      </APISubListItem>\n      <APISubListItem parent=\"props\" name=\"onClick\" type=\"function\">\n        Callback to handle the click event. Toggles the indent list of the specified node type and focuses the editor.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(styles)/list.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "text-align-docs",
      "title": "Text Align",
      "description": "Align block content.",
      "files": [
        {
          "path": "../../content/docs/(plugins)/(styles)/text-align.mdx",
          "content": "---\ntitle: Text Align\ndescription: Align block content.\ndocs:\n  - route: /docs/examples/align\n    title: Align Demo\n  - route: /docs/components/align-toolbar-button\n    title: Align Toolbar Button\n---\n\n`TextAlignPlugin` stores block alignment in an `align` property and renders it as CSS `text-align`. Setting the default `start` value removes the property from matching blocks.\n\n<ComponentPreview name=\"align-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Block-level text alignment.\n- `start`, `left`, `center`, `right`, `end`, and `justify` values.\n- HTML `text-align` style deserialization.\n- Default-value cleanup.\n- Configurable target block types.\n- Registry dropdown toolbar for common alignments.\n\n</PackageInfo>\n\n## Kit Usage\n\n<Steps>\n\n### Add the Kit\n\nUse `AlignKit` for the Plate UI setup. It targets headings, paragraphs, images, and media embeds.\n\n<ComponentSource name=\"align-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { AlignKit } from '@/components/editor/plugins/align-kit';\n\nexport const editor = createPlateEditor({\n  plugins: [...AlignKit],\n});\n```\n\n### Add the Toolbar Control\n\n`AlignToolbarButton` shows left, center, right, and justify controls.\n\n```tsx\nimport { AlignToolbarButton } from '@/components/ui/align-toolbar-button';\n\nexport function AlignControls() {\n  return <AlignToolbarButton />;\n}\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Install Package\n\n```bash\nnpm install @platejs/basic-styles\n```\n\n### Add the Plugin\n\nConfigure the blocks that can store alignment.\n\n```tsx\nimport { TextAlignPlugin } from '@platejs/basic-styles/react';\nimport { KEYS } from 'platejs';\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    TextAlignPlugin.configure({\n      inject: {\n        nodeProps: {\n          defaultNodeValue: 'start',\n          nodeKey: 'align',\n          styleKey: 'textAlign',\n          validNodeValues: [\n            'start',\n            'left',\n            'center',\n            'right',\n            'end',\n            'justify',\n          ],\n        },\n        targetPlugins: [...KEYS.heading, KEYS.p],\n      },\n    }),\n  ],\n});\n```\n\n### Set Alignment\n\nUse the bound transform or the headless utility.\n\n```tsx\nimport { setAlign } from '@platejs/basic-styles';\n\neditor.tf.textAlign.setNodes('center');\n\nsetAlign(editor, 'start', { at: [] });\n```\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BaseTextAlignPlugin` | `@platejs/basic-styles` | Headless plugin that stores alignment, injects block props, and parses HTML text-align styles. |\n| `TextAlignPlugin` | `@platejs/basic-styles/react` | React wrapper around `BaseTextAlignPlugin`. |\n| `setAlign` | `@platejs/basic-styles` | Sets or clears alignment on matching blocks. |\n| `tf.textAlign.setNodes` | `@platejs/basic-styles` | Bound transform exposed by the plugin. |\n| `BaseAlignKit` | Registry kit | Static/headless setup for headings, paragraphs, images, and media embeds. |\n| `AlignKit` | Registry kit | React setup plus toolbar dependency. |\n| `AlignToolbarButton` | Registry UI | Dropdown that writes alignment through `textAlign.setNodes`. |\n\nThe package owns alignment storage and parsing. The registry owns the dropdown UI.\n\n## Behavior\n\n| Behavior | Source |\n|----------|--------|\n| Plugin key | `KEYS.textAlign` |\n| Stored property | `align` |\n| Rendered CSS style | `textAlign` |\n| Default target plugins | `[KEYS.p]` |\n| Registry target plugins | `[...KEYS.heading, KEYS.p, KEYS.img, KEYS.mediaEmbed]` |\n| Default value | `start` |\n| Valid values | `start`, `left`, `center`, `right`, `end`, `justify` |\n| HTML parser | Reads `element.style.textAlign`. |\n| Setting a custom value | Sets `{ align: value }` on matching blocks. |\n| Setting `start` | Unsets the stored alignment property. |\n| Non-target blocks | Are ignored by `setAlign`. |\n\n## HTML\n\nThe plugin injects an HTML deserializer into each target plugin. Pasted HTML with a `text-align` style becomes an `align` prop on matching blocks.\n\n```html\n<p style=\"text-align: center\">Centered text</p>\n```\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `TextAlignPlugin` | `@platejs/basic-styles/react` | React alignment plugin. |\n| `BaseTextAlignPlugin` | `@platejs/basic-styles` | Headless alignment plugin. |\n| `editor.tf.textAlign.setNodes(value, options?)` | `@platejs/basic-styles` | Sets or clears alignment on matching blocks. |\n| `setAlign(editor, value, options?)` | `@platejs/basic-styles` | Headless transform behind the bound API. |\n\n## Options\n\n| Option | Surface | Use |\n|--------|---------|-----|\n| `inject.targetPlugins` | `TextAlignPlugin` | Block types that can keep `align`. |\n| `inject.nodeProps.nodeKey` | `TextAlignPlugin` | Stored block property; registry kits use `align`. |\n| `inject.nodeProps.defaultNodeValue` | `TextAlignPlugin` | Value that clears the stored property when selected. |\n| `inject.nodeProps.styleKey` | `TextAlignPlugin` | CSS property used for rendering. |\n| `inject.nodeProps.validNodeValues` | Registry toolbar | Values exposed to alignment UI. |\n| `SetNodesOptions` | `setAlign` | Slate node update options passed to `setNodes` or `unsetNodes`. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/(plugins)/(styles)/text-align.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-cn-docs",
      "title": "cn",
      "description": "API reference for @udecode/cn.",
      "files": [
        {
          "path": "../../content/docs/api/cn.mdx",
          "content": "---\ntitle: cn\ndescription: API reference for @udecode/cn.\n---\n\n`@udecode/cn` provides class-name helpers and component wrappers for React UI primitives. The package also re-exports `@udecode/react-utils` from its root entry.\n\n## Install\n\n```bash\nnpm install @udecode/cn\n```\n\n## Quick Use\n\n```tsx\nimport * as React from 'react';\n\nimport { cn, withCn, withProps, withVariants } from '@udecode/cn';\n\nconst active = true;\nconst className = cn('px-2', active && 'bg-accent', 'px-4');\n\nconst ButtonBase = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<'button'>\n>((props, ref) => <button ref={ref} {...props} />);\n\nconst Button = withCn(ButtonBase, 'inline-flex items-center rounded-md');\n\nconst PrimaryButton = withProps(Button, {\n  type: 'button',\n  className: 'bg-primary text-primary-foreground',\n});\n```\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `cn` | `@udecode/cn` | Combines `class-variance-authority` `cx` with `tailwind-merge`. |\n| `withCn` | `@udecode/cn` | Creates a component with a default merged `className`. |\n| `withProps` | `@udecode/cn` | Creates a `forwardRef` component with default props. |\n| `withVariants` | `@udecode/cn` | Creates a `forwardRef` component that computes classes from `cva` variants. |\n| React utility exports | `@udecode/react-utils` | Re-exported from the `@udecode/cn` root entry. |\n\n## Behavior\n\n| Helper | Behavior |\n|--------|----------|\n| `cn(...inputs)` | Runs values through `cx`, then resolves Tailwind conflicts with `twMerge`. |\n| `withCn(Component, ...inputs)` | Calls `withProps` with a default `className` created from `cn`. |\n| `withProps(Component, defaultProps)` | Merges default props first and consumer props second. |\n| `withProps` class names | Merges `defaultProps.className` and `props.className` with `cn`. |\n| `withProps` refs | Returns `React.forwardRef`. |\n| `withVariants(Component, variants, onlyVariantsProps?)` | Applies `variants(variantProps)` and merges the result with `className`. |\n| `onlyVariantsProps` | Removes variant-only props from the rendered component props. |\n\n## API Reference\n\n### `cn`\n\nMerge conditional class values and resolve Tailwind conflicts.\n\n```tsx\ncn('px-2', active && 'bg-accent', 'px-4');\n```\n\n<API name=\"cn\">\n<APIParameters>\n  <APIItem name=\"...inputs\" type=\"CxOptions\">\n    Values accepted by `class-variance-authority` `cx`.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"string\">\n  The merged class name.\n</APIReturns>\n</API>\n\n### `withCn`\n\nCreate a component with default classes.\n\n```tsx\nconst Card = withCn('div', 'rounded-md border bg-card');\n```\n\n<API name=\"withCn\">\n<APIParameters>\n  <APIItem name=\"Component\" type=\"React.ComponentType\">\n    Component receiving the default class name.\n  </APIItem>\n  <APIItem name=\"...inputs\" type=\"CxOptions\">\n    Default class values.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A component that merges the default class name with consumer `className`.\n</APIReturns>\n</API>\n\n### `withProps`\n\nCreate a component with default props. Consumer props override default props, except class names are merged.\n\n```tsx\nconst SubmitButton = withProps('button', {\n  type: 'submit',\n  className: 'font-medium',\n});\n```\n\n<API name=\"withProps\">\n<APIParameters>\n  <APIItem name=\"Component\" type=\"React.ElementType\">\n    Component receiving default props.\n  </APIItem>\n  <APIItem name=\"defaultProps\" type=\"Partial<React.ComponentPropsWithoutRef<T>>\">\n    Props applied before consumer props.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A `forwardRef` component with merged class names.\n</APIReturns>\n</API>\n\n### `withVariants`\n\nCreate a component backed by `class-variance-authority` variants.\n\n```tsx\nimport { cva } from 'class-variance-authority';\n\nconst buttonVariants = cva('inline-flex items-center', {\n  variants: {\n    size: {\n      sm: 'h-8 px-2',\n      md: 'h-9 px-3',\n    },\n  },\n});\n\nconst Button = withVariants('button', buttonVariants, ['size']);\n```\n\n<API name=\"withVariants\">\n<APIParameters>\n  <APIItem name=\"Component\" type=\"React.ElementType\">\n    Component receiving variant classes.\n  </APIItem>\n  <APIItem name=\"variants\" type=\"V extends ReturnType<typeof cva>\">\n    `cva` variant function.\n  </APIItem>\n  <APIItem name=\"onlyVariantsProps\" type=\"(keyof VariantProps<V>)[]\" optional>\n    Variant props to remove before rendering the component.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A `forwardRef` component with variant props and merged class names.\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/cn.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-plate-components-docs",
      "title": "Plate Components",
      "description": "API reference for Plate React components.",
      "files": [
        {
          "path": "../../content/docs/api/core/plate-components.mdx",
          "content": "---\ntitle: Plate Components\ndescription: API reference for Plate React components.\n---\n\nPlate components connect a `PlateEditor` to React rendering. Use `Plate` and `PlateContent` for editable editors, `PlateView` for read-only static views, and the node primitives when writing custom plugin components.\n\n## Editable Editor\n\n`Plate` owns the editor store. `PlateContent` renders the editable surface under that store.\n\n```tsx title=\"components/editor.tsx\"\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nexport function Editor() {\n  const editor = usePlateEditor({\n    value: [\n      {\n        children: [{ text: 'Start writing.' }],\n        type: 'p',\n      },\n    ],\n  });\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent placeholder=\"Write...\" />\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"warning\" title=\"Provider required\">\n  `PlateContent` must render below `Plate`. Hooks such as `useEditorRef`,\n  `useEditorReadOnly`, and `usePlateStore` throw when there is no `Plate` or\n  `PlateController` above them.\n</Callout>\n\n## Read-Only View\n\nUse `PlateView` with a static editor when you need rendered content and Plate copy behavior without an editable surface.\n\n```tsx title=\"components/read-only-editor.tsx\"\nimport { PlateView, usePlateViewEditor } from 'platejs/react';\n\nconst value = [\n  {\n    children: [{ text: 'Published content.' }],\n    type: 'p',\n  },\n];\n\nexport function ReadOnlyEditor() {\n  const editor = usePlateViewEditor({ value });\n\n  if (!editor) return null;\n\n  return <PlateView editor={editor} />;\n}\n```\n\n`PlateView` wraps `PlateStatic`. Its default `onCopy` writes Plate fragment data to the clipboard, unless you pass your own `onCopy` prop.\n\n## Component Map\n\n| Component | Use For |\n|-----------|---------|\n| `Plate` | Store provider for one editor instance. |\n| `PlateContent` | Editable Slate surface with plugin handlers, decorators, renderers, hotkeys, and editor effects. |\n| `PlateView` | Static read-only rendering with Plate fragment copy support. |\n| `PlateContainer` | Editor container `div` plus `beforeContainer` and `afterContainer` plugin slots. |\n| `PlateSlate` | Slate provider wrapper used by `PlateContent`; also applies `aboveSlate` plugin wrappers. |\n| `PlateElement` | Default element renderer for block and inline elements. |\n| `PlateLeaf` | Default decorated text-leaf renderer. |\n| `PlateText` | Default text-node renderer for non-decoration leaf rendering. |\n| `ContentVisibilityChunk` | Default chunk renderer when chunking uses `content-visibility: auto`. |\n| `PlateTest` | Test helper that creates or wraps an editor and renders `PlateContent` with test attributes. |\n\n## Render Pipeline\n\n`PlateContent` builds the editable props with `useEditableProps`. That pipeline combines store-level renderers, `PlateContent` render props, plugin decorators, plugin DOM handlers, and chunking.\n\n| Stage | Source |\n|-------|--------|\n| Slate provider | `PlateSlate` uses `editor.children`, `editor.meta.key`, and store callbacks. |\n| Editable props | `useEditableProps` pipes decorators, DOM handlers, `renderChunk`, `renderElement`, `renderLeaf`, and `renderText`. |\n| Plugin slots | `beforeEditable`, `aboveEditable`, and `afterEditable` wrap or sit around the editable surface. |\n| Effects | `EditorMethodsEffect`, `EditorHotkeysEffect`, `EditorRefEffect`, and `PlateControllerEffect` run inside `PlateContent`. |\n| Read-only state | `disabled` forces read-only; `readOnly` syncs back into the Plate store. |\n\n## Node Primitives\n\nUse `PlateElement`, `PlateLeaf`, and `PlateText` inside plugin components. They merge Slate attributes with your `className`, `style`, and `ref`.\n\n```tsx title=\"components/paragraph-element.tsx\"\nimport { PlateElement, type PlateElementProps } from 'platejs/react';\n\nexport function ParagraphElement(props: PlateElementProps) {\n  return <PlateElement as=\"p\" className=\"leading-7\" {...props} />;\n}\n```\n\n| Primitive | Behavior |\n|-----------|----------|\n| `PlateElement` | Adds `data-slate-node=\"element\"`, preserves inline metadata, sets `data-block-id` for mounted block elements with an `id`, and adds directional-affinity spacers when needed. |\n| `PlateLeaf` | Renders a text leaf and adds hard-affinity spacers when needed. |\n| `PlateText` | Renders a text node without leaf-decoration matching. |\n| `useNodeAttributes` | Merges Slate attributes, refs, class names, and styles for node primitives. |\n\n## API Reference\n\n### `Plate`\n\nRoot provider for one editor instance.\n\n<API name=\"Plate\">\n<APIProps>\n  <APIItem name=\"editor\" type=\"PlateEditor | null\">\n    Editor instance. When `null`, `Plate` renders nothing.\n  </APIItem>\n  <APIItem name=\"children\" type=\"React.ReactNode\">\n    React children that can read the Plate store.\n  </APIItem>\n  <APIItem name=\"decorate\" type=\"({ editor, entry }) => TRange[]\" optional>\n    Store-level decorate function used by `PlateContent`.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\" optional>\n    Store-level read-only state. Defaults to `editor.dom.readOnly`.\n  </APIItem>\n  <APIItem name=\"primary\" type=\"boolean\" optional>\n    Registers the editor as a primary editor for `PlateController`.\n  </APIItem>\n  <APIItem name=\"renderElement\" type=\"EditableProps['renderElement']\" optional>\n    Fallback element renderer stored on the Plate store.\n  </APIItem>\n  <APIItem name=\"renderLeaf\" type=\"EditableProps['renderLeaf']\" optional>\n    Fallback leaf renderer stored on the Plate store.\n  </APIItem>\n  <APIItem name=\"onChange\" type=\"({ editor, value }) => void\" optional>\n    Runs after Slate change handling when plugin `onChange` handlers do not handle the event.\n  </APIItem>\n  <APIItem name=\"onValueChange\" type=\"({ editor, value }) => void\" optional>\n    Runs when Slate reports a value change.\n  </APIItem>\n  <APIItem name=\"onSelectionChange\" type=\"({ editor, selection }) => void\" optional>\n    Runs when Slate reports a selection change.\n  </APIItem>\n  <APIItem name=\"onNodeChange\" type=\"({ editor, node, operation, prevNode }) => void\" optional>\n    Stored on `SlateExtensionPlugin` by `PlateContent` and called for node operations.\n  </APIItem>\n  <APIItem name=\"onTextChange\" type=\"({ editor, node, operation, prevText, text }) => void\" optional>\n    Stored on `SlateExtensionPlugin` by `PlateContent` and called for text operations.\n  </APIItem>\n  <APIItem name=\"suppressInstanceWarning\" type=\"boolean\" optional>\n    Suppresses the multiple-instance warning from `usePlateInstancesWarn`.\n  </APIItem>\n</APIProps>\n</API>\n\n### `PlateContent`\n\nEditable surface for a `Plate` editor.\n\n<API name=\"PlateContent\">\n<APIProps>\n  <APIItem name=\"id\" type=\"string\" optional>\n    Editor scope used by `useEditorRef(id)` and `usePlateStore(id)`.\n  </APIItem>\n  <APIItem name=\"autoFocusOnEditable\" type=\"boolean\" optional>\n    Focuses the editor at the end when `readOnly` changes from `true` to `false`.\n  </APIItem>\n  <APIItem name=\"disabled\" type=\"boolean\" optional>\n    Forces read-only state and sets `aria-disabled`.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\" optional>\n    Overrides the store read-only value and syncs it back to the store.\n  </APIItem>\n  <APIItem name=\"decorate\" type=\"({ editor, entry }) => TRange[]\" optional>\n    Editable-level decorate function. Store-level `decorate` wins when present.\n  </APIItem>\n  <APIItem name=\"renderEditable\" type=\"(editable: React.ReactElement) => React.ReactNode\" optional>\n    Wraps or replaces the generated `Editable` element.\n  </APIItem>\n  <APIItem name=\"renderChunk\" type=\"RenderChunkFn\" optional>\n    Custom chunk renderer. Defaults to `ContentVisibilityChunk` when chunking enables `contentVisibilityAuto`.\n  </APIItem>\n  <APIItem name=\"renderElement\" type=\"RenderElementFn\" optional>\n    Fallback element renderer after plugin renderers.\n  </APIItem>\n  <APIItem name=\"renderLeaf\" type=\"RenderLeafFn\" optional>\n    Fallback leaf renderer after plugin leaf renderers.\n  </APIItem>\n  <APIItem name=\"renderText\" type=\"RenderTextFn\" optional>\n    Fallback text renderer after non-decoration text renderers.\n  </APIItem>\n  <APIItem name=\"renderPlaceholder\" type=\"EditableProps['renderPlaceholder']\" optional>\n    Placeholder renderer passed to Slate `Editable`.\n  </APIItem>\n  <APIItem name=\"placeholder\" type=\"string\" optional>\n    Placeholder text passed to Slate `Editable`.\n  </APIItem>\n  <APIItem name=\"scrollSelectionIntoView\" type=\"(editor, domRange) => void\" optional>\n    Slate selection scrolling hook.\n  </APIItem>\n  <APIItem name=\"onDOMBeforeInput\" type=\"(event: InputEvent) => void\" optional>\n    DOM before-input handler passed through the plugin handler pipeline.\n  </APIItem>\n  <APIItem name=\"onKeyDown\" type=\"(event: React.KeyboardEvent) => void\" optional>\n    Keyboard handler passed through the plugin handler pipeline.\n  </APIItem>\n  <APIItem name=\"as\" type=\"React.ElementType\" optional>\n    Element type passed to Slate `Editable`.\n  </APIItem>\n  <APIItem name=\"disableDefaultStyles\" type=\"boolean\" optional>\n    Passed to Slate `Editable`.\n  </APIItem>\n  <APIItem name=\"role\" type=\"string\" optional>\n    ARIA role passed to Slate `Editable`.\n  </APIItem>\n  <APIItem name=\"style\" type=\"React.CSSProperties\" optional>\n    Style object passed to Slate `Editable`.\n  </APIItem>\n</APIProps>\n</API>\n\n`PlateContent` also accepts the DOM handler props listed in `DOMHandlers`, including clipboard, composition, focus, keyboard, pointer, mouse, drag, touch, media, and form handlers.\n\n### `PlateView`\n\nRead-only static renderer with Plate copy support.\n\n<API name=\"PlateView\">\n<APIProps>\n  <APIItem name=\"editor\" type=\"SlateEditor\">\n    Static editor instance.\n  </APIItem>\n  <APIItem name=\"value\" type=\"Value\" optional>\n    Controlled value alias. When present, `PlateStatic` assigns it to `editor.children`.\n  </APIItem>\n  <APIItem name=\"onCopy\" type=\"React.ClipboardEventHandler<HTMLDivElement>\" optional>\n    Overrides the default Plate fragment copy handler.\n  </APIItem>\n  <APIItem name=\"className\" type=\"string\" optional>\n    Merged with the `slate-editor` class by `PlateStatic`.\n  </APIItem>\n  <APIItem name=\"style\" type=\"React.CSSProperties\" optional>\n    Style object passed to the static root `div`.\n  </APIItem>\n</APIProps>\n</API>\n\n### `PlateContainer`\n\nContainer `div` with plugin container slots.\n\n<API name=\"PlateContainer\">\n<APIProps>\n  <APIItem name=\"children\" type=\"React.ReactNode\" optional>\n    Content rendered inside the container.\n  </APIItem>\n  <APIItem name=\"...props\" type=\"React.HTMLAttributes<HTMLDivElement>\" optional>\n    HTML props passed to the container `div` and container slot components.\n  </APIItem>\n</APIProps>\n</API>\n\n### Render Primitives\n\n| API | Default Element | Notes |\n|-----|-----------------|-------|\n| `PlateElement` | `div` | Accepts `as`, `attributes`, `className`, `style`, `ref`, `element`, `path`, `editor`, `plugin`, and `insetProp`. |\n| `PlateLeaf` | `span` | Accepts `as`, `attributes`, `className`, `style`, `ref`, `leaf`, `text`, `editor`, `plugin`, and `inset`. |\n| `PlateText` | `span` | Accepts `as`, `attributes`, `className`, `style`, `ref`, `text`, `editor`, and `plugin`. |\n| `ContentVisibilityChunk` | `div` | Wraps children only when `lowest` is true. |\n| `withHOC` | `React.forwardRef` | Wraps one ref-capable component with another ref-capable component. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core/plate-components.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-plate-controller-docs",
      "title": "Plate Controller",
      "description": "API reference for PlateController.",
      "files": [
        {
          "path": "../../content/docs/api/core/plate-controller.mdx",
          "content": "---\ntitle: Plate Controller\ndescription: API reference for PlateController.\n---\n\n`PlateController` lets UI outside a single `<Plate>` subtree read the active editor store. Use it for shared toolbars, side panels, inspectors, and multi-editor shells.\n\n## Quick Use\n\nWrap the shared UI and all editors in `PlateController`. `PlateContent` registers each mounted editor store through `PlateControllerEffect`.\n\n```tsx title=\"components/editor-shell.tsx\" showLineNumbers\nimport {\n  Plate,\n  PlateContent,\n  PlateController,\n  usePlateEditor,\n} from 'platejs/react';\n\nexport function EditorShell() {\n  return (\n    <PlateController>\n      <ActiveEditorLabel />\n      <MainEditor />\n      <SecondaryEditor />\n    </PlateController>\n  );\n}\n\nfunction MainEditor() {\n  const editor = usePlateEditor({ id: 'main' });\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent />\n    </Plate>\n  );\n}\n\nfunction SecondaryEditor() {\n  const editor = usePlateEditor({ id: 'secondary' });\n\n  return (\n    <Plate editor={editor} primary={false}>\n      <PlateContent />\n    </Plate>\n  );\n}\n```\n\n`primary` belongs on `Plate`, not on `createPlateEditor` or `usePlateEditor`.\n\n## Active Editor Lookup\n\nHooks such as `useEditorRef()` and `useEditorMounted()` normally read the nearest `Plate` store. Inside `PlateController`, the same hooks can resolve a store outside a specific editor tree.\n\n| Lookup | Behavior |\n|--------|----------|\n| `useEditorRef('main')` | Resolves the store registered for `main`. |\n| `useEditorRef()` | Resolves the active editor store, then the first mounted primary editor store. |\n| Missing store with controller | Returns the fallback store, so `useEditorRef()` returns a fallback editor. |\n| Missing store without controller | Throws `Plate hooks must be used inside a Plate or PlateController`. |\n\nController lookup order without an explicit ID:\n\n1. `activeId`\n2. each ID in `primaryEditorIds`\n3. fallback store when no store is available\n\n## Fallback Editors\n\nThe fallback editor exists so read-only UI can render while no editor is active. It is not safe for transforms.\n\n```tsx title=\"components/active-editor-label.tsx\"\nimport { useEditorMounted, useEditorRef } from 'platejs/react';\n\nexport function ActiveEditorLabel() {\n  const editor = useEditorRef();\n  const mounted = useEditorMounted();\n\n  if (!mounted || editor.meta.isFallback) {\n    return <p>No editor selected.</p>;\n  }\n\n  return <p>Active editor: {editor.id}</p>;\n}\n```\n\n<Callout type=\"warning\" title=\"Guard transforms\">\n  Check `useEditorMounted(id?)` or `!editor.meta.isFallback` before running\n  transforms from UI that lives under `PlateController`.\n</Callout>\n\n## Registration\n\n`PlateControllerEffect` runs inside `PlateContent`. It registers the current `Plate` store by editor ID, appends primary editors to `primaryEditorIds`, removes them on unmount, and sets `activeId` when Slate focus enters that editor.\n\n| State | Owner | Behavior |\n|-------|-------|----------|\n| `editorStores` | `PlateControllerEffect` | Maps mounted editor IDs to their Jotai stores. Unmounted IDs are set to `null`. |\n| `primaryEditorIds` | `PlateControllerEffect` | Appends mounted editors whose `Plate` store has `primary: true`; removes them on unmount. |\n| `activeId` | `PlateControllerEffect` | Set to the focused editor ID. Cleared on unmount when the unmounted editor was active. |\n\n## API Reference\n\n### `PlateController`\n\nProvider for cross-editor lookup state.\n\n<API name=\"PlateController\">\n<APIProps>\n  <APIItem name=\"children\" type=\"React.ReactNode\">\n    Shared UI and editor trees that should participate in controller lookup.\n  </APIItem>\n  <APIItem name=\"activeId\" type=\"string | null\" optional>\n    Initial active editor ID.\n  </APIItem>\n  <APIItem name=\"editorStores\" type=\"Record<string, JotaiStore | null>\" optional>\n    Initial editor-store map.\n  </APIItem>\n  <APIItem name=\"primaryEditorIds\" type=\"string[]\" optional>\n    Initial primary editor ID list.\n  </APIItem>\n</APIProps>\n</API>\n\n### Controller Store State\n\n| State | Type | Default |\n|-------|------|---------|\n| `activeId` | `string \\| null` | `null` |\n| `editorStores` | `Record<string, JotaiStore \\| null>` | `{}` |\n| `primaryEditorIds` | `string[]` | `[]` |\n\n### `usePlateControllerStore`\n\nResolve a Plate Jotai store from the controller.\n\n<API name=\"usePlateControllerStore\">\n<APIParameters>\n  <APIItem name=\"idProp\" type=\"string\" optional>\n    Editor ID to resolve directly.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"JotaiStore | null\">\n  Matching editor store, active editor store, first mounted primary editor store, or `null`.\n</APIReturns>\n</API>\n\n### `usePlateControllerExists`\n\nCheck whether a local controller provider exists.\n\n<API name=\"usePlateControllerExists\">\n<APIReturns type=\"boolean\">\n  `true` when `usePlateControllerLocalStore()` finds a controller store.\n</APIReturns>\n</API>\n\n### `usePlateControllerLocalStore`\n\nRead the local controller atom store.\n\n<API name=\"usePlateControllerLocalStore\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"string | { scope?: string; warnIfNoStore?: boolean }\" optional>\n    Scope options passed to the generated controller store hook. A string is treated as `scope`.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"PlateControllerStore\">\n  Local controller store hook result.\n</APIReturns>\n</API>\n\n### `PlateControllerEffect`\n\nRegister a `Plate` store with the nearest controller.\n\n<API name=\"PlateControllerEffect\">\n<APIProps>\n  <APIItem name=\"id\" type=\"string\" optional>\n    Editor ID to register. Defaults to the ID from the current Plate store.\n  </APIItem>\n</APIProps>\n</API>\n\n`PlateContent` renders `PlateControllerEffect` for you. Render it directly only when you build a custom content surface that still needs controller registration.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core/plate-controller.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-plate-editor-docs",
      "title": "Plate Editor",
      "description": "API reference for the Plate editor runtime.",
      "files": [
        {
          "path": "../../content/docs/api/core/plate-editor.mdx",
          "content": "---\ntitle: Plate Editor\ndescription: API reference for the Plate editor runtime.\n---\n\n`PlateEditor` is the React editor type returned by `createPlateEditor`, `usePlateEditor`, and `withPlate`. It extends the base Slate editor with plugin registries, typed `api` and `tf` surfaces, DOM state, metadata, and plugin option helpers.\n\n## Ownership\n\n| Surface | Owner | Notes |\n| --- | --- | --- |\n| `PlateEditor` | `@platejs/core/react` | React editor type with Plate plugin APIs, transforms, handlers, renders, and hooks. |\n| `SlateEditor` | `@platejs/core` | Non-React editor type used by server-side and static editor paths. |\n| Slate primitives | `@platejs/slate` | `children`, `selection`, `operations`, core `api`, and core `tf` transforms. |\n| Core plugins | `@platejs/core` | Debugging, HTML parsing, parser pipeline, length, node id, history, input rules, and base paragraph behavior. |\n| React core plugins | `@platejs/core/react` | React extension, DOM integration, event editor, navigation feedback, and React paragraph plugin. |\n\nUse `PlateEditor` when a page or component runs inside React. Use `SlateEditor` when you need the headless editor from `createSlateEditor`.\n\n## Editor Shape\n\nThe editor is still a Slate editor. Plate adds typed plugin access, plugin metadata, DOM state, and option stores on top of that shape.\n\n<API name=\"PlateEditor\">\n<APIAttributes>\n  <APIItem name=\"id\" type=\"string\">\n    Unique editor instance id. `withSlate` uses the provided `id`, an existing editor id, or `nanoid()`.\n  </APIItem>\n  <APIItem name=\"children\" type=\"Value\">\n    Current document value.\n  </APIItem>\n  <APIItem name=\"selection\" type=\"TRange | null\">\n    Current Slate selection.\n  </APIItem>\n  <APIItem name=\"operations\" type=\"Operation[]\">\n    Operations applied since Slate last flushed the editor.\n  </APIItem>\n  <APIItem name=\"api\" type=\"EditorApi & CorePluginApi\">\n    Core Slate APIs plus APIs contributed by resolved Plate plugins.\n  </APIItem>\n  <APIItem name=\"tf\" type=\"EditorTransforms & CorePluginTransforms\">\n    Core Slate transforms plus transforms contributed by resolved Plate plugins.\n  </APIItem>\n  <APIItem name=\"transforms\" type=\"PlateEditor['tf']\">\n    Alias for `tf`.\n  </APIItem>\n  <APIItem name=\"plugins\" type=\"Record<string, AnyEditorPlatePlugin>\">\n    Resolved plugin map keyed by plugin key.\n  </APIItem>\n  <APIItem name=\"dom\" type=\"PlateEditor['dom']\">\n    Runtime DOM state owned by the editor instance.\n  </APIItem>\n  <APIItem name=\"meta\" type=\"PlateEditor['meta']\">\n    Runtime metadata and plugin caches built during plugin resolution.\n  </APIItem>\n</APIAttributes>\n</API>\n\n## Runtime State\n\n`editor.dom` is mutable runtime state. It is updated by React integration, event handlers, focus tracking, and read-only setup.\n\n| Field | Type | Set by |\n| --- | --- | --- |\n| `composing` | `boolean` | Composition handlers. |\n| `currentKeyboardEvent` | `KeyboardEventLike \\| null` | `SlateReactExtensionPlugin` while handling keyboard shortcuts. |\n| `focused` | `boolean` | DOM focus integration. |\n| `prevSelection` | `TRange \\| null` | Selection tracking. |\n| `readOnly` | `boolean` | `withSlate({ readOnly })`, then React read-only state. |\n\n`editor.meta` carries plugin resolution output. Most application code reads this indirectly through helpers like `getPlugin`, `getOptions`, and render utilities.\n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `key` | `string` | Internal editor key. `withSlate` creates one with `nanoid()` when missing. |\n| `uid` | `string \\| undefined` | Stable id used by Plate containers across RSC and client hydration. |\n| `userId` | `string \\| null \\| undefined` | Collaborative identity passed through editor options. |\n| `components` | `NodeComponents` | Resolved node components keyed by plugin key. |\n| `isFallback` | `boolean` | `false` for normal editors. Fallback editors are created by the controller layer. |\n| `pluginList` | `AnyEditorPlatePlugin[]` | Ordered resolved plugin list. |\n| `inputRules` | `ResolvedInputRulesMeta` | Input-rule metadata built by the input-rules plugin. |\n| `shortcuts` | `Shortcuts` | Resolved shortcut metadata. |\n| `pluginCache` | `object` | Precomputed plugin key lists for render hooks, handlers, rules, nodes, decorators, and injection. |\n\n## Plugin Access\n\nUse editor helpers when you need the resolved plugin instance, typed plugin API, typed transforms, or live plugin options.\n\n```tsx title=\"Plugin option access\"\nimport { ParagraphPlugin, useEditorPlugin } from 'platejs/react';\n\nexport function ParagraphType() {\n  const { editor } = useEditorPlugin(ParagraphPlugin);\n\n  return <span>{editor.getType(ParagraphPlugin.key)}</span>;\n}\n```\n\n| Helper | Type | Use it for |\n| --- | --- | --- |\n| `getPlugin(plugin)` | `<C>(plugin: WithRequiredKey<C>) => EditorPlatePlugin<C>` | Read the resolved plugin instance after overrides and configuration. |\n| `getApi(plugin?)` | `<C>(plugin?: WithRequiredKey<C>) => editor.api & InferApi<C>` | Get a typed view of editor APIs. The runtime value is `editor.api`. |\n| `getTransforms(plugin?)` | `<C>(plugin?: WithRequiredKey<C>) => editor.tf & InferTransforms<C>` | Get a typed view of editor transforms. The runtime value is `editor.transforms`. |\n| `getType(pluginKey)` | `(pluginKey: string) => string` | Resolve the node type for a plugin key. |\n| `getInjectProps(plugin)` | `(plugin) => InjectNodeProps` | Read injected node props with default `nodeKey` and `styleKey` filled from the plugin type. |\n| `getOptionsStore(plugin)` | `(plugin) => TStateApi` | Read the plugin option store. |\n| `getOptions(plugin)` | `(plugin) => InferOptions<C>` | Read all current options for a plugin. |\n| `getOption(plugin, key, ...args)` | `(plugin, key, ...args) => value` | Read one option or selector result. Missing stored keys report through `editor.api.debug.error`. |\n| `setOption(plugin, key, value)` | `(plugin, key, value) => void` | Update one option in the plugin store. |\n| `setOptions(plugin, options)` | `(plugin, partialOrRecipe) => void` | Merge a partial object or run a mutative recipe against the plugin state. |\n\n## Initialization\n\n`withPlate` wraps `withSlate` with React defaults. It uses `createZustandStore` for plugin option stores and prepends the React core plugins before user plugins.\n\n```tsx title=\"Create a typed editor\"\nimport { usePlateEditor } from 'platejs/react';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\n\nexport function useBasicEditor() {\n  return usePlateEditor({\n    plugins: [BoldPlugin],\n    value: [\n      {\n        type: 'p',\n        children: [{ text: 'Bold text is ready.' }],\n      },\n    ],\n  });\n}\n```\n\n`withSlate` does the lower-level setup:\n\n| Step | Behavior |\n| --- | --- |\n| Editor identity | Sets `editor.id`, `editor.meta.key`, `editor.meta.isFallback`, `editor.meta.userId`, and `editor.dom`. |\n| Helper methods | Installs `getApi`, `getTransforms`, `getPlugin`, `getType`, option helpers, and injection helpers. |\n| Core plugins | Resolves core plugins, replaces core plugins with custom plugins that share the same key, and resolves the root plugin. |\n| Components | Merges `components` into root-plugin component overrides. |\n| Normalization guard | Wraps `normalizeNode` so `editor.api.shouldNormalizeNode(entry)` can skip a normalization pass. |\n| Initial value | Calls `editor.tf.init({ value, selection, autoSelect, shouldNormalizeEditor, onReady })` unless `skipInitialization` is `true`. |\n\n`value` accepts a Plate value, an HTML string, or a function that returns the value. `onReady` receives `{ editor, isAsync, value }` after initialization completes.\n\n## Core APIs\n\nThese APIs exist on every Plate editor because core plugins are always resolved before user plugins.\n\n<API name=\"Core plugin APIs\">\n<APIMethods>\n  <APIItem name=\"editor.api.debug.log\" type=\"(message: string, type?: DebugErrorType, details?: any) => void\">\n    Log a debug message when debug logging is enabled.\n  </APIItem>\n  <APIItem name=\"editor.api.debug.info\" type=\"(message: string, type?: DebugErrorType, details?: any) => void\">\n    Log an info message when the configured log level allows it.\n  </APIItem>\n  <APIItem name=\"editor.api.debug.warn\" type=\"(message: string, type?: DebugErrorType, details?: any) => void\">\n    Log a warning when the configured log level allows it.\n  </APIItem>\n  <APIItem name=\"editor.api.debug.error\" type=\"(message: unknown, type?: DebugErrorType, details?: any) => void\">\n    Throw a `PlateError` by default in development. Configure `DebugPlugin` to change logging or `throwErrors`.\n  </APIItem>\n  <APIItem name=\"editor.api.html.deserialize\" type=\"(options: { element: HTMLElement | string; collapseWhiteSpace?: boolean; defaultElementPlugin?: WithRequiredKey }) => Descendant[]\">\n    Deserialize an HTML element into Plate nodes. The HTML parser plugin calls this for `text/html` paste data.\n  </APIItem>\n  <APIItem name=\"editor.api.redecorate\" type=\"() => void\">\n    Trigger decoration refresh. The React extension warns through `editor.api.debug.warn` until an integration overrides it.\n  </APIItem>\n  <APIItem name=\"editor.api.navigation.activeTarget\" type=\"() => NavigationFeedbackTarget | null\">\n    Read the current navigation feedback target and clear it if the stored target no longer resolves.\n  </APIItem>\n  <APIItem name=\"editor.api.navigation.clear\" type=\"() => void\">\n    Clear the current navigation feedback target.\n  </APIItem>\n  <APIItem name=\"editor.api.navigation.isTarget\" type=\"(path: Path) => boolean\">\n    Check whether a path matches the active navigation feedback target.\n  </APIItem>\n</APIMethods>\n</API>\n\n## Core Transforms\n\nCore transforms live on `editor.tf` and `editor.transforms`. Plugin docs normally show the plugin-specific transform names, but the editor always includes these runtime transforms.\n\n<API name=\"Core transforms\">\n<APITransforms>\n  <APIItem name=\"editor.tf.init\" type=\"(options: InitOptions) => void\">\n    Initialize value, selection, optional normalization, optional auto-selection, and `onReady`.\n  </APIItem>\n  <APIItem name=\"editor.tf.insertExitBreak\" type=\"(options?: InsertExitBreakOptions) => boolean | undefined\">\n    Insert an exit break for plugins that route to the core exit-break transform.\n  </APIItem>\n  <APIItem name=\"editor.tf.liftBlock\" type=\"(options?: LiftBlockOptions) => boolean | undefined\">\n    Lift the selected block through Plate's block transform wrapper.\n  </APIItem>\n  <APIItem name=\"editor.tf.resetBlock\" type=\"(options?: { at?: Path }) => boolean | undefined\">\n    Reset the selected block to the requested type or default block type.\n  </APIItem>\n  <APIItem name=\"editor.tf.setValue\" type=\"(value?: Value | string) => void\">\n    Replace editor children. Use controlled state patterns when React owns the value.\n  </APIItem>\n  <APIItem name=\"editor.tf.navigation.clear\" type=\"() => void\">\n    Clear navigation feedback state.\n  </APIItem>\n  <APIItem name=\"editor.tf.navigation.flashTarget\" type=\"(options: NavigationFlashTargetOptions) => boolean\">\n    Store a target temporarily so components can render navigation feedback.\n  </APIItem>\n  <APIItem name=\"editor.tf.navigation.navigate\" type=\"(options: NavigationNavigateOptions) => boolean\">\n    Navigate to a target and flash it through the navigation feedback plugin.\n  </APIItem>\n  <APIItem name=\"editor.tf.reset\" type=\"(options?: ResetOptions) => void\">\n    React extension wrapper. It restores focus to the editor when the editor was focused before reset.\n  </APIItem>\n</APITransforms>\n</API>\n\n## Plugin Pipeline Effects\n\nSome core behavior is exposed by overriding existing Slate transforms rather than by adding named methods.\n\n| Plugin | Effect |\n| --- | --- |\n| `ParserPlugin` | Overrides `insertData` and scans plugin parsers in reverse plugin order. Matching parsers transform data, deserialize a fragment, transform the fragment, and insert it. |\n| `LengthPlugin` | Wraps `apply` in `withoutNormalizing` and trims overflow when `maxLength` is configured. |\n| `SlateExtensionPlugin` | Wraps `apply` so `onNodeChange` and `onTextChange` handlers can receive previous and next node or text state. |\n| `SlateReactExtensionPlugin` | Handles line movement, tab, untab, select-all, escape, `currentKeyboardEvent`, focus-preserving reset, and `_memo` cleanup during normalization. |\n| `HtmlPlugin` | Registers the `text/html` parser path and delegates HTML elements to `editor.api.html.deserialize`. |\n| `BaseParagraphPlugin` | Registers the default paragraph element under key `p` and maps HTML `<p>` elements, excluding code-font paragraphs. |\n\n## Type Helpers\n\nUse `TPlateEditor` when you want an editor typed to a specific value and plugin union.\n\n```ts title=\"Typed editor helper\"\nimport type { TPlateEditor } from 'platejs/react';\nimport type { Value } from 'platejs';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\n\ntype BasicEditor = TPlateEditor<Value, typeof BoldPlugin>;\n```\n\n| Type | Purpose |\n| --- | --- |\n| `PlateEditor` | Runtime editor type with the default Plate core plugin surface. |\n| `TPlateEditor<V, P>` | Typed editor for a specific `Value` and plugin union. |\n| `KeyofPlugins<T>` | String key union for Plate core plugins plus the supplied plugin config union. |\n\n## Related APIs\n\n- [Plate components](/docs/plate/api/core/plate-components) covers `Plate`, `PlateContent`, `PlateView`, and component-layer runtime effects.\n- [PlateController](/docs/plate/api/core/plate-controller) covers active, primary, and fallback editor lookup.\n- [Plate plugin](/docs/plate/api/core/plate-plugin) covers plugin configuration, methods, options, handlers, and render hooks.\n- [Controlled Value](/docs/plate/controlled) covers React-owned value patterns around `editor.tf.setValue`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core/plate-editor.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-plate-plugin-docs",
      "title": "Plate Plugin",
      "description": "API reference for Plate plugins.",
      "files": [
        {
          "path": "../../content/docs/api/core/plate-plugin.mdx",
          "content": "---\ntitle: Plate Plugin\ndescription: API reference for Plate plugins.\n---\n\nPlate plugins are objects passed to `Plate` [plugins](/docs/plate/api/core/plate-components#plugins) prop.\n\n## Plugin Properties\n\n<API name=\"Properties\">\n<APIAttributes>\n<APIItem name=\"key\" type=\"C['key']\" required>\nUnique identifier used by Plate to store the plugins by key in `editor.plugins`.\n</APIItem>\n\n<APIItem name=\"api\" type=\"Record<string, Function>\">\nAn object of API functions provided by the plugin. These functions are accessible via `editor.api[key]`.\n</APIItem>\n\n<APIItem name=\"transforms\" type=\"Record<string, Function>\">\nTransform functions provided by the plugin that modify the editor state. These are accessible via `editor.tf[key]`.\n</APIItem>\n\n<APIItem name=\"options\" type=\"Record<string, any>\">\nExtended properties used by the plugin as options.\n</APIItem>\n\n<APIItem name=\"handlers\" type=\"{ onChange?: (editor: PlateEditor) => void } & Record<string, Function>\">\nEvent handlers for various editor events.\n\n<APISubList>\n<APISubListItem parent=\"handlers\" name=\"onChange\" type=\"(editor: PlateEditor) => void\" optional>\nCalled whenever the editor content changes.\n</APISubListItem>\n<APISubListItem parent=\"handlers\" name=\"onNodeChange\" type=\"OnNodeChange\" optional>\nCalled whenever a node operation occurs (insert, remove, set, merge, split, move).\n\n```ts\ntype OnNodeChange = (ctx: PlatePluginContext & {\n  node: Descendant;\n  operation: NodeOperation;\n  prevNode: Descendant;\n}) => HandlerReturnType;\n```\n\n**Parameters:**\n- `node`: The node after the operation\n- `operation`: The node operation that occurred\n- `prevNode`: The node before the operation\n\n**Note:** For `insert_node` and `remove_node` operations, both `node` and `prevNode` contain the same value to avoid null cases.\n</APISubListItem>\n<APISubListItem parent=\"handlers\" name=\"onTextChange\" type=\"OnTextChange\" optional>\nCalled whenever a text operation occurs (insert or remove text).\n\n```ts\ntype OnTextChange = (ctx: PlatePluginContext & {\n  node: Descendant;\n  operation: TextOperation;\n  prevText: string;\n  text: string;\n}) => HandlerReturnType;\n```\n\n**Parameters:**\n- `node`: The parent node containing the text that changed\n- `operation`: The text operation that occurred (`insert_text` or `remove_text`)\n- `prevText`: The text content before the operation\n- `text`: The text content after the operation\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"inject\" type=\"object\">\nDefines how the plugin injects functionality into other plugins or the editor.\n\n<APISubList>\n<APISubListItem parent=\"inject\" name=\"nodeProps\" type=\"Record<string, any>\" optional>\nProperties used by Plate to inject props into any node component.\n</APISubListItem>\n\n\n<APISubListItem parent=\"inject\" name=\"excludePlugins\" type=\"string[]\" optional>\nAn array of plugin keys to exclude from node prop injection.\n</APISubListItem>\n\n<APISubListItem parent=\"inject\" name=\"excludeBelowPlugins\" type=\"string[]\" optional>\nAn array of plugin keys. Node prop injection will be excluded for any nodes that are descendants of elements with these plugin types.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"isBlock\" type=\"boolean\" optional>\nIf true, only matches block elements. Used to restrict prop injection to block-level nodes.\n</APISubListItem>\n\n<APISubListItem parent=\"inject\" name=\"isElement\" type=\"boolean\" optional>\nIf true, only matches element nodes. Used to restrict prop injection to element nodes.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"isLeaf\" type=\"boolean\" optional>\nIf true, only matches leaf nodes. Used to restrict prop injection to leaf nodes.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"maxLevel\" type=\"number\" optional>\nMaximum nesting level for node prop injection. Nodes deeper than this level will not receive injected props.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"plugins\" type=\"Record<string, Partial<PlatePlugin>>\" optional>\nProperty that can be used by a plugin to allow other plugins to inject code.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"targetPluginToInject\" type=\"function\" optional>\nA function that returns a plugin config to be injected into other plugins `inject.plugins` specified by targetPlugins.\n</APISubListItem>\n<APISubListItem parent=\"inject\" name=\"targetPlugins\" type=\"string[]\" optional>\nPlugin keys used by `InjectNodeProps` and the `targetPluginToInject` function.\n\n- **Default:** `[ParagraphPlugin.key]`\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"node\" type=\"object\">\nDefines the node-specific configuration for the plugin.\n\n<APISubList>\n<APISubListItem parent=\"node\" name=\"isDecoration\" type=\"boolean\" optional>\nIndicates if this plugin's nodes can be rendered as decorated leaf. Set to false to render node component only once per text node.\n\n- **Default:** `true`\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isElement\" type=\"boolean\" optional>\nIndicates if this plugin's nodes should be rendered as elements.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isInline\" type=\"boolean\" optional>\nIndicates if this plugin's elements should be treated as inline.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isLeaf\" type=\"boolean\" optional>\nIndicates if this plugin's nodes should be rendered as leaves.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isContainer\" type=\"boolean\" optional>\nWhen `true`, indicates that the plugin's elements are primarily containers for other content. This property is typically used by fragment queries to unwrap the container nodes.\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"selection.affinity\" type=\"'default' | 'directional' | 'outward' | 'hard'\" optional>\nDefines the selection behavior at the boundaries of nodes. See [Plugin Rules](/docs/plate/plugin-rules#rulesselection).\n\n- `'default'`: Uses Slate's default behavior\n- `'directional'`: Selection affinity is determined by the direction of cursor movement. Maintains inward or outward affinity based on approach\n- `'outward'`: Forces outward affinity. Typing at the edge of a mark will not apply the mark to new text\n- `'hard'`: Creates a 'hard' edge that requires two key presses to move across. Uses offset-based navigation\n\n- **Default:** `undefined` (Slate's default behavior)\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isMarkableVoid\" type=\"boolean\" optional>\nIndicates if this plugin's void elements should be markable.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isSelectable\" type=\"boolean\" optional>\nIndicates if this plugin's nodes should be selectable.\n\n- **Default:** `true`\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isStrictSiblings\" type=\"boolean\" optional>\nIndicates whether this element enforces strict sibling type constraints. Set to `true` when the element only allows specific siblings (e.g., `td` can only have `td` siblings, `column` can only have `column` siblings) and prevents standard text blocks like paragraphs from being inserted as siblings.\n\nUsed by exit break functionality to determine appropriate exit points in nested structures. See [Exit Break](/docs/plate/exit-break).\n\n- **Default:** `false`\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"break.empty\" type=\"'default' | 'deleteExit' | 'exit' | 'lift' | 'reset'\" optional>\nAction when Enter is pressed in an empty block. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- `'default'`: Default behavior\n- `'reset'`: Reset block to default paragraph type\n- `'exit'`: Exit the current block\n- `'lift'`: Lift the current block out of the nearest matching ancestor\n- `'deleteExit'`: Delete backward then exit\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"break.emptyLineEnd\" type=\"'default' | 'deleteExit' | 'exit'\" optional>\nAction when Enter is pressed at the end of an empty line. This is typically used with `rules.break.default: 'lineBreak'`. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- `'default'`: Default behavior\n- `'exit'`: Exit the current block\n- `'deleteExit'`: Delete backward then exit\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"break.default\" type=\"'default' | 'deleteExit' | 'exit' | 'lineBreak'\" optional>\nDefault action when Enter is pressed. Defaults to splitting the block. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- `'default'`: Default behavior\n- `'exit'`: Exit the current block\n- `'lineBreak'`: Insert newline character\n- `'deleteExit'`: Delete backward then exit\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"break.splitReset\" type=\"boolean\" optional>\nIf true, the new block after splitting will be reset to the default type. See [Plugin Rules](/docs/plate/plugin-rules).\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"delete.start\" type=\"'default' | 'lift' | 'reset'\" optional>\nAction when Backspace is pressed at the start of the block. This applies whether the block is empty or not. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- `'default'`: Default behavior\n- `'lift'`: Lift the current block out of the nearest matching ancestor\n- `'reset'`: Reset block to default paragraph type\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"delete.empty\" type=\"'default' | 'reset'\" optional>\nAction when Backspace is pressed and the block is empty. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- `'default'`: Default behavior\n- `'reset'`: Reset block to default paragraph type\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"match\" type=\"MatchRules\" optional>\nFunction to determine if this plugin's rules should apply to a node. Used to override behavior based on node properties beyond just type matching.\n\n**Default:** `type === node.type`\n\n**Example:** `matchRules: ({ node }) => Boolean(node.listStyleType)`\n\nExample: List plugin sets `match: ({ node }) => !!node.listStyleType` to override paragraph behavior when the paragraph is a list item.\n\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"merge.removeEmpty\" type=\"boolean\" optional>\nWhether to remove the node when it's empty during merge operations. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- **Default:** `false`\n</APISubListItem>\n<APISubListItem parent=\"rules\" name=\"normalize.removeEmpty\" type=\"boolean\" optional>\nWhether to remove nodes with empty text during normalization. See [Plugin Rules](/docs/plate/plugin-rules).\n\n- **Default:** `false`\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"isVoid\" type=\"boolean\" optional>\nIndicates if this plugin's elements should be treated as void.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"type\" type=\"string\" optional>\nSpecifies the type identifier for this plugin's nodes. \n\n- **Default:** `plugin.key`\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"component\" type=\"NodeComponent | null\" optional>\nReact component used to render this plugin's nodes.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"leafProps\" type=\"LeafNodeProps<WithAnyKey<C>>\" optional>\nOverride `data-slate-leaf` element attributes.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"props\" type=\"NodeProps<WithAnyKey<C>>\" optional>\nOverride node attributes.\n</APISubListItem>\n<APISubListItem parent=\"node\" name=\"textProps\" type=\"TextNodeProps<WithAnyKey<C>>\" optional>\nOverride `data-slate-node=\"text\"` element attributes.\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"override\" type=\"object\">\nAllows overriding components and plugins by key.\n\n<APISubList>\n<APISubListItem parent=\"override\" name=\"components\" type=\"Record<string, NodeComponent>\" optional>\nReplace plugin `NodeComponent` by key.\n</APISubListItem>\n<APISubListItem parent=\"override\" name=\"plugins\" type=\"Record<string, Partial<EditorPlatePlugin<AnyPluginConfig>>>\" optional>\nExtend `PlatePlugin` by key.\n</APISubListItem>\n<APISubListItem parent=\"override\" name=\"enabled\" type=\"Partial<Record<string, boolean>>\" optional>\nEnable or disable plugins.\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"parser\" type=\"Nullable<Parser<WithAnyKey<C>>>\">\nDefines how the plugin parses content.\n</APIItem>\n\n<APIItem name=\"parsers\" type=\"object\">\nDefines serializers and deserializers for various formats.\n\n<APISubList>\n<APISubListItem parent=\"parsers\" name=\"html\" type=\"Nullable<{ deserializer?: HtmlDeserializer<WithAnyKey<C>>; serializer?: HtmlSerializer<WithAnyKey<C>> }>\" optional>\nHTML parser configuration.\n</APISubListItem>\n<APISubListItem parent=\"parsers\" name=\"htmlReact\" type=\"Nullable<{ serializer?: HtmlReactSerializer<WithAnyKey<C>> }>\" optional>\nHTML React serializer configuration.\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"render\" type=\"object\">\nDefines how the plugin renders components.\n\n<APISubList>\n<APISubListItem parent=\"render\" name=\"aboveEditable\" type=\"Component\" optional>\nComponent rendered above the Editable component but inside the Slate wrapper.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"aboveNodes\" type=\"RenderNodeWrapper<WithAnyKey<C>>\" optional>\nCreate a function that generates a parent React node for all other plugins' node components.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"aboveSlate\" type=\"Component\" optional>\nComponent rendered above the Slate wrapper.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"afterEditable\" type=\"EditableSiblingComponent\" optional>\nRenders a component after the Editable component.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"beforeEditable\" type=\"EditableSiblingComponent\" optional>\nRenders a component before the Editable component.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"belowNodes\" type=\"RenderNodeWrapper<WithAnyKey<C>>\" optional>\nCreate a function that generates a React node below all other plugins' node React node, but above their children.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"belowRootNodes\" type=\"(props: PlateElementProps<TElement, C>) => React.ReactNode\" optional>\nRenders a component after the direct children of the root element. This differs from `belowNodes` in that it's the direct child of `PlateElement` rather than wrapping the children that could be nested. This is useful when you need components relative to the root element.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"leaf\" type=\"NodeComponent\" optional>\nRenders a component below leaf nodes when `isLeaf: true` and `isDecoration: false`. Use `render.node` instead when `isDecoration: true`.\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"node\" type=\"NodeComponent\" optional>\nRenders a component for:\n- Elements nodes if `isElement: true`\n- Below text nodes if `isLeaf: true` and `isDecoration: false`\n- Below leaf if `isLeaf: true` and `isDecoration: true`\n</APISubListItem>\n<APISubListItem parent=\"render\" name=\"as\" type=\"keyof HTMLElementTagNameMap\" optional>\nSpecifies the HTML tag name to use when rendering the node component. Only used when no custom `component` is provided for the plugin.\n\n- **Default:** `'div'` for elements, `'span'` for leaves\n\n</APISubListItem>\n</APISubList>\n</APIItem>\n\n<APIItem name=\"shortcuts\" type=\"Shortcuts\">\nDefines keyboard shortcuts for the plugin.\n</APIItem>\n\n<APIItem name=\"useOptionsStore\" type=\"StoreApi<C['key'], C['options']>\">\nZustand store for managing plugin options.\n</APIItem>\n\n<APIItem name=\"dependencies\" type=\"string[]\">\nAn array of plugin keys that this plugin depends on.\n</APIItem>\n\n<APIItem name=\"enabled\" type=\"boolean\" optional>\nEnables or disables the plugin. Used by Plate to determine if the plugin should be used.\n</APIItem>\n\n<APIItem name=\"plugins\" type=\"any[]\">\nRecursive plugin support to allow having multiple plugins in a single plugin.\n</APIItem>\n\n<APIItem name=\"priority\" type=\"number\">\nDefines the order in which plugins are registered and executed.\n\n- **Default:** `100`\n</APIItem>\n\n<APIItem name=\"decorate\" type=\"Decorate<WithAnyKey<C>>\" optional>\nProperty used by Plate to decorate editor ranges.\n</APIItem>\n\n<APIItem name=\"extendEditor\" type=\"ExtendEditor<WithAnyKey<C>>\" optional>\nFunction to extend the editor instance. Used primarily for integrating legacy Slate plugins that need direct editor mutation. Only one `extendEditor` is allowed per plugin.\n\n```ts\nextendEditor: ({ editor }) => {\n  // Example: Integrating a legacy Slate plugin\n  return withYjs(editor);\n}\n```\n</APIItem>\n\n<APIItem name=\"useHooks\" type=\"() => void\" optional>\nHook called when the editor is initialized.\n</APIItem>\n\n<APIItem name=\"editOnly\" type=\"boolean | EditOnlyConfig\" optional>\nConfigures which plugin functionalities should only be active when the editor is not read-only.\n\nCan be either a boolean or an object configuration:\n\n```ts\ntype EditOnlyConfig = {\n  render?: boolean;      // default: true\n  handlers?: boolean;    // default: true\n  inject?: boolean;      // default: true\n  transformInitialValue?: boolean;  // default: false\n}\n```\n\nWhen set to `true` (boolean):\n- `render`, `handlers`, and `inject.nodeProps` are only active when editor is not read-only\n- `transformInitialValue` remains active regardless of read-only state\n\nWhen set to an object:\n- Each property can be individually configured\n- Properties default to being edit-only (`true`) except `transformInitialValue` which defaults to always active (`false`)\n- Set a property to `false` to make it always active regardless of read-only state\n- For `transformInitialValue`, set to `true` to make it edit-only\n\nExamples:\n```ts\n// All features (except transformInitialValue) are edit-only\neditOnly: true\n\n// transformInitialValue is edit-only, others remain edit-only by default\neditOnly: { transformInitialValue: true }\n\n// render is always active, others follow default behavior\neditOnly: { render: false }\n```\n</APIItem>\n</APIAttributes>\n</API>\n\n## Plugin Methods\n\n<API name=\"Methods\">\n<APIMethods>\n<APIItem name=\"configure\" type=\"(config: PlatePluginConfig | ((ctx: PlatePluginContext) => PlatePluginConfig)) => PlatePlugin\">\nCreates a new plugin instance with updated options.\n\n```ts\n(config: PlatePluginConfig<C['key'], InferOptions<C>, InferApi<C>, InferTransforms<C>> | ((ctx: PlatePluginContext<C>) => PlatePluginConfig<C['key'], InferOptions<C>, InferApi<C>, InferTransforms<C>>)) => PlatePlugin<C>\n```\n</APIItem>\n\n<APIItem name=\"extend\" type=\"(config: Partial<PlatePlugin> | ((ctx: PlatePluginContext) => Partial<PlatePlugin>)) => PlatePlugin\">\nCreates a new plugin instance with additional configuration.\n\n```ts\n(extendConfig: Partial<PlatePlugin> | ((ctx: PlatePluginContext<AnyPluginConfig>) => Partial<PlatePlugin>)) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"extendPlugin\" type=\"(key: string, config: Partial<PlatePlugin> | ((ctx: PlatePluginContext) => Partial<PlatePlugin>)) => PlatePlugin\">\nExtends an existing nested plugin or adds a new one if not found. Supports deep nesting.\n\n```ts\n(key: string, extendConfig: Partial<PlatePlugin> | ((ctx: PlatePluginContext<AnyPluginConfig>) => Partial<PlatePlugin>)) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"withComponent\" type=\"function\">\nSets or replaces the component associated with a plugin.\n\n```ts\n(component: NodeComponent) => PlatePlugin<C>\n```\n</APIItem>\n\n<APIItem name=\"overrideEditor\" type=\"function\">\nCreates a new plugin instance with overridden editor methods. Provides access to original methods via `tf` and `api` parameters. Can be called multiple times to layer different overrides.\n\n```ts\noverrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({\n  transforms: {\n    // Override transforms\n    deleteForward(options) {\n      deleteForward(options);\n    },\n  },\n  api: {\n    // Override API methods\n    isInline(element) {\n      return isInline(element);\n    },\n  },\n})) => PlatePlugin<C>\n```\n\n- Preferred method for modifying editor behavior\n- Type-safe access to original methods\n- Clean separation between transforms and API\n- Can be chained multiple times\n</APIItem>\n\n<APIItem name=\"extendApi\" type=\"(api: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\">\nExtends the plugin's API.\n\n```ts\n(api: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"extendEditorApi\" type=\"(api: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\">\nExtends the editor's API with plugin-specific methods.\n\n```ts\n(api: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"extendTransforms\" type=\"(transforms: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\">\nExtends the plugin's transforms.\n\n```ts\n(transforms: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"extendEditorTransforms\" type=\"(transforms: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\">\nExtends the editor's transforms with plugin-specific methods.\n\n```ts\n(transforms: (ctx: PlatePluginContext) => Record<string, Function>) => PlatePlugin\n```\n</APIItem>\n\n<APIItem name=\"extendSelectors\" type=\"(options: (ctx: PlatePluginContext) => Record<string, any>) => PlatePlugin\">\nExtends the plugin with selectors.\n\n```ts\n(options: (ctx: PlatePluginContext) => Record<string, any>) => PlatePlugin\n```\n</APIItem>\n</APIMethods>\n</API>\n\n## Plugin Context\n\n<API name=\"Context\">\n<APIAttributes>\n<APIItem name=\"editor\" type=\"PlateEditor\">\nThe current editor instance.\n</APIItem>\n<APIItem name=\"plugin\" type=\"EditorPlatePlugin<C>\">\nThe current plugin instance.\n</APIItem>\n<APIItem name=\"getOption\" type=\"function\">\nFunction to get a specific option value.\n</APIItem>\n<APIItem name=\"getOptions\" type=\"function\">\nFunction to get all options for the plugin.\n</APIItem>\n<APIItem name=\"setOption\" type=\"function\">\nFunction to set a specific option value.\n</APIItem>\n<APIItem name=\"setOptions\" type=\"function\">\nFunction to set multiple options.\n</APIItem>\n</APIAttributes>\n</API>\n\nFor more detailed information on specific aspects of Plate plugins, refer to the individual guides on [Plugin Configuration](/docs/plate/plugin), [Plugin Methods](/docs/plate/plugin-methods), [Plugin Context](/docs/plate/plugin-context), [Plugin Components](/docs/plate/plugin-components), and [Plugin Shortcuts](/docs/plate/plugin-shortcuts).\n\n## Generic Types\n\n<API name=\"GenericTypes\">\n<APIAttributes>\n<APIItem name=\"C\" type=\"AnyPluginConfig = PluginConfig\">\nRepresents the plugin configuration. This type extends `PluginConfig` which includes `key`, `options`, `api`, and `transforms`.\n</APIItem>\n</APIAttributes>\n</API>\n\nUsage example:\n\n```typescript\ntype MyPluginConfig = PluginConfig<\n  'myPlugin',\n  { customOption: boolean },\n  { getData: () => string },\n  { customTransform: () => void }\n>;\n\nconst MyPlugin = createPlatePlugin<MyPluginConfig>({\n  key: 'myPlugin',\n  // plugin implementation\n});\n```\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core/plate-plugin.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-plate-store-docs",
      "title": "Store",
      "description": "API reference for Plate store.",
      "files": [
        {
          "path": "../../content/docs/api/core/plate-store.mdx",
          "content": "---\ntitle: Store\ndescription: API reference for Plate store.\n---\n\n`Plate` is using [jotai-x](https://github.com/udecode/jotai-x) to store the state of the editor.\n\n## Plate Store\n\n<API name=\"Store\">\nThe `PlateStoreState` object stores the state of the Plate editor. It contains information about the editor's ID, its current value, its plugins, and other settings.\n\n<APIState>\n<APIItem name=\"editor\" type=\"PlateEditor\">\nPlate editor reference.\n\n- **Default:** `createPlateFallbackEditor()`\n</APIItem>\n\n<APIItem name=\"id\" type=\"string\">\nA unique ID used as a provider scope. Use it if you have multiple `Plate` in the same React tree.\n\n- **Default:** random id\n</APIItem>\n\n<APIItem name=\"containerRef\" type=\"React.RefObject<HTMLDivElement>\">\nA reference to the editor container element.\n</APIItem>\n\n<APIItem name=\"decorate\" type=\"function\" optional>\nFunction used to decorate ranges in the editor.\n\n```ts\n(options: { editor: PlateEditor; entry: NodeEntry }) => TRange[]\n```\n</APIItem>\n\n<APIItem name=\"isMounted\" type=\"boolean\" optional>\nWhether `Editable` is rendered so slate DOM is resolvable.\n</APIItem>\n\n<APIItem name=\"onChange\" type=\"function\" optional>\nControlled callback called when the editor state changes.\n\n```ts\n(options: { editor: PlateEditor; value: ValueOf<PlateEditor> }) => void\n```\n</APIItem>\n\n<APIItem name=\"onSelectionChange\" type=\"function\" optional>\nControlled callback called when the editor.selection changes.\n\n```ts\n(options: { editor: PlateEditor; selection: TSelection }) => void\n```\n</APIItem>\n\n<APIItem name=\"onValueChange\" type=\"function\" optional>\nControlled callback called when the editor.children changes.\n\n```ts\n(options: { editor: PlateEditor; value: ValueOf<PlateEditor> }) => void\n```\n</APIItem>\n\n<APIItem name=\"onNodeChange\" type=\"function\" optional>\nControlled callback called when a node operation occurs.\n\n```ts\n(options: { \n  editor: PlateEditor; \n  node: Descendant; \n  operation: NodeOperation; \n  prevNode: Descendant \n}) => void\n```\n\n**Parameters:**\n- `editor`: The Plate editor instance\n- `node`: The node after the operation\n- `operation`: The node operation that occurred (insert, remove, set, merge, split, move)\n- `prevNode`: The node before the operation\n\n**Note:** For `insert_node` and `remove_node` operations, both `node` and `prevNode` contain the same value to avoid null cases.\n</APIItem>\n\n<APIItem name=\"onTextChange\" type=\"function\" optional>\nControlled callback called when a text operation occurs.\n\n```ts\n(options: { \n  editor: PlateEditor; \n  node: Descendant; \n  operation: TextOperation; \n  prevText: string; \n  text: string \n}) => void\n```\n\n**Parameters:**\n- `editor`: The Plate editor instance\n- `node`: The parent node containing the text that changed\n- `operation`: The text operation that occurred (`insert_text` or `remove_text`)\n- `prevText`: The text content before the operation\n- `text`: The text content after the operation\n</APIItem>\n\n<APIItem name=\"primary\" type=\"boolean\" optional>\nWhether the editor is primary. If no editor is active, then PlateController will use the first-mounted primary editor.\n\n- **Default:** `true`\n</APIItem>\n\n<APIItem name=\"readOnly\" type=\"boolean\" optional>\nWhether the editor is read-only.\n</APIItem>\n\n<APIItem name=\"renderElement\" type=\"function\" optional>\nFunction to render elements in the editor.\n</APIItem>\n\n<APIItem name=\"renderLeaf\" type=\"function\" optional>\nFunction to render leaf nodes in the editor.\n</APIItem>\n\n<APIItem name=\"versionDecorate\" type=\"number\" optional>\nVersion incremented when calling `redecorate`. This is a dependency of the `decorate` function.\n</APIItem>\n\n<APIItem name=\"versionEditor\" type=\"number\" optional>\nVersion incremented on each editor change.\n</APIItem>\n\n<APIItem name=\"versionSelection\" type=\"number\" optional>\nVersion incremented on each editor.selection change.\n</APIItem>\n\n<APIItem name=\"versionValue\" type=\"number\" optional>\nVersion incremented on each editor.children change.\n</APIItem>\n</APIState>\n</API>\n\n## Accessing the Store\n\n```ts\nimport { usePlateStore, useEditorRef, useEditorPlugin } from 'platejs/react'\n\n// Direct store access\nconst store = usePlateStore(id?) \n\n// Via editor reference\nconst store = useEditorRef().store\n\n// Via plugin context\nconst store = useEditorPlugin(myPlugin).store\n```\n\nNote: The `id` parameter is optional and defaults to the closest editor.\n\n## Store Hooks\n\nThe following hooks are available to interact with the Plate store:\n\n```ts\nimport { usePlateState, usePlateValue, usePlateSet } from 'platejs/react'\n```\n\n### usePlateState\n\nGet and set a store property value.\n\n```ts\nconst [readOnly, setReadOnly] = usePlateState('readOnly', id?)\n```\n\n### usePlateValue\n\nSubscribe to a store property value.\n\n```ts\nconst readOnly = usePlateValue('readOnly', id?)\n```\n\n### usePlateSet\n\nSet a store property value.\n\n```ts\nconst setReadOnly = usePlateSet('readOnly', id?)\n```\n\n## Event Editor Store\n\nThis store is an object whose property keys are event names (e.g. `'focus'`) and whose property values are [editor IDs](Plate#id).\n\n- This is useful when having [multiple editors](multiple-editors) and get one based on DOM events (e.g. the last focused editor).\n- One of the core plugins of [Plate](Plate) will store the following events.\n\n<API name=\"EventEditorStore\">\n<APIState>\n<APIItem name=\"blur\" type=\"string | null\">\n\nLast editor ID that has been blurred.\n\n</APIItem>\n\n<APIItem name=\"focus\" type=\"string | null\">\n\nEditor ID that is currently being focused.\n\n</APIItem>\n\n<APIItem name=\"last\" type=\"string | null\">\n\nLast editor ID.\n\n</APIItem>\n</APIState>\n</API>\n\n\n```ts\nimport { EventEditorStore, useEventEditorValue } from 'platejs'\n\n// Get a value\nconst focusedId = EventEditorStore.get('focus')\n\n// Set a value\nEventEditorStore.set('focus', editorId)\n\n// Subscribe to changes\nconst focusedId = useEventEditorValue('focus')\n```\n\n### `useEventPlateId`\n\nGet the last event editor ID.\n\n<API name=\"useEventPlateId\">\n<APIParameters>\n<APIItem name=\"id\" type=\"string | null\">\n\nReturned ID if defined.\n\n</APIItem>\n</APIParameters>\n\n<APIReturns type=\"string\">\n  The plate id from the context if available, otherwise the last event editor\n  ID or `PLATE_SCOPE`.\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core/plate-store.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-core-docs",
      "title": "Plate Core",
      "description": "API reference for @platejs/core.",
      "files": [
        {
          "path": "../../content/docs/api/core.mdx",
          "content": "---\ntitle: Plate Core\ndescription: API reference for @platejs/core.\n---\n\n## API\n\n### `createPlateEditor`\n\nGenerates a new instance of a `PlateEditor`, initialized with a set of plugins and their configurations.\n\n<API name=\"createPlateEditor\">\n<APIOptions type=\"CreatePlateEditorOptions\">\n  <APIItem name=\"id\" type=\"any\" optional>\n    Unique identifier for the editor.\n  </APIItem>\n  <APIItem name=\"editor\" type=\"E\" optional>\n    Initial editor without `withPlate`.\n  </APIItem>\n  <APIItem name=\"plugins\" type=\"P[]\" optional>\n    An array of editor plugins.\n  </APIItem>\n  <APIItem name=\"value\" type=\"V | string | ((editor: PlateEditor) => V | Promise<V>)\" optional>\n    Initial value of the editor. Can be:\n    - A static value array\n    - An HTML string to be deserialized\n    - A function that returns a value (can be async)\n  </APIItem>\n  <APIItem name=\"autoSelect\" type=\"'end' | 'start' | boolean\" optional>\n    Select the editor after initialization.\n    - **Default:** `false`\n    - `true` | 'end': Select the end of the editor\n    - `false`: Do not select anything\n    - `'start'`: Select the start of the editor\n  </APIItem>\n  <APIItem name=\"onReady\" type=\"(ctx: { editor: PlateEditor; isAsync: boolean; value: V }) => void\" optional>\n    Callback called when the editor initialization completes. The `isAsync` flag indicates whether the value was loaded asynchronously.\n  </APIItem>\n  <APIItem name=\"maxLength\" type=\"number\" optional>\n    Specifies the maximum number of characters allowed in the editor.\n  </APIItem>\n  <APIItem name=\"navigationFeedback\" type=\"object | boolean\" optional>\n    Configuration for the built-in navigation feedback plugin.\n    <APISubList>\n      <APISubListItem parent=\"navigationFeedback\" name=\"duration\" type=\"number\" optional>\n        Default flash duration in milliseconds.\n        - **Default:** `1600`\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"nodeId\" type=\"object | boolean\" optional>\n    Configuration for automatic node ID generation and management.\n    <APISubList>\n      <APISubListItem parent=\"nodeId\" name=\"disableInsertOverrides\" type=\"boolean\" optional>\n        Disable using existing IDs when inserting nodes.\n        - When `false`: Keeps existing IDs if they don't exist in document\n        - When `true`: Always generates new IDs\n        - **Default:** `false`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"filterInline\" type=\"boolean\" optional>\n        Filter inline Element nodes from receiving IDs.\n        - **Default:** `true`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"filterText\" type=\"boolean\" optional>\n        Filter Text nodes from receiving IDs.\n        - **Default:** `true`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"idCreator\" type=\"() => any\" optional>\n        Function to generate unique IDs.\n        - **Default:** `() => nanoid(10)`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"idKey\" type=\"string\" optional>\n        Property key used to store node IDs.\n        - **Default:** `'id'`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"initialValueIds\" type=\"false | 'always' | 'if-needed'\" optional>\n        Controls how missing IDs are assigned in the initial value.\n        - When `'if-needed'`: Only checks the first and last top-level nodes\n        - When `'always'`: Walks the whole initial value and fills missing IDs\n        - When `false`: Skips initial-value ID assignment\n        - **Default:** `'if-needed'`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"normalizeInitialValue\" type=\"boolean | null\" optional>\n        Deprecated alias for `initialValueIds`.\n        - When `false`: Same as `'if-needed'`\n        - When `true`: Same as `'always'`\n        - When `null`: Same as `false`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"reuseId\" type=\"boolean\" optional>\n        Reuse IDs on undo/redo and copy/paste.\n        - When `true`: Keeps IDs if they don't exist in document\n        - When `false`: Always generates new IDs (safer across documents)\n        - **Default:** `false`\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"allow\" type=\"string[]\" optional>\n        Node types that should receive IDs.\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"exclude\" type=\"string[]\" optional>\n        Node types that should not receive IDs.\n      </APISubListItem>\n      <APISubListItem parent=\"nodeId\" name=\"filter\" type=\"(node: NodeEntry) => boolean\" optional>\n        Custom filter function for nodes that should receive IDs.\n        - **Default:** `() => true`\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"chunking\" type=\"object | boolean\" optional>\n    Configure Slate's chunking optimization, which reduces latency while typing. Set to `false` to disable. [Learn more about chunking.](https://docs.slatejs.org/walkthroughs/09-performance)\n    <APISubList>\n      <APISubListItem parent=\"chunking\" name=\"chunkSize\" type=\"number\" optional>\n        The number of blocks per chunk.\n        - **Default:** 1000\n      </APISubListItem>\n      <APISubListItem parent=\"chunking\" name=\"contentVisibilityAuto\" type=\"boolean\" optional>\n        Whether to render each chunk as a DOM element with `content-visibility: auto`, which optimizes DOM painting. When set to `false`, no DOM element will be rendered for each chunk.\n        - **Default:** `true`\n      </APISubListItem>\n      <APISubListItem parent=\"chunking\" name=\"query\" type=\"(ancestor: Ancestor) => boolean\" optional>\n        Determine which ancestors should have chunking applied to their children. Only blocks containing other blocks can have chunking applied.\n        - **Default:** `NodeApi.isEditor`\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"selection\" type=\"TSelection\" optional>\n    Initial selection for the editor.\n  </APIItem>\n  <APIItem name=\"shouldNormalizeEditor\" type=\"boolean\" optional>\n    When `true`, it will normalize the initial `value` passed to the `editor`.\n    - **Default:** `false`\n  </APIItem>\n  <APIItem name=\"rootPlugin\" type=\"(plugin: AnyPlatePlugin) => AnyPlatePlugin\" optional>\n    Function to configure the root plugin.\n  </APIItem>\n  <APIItem name=\"api\" type=\"object\" optional>\n    API methods for the editor.\n  </APIItem>\n  <APIItem name=\"decorate\" type=\"function\" optional>\n    Decoration function for the editor.\n  </APIItem>\n  <APIItem name=\"extendEditor\" type=\"function\" optional>\n    Function to extend the editor.\n  </APIItem>\n  <APIItem name=\"handlers\" type=\"object\" optional>\n    Event handlers for the editor.\n  </APIItem>\n  <APIItem name=\"inject\" type=\"object\" optional>\n    Injection configuration for the editor.\n  </APIItem>\n  <APIItem name=\"transformInitialValue\" type=\"function\" optional>\n    Function to transform the initial value before the editor is ready.\n  </APIItem>\n  <APIItem name=\"normalizeInitialValue\" type=\"function\" optional>\n    Deprecated alias for `transformInitialValue`. Legacy hooks may either\n    mutate `value` in place or return the next value.\n  </APIItem>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Additional options for the editor.\n  </APIItem>\n  <APIItem name=\"override\" type=\"object\" optional>\n    Override configuration for the editor.\n  </APIItem>\n  <APIItem name=\"priority\" type=\"number\" optional>\n    Priority of the editor plugin.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\" optional>\n    Editor read-only initial state. For dynamic value, use\n    `Plate.readOnly` prop.\n  </APIItem>\n  <APIItem name=\"render\" type=\"object\" optional>\n    Render functions for the editor.\n  </APIItem>\n  <APIItem name=\"shortcuts\" type=\"object\" optional>\n    Keyboard shortcuts for the editor.\n  </APIItem>\n  <APIItem name=\"transforms\" type=\"object\" optional>\n    Transform functions for the editor.\n  </APIItem>\n  <APIItem name=\"useHooks\" type=\"function\" optional>\n    Hook to use with the editor.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"PlateEditor\">\n  An editor instance with plugins and config applied.\n</APIReturns>\n</API>\n\nFor more details on editor configuration, refer to the [Editor Configuration](/docs/plate/editor) guide.\n\n### `createPlatePlugin`\n\nCreates a new Plate plugin with the given configuration, supporting extension, nested plugin manipulation, and runtime configuration.\n\n<API name=\"createPlatePlugin\">\n<APIParameters>\n  <APIItem name=\"config\" type=\"PlatePluginConfig | ((editor: PlateEditor) => PlatePluginConfig)\">\n    The configuration object for the plugin, or a function that returns the configuration. If a function is provided, it will be executed when the plugin is resolved with the editor.\n\n    For details on the `PlatePluginConfig` type, refer to the [PlatePlugin API](/docs/plate/api/core/plate-plugin#plugin-properties).\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlatePlugin\">\n  A new plugin instance.\n</APIReturns>\n</API>\n\n### `createTPlatePlugin`\n\nExplicitly typed version of `createPlatePlugin`.\n\n<API name=\"createTPlatePlugin\">\n<APIParameters>\n  <APIItem name=\"config\" type=\"TPlatePluginConfig<C> | ((editor: PlateEditor) => TPlatePluginConfig<C>)\">\n    The configuration object for the plugin, or a function that returns the configuration. This version requires an explicit type parameter `C` extending `AnyPluginConfig`.\n\n    For details on the `TPlatePluginConfig` type, refer to the [PlatePlugin API](/docs/plate/api/core/plate-plugin#plugin-properties).\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlatePlugin<C>\">\n  A new plugin instance.\n</APIReturns>\n</API>\n\n### `toPlatePlugin`\n\nExtends a SlatePlugin to create a React PlatePlugin.\n\n<API name=\"toPlatePlugin\">\n<APIParameters>\n  <APIItem name=\"basePlugin\" type=\"SlatePlugin\">\n    The base SlatePlugin to be extended.\n  </APIItem>\n  <APIItem name=\"extendConfig\" type=\"PlatePluginConfig | ((ctx: PlatePluginContext<C>) => PlatePluginConfig)\" optional>\n    A function or object that provides the extension configuration. If a function, it receives the plugin context and should return a partial PlatePlugin. If an object, it should be a partial PlatePlugin configuration.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlatePlugin\">\n  A new plugin instance that combines the base SlatePlugin functionality with React-specific features defined in the extension configuration.\n</APIReturns>\n</API>\n\n### `toTPlatePlugin`\n\nExplicitly typed version of `toPlatePlugin`.\n\n<API name=\"toTPlatePlugin\">\n<APIParameters>\n  <APIItem name=\"basePlugin\" type=\"SlatePlugin<TContext>\">\n    The base SlatePlugin to be extended.\n  </APIItem>\n  <APIItem name=\"extendConfig\" type=\"ExtendPluginConfig<C> | ((ctx: PlatePluginContext<TContext>) => ExtendPluginConfig<C>)\" optional>\n    A function or object that provides the extension configuration. This version requires explicit type parameters for both the base plugin configuration (`TContext`) and the extension configuration (`C`).\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlatePlugin<C>\">\n  A new plugin instance with precise type control.\n</APIReturns>\n</API>\n\n### `usePlateEditor`\n\nCreates a memoized Plate editor for React components.\n\n<API name=\"usePlateEditor\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"CreatePlateEditorOptions & { enabled?: boolean; onReady?: (ctx: { editor: PlateEditor; isAsync: boolean; value: V }) => void }\" optional>\n    Configuration options for creating the Plate editor. All options from `createPlateEditor` are supported, plus:\n    <APISubList>\n      <APISubListItem parent=\"options\" name=\"enabled\" type=\"boolean\" optional>\n        Whether the editor should be created. When `false`, returns `null`.\n        - **Default:** `true`\n      </APISubListItem>\n      <APISubListItem parent=\"options\" name=\"onReady\" type=\"(ctx: { editor: PlateEditor; isAsync: boolean; value: V }) => void\" optional>\n        Callback called when the editor initialization completes. The `isAsync` flag indicates whether the value was loaded asynchronously.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"deps\" type=\"React.DependencyList\" optional>\n    Additional dependencies for the useMemo hook.\n    - **Default:** `[]`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlateEditor | null\">\n  A memoized Plate editor instance. Returns `null` if `enabled` is `false`.\n</APIReturns>\n</API>\n\n### `useEditorContainerRef`\n\n<API name=\"useEditorContainerRef\">\n<APIReturns type=\"React.RefObject<HTMLDivElement>\">\n  The editor container DOM reference.\n</APIReturns>\n</API>\n\n### `useEditorScrollRef`\n\n<API name=\"useEditorScrollRef\">\n<APIReturns type=\"React.RefObject<HTMLDivElement>\">\n  The editor scroll container DOM reference.\n</APIReturns>\n</API>\n\n### `useScrollRef`\n\n<API name=\"useScrollRef\">\n<APIReturns type=\"React.RefObject<HTMLDivElement>\">\nThe editor scroll container reference. Returns the scroll ref if it exists, otherwise returns the container ref.\n</APIReturns>\n</API>\n\n### `useEditorPlugin`\n\nGet editor and plugin context.\n\n<API name=\"useEditorPlugin\">\n<APIParameters>\n  <APIItem name=\"p\" type=\"WithRequiredKey<P>\">\n    The plugin or plugin configuration with a required key.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlatePluginContext\">\n  <APIItem name=\"editor\" type=\"PlateEditor\">\n      The current editor instance.\n  </APIItem>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n      The plugin instance.\n  </APIItem>\n  <APIItem name=\"getOption\" type=\"function\">\n      Function to get a specific option value.\n  </APIItem>\n  <APIItem name=\"getOptions\" type=\"function\">\n      Function to get all options for the plugin.\n  </APIItem>\n  <APIItem name=\"setOption\" type=\"function\">\n      Function to set a specific option value.\n  </APIItem>\n  <APIItem name=\"setOptions\" type=\"function\">\n      Function to set multiple options.\n  </APIItem>\n  <APIItem name=\"store\" type=\"PlateStore\">\n      The Plate store for the editor.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `useEditorRef`\n\nGet the Plate editor reference without re-rendering. The returned editor object is enhanced with a `store` property that provides access to the Plate store.\n\n<API name=\"useEditorRef\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    Editor ID used for accessing nested editors. When not provided, returns the closest editor instance in the React tree. Only use this parameter when working with nested editors to target a specific scope.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlateEditor & { store: PlateStore }\">\n  The editor reference with attached store.\n</APIReturns>\n</API>\n\n### `useEditorSelector`\n\nSubscribe to a specific property of the editor.\n\n<API name=\"useEditorSelector\">\n<APIParameters>\n  <APIItem name=\"selector\" type=\"(editor: PlateEditor<V>, prev?: T) => T\">\n    The selector function.\n  </APIItem>\n  <APIItem name=\"deps\" type=\"DependencyList\">\n    The dependency list for the selector function.\n  </APIItem>\n  <APIItem name=\"options\" type=\"UseEditorSelectorOptions<T>\" optional>\n    Options for the selector function.\n  </APIItem>\n</APIParameters>\n\n<APIOptions>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor. Useful only when nesting editors. Default is using the closest editor id.\n  </APIItem>\n  <APIItem name=\"equalityFn\" type=\"(a: T, b: T) => boolean\" optional>\n    Equality function to determine whether the result of the selector function has changed. Default is `(a, b) => a === b`.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"T\">\n  The return value of the selector function.\n</APIReturns>\n</API>\n\n### `useEditorState`\n\nGet the Plate editor reference with re-rendering.\n\n<API name=\"useEditorState\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor. Default is using the closest editor id.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"PlateEditor\">\n  The editor reference.\n</APIReturns>\n</API>\n\n### `useEditorComposing`\n\nGet the editor's `composing` state.\n\n<API name=\"useEditorComposing\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"boolean\">\n  Whether the editor is composing.\n</APIReturns>\n</API>\n\n### `useEditorReadOnly`\n\nGet the editor's `readOnly` state.\n<API name=\"useEditorReadOnly\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"boolean\">\n  Whether the editor is read-only.\n</APIReturns>\n</API>\n\n### `useEditorMounted`\n\nGet the editor's `isMounted` state.\n\n<API name=\"useEditorMounted\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"boolean\">\n  Whether the editor is mounted.\n</APIReturns>\n</API>\n\n### `useEditorSelection`\n\nGet the editor's selection. Memoized so it does not re-render if the range is the same.\n\n<API name=\"useEditorSelection\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"TRange | null\">\nThe current selection in the editor.\n</APIReturns>\n</API>\n\n### `useEditorVersion`\n\nGet the version of the editor value. That version is incremented on each editor change.\n\n<API name=\"useEditorVersion\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"number\">\nThe current version of the editor value.\n</APIReturns>\n</API>  \n\n### `useSelectionVersion`\n\nGet the version of the editor selection. That version is incremented on each selection change (the range being different).\n\n<API name=\"useSelectionVersion\">\n<APIParameters>\n  <APIItem name=\"id\" type=\"string\" optional>\n    The ID of the plate editor.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"number\">\nThe current version of the editor selection.\n</APIReturns>\n</API>\n\n### `useSelectionCollapsed`\n\n<API name=\"useSelectionCollapsed\">\n<APIReturns type=\"boolean\">\nWhether the current selection is collapsed.\n</APIReturns>\n</API>\n\n### `useSelectionExpanded`\n\n<API name=\"useSelectionExpanded\">\n<APIReturns type=\"boolean\">\nWhether the current selection is expanded.\n</APIReturns>\n</API>\n\n### `useSelectionWithinBlock`\n\n<API name=\"useSelectionWithinBlock\">\n<APIReturns type=\"boolean\">\nWhether the current selection is within a single block.\n</APIReturns>\n</API>\n\n### `useSelectionAcrossBlocks`\n\n<API name=\"useSelectionAcrossBlocks\">\n<APIReturns type=\"boolean\">\nWhether the current selection spans across multiple blocks.\n</APIReturns>\n</API>\n\n### `useSelectionFragment`\n\nReturns the fragment of the current selection, optionally unwrapping structural nodes.\n\n<API name=\"useSelectionFragment\">\n<APIReturns type=\"TElement[]\">\n  The fragment of the current selection. Returns an empty array if the selection is not expanded or if no fragment is found.\n</APIReturns>\n</API>\n\n### `useSelectionFragmentProp`\n\nReturns a prop value derived from the current selection fragment.\n    \n<API name=\"useSelectionFragmentProp\">\n<APIOptions type=\"GetSelectionFragmentOptions & GetFragmentPropOptions\" optional>\n<APIItem name=\"key\" type=\"string\" optional>\nThe key of the property to extract from each node.\n</APIItem>\n<APIItem name=\"defaultValue\" type=\"string\" optional>\nThe default value to return if no valid prop is found.\n</APIItem>\n<APIItem name=\"getProp\" type=\"(node: TElement | TText) => any\" optional>\nCustom function to extract the prop value from a node.\n</APIItem>\n<APIItem name=\"mode\" type=\"'all' | 'block' | 'text'\" optional>\nDetermines how to traverse the fragment:\n- 'all': Check both block and text nodes\n- 'block': Only check block nodes\n- 'text': Only check text nodes\n  \n- **Default**: `'block'`\n</APIItem>\n</APIOptions>\n\n<APIReturns>\nA value derived from the fragment nodes, or undefined if no consistent value is found across the specified nodes.\n</APIReturns>\n</API>\n\n### `useNodePath`\n\nReturns the path of a node in the editor.\n\n<API name=\"useNodePath\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to find the path for.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  A memoized Path array representing the location of the node in the editor's tree structure.\n</APIReturns>\n</API>\n\n### `usePath`\n\nGet the memoized path of the closest element.\n\n<API name=\"usePath\">\n<APIParameters>\n  <APIItem name=\"pluginKey\" type=\"string\" optional>\n    The key of the plugin to get the path for.\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  The path of the element, or `undefined` if used outside of a node component's context.\n</APIReturns>\n</API>  \n\n### `usePluginOption`\n\nHook to access plugin options from the plugin store. For usage inside `<Plate>`.\n\n<API name=\"usePluginOption\">\n<APIParameters>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n    The plugin to get options from.\n  </APIItem>\n  <APIItem name=\"key\" type=\"keyof (InferOptions<C> | InferSelectors<C>) | 'state'\">\n    The key of the option or selector to access.\n  </APIItem>\n  <APIItem name=\"...args\" type=\"any[]\" optional>\n    Additional arguments:\n    - For selectors: The selector parameters\n    - Last argument can be an equality function `(a: T, b: T) => boolean`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"T\">\n  The value of the option or selector result:\n  - For 'state': Returns the entire state object\n  - For selector keys: Returns the selector's return value\n  - For option keys: Returns the option value\n</APIReturns>\n\n```tsx\n// Access a simple option\nconst value = usePluginOption(plugin, 'value');\n// Access a selector with parameters\nconst doubleValue = usePluginOption(plugin, 'doubleValue', 2);\n// Access with equality function\nconst value = usePluginOption(plugin, 'value', (a, b) => a === b);\n// Access entire state\nconst state = usePluginOption(plugin, 'state');\n```\n\n</API>\n\n### `useEditorPluginOption`\n\nHook to access plugin options from the plugin store. For usage outside `<Plate>`.\n\n<API name=\"useEditorPluginOption\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"PlateEditor\">\n    The editor instance.\n  </APIItem>\n  <APIItem name=\"plugin\" type=\"PlatePlugin\">\n    The plugin to get options from.\n  </APIItem>\n  <APIItem name=\"key\" type=\"keyof (InferOptions<C> | InferSelectors<C>) | 'state'\">\n    The key of the option or selector to access.\n  </APIItem>\n  <APIItem name=\"...args\" type=\"any[]\" optional>\n    Additional arguments:\n    - For selectors: The selector parameters\n    - Last argument can be an equality function `(a: T, b: T) => boolean`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"T\">\n  The value of the option or selector result:\n  - For 'state': Returns the entire state object\n  - For selector keys: Returns the selector's return value\n  - For option keys: Returns the option value\n</APIReturns>\n\n```tsx\n// Access a simple option\nconst value = useEditorPluginOption(editor, plugin, 'value');\n// Access a selector with parameters\nconst doubleValue = useEditorPluginOption(editor, plugin, 'doubleValue', 2);\n// Access with equality function\nconst value = useEditorPluginOption(editor, plugin, 'value', (a, b) => a === b);\n// Access entire state\nconst state = useEditorPluginOption(editor, plugin, 'state');\n```\n</API>\n\n\n### `useElement`\n\nGet the element by plugin key.\n\n<API name=\"useElement\">\n<APIParameters>\n  <APIItem name=\"pluginKey\" type=\"string\" optional>\n    The key of the plugin to get the element for.\n    - **Default:** `'element'`\n  </APIItem>\n</APIParameters>\n\n<APIReturns>\n  The element of type `T extends TElement`, or an empty object if used outside of a node component's context.\n</APIReturns>\n</API>\n\n## Core plugins\n\n### `DebugPlugin`\nProvides debugging capabilities with configurable log levels and error handling. \n\nSee [Debugging](/docs/plate/debugging) for more details.\n\n### `SlateExtensionPlugin & SlateReactExtensionPlugin`\nExtend core apis and improve default functionality.\n\n`SlateExtensionPlugin` exposes `editor.api.isElementStateEmpty(element)`. It\nchecks the element's own props, not its text content. By default, only `type`\nand props claimed by plugins through `node.isMetadataProp` are ignored; any other\nprop means the element carries state. `NodeIdPlugin` uses `node.isMetadataProp` for\nthe configured NodeId key.\n\n```tsx\neditor.api.isElementStateEmpty({\n  children: [{ text: '' }],\n  type: 'p',\n}); // true\n\neditor.api.isElementStateEmpty({\n  children: [{ text: '' }],\n  listStyleType: 'disc',\n  type: 'p',\n}); // false\n\nconst CustomMetadataPlugin = createSlatePlugin({\n  key: 'customMetadata',\n  node: {\n    isMetadataProp: ({ key }) => key === 'customId',\n  },\n});\n```\n\n### `DOMPlugin & ReactPlugin`\nIntegrates React-specific functionality into the editor.\n\n### `HistoryPlugin`\nEnables undo and redo functionality for the editor.\n\n### `InlineVoidPlugin`\nManages inline and void elements in the editor.\n\n### `ParserPlugin`\nHandles parsing of content for the editor.\n\n### `LengthPlugin`\nEnforces a maximum length for the editor content.\n\n### `HtmlPlugin`\nEnables HTML serialization and deserialization.\n\n### `AstPlugin`\nHandles Abstract Syntax Tree (AST) operations for the editor.\n\n### `ParagraphPlugin`\nProvides paragraph formatting functionality.\n\n### `EventEditorPlugin`\nManages editor events such as focus and blur.\n\n## Utils\n\n### `isType`\n\nChecks whether a node matches the provided type.\n\n<API name=\"isType\">\n<APIParameters>\n  <APIItem name=\"editor\" type=\"PlateEditor<V>\">\n    The editor in which the node is present.\n  </APIItem>\n  <APIItem name=\"node\" type=\"any\">\n    The node to be checked.\n  </APIItem>\n  <APIItem name=\"key\" type=\"string | string[]\" optional>\n    The type or types to match the node against. Can be a string or an array of\n    strings.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  A boolean indicating whether the node's type matches the provided type or\n  types.\n</APIReturns>\n</API>\n\n## Components\n\n### `<PlateElement>`\n\nGeneric component for rendering an element.\n\n<API name=\"PlateElement\">\n<APIProps>\n  <APIItem name=\"className\" type=\"string\" optional>\n    The CSS class to apply to the component.\n  </APIItem>\n  <APIItem name=\"editor\" type=\"E\">\n    The editor instance. Also available using `useEditorRef` hook.\n  </APIItem>\n  <APIItem name=\"element\" type=\"TElement\">\n    The element node. Also available using `useElement` hook.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the element in the editor tree. Also available using `usePath` hook.\n  </APIItem>\n  <APIItem name=\"attributes\" type=\"HTMLAttributes<HTMLElement>\">\n    Attributes of the element to be spread on the top-level element.\n    <APISubList>\n      <APISubListItem parent=\"attributes\" name=\"data-slate-node\" type=\"'element'\">\n        Always set to `'element'`.\n      </APISubListItem>\n      <APISubListItem parent=\"attributes\" name=\"data-slate-inline\" type=\"boolean\" optional />\n      <APISubListItem parent=\"attributes\" name=\"data-slate-void\" type=\"boolean\" optional />\n      <APISubListItem parent=\"attributes\" name=\"dir\" type=\"string\" optional />\n      <APISubListItem parent=\"attributes\" name=\"ref\" type=\"any\">\n        The reference to the element. If using your own reference, merge it with this one.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"children\" type=\"any\">\n    Necessary for rendering the node children.\n  </APIItem>\n  <APIItem name=\"as\" type=\"React.ElementType\" optional>\n    The component type to render as.\n    - **Default:** `'div'`\n  </APIItem>\n</APIProps>\n</API>\n\n### `<PlateLeaf>`\n\nGeneric component for rendering a leaf.\n\n<API name=\"PlateLeaf\">\n<APIProps>\n  <APIItem name=\"className\" type=\"string\" optional>\n    The CSS class to apply to the component.\n  </APIItem>\n  <APIItem name=\"editor\" type=\"E\">\n    The editor context.\n  </APIItem>\n  <APIItem name=\"children\" type=\"any\">\n    Necessary for rendering the node children.\n  </APIItem>\n  <APIItem name=\"leaf\" type=\"TText\">\n    The leaf node.\n  </APIItem>\n  <APIItem name=\"text\" type=\"TText\">\n    The text node.\n  </APIItem>\n  <APIItem name=\"attributes\" type=\"HTMLAttributes<HTMLElement>\">\n    Attributes of the leaf to be spread on the top-level element.\n    <APISubList>\n      <APISubListItem parent=\"attributes\" name=\"data-slate-leaf\" type=\"true\">\n        Always set to `true`.\n      </APISubListItem>\n    </APISubList>\n  </APIItem>\n  <APIItem name=\"as\" type=\"React.ElementType\" optional>\n    The component type to render as.\n    - **Default:** `'span'`\n  </APIItem>\n</APIProps>\n</API>\n\n### `<PlateText>`\n\nGeneric component for rendering text.\n\n<API name=\"PlateText\">\n<APIProps>\n  <APIItem name=\"className\" type=\"string\" optional>\n    The CSS class to apply to the component.\n  </APIItem>\n  <APIItem name=\"text\" type=\"TText\">\n    The text node.\n  </APIItem>\n  <APIItem name=\"attributes\" type=\"HTMLAttributes<HTMLElement>\">\n    Attributes of the text to be spread on the top-level element.\n  </APIItem>\n  <APIItem name=\"children\" type=\"any\">\n    Necessary for rendering the node children.\n  </APIItem>\n  <APIItem name=\"as\" type=\"React.ElementType\" optional>\n    The component type to render as.\n    - **Default:** `'span'`\n  </APIItem>\n</APIProps>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/core.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-floating-docs",
      "title": "Floating",
      "description": "API reference for @platejs/floating.",
      "files": [
        {
          "path": "../../content/docs/api/floating.mdx",
          "content": "---\ntitle: Floating\ndescription: API reference for @platejs/floating.\n---\n\n`@platejs/floating` contains the React hooks and rectangle utilities used by floating toolbars, cursor-anchored UI, and virtual elements in Plate. It wraps Floating UI and exports the Floating UI primitives Plate components use.\n\n## Installation\n\n```bash\nnpm install @platejs/floating\n```\n\n## Ownership\n\n| Surface | Owner | Use |\n| --- | --- | --- |\n| `useVirtualFloating` | `@platejs/floating` | `useFloating` with a controlled virtual reference element. |\n| `useFloatingToolbarState` | `@platejs/floating` | Builds toolbar state from editor focus, selection, read-only state, and Floating UI options. |\n| `useFloatingToolbar` | `@platejs/floating` | Turns toolbar state into DOM props, ref, outside-click ref, and hidden state. |\n| Rect utilities | `@platejs/floating` | Convert editor ranges, DOM selection, and client rect arrays into Floating UI-compatible rects. |\n| Floating UI exports | `@platejs/floating` | Re-exported middleware and hooks from `@floating-ui/react`. |\n\n## Virtual Floating\n\n`useVirtualFloating` creates a Floating UI virtual reference. Use it when the floating element follows a selection, cursor, or computed rectangle instead of a real DOM reference element.\n\n```tsx title=\"Virtual floating element\"\nimport {\n  flip,\n  getDefaultBoundingClientRect,\n  offset,\n  useVirtualFloating,\n} from '@platejs/floating';\n\nexport function SelectionPopover({\n  open,\n  rect,\n}: {\n  open: boolean;\n  rect?: DOMRect;\n}) {\n  const floating = useVirtualFloating({\n    getBoundingClientRect: () => rect ?? getDefaultBoundingClientRect(),\n    middleware: [offset(8), flip()],\n    open,\n    placement: 'top',\n  });\n\n  return (\n    <div ref={floating.refs.setFloating} style={floating.style}>\n      Selection actions\n    </div>\n  );\n}\n```\n\n<API name=\"useVirtualFloating\">\n<APIOptions type=\"UseVirtualFloatingOptions\">\n  <APIItem name=\"getBoundingClientRect\" type=\"() => ClientRectObject\" optional>\n    Supplies the virtual element rect. Defaults to `getDefaultBoundingClientRect`.\n  </APIItem>\n  <APIItem name=\"open\" type=\"boolean\" optional>\n    When `false`, the returned style sets `display: 'none'`.\n  </APIItem>\n  <APIItem name=\"whileElementsMounted\" type=\"UseFloatingOptions['whileElementsMounted']\" optional>\n    Defaults to Floating UI `autoUpdate`.\n  </APIItem>\n  <APIItem name=\"...floatingOptions\" type=\"Partial<UseFloatingOptions>\" optional>\n    Forwarded to Floating UI `useFloating`.\n  </APIItem>\n</APIOptions>\n<APIReturns type=\"UseVirtualFloatingReturn\">\n  <APIItem name=\"style\" type=\"React.CSSProperties\">\n    Absolute/fixed position style: `position`, `left`, `top`, `display`, and `visibility`.\n  </APIItem>\n  <APIItem name=\"virtualElementRef\" type=\"React.MutableRefObject<VirtualElement>\">\n    Mutable virtual reference element. The hook updates its `getBoundingClientRect`.\n  </APIItem>\n  <APIItem name=\"refs\" type=\"UseFloatingReturn['refs']\">\n    Floating UI refs. Attach `refs.setFloating` to the floating element.\n  </APIItem>\n  <APIItem name=\"update\" type=\"UseFloatingReturn['update']\">\n    Floating UI manual position update.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Floating Toolbar\n\nFloating toolbar setup is split into two hooks. Build state first, then pass that state to `useFloatingToolbar`.\n\n```tsx title=\"Floating toolbar state\"\nimport {\n  flip,\n  offset,\n  useFloatingToolbar,\n  useFloatingToolbarState,\n} from '@platejs/floating';\nimport { useEditorId, useEventEditorValue } from 'platejs/react';\n\nexport function ToolbarShell() {\n  const editorId = useEditorId();\n  const focusedEditorId = useEventEditorValue('focus');\n\n  const state = useFloatingToolbarState({\n    editorId,\n    focusedEditorId,\n    floatingOptions: {\n      middleware: [offset(12), flip({ padding: 12 })],\n      placement: 'top',\n    },\n  });\n\n  const { clickOutsideRef, hidden, props, ref } = useFloatingToolbar(state);\n\n  if (hidden) return null;\n\n  return (\n    <div ref={clickOutsideRef}>\n      <div ref={ref} {...props}>\n        Toolbar\n      </div>\n    </div>\n  );\n}\n```\n\n<API name=\"useFloatingToolbarState\">\n<APIOptions type=\"FloatingToolbarState & { editorId: string; focusedEditorId: string | null }\">\n  <APIItem name=\"editorId\" type=\"string\" required>\n    Current editor id.\n  </APIItem>\n  <APIItem name=\"focusedEditorId\" type=\"string | null\" required>\n    Focused editor id from `useEventEditorValue('focus')`.\n  </APIItem>\n  <APIItem name=\"floatingOptions\" type=\"UseVirtualFloatingOptions\" optional>\n    Options passed to `useVirtualFloating`.\n  </APIItem>\n  <APIItem name=\"hideToolbar\" type=\"boolean\" optional>\n    Force the toolbar closed.\n  </APIItem>\n  <APIItem name=\"showWhenReadOnly\" type=\"boolean\" optional>\n    Allow the toolbar to show when the editor is read-only.\n  </APIItem>\n</APIOptions>\n<APIReturns type=\"ReturnType<typeof useFloatingToolbarState>\">\n  Internal toolbar state for `useFloatingToolbar`.\n</APIReturns>\n</API>\n\n<API name=\"useFloatingToolbar\">\n<APIOptions type=\"ReturnType<typeof useFloatingToolbarState>\">\n  <APIItem name=\"state\" type=\"ReturnType<typeof useFloatingToolbarState>\" required>\n    State returned by `useFloatingToolbarState`.\n  </APIItem>\n</APIOptions>\n<APIReturns type=\"object\">\n  <APIItem name=\"clickOutsideRef\" type=\"React.RefObject<HTMLElement>\">\n    Ref from `useOnClickOutside`. It closes the toolbar and ignores `.ignore-click-outside/toolbar`.\n  </APIItem>\n  <APIItem name=\"hidden\" type=\"boolean\">\n    `true` when the toolbar should not render.\n  </APIItem>\n  <APIItem name=\"props\" type=\"{ style: React.CSSProperties }\">\n    Props to spread on the toolbar root.\n  </APIItem>\n  <APIItem name=\"ref\" type=\"UseFloatingReturn['refs']['setFloating']\">\n    Floating element ref callback.\n  </APIItem>\n</APIReturns>\n</API>\n\nThe toolbar opens only for an expanded selection with text. It stays hidden while the mouse is down, when `hideToolbar` is true, when a different editor owns focus, or when the editor is read-only and `showWhenReadOnly` is not set.\n\n## Rectangle Utilities\n\nThese helpers normalize editor locations and DOM ranges into rectangles.\n\n<API name=\"Rectangle utilities\">\n<APIMethods>\n  <APIItem name=\"getDefaultBoundingClientRect\" type=\"() => ClientRectObject\">\n    Returns a zero-size offscreen rect used as a safe Floating UI fallback.\n  </APIItem>\n  <APIItem name=\"createVirtualElement\" type=\"() => VirtualElement\">\n    Creates a Floating UI virtual element with `getDefaultBoundingClientRect`.\n  </APIItem>\n  <APIItem name=\"createVirtualRef\" type=\"(editor: Editor, at?: TLocation | TLocation[], options?: { fallbackRect?: ClientRect }) => VirtualRef\">\n    Creates a ref-like object whose `current.getBoundingClientRect()` reads editor locations. It throws when no rect exists and no `fallbackRect` is provided.\n  </APIItem>\n  <APIItem name=\"getBoundingClientRect\" type=\"(editor: Editor, at?: TLocation | TLocation[]) => DOMRect | undefined\">\n    Reads one or more editor locations, converts them to DOM ranges, and returns the merged bounding rect. If `at` is omitted, it uses `editor.selection`.\n  </APIItem>\n  <APIItem name=\"getRangeBoundingClientRect\" type=\"(editor: Editor, at: TRange | null) => ClientRectObject\">\n    Returns the DOM rect for a range, or `getDefaultBoundingClientRect()` when the range or DOM range is missing.\n  </APIItem>\n  <APIItem name=\"getSelectionBoundingClientRect\" type=\"(editor: PlateEditor) => ClientRectObject\">\n    Returns the selection rect only when the editor selection is expanded. Collapsed selections return the default rect.\n  </APIItem>\n  <APIItem name=\"getDOMSelectionBoundingClientRect\" type=\"() => ClientRectObject\">\n    Returns `window.getSelection().getRangeAt(0).getBoundingClientRect()`, or the default rect when no DOM selection exists.\n  </APIItem>\n  <APIItem name=\"makeClientRect\" type=\"(rect: { bottom: number; left: number; right: number; top: number }) => DOMRect\">\n    Creates a DOMRect-like object and computes `width`, `height`, `x`, and `y`.\n  </APIItem>\n  <APIItem name=\"mergeClientRects\" type=\"(clientRects: DOMRect[]) => DOMRect\">\n    Merges client rects by min left/top and max right/bottom. It throws when the array is empty.\n  </APIItem>\n</APIMethods>\n</API>\n\n## Floating UI Re-exports\n\n`@platejs/floating` re-exports the Floating UI middleware and React hooks used by Plate UI, including `autoUpdate`, `flip`, `hide`, `inline`, `offset`, `shift`, `size`, `useFloating`, `useInteractions`, `useClick`, `useDismiss`, `FloatingPortal`, and related types.\n\nUse those exports when a Plate UI component already imports from `@platejs/floating`; use `@floating-ui/react` directly only when the component is not coupled to Plate.\n\n## Related Components\n\n- [Toolbar](/docs/plate/toolbar) covers the registry floating-toolbar component that consumes these hooks.\n- [Plate Store](/docs/plate/api/core/plate-store) covers `useEditorId` and `useEventEditorValue`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/floating.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-plate-docs",
      "title": "Plate",
      "description": "API reference for the platejs package.",
      "files": [
        {
          "path": "../../content/docs/api/plate.mdx",
          "content": "---\ntitle: Plate\ndescription: API reference for the platejs package.\n---\n\n`platejs` is the umbrella package for Plate's base, React, and static APIs. It re-exports the core runtime, Slate runtime, utilities, React helpers, and static renderer through three public subpaths.\n\n## Installation\n\n```bash\nnpm install platejs\n```\n\n`platejs` has React peer dependencies because the package also owns `platejs/react`.\n\n## Import Paths\n\n| Import | Re-exports | Use |\n| --- | --- | --- |\n| `platejs` | `@platejs/core`, `@platejs/slate`, `@platejs/utils`, `@udecode/utils` | Headless editor creation, Slate APIs, core types, and shared utilities. |\n| `platejs/react` | `@platejs/core/react`, `@platejs/utils/react`, `@udecode/react-hotkeys`, `@udecode/react-utils` | React editors, components, hooks, stores, plugins, and React utility hooks. |\n| `platejs/static` | `@platejs/core/static` | Static editor creation, static rendering, HTML serialization, and static node renderers. |\n\nUse the umbrella imports in application code. Package code can import direct package surfaces such as `@platejs/core/react` when it needs a tighter dependency boundary.\n\n## Base Runtime\n\nUse `platejs` for non-React editor work and shared types.\n\n```ts title=\"lib/create-headless-editor.ts\"\nimport { createSlateEditor } from 'platejs';\n\nexport const editor = createSlateEditor({\n  value: [\n    {\n      type: 'p',\n      children: [{ text: 'Headless editor.' }],\n    },\n  ],\n});\n```\n\n## React Runtime\n\nUse `platejs/react` for app editors.\n\n```tsx title=\"components/basic-editor.tsx\"\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nexport function BasicEditor() {\n  const editor = usePlateEditor({\n    value: [\n      {\n        type: 'p',\n        children: [{ text: 'React editor.' }],\n      },\n    ],\n  });\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent />\n    </Plate>\n  );\n}\n```\n\n## Static Runtime\n\nUse `platejs/static` for rendering or serialization paths that do not mount an editable React editor.\n\n```tsx title=\"lib/create-static-editor.ts\"\nimport { createStaticEditor } from 'platejs/static';\n\nexport const staticEditor = createStaticEditor({\n  value: [\n    {\n      type: 'p',\n      children: [{ text: 'Static editor.' }],\n    },\n  ],\n});\n```\n\n## API Pages\n\n| Page | Covers |\n| --- | --- |\n| [Plate Core](/docs/plate/api/core) | Editor creation, React runtime, plugins, stores, and render primitives. |\n| [Slate](/docs/plate/api/slate) | Slate-compatible editor API, transforms, nodes, locations, operations, and refs. |\n| [Plate Utils](/docs/plate/api/utils) | Shared non-React utilities. |\n| [React Utils](/docs/plate/api/react-utils) | React utility hooks and helpers re-exported through `platejs/react`. |\n| [Plate Components](/docs/plate/api/core/plate-components) | `Plate`, `PlateContent`, `PlateView`, and component primitives. |\n| [Plate Editor](/docs/plate/api/core/plate-editor) | Runtime editor type, core APIs, transforms, DOM state, and metadata. |\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/plate.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-react-utils-docs",
      "title": "React Utils",
      "description": "API reference for @udecode/react-utils.",
      "files": [
        {
          "path": "../../content/docs/api/react-utils.mdx",
          "content": "---\ntitle: React Utils\ndescription: API reference for @udecode/react-utils.\n---\n\n`@udecode/react-utils` provides small React primitives used across Plate UI packages. It is also re-exported from `platejs/react` and `@udecode/cn`.\n\n## Installation\n\n```bash\nnpm install @udecode/react-utils\n```\n\nUse direct imports in shared UI packages. Use `platejs/react` when you are already inside a Plate app surface.\n\n## Components\n\n| Component | Renders | Notes |\n| --- | --- | --- |\n| `PortalBody` | `ReactDOM.createPortal(children, element ?? document.body)` | Returns children directly when no DOM container is available. |\n| `Box` | Slot-aware `div` | Created with `createSlotComponent('div')`. Supports `as` and `asChild`. |\n| `Text` | Slot-aware `span` | Created with `createSlotComponent('span')`. Supports `as` and `asChild`. |\n| `MemoizedChildren` | `React.memo(({ children }) => <>{children}</>)` | Prevents child-only rerenders when parent props are stable. |\n\n```tsx title=\"Portal to body\"\nimport { PortalBody } from '@udecode/react-utils';\n\nexport function BodyOverlay() {\n  return (\n    <PortalBody>\n      <div role=\"status\">Saving</div>\n    </PortalBody>\n  );\n}\n```\n\n## Primitive Factories\n\nUse primitive factories when a component needs `asChild`, composed refs, hook-provided props, or hook-provided state.\n\n<API name=\"createSlotComponent\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"React.ElementType\">\n    Default element or component.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A component that renders `Slot` when `asChild` is true, `as` when provided, otherwise the default element.\n</APIReturns>\n</API>\n\n<API name=\"createPrimitiveElement\">\n<APIParameters>\n  <APIItem name=\"tag\" type=\"keyof HTMLElementTagNameMap\">\n    HTML tag to render.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A typed `forwardRef` component for that intrinsic element.\n</APIReturns>\n</API>\n\n<API name=\"createPrimitiveComponent\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"React.ElementType\">\n    Default element or component.\n  </APIItem>\n  <APIItem name=\"stateHook\" type=\"(options: any) => any\" optional>\n    Hook used to create state when the caller does not provide `state`.\n  </APIItem>\n  <APIItem name=\"propsHook\" type=\"(state: any) => { hidden?: boolean; props?: object; ref?: React.Ref<any> }\" optional>\n    Hook used to derive props, hidden state, and a ref from state.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  A primitive component with `as`, `asChild`, `options`, `state`, `className`, `style`, and `setProps`.\n</APIReturns>\n</API>\n\n`createPrimitiveComponent` merges hook class names before consumer class names, merges hook style before consumer style, composes forwarded refs with hook refs, and returns `null` when `hidden` is true unless `asChild` is set.\n\n## Ref and Effect Hooks\n\n| API | Type | Behavior |\n| --- | --- | --- |\n| `composeRefs(...refs)` | `(...refs) => (node) => cleanup?` | Sets callback refs and ref objects to the same node. If refs return cleanup functions, the composed ref returns a cleanup. |\n| `useComposedRef(...refs)` | `(...refs) => refCallback` | Memoized `composeRefs` callback. |\n| `useStableFn(fn, deps?)` | `(fn, deps = []) => stableFn` | Returns a stable function that calls the latest `fn`. |\n| `useStableMemo(producer, deps?)` | `(producer, deps?) => value` | Stores a produced value in state and updates it in a layout effect. |\n| `useEffectOnce(effect, deps)` | `(effect, deps) => void` | Runs the effect on first render and again when the dependency values change. |\n| `useIsomorphicLayoutEffect` | `React.useLayoutEffect \\| React.useEffect` | Uses layout effect in the browser and effect during SSR. |\n\n```tsx title=\"Compose refs\"\nimport * as React from 'react';\nimport { useComposedRef } from '@udecode/react-utils';\n\nexport const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(\n  (props, ref) => {\n    const localRef = React.useRef<HTMLInputElement>(null);\n    const composedRef = useComposedRef(ref, localRef);\n\n    return <input ref={composedRef} {...props} />;\n  }\n);\n```\n\n## Outside Click\n\n`useOnClickOutside` returns a callback ref unless you pass explicit refs.\n\n<API name=\"useOnClickOutside\">\n<APIParameters>\n  <APIItem name=\"callback\" type=\"(event: Event) => void\">\n    Called when a configured event lands outside every tracked element.\n  </APIItem>\n  <APIItem name=\"options.disabled\" type=\"boolean\" optional>\n    Removes listeners while true.\n  </APIItem>\n  <APIItem name=\"options.eventTypes\" type=\"string[]\" optional>\n    Defaults to `['mousedown', 'touchstart']`.\n  </APIItem>\n  <APIItem name=\"options.ignoreClass\" type=\"string | string[]\" optional>\n    Defaults to `ignore-onclickoutside`. Matching ancestors are ignored.\n  </APIItem>\n  <APIItem name=\"options.excludeScrollbar\" type=\"boolean\" optional>\n    Ignores scrollbar clicks.\n  </APIItem>\n  <APIItem name=\"options.detectIFrame\" type=\"boolean\" optional>\n    Defaults to `true`. Uses window blur to detect iframe focus.\n  </APIItem>\n  <APIItem name=\"options.refs\" type=\"React.RefObject<HTMLElement | null>[]\" optional>\n    Explicit refs to observe instead of the returned callback ref.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"(element: HTMLElement | null) => void\">\n  Callback ref that registers an element for outside-click detection.\n</APIReturns>\n</API>\n\n## Memo and Event Helpers\n\n| API | Type | Behavior |\n| --- | --- | --- |\n| `useMemoizedSelector(selector, deps, equalityFn?)` | `(selector, deps, equalityFn?) => value` | Re-renders only when the selector result changes. The default equality is strict equality. |\n| `composeEventHandlers(original, next, options?)` | `(event) => void` | Calls `original`, then calls `next` unless `event.defaultPrevented` and `checkForDefaultPrevented` is true. |\n\n## Component Wrappers\n\n<API name=\"withRef\">\n<APIParameters>\n  <APIItem name=\"renderFunction\" type=\"React.ForwardRefRenderFunction\">\n    Forward-ref render function.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"React.ForwardRefExoticComponent\">\n  Typed `React.forwardRef` result.\n</APIReturns>\n</API>\n\n<API name=\"withProviders\">\n<APIParameters>\n  <APIItem name=\"...providers\" type=\"React.ComponentType | [React.ComponentType, props][]\">\n    Providers to wrap around the component. Array entries pass props to a provider.\n  </APIItem>\n  <APIItem name=\"WrappedComponent\" type=\"React.FC<T>\">\n    Component to wrap.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"(props: T) => React.ReactElement\">\n  Component wrapped by the providers from right to left.\n</APIReturns>\n</API>\n\n```tsx title=\"Wrap providers\"\nimport { withProviders } from '@udecode/react-utils';\n\nconst ThemeProvider = ({ children }: { children: React.ReactNode }) => (\n  <div data-theme=\"dark\">{children}</div>\n);\n\nconst Page = () => <main>Docs</main>;\n\nexport const ThemedPage = withProviders(ThemeProvider)(Page);\n```\n\n## Related APIs\n\n- [cn](/docs/plate/api/cn) covers `@udecode/cn`, which re-exports this package.\n- [Plate](/docs/plate/api/plate) covers the `platejs/react` umbrella export.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/react-utils.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-resizable-docs",
      "title": "Resizable",
      "description": "API reference for @platejs/resizable.",
      "files": [
        {
          "path": "../../content/docs/api/resizable.mdx",
          "content": "---\ntitle: Resizable\ndescription: API reference for @platejs/resizable.\n---\n\n`@platejs/resizable` provides the headless resize wrapper, resize handle primitive,\nshared resize stores, and length utilities used by Plate media components. It\nowns behavior; registry components own styling.\n\n## Installation\n\n```bash\nnpm install @platejs/resizable\n```\n\n## Ownership\n\n| Surface | Owner | Use |\n| --- | --- | --- |\n| `Resizable` | `@platejs/resizable` | Wraps a Plate element, tracks width, clamps resize values, and writes the final width to the node. |\n| `ResizeHandle` | `@platejs/resizable` | Primitive handle that starts mouse/touch resizing and emits `ResizeEvent` values. |\n| `ResizableProvider` | `@platejs/resizable` | Stores the active width for the current resizable subtree. |\n| `ResizeHandleProvider` | `@platejs/resizable` | Shares the wrapper `onResize` callback with nested handles. |\n| Resize hooks | `@platejs/resizable` | Split state from DOM props for custom wrappers and handles. |\n| Length utilities | `@platejs/resizable` | Convert and clamp static pixel widths and relative percent widths. |\n\n## Media Pattern\n\nWrap each resizable element with `ResizableProvider`. Media components use the\nprovider width for captions and use `Resizable` plus left/right handles around\nthe media body.\n\n```tsx title=\"Resizable media element\"\nimport type { TImageElement } from 'platejs';\nimport type { PlateElementProps } from 'platejs/react';\n\nimport {\n  Resizable,\n  ResizableProvider,\n  ResizeHandle,\n  useResizableValue,\n} from '@platejs/resizable';\nimport { PlateElement, withHOC } from 'platejs/react';\n\nexport const ImageElement = withHOC(\n  ResizableProvider,\n  function ImageElement(props: PlateElementProps<TImageElement>) {\n    const width = useResizableValue('width');\n\n    return (\n      <PlateElement {...props}>\n        <figure contentEditable={false}>\n          <Resizable\n            options={{\n              align: 'center',\n              maxWidth: '100%',\n              minWidth: 120,\n            }}\n          >\n            <ResizeHandle options={{ direction: 'left' }} />\n            <img alt=\"\" src={props.element.url as string} />\n            <ResizeHandle options={{ direction: 'right' }} />\n          </Resizable>\n\n          <figcaption style={{ width }}>Caption</figcaption>\n        </figure>\n\n        {props.children}\n      </PlateElement>\n    );\n  }\n);\n```\n\nThe registry `resize-handle` component imports these primitives and adds the absolute positioning, hover affordance, and alignment classes.\n\n## Resizable Wrapper\n\n`Resizable` composes `useResizableState` and `useResizable`. It renders a\nrelative outer wrapper and a relative inner wrapper, then provides its resize\ncallback to descendants through `ResizeHandleProvider`.\n\n<API name=\"Resizable\">\n<APIOptions type=\"{ options: ResizableOptions } & React.HTMLAttributes<HTMLDivElement>\">\n  <APIItem name=\"options\" type=\"ResizableOptions\" required>\n    Width constraints and alignment used by the resize calculation.\n  </APIItem>\n  <APIItem name=\"children\" type=\"React.ReactNode\" optional>\n    Media body, resize handles, or custom controls.\n  </APIItem>\n</APIOptions>\n</API>\n\n<API name=\"useResizableState\">\n<APIOptions type=\"ResizableOptions\">\n  <APIItem name=\"align\" type=\"'center' | 'left' | 'right'\" optional>\n    Alignment used to calculate resize delta. Defaults to `center`.\n  </APIItem>\n  <APIItem name=\"maxWidth\" type=\"ResizeLength\" optional>\n    Maximum width. Defaults to `100%`.\n  </APIItem>\n  <APIItem name=\"minWidth\" type=\"ResizeLength\" optional>\n    Minimum width. Defaults to `92`.\n  </APIItem>\n  <APIItem name=\"readOnly\" type=\"boolean\" optional>\n    Reserved in the options type. `Resizable` itself does not read this value.\n  </APIItem>\n</APIOptions>\n<APIReturns type=\"ReturnType<typeof useResizableState>\">\n  <APIItem name=\"align\" type=\"'center' | 'left' | 'right'\">\n    Alignment used by resize math.\n  </APIItem>\n  <APIItem name=\"maxWidth\" type=\"ResizeLength\">\n    Maximum width passed to the wrapper style and clamp utility.\n  </APIItem>\n  <APIItem name=\"minWidth\" type=\"ResizeLength\">\n    Minimum width passed to the wrapper style and clamp utility.\n  </APIItem>\n  <APIItem name=\"setNodeWidth\" type=\"(width: number) => void\">\n    Writes the finished width to the current `TResizableElement`. If the width is unchanged, it selects the node.\n  </APIItem>\n  <APIItem name=\"setWidth\" type=\"(width: React.CSSProperties['width']) => void\">\n    Updates the transient provider width while dragging.\n  </APIItem>\n  <APIItem name=\"width\" type=\"React.CSSProperties['width']\">\n    Current provider width. It is synced from `element.width ?? '100%'`.\n  </APIItem>\n</APIReturns>\n</API>\n\n<API name=\"useResizable\">\n<APIParameters>\n  <APIItem name=\"state\" type=\"ReturnType<typeof useResizableState>\">\n    State returned by `useResizableState`.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"object\">\n  <APIItem name=\"context.onResize\" type=\"(event: ResizeEvent) => void\">\n    Converts a handle delta into a new width, clamps it, stores it while dragging, and writes it to the node when `finished` is true.\n  </APIItem>\n  <APIItem name=\"props\" type=\"{ style: React.CSSProperties }\">\n    Inner wrapper props with `position: 'relative'`, `width`, `minWidth`, and `maxWidth`.\n  </APIItem>\n  <APIItem name=\"wrapperProps\" type=\"{ style: React.CSSProperties }\">\n    Outer wrapper props with `position: 'relative'`.\n  </APIItem>\n  <APIItem name=\"wrapperRef\" type=\"React.RefObject<HTMLDivElement>\">\n    Measures the static wrapper width for percent-to-pixel conversion.\n  </APIItem>\n</APIReturns>\n</API>\n\nFor centered elements, left and right handles double the delta so the element grows from both sides. Left handles invert the delta before clamping.\n\n## Resize Handles\n\n`ResizeHandle` is a primitive `div` created with `createPrimitiveComponent`. It\nreturns `null` in read-only mode because `useResizeHandle` sets `hidden` from\n`useReadOnly()`.\n\n<API name=\"ResizeHandle\">\n<APIOptions type=\"ResizeHandleProps\">\n  <APIItem name=\"options\" type=\"ResizeHandleOptions\" optional>\n    Direction, initial size, and lifecycle callbacks for the handle.\n  </APIItem>\n  <APIItem name=\"state\" type=\"ReturnType<typeof useResizeHandleState>\" optional>\n    Precomputed state when composing your own handle pipeline.\n  </APIItem>\n</APIOptions>\n</API>\n\n<API name=\"useResizeHandleState\">\n<APIOptions type=\"ResizeHandleOptions\">\n  <APIItem name=\"direction\" type=\"ResizeDirection\" optional>\n    Resize edge. Defaults to `left`.\n  </APIItem>\n  <APIItem name=\"initialSize\" type=\"number\" optional>\n    Starting width or height. If omitted, the handle reads its parent element on pointer start.\n  </APIItem>\n  <APIItem name=\"onHover\" type=\"() => void\" optional>\n    Called on mouse over and touch move.\n  </APIItem>\n  <APIItem name=\"onHoverEnd\" type=\"() => void\" optional>\n    Called after hover ends or resizing finishes.\n  </APIItem>\n  <APIItem name=\"onMouseDown\" type=\"React.MouseEventHandler\" optional>\n    Called after the hook records the starting pointer position and size.\n  </APIItem>\n  <APIItem name=\"onResize\" type=\"(event: ResizeEvent) => void\" optional>\n    Resize callback. Defaults to the nearest `ResizeHandleProvider` value.\n  </APIItem>\n  <APIItem name=\"onTouchStart\" type=\"React.TouchEventHandler\" optional>\n    Called after the hook records the starting touch position and size.\n  </APIItem>\n</APIOptions>\n<APIReturns type=\"ReturnType<typeof useResizeHandleState>\">\n  Direction, pointer state, horizontal/vertical mode, read-only state, setters, and callbacks consumed by `useResizeHandle`.\n</APIReturns>\n</API>\n\n<API name=\"useResizeHandle\">\n<APIParameters>\n  <APIItem name=\"state\" type=\"ReturnType<typeof useResizeHandleState>\">\n    State returned by `useResizeHandleState`.\n  </APIItem>\n</APIParameters>\n<APIReturns type=\"object\">\n  <APIItem name=\"hidden\" type=\"boolean\">\n    `true` while the editor is read-only.\n  </APIItem>\n  <APIItem name=\"props\" type=\"React.HTMLAttributes<HTMLDivElement>\">\n    Mouse and touch handlers for starting resize, tracking hover, and finishing resize.\n  </APIItem>\n</APIReturns>\n</API>\n\nResize handles listen on `window` while dragging. Mouse and touch move events emit `finished: false`; mouse up and touch end emit `finished: true`.\n\n## Stores\n\n| API | State | Use |\n| --- | --- | --- |\n| `ResizableProvider` | `{ width: React.CSSProperties['width'] }` | Wrap a resizable node and expose the active width to captions or overlays. |\n| `useResizableValue('width')` | `React.CSSProperties['width']` | Read the current width. |\n| `useResizableSet('width')` | setter | Set the current width. |\n| `useResizableStore` / `resizableStore` | atom store | Advanced access to the resizable store. |\n| `ResizeHandleProvider` | `{ onResize: (event: ResizeEvent) => void }` | Provides the wrapper resize callback to nested handles. |\n| `useResizeHandleValue('onResize')` | callback | Read the current resize callback. |\n| `useResizeHandleSet('onResize')` | setter | Replace the current resize callback. |\n| `useResizeHandleStore` | atom store | Advanced access to the handle store. |\n\n## Types\n\n| Type | Value |\n| --- | --- |\n| `ResizeDirection` | `'bottom' \\| 'left' \\| 'right' \\| 'top'` |\n| `ResizeLength` | `number \\| string` |\n| `ResizeLengthStatic` | `number` |\n| `ResizeLengthRelative` | `string` |\n| `ResizeEvent` | `{ delta: number; direction: ResizeDirection; finished: boolean; initialSize: number }` |\n\n## Length Utilities\n\n<API name=\"Length utilities\">\n<APIMethods>\n  <APIItem name=\"resizeLengthClampStatic\" type=\"(length: number, options: { min?: number; max?: number }) => number\">\n    Clamps a pixel length to pixel min/max values.\n  </APIItem>\n  <APIItem name=\"resizeLengthClamp\" type=\"<T extends ResizeLength>(length: T, parentLength: number, options: { min?: ResizeLength; max?: ResizeLength }) => T\">\n    Converts length and constraints to pixels, clamps the value, then returns the same length kind as the input.\n  </APIItem>\n  <APIItem name=\"resizeLengthToRelative\" type=\"(length: ResizeLength, parentLength: number) => string\">\n    Converts a pixel length to a percent string. Percent strings pass through unchanged.\n  </APIItem>\n  <APIItem name=\"resizeLengthToStatic\" type=\"(length: ResizeLength, parentLength: number) => number\">\n    Converts a percent string to pixels. Numbers pass through unchanged.\n  </APIItem>\n  <APIItem name=\"isTouchEvent\" type=\"(event: MouseEvent | TouchEvent) => event is TouchEvent\">\n    Narrows pointer events by checking for `touches`.\n  </APIItem>\n</APIMethods>\n</API>\n\n`resizeLengthToStatic` parses strings as percentages. Use numeric pixel lengths when the source value is not a percentage.\n\n## Related Components\n\n- [Media](/docs/plate/media) covers the image, video, audio, and embed elements that consume the resizable primitives.\n- [Resize Handle](/docs/plate/components/resize-handle) covers the styled registry wrapper.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/resizable.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-editor-api-docs",
      "title": "Editor API",
      "description": "API reference for the Editor API.",
      "files": [
        {
          "path": "../../content/docs/api/slate/editor-api.mdx",
          "content": "---\ntitle: Editor API\ndescription: API reference for the Editor API.\n---\n\nThe Editor API provides a set of helper functions for querying and manipulating the editor state.\n\n## Common Options\n\n### `At`\n\nA location reference in the editor. Can be either a Location or a Node.\n\n```ts\ntype At = TLocation | TNode\n```\n\nWhen a Node is passed, its path will be found using [`editor.api.findPath()`](/docs/plate/api/slate/editor-api#findpath). This allows you to reference a location by either:\n- A [Location](/docs/plate/api/slate/location) ([Path](/docs/plate/api/slate/path), [Point](/docs/plate/api/slate/point), or [Range](/docs/plate/api/slate/range))\n- A [Node](/docs/plate/api/slate/node)\n\nExample:\n```ts\n// Using a location\neditor.api.nodes({ at: [0, 0] }) // Path location\neditor.api.nodes({ at: { path: [0], offset: 0 } }) // Point location \neditor.api.nodes({ at: { anchor: point1, focus: point2 } }) // Range location\n\n// Using a node reference\nconst node = editor.children[0]\neditor.api.nodes({ at: node }) // Will find node's path internally\n```\n\n### Match\n\nA predicate for matching nodes. The predicate can be either:\n- A function that takes a `node` and its `path` and returns a `boolean`\n- An object where each key-value pair must match the node's properties\n  - Values can be single values or arrays of values to match against\n\nExample:\n```ts\n// Function predicate\neditor.api.nodes({\n  match: (node) => node.type === 'p'\n})\n\n// Object predicate\neditor.api.nodes({\n  match: { type: 'p' }\n})\n\n// Object predicate with multiple possible values\neditor.api.nodes({\n  match: { type: ['p', 'h1'] }\n})\n```\n\n### `QueryMode`\n\nMode for querying nodes in a hierarchy.\n\n<API name=\"QueryMode\">\n<APIOptions type=\"QueryMode\">\n  <APIItem name=\"mode\" type=\"'all' | 'highest' | 'lowest'\" optional>\n    - `'all'` (default): Return all matching nodes\n    - `'highest'`: In a hierarchy of nodes, only return the highest-level matching nodes\n    - `'lowest'`: In a hierarchy of nodes, only return the lowest-level matching nodes\n\n    Example:\n    ```ts\n    // Given this structure:\n    // - blockquote (matches)\n    //   - paragraph (matches)\n    //     - text\n    \n    // mode: 'all' returns both blockquote and paragraph\n    editor.api.nodes({ match: { type: ['blockquote', 'paragraph'] }, mode: 'all' })\n    \n    // mode: 'highest' returns only blockquote\n    editor.api.nodes({ match: { type: ['blockquote', 'paragraph'] }, mode: 'highest' })\n    \n    // mode: 'lowest' returns only paragraph\n    editor.api.nodes({ match: { type: ['blockquote', 'paragraph'] }, mode: 'lowest' })\n    ```\n  </APIItem>\n</APIOptions>\n</API>\n\n### `QueryOptions`\n\nCommon options for querying nodes in the editor.\n\n<API name=\"QueryOptions\">\n<APIOptions type=\"QueryOptions<V>\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    Where to start querying from. Defaults to current editor selection.\n  </APIItem>\n  <APIItem name=\"block\" type=\"boolean\" optional>\n    Match block nodes. When true, only matches block elements.\n  </APIItem>\n  <APIItem name=\"empty\" type=\"boolean\" optional>\n    Match empty/non-empty nodes.\n    - When true, matches only empty nodes\n    - When false, matches only non-empty nodes\n  </APIItem>\n  <APIItem name=\"id\" type=\"boolean | string\" optional>\n    Match the node by id.\n    - When true, matches all nodes with an id\n    - When string, matches nodes with that specific id\n  </APIItem>\n  <APIItem name=\"match\" type=\"Predicate<NodeIn<V>>\" optional>\n    Custom function or object to match nodes.\n    - Function: `(node, path) => boolean`\n    - Object: Key-value pairs that should match the node\n  </APIItem>\n  <APIItem name=\"text\" type=\"boolean\" optional>\n    Match text nodes. When true, matches only text nodes.\n  </APIItem>\n</APIOptions>\n</API>\n\n## `editor.api`\n\n### `above`\n\nGet the matching ancestor above a location in the document.\n\n<API name=\"above\">\n<APIOptions type=\"EditorAboveOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode options.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  A tuple containing the matching ancestor node and its path, or `undefined` if no match is found.\n</APIReturns>\n</API>\n\n### `block`\n\nGet the block at a location or find the first block that matches options.  \nBlocks are typically top-level nodes, so this is a common way to retrieve the ancestor block.\n\n```ts\neditor.api.block() // Get block above selection\neditor.api.block({ above: true }) // Get block above selection\neditor.api.block({ at: [0, 0] }) // Get block at [0, 0]\neditor.api.block({ at: [0, 0], above: true }) // Get block at [0]\neditor.api.block({ highest: true }) // Get highest block at selection\n```\n\n<API name=\"block\">\n<APIOptions type=\"EditorBlockOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching blocks.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    The location to query at. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to ignore non-selectable nodes during traversal.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order.\n  </APIItem>\n  <APIItem name=\"universal\" type=\"boolean\" optional>\n    Whether to ensure the operation works universally across all nodes.\n  </APIItem>\n  <APIItem name=\"above\" type=\"boolean\" optional>\n    If true, get the block above the location. Ignored if `at` is not a block path.\n  </APIItem>\n  <APIItem name=\"highest\" type=\"boolean\" optional>\n    If true, get the highest block at the location (root-level block).\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching blocks.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The matching block node entry or `undefined` if no match is found.\n</APIReturns>\n</API>\n\n### `blocks`\n\nReturns all matching blocks.\n\n<API name=\"blocks\">\n<APIOptions type=\"EditorNodesOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching blocks.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    The location to query at. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to ignore non-selectable nodes during traversal.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order.\n  </APIItem>\n  <APIItem name=\"universal\" type=\"boolean\" optional>\n    Whether to ensure the operation works universally across all nodes.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching blocks.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<ElementIn<V>>[]\">\n  An array of matching block node entries.\n</APIReturns>\n</API>\n\n### `edgeBlocks`\n\nReturns the edge blocks above a location (default: selection).  \nUseful for retrieving the start and end block of a range.\n\n<API name=\"edgeBlocks\">\n<APIOptions type=\"EditorNodesOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching blocks.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    The location to get edge blocks from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to ignore non-selectable nodes during traversal.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order.\n  </APIItem>\n  <APIItem name=\"universal\" type=\"boolean\" optional>\n    Whether to ensure the operation works universally across all nodes.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching blocks.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"[NodeEntry<N1>, NodeEntry<N2>] | null\">\n  A tuple of `[startBlock, endBlock]` above the location, or `null` if not found.\n</APIReturns>\n</API>\n\n### `first`\n\nGet the first node at a location.\n\n<API name=\"first\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\">\n    The location to get the first node from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<DescendantIn<V>> | undefined\">\n  A tuple containing the first node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `fragment`\n\nGet the fragment at a location or selection.\n\n<API name=\"fragment\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At | null\" optional>\n    The location to extract the fragment from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorFragmentOptions\" optional>\n    Options for extracting and processing the fragment.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"ElementOrTextIn<V>[] | undefined\">\n  The fragment at the location.\n</APIReturns>\n</API>\n\n### `getFragment`\n\nReturns the fragment at the current selection. Used when cutting or copying, as an example, to get the fragment at the current selection.\n\n<API name=\"getFragment\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get the fragment from. Defaults to current selection.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"ElementOrTextIn<V>[]\">\n  The fragment at the current selection.\n</APIReturns>\n</API>\n\n### `hasBlocks`\n\nCheck if a node has block children.\n\n<API name=\"hasBlocks\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element has block children, false otherwise.\n</APIReturns>\n</API>\n\n### `hasInlines`\n\nCheck if a node has inline and text children.\n\n<API name=\"hasInlines\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element has inline and text children, false otherwise.\n</APIReturns>\n</API>\n\n### `hasMark`\n\nCheck if mark is active at selection.\n\n<API name=\"hasMark\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"keyof MarksIn<V>\">\n    The mark key to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the mark is active at the current selection, false otherwise.\n</APIReturns>\n</API>\n\n### `hasPath`\n\nCheck if a path exists in the editor.\n\n<API name=\"hasPath\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the path exists, false otherwise.\n</APIReturns>\n</API>\n\n### `hasTexts`\n\nCheck if a node has text children.\n\n<API name=\"hasTexts\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element has text children, false otherwise.\n</APIReturns>\n</API>\n\n### `isAt`\n\nCheck if a location (point/range) is at a specific position.\n\n```ts\n// For ranges:\neditor.api.isAt({ text: true }) // Check if range is in a single text node\neditor.api.isAt({ block: true }) // Check if range is in a single block\neditor.api.isAt({ blocks: true }) // Check if range is across multiple blocks\neditor.api.isAt({ start: true }) // Check if range starts at block start\neditor.api.isAt({ end: true }) // Check if range ends at block end\n\n// For points:\neditor.api.isAt({ word: true }) // Check relative to word boundaries\neditor.api.isAt({ start: true }) // Check if at start\neditor.api.isAt({ end: true }) // Check if at end\n```\n\n<API name=\"isAt\">\n<APIOptions type=\"object\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to check. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"text\" type=\"boolean\" optional>\n    Check if range is in a single text node.\n  </APIItem>\n  <APIItem name=\"block\" type=\"boolean\" optional>\n    Check if range is in a single block.\n  </APIItem>\n  <APIItem name=\"blocks\" type=\"boolean\" optional>\n    Check if range is across multiple blocks.\n  </APIItem>\n  <APIItem name=\"start\" type=\"boolean\" optional>\n    Check if at start position.\n  </APIItem>\n  <APIItem name=\"end\" type=\"boolean\" optional>\n    Check if at end position.\n  </APIItem>\n  <APIItem name=\"word\" type=\"boolean\" optional>\n    Check relative to word boundaries.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  True if the location matches all specified position criteria, false otherwise.\n</APIReturns>\n</API>\n\n### `isCollapsed`\n\nCheck if the selection is collapsed (start and end points are the same).\n\n<API name=\"isCollapsed\">\n<APIReturns type=\"boolean\">\n  True if the selection is collapsed, false otherwise.\n</APIReturns>\n</API>\n\n### `isEdge`\n\nCheck if a point is an edge of a location.\n\n<API name=\"isEdge\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to check.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to check against. Defaults to current selection.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the point is an edge of the location, false otherwise.\n</APIReturns>\n</API>\n\n\n### `isEditorEnd`\n\nCheck if selection is at editor end.\n\n<API name=\"isEditorEnd\">\n<APIReturns type=\"boolean\">\n  True if the selection is at the editor end, false otherwise.\n</APIReturns>\n</API>\n\n### `isEmpty`\n\nCheck if an element is empty, accounting for void nodes.\n\n```ts\neditor.api.isEmpty() // Check if editor is empty\neditor.api.isEmpty(at) // Check if nodes at location are empty\neditor.api.isEmpty(at, { after: true }) // Check if text after location is empty\neditor.api.isEmpty(at, { block: true }) // Check if block above location is empty\n```\n\n<API name=\"isEmpty\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At | null\" optional>\n    The location to check for emptiness. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorEmptyOptions\" optional>\n    Options for determining emptiness.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorEmptyOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional />\n  <APIItem name=\"after\" type=\"boolean\" optional>\n    Check if text after selection is empty.\n  </APIItem>\n  <APIItem name=\"block\" type=\"boolean\" optional>\n    Check if the block above location is empty.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\" />\n</API>\n\n### `isEnd`\n\nCheck if a point is the end point of a location.\n\n<API name=\"isEnd\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to check.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to check against. Defaults to current selection.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the point is the end point of the location, false otherwise.\n</APIReturns>\n</API>\n\n### `isExpanded`\n\nCheck if the selection is expanded (start and end points are different).\n\n<API name=\"isExpanded\">\n<APIReturns type=\"boolean\">\n  True if the selection is expanded, false otherwise.\n</APIReturns>\n</API>\n\n### `isNormalizing`\n\nCheck if the editor is currently normalizing after each operation.\n\n<API name=\"isNormalizing\">\n<APIReturns type=\"boolean\">\n  True if the editor is currently normalizing, false otherwise.\n</APIReturns>\n</API>\n\n### `isStart`\n\nCheck if a point is the start point of a location.\n\n<API name=\"isStart\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to check.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to check against. Defaults to current selection.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the point is the start point of the location, false otherwise.\n</APIReturns>\n</API>\n\n### `isSelected`\n\nCheck if a path is selected by the current selection.\n\n<API name=\"isSelected\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"Path | TRange\">\n    The path or range to check.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorIsSelectedOptions\" optional>\n    Options for checking selection.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorIsSelectedOptions\">\n  <APIItem name=\"contains\" type=\"boolean\" optional>\n    Check if selection contains the entire path range.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  True if the path is selected, false otherwise.\n</APIReturns>\n</API>\n\n### `leaf`\n\nGet the leaf text node at a location.\n\n<API name=\"leaf\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\">\n    The location to get the leaf from.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorLeafOptions\" optional>\n    Options for getting the leaf.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorLeafOptions\">\n  <APIItem name=\"depth\" type=\"number\" optional>\n    The depth to traverse to find the leaf.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"LeafEdge\" optional>\n    Which edge of the location to get the leaf from (`'start' | 'end'`).\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<TextIn<V>> | undefined\">\n  A tuple containing the leaf text node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `levels`\n\nIterate through all levels at a location. This includes all ancestors up to the root editor node.\n\n<API name=\"levels\">\n<APIOptions type=\"EditorLevelsOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching levels.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order (bottom-up vs. top-down).\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the traversal.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<NodeIn<V>>, void, undefined>\">\n  A generator that yields tuples of [node, path] for each ancestor level.\n</APIReturns>\n</API>\n\n### `last`\n\nGet the last node at a location.\n\n<API name=\"last\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\">\n    The location to get the last node from.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorLastOptions\" optional>\n    Options for getting the last node.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorLastOptions\">\n  <APIItem name=\"level\" type=\"number\" optional>\n    Get last node at this level (0-based).\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<DescendantIn<V>> | undefined\">\n  A tuple containing the last node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `mark`\n\nReturns the selection mark value by key.\n\n<API name=\"mark\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"keyof MarksIn<V>\">\n    The mark key.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"MarksIn<V>[K] | null | undefined\">\n  The mark value if it exists, null if not set, or undefined if multiple different values exist.\n</APIReturns>\n</API>\n\n### `marks`\n\nGet the marks that would be added to text at the current selection.\n\n<API name=\"marks\">\n<APIReturns type=\"MarksIn<V> | null\">\n  The marks at the current selection, or null if there are no marks.\n</APIReturns>\n</API>\n\n### `next`\n\nGet the matching node in the branch of the document after a location.\n\n<API name=\"next\">\n<APIOptions type=\"EditorNextOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching nodes.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    The location to start searching from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching nodes.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n  <APIItem name=\"from\" type=\"'after' | 'child'\" optional>\n    - `'after'`: Start from point after current location\n    - `'child'`: Start from the first child of current path\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<DescendantIn<V>> | undefined\">\n  A tuple containing the next matching node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n\n### `node`\n\nGet the node at a location or find the first node that matches options.\n\n<API name=\"node\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get a node from.\n  </APIItem>\n  <APIItem name=\"nodeOptions\" type=\"EditorNodeOptions\" optional>\n    Options for getting a node.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorNodeOptions\">\n  <APIItem name=\"depth\" type=\"number\" optional>\n    The depth to traverse to find the node.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"'start' | 'end'\" optional>\n    Which edge of the location to get the node from.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<NodeIn<V>> | undefined\">\n  A tuple containing the matching node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `nodes`\n\nIterate through all nodes in the editor that match the given options.\n\n<API name=\"nodes\">\n<APIOptions type=\"EditorNodesOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching nodes.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    Where to start iterating. Defaults to editor selection.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to ignore non-selectable nodes during traversal.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order.\n  </APIItem>\n  <APIItem name=\"universal\" type=\"boolean\" optional>\n    Whether to ensure the operation works universally across all nodes.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    - `'all'`: Return all matching nodes\n    - `'highest'`: Return highest-level matching nodes\n    - `'lowest'`: Return lowest-level matching nodes\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<DescendantIn<V>>, void, undefined>\">\n  A generator that yields tuples of [node, path] for each matching node.\n</APIReturns>\n</API>\n\n### `parent`\n\nGet the parent node of a location.\n\n<API name=\"parent\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get the parent from.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorParentOptions\" optional>\n    Options for getting the parent node.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorParentOptions\">\n  <APIItem name=\"depth\" type=\"number\" optional>\n    Number of levels to traverse up to find the parent.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"'start' | 'end'\" optional>\n    Which edge of the location to get the parent from.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<AncestorIn<V>> | undefined\">\n  A tuple containing the parent node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `previous`\n\nGet the matching node in the branch of the document before a location.\n\n<API name=\"previous\">\n<APIOptions type=\"EditorPreviousOptions<V>\">\n  <APIItem name=\"...options\" type=\"QueryOptions<V>\" optional>\n    Common query options for matching nodes.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At | Span\" optional>\n    The location to start searching from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching nodes.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n  <APIItem name=\"sibling\" type=\"boolean\" optional>\n    Whether to get the previous sibling node instead of any previous node.\n  </APIItem>\n  <APIItem name=\"from\" type=\"'before' | 'parent'\" optional>\n    - `'before'`: Start from point before current location\n    - `'parent'`: Start from parent of current location\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<DescendantIn<V>> | undefined\">\n  A tuple containing the previous matching node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n### `prop`\n\nGet a property value from a list of nodes. Returns `undefined` if the property value is not consistent across all nodes.\n\n<API name=\"prop\">\n<APIOptions type=\"EditorPropOptions<V>\">\n  <APIItem name=\"nodes\" type=\"TElement[]\">\n    The list of nodes to get the property value from.\n  </APIItem>\n  <APIItem name=\"key\" type=\"string\" optional>\n    The property key to get from the nodes.\n  </APIItem>\n  <APIItem name=\"defaultValue\" type=\"string\" optional>\n    Default value to return if property is not found.\n  </APIItem>\n  <APIItem name=\"getProp\" type=\"(node: DescendantIn<V>) => any\" optional>\n    Custom function to extract property value from a node.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"'all' | 'block' | 'text'\" optional>\n    - `'all'`: Get property from all nodes\n    - `'block'`: Get property from the first block node\n    - `'text'`: Get property from the first text node\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"string | undefined\">\n  The consistent property value across all nodes, or `undefined` if values differ.\n</APIReturns>\n</API>\n\n### `string`\n\nGet the text string content of a location.\n\n<API name=\"string\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get text content from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorStringOptions\" optional>\n    Options for getting text content.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorStringOptions\">\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include text content from void nodes.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"string\">\n  The text content at the specified location.\n</APIReturns>\n</API>\n\n### `void`\n\nMatch a void node in the current branch of the editor.\n\n<API name=\"void\">\n<APIOptions type=\"EditorVoidOptions\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to search from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for matching nodes.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"NodeEntry<ElementIn<V>> | undefined\">\n  A tuple containing the void node and its path, or undefined if not found.\n</APIReturns>\n</API>\n\n## Location\n\n### `findPath`\n\nFind the path of a Plate node in the editor.\n\n<API name=\"findPath\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to find the path for in the editor tree.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorFindPathOptions\" optional>\n    Options for finding the node's path.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorFindPathOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions<Value>\" optional>\n    Common query options for finding nodes.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to ignore non-selectable nodes during traversal.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Whether to traverse in reverse order.\n  </APIItem>\n  <APIItem name=\"universal\" type=\"boolean\" optional>\n    Whether to ensure the operation works universally across all nodes.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"QueryMode\" optional>\n    Query mode for finding nodes.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include void nodes in the search.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Path | undefined\">\n  The path of the node if found, undefined otherwise.\n</APIReturns>\n</API>\n\n### `path`\n\nGet the path of a location.\n\n<API name=\"path\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get the path from. Defaults to current selection.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The path of the location.\n</APIReturns>\n</API>\n\n### `point`\n\nGet the `start` or `end` (default is `start`) point of a location.\n\n<API name=\"point\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get the point from. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorPointOptions\" optional>\n    Options for getting the point.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorPointOptions\">\n  <APIItem name=\"edge\" type=\"'start' | 'end'\" optional>\n    Which edge of the location to get the point from.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Point\">\n  The point at the specified location and edge.\n</APIReturns>\n</API>\n\n### `positions`\n\nIterate through all possible point positions in the document.\n\n<API name=\"positions\">\n<APIOptions type=\"EditorPositionsOptions\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    Where to start iterating. Defaults to editor selection.\n  </APIItem>\n  <APIItem name=\"unit\" type=\"TextUnitAdjustment\" optional>\n    - `'offset'`: Moves to the next offset Point\n    - `'character'`: Moves to the next character\n    - `'word'`: Moves to the position after the next word\n    - `'line'` | 'block': Moves between block boundaries\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    When true returns positions in reverse order.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to include positions inside void nodes.\n  </APIItem>\n  <APIItem name=\"ignoreNonSelectable\" type=\"boolean\" optional>\n    Whether to skip positions in non-selectable nodes.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<Point, void, undefined>\">\n  A generator that yields each valid point position in the document.\n</APIReturns>\n</API>\n\n### `nodesRange`\n\nReturns the range spanning the given node entries.\n\n<API name=\"nodesRange\">\n<APIParameters>\n  <APIItem name=\"nodes\" type=\"NodeEntry[]\">\n    The node entries to get the range for.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TRange | undefined\">\n  The range spanning the nodes, or undefined if no valid range can be created.\n</APIReturns>\n</API>\n\n### `range`\n\nCreate a range between two locations.\n\n<API name=\"range\">\n<APIOptions type=\"EditorRangeOptions\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to create the range at. Defaults to current selection.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"Point\" optional>\n    The focus (end) point of the range.\n  </APIItem>\n  <APIItem name=\"anchor\" type=\"Point\" optional>\n    The anchor (start) point of the range.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"TRange\">\n  A new range between the specified points.\n</APIReturns>\n</API>\n\n### `start`\n\nGet the start point of a location.\n\n<API name=\"start\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\" optional>\n    The location to get the start point from.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorStartOptions\" optional>\n    Options for getting the start point.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorStartOptions\">\n  <APIItem name=\"next\" type=\"boolean\" optional>\n    Get the start point of the next node instead of the current one.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Point\">\n  The start point of the location.\n</APIReturns>\n</API>\n\n### `unhangRange`\n\nConvert a range into a non-hanging one.\n\nA \"hanging\" range is one created by the browser's \"triple-click\" selection behavior. When triple-clicking a block, the browser selects from the start of that block to the start of the _next_ block. The range thus \"hangs over\" into the next block. If `unhangRange` is given such a range, it moves the end backwards until it's in a non-empty text node that precedes the hanging block.\n\nNote that `unhangRange` is designed for the specific purpose of fixing triple-clicked blocks, and therefore currently has a number of caveats:\n\n- It does not modify the start of the range; only the end. For example, it does not \"unhang\" a selection that starts at the end of a previous block.\n- It only does anything if the start block is fully selected. For example, it does not handle ranges created by double-clicking the end of a paragraph (which browsers treat by selecting from the end of that paragraph to the start of the next).\n\n<API name=\"unhangRange\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to unhang.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorUnhangRangeOptions\" optional>\n    Options for un-hanging the range.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorUnhangRangeOptions\">\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Allow placing the end of the selection in a void node.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"TRange\">\n  A new range with the end point moved backwards if it was hanging.\n</APIReturns>\n</API>\n\n## Element\n\n### `elementReadOnly`\n\nCheck if an element is read-only.\n\n<API name=\"elementReadOnly\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check for read-only status.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element is read-only, false otherwise.\n</APIReturns>\n</API>\n\n### `isBlock`\n\nCheck if a value is a block `Element` object.\n\n<API name=\"isBlock\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the value is a block element, false otherwise.\n</APIReturns>\n</API>\n\n### `isInline`\n\nCheck if a value is an inline `Element` object.\n\n<API name=\"isInline\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"DescendantIn<V>\">\n    The element to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element is inline, false otherwise.\n</APIReturns>\n</API>\n\n### `isSelectable`\n\nCheck if a value is a selectable `Element` object.\n\n<API name=\"isSelectable\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element is selectable, false otherwise.\n</APIReturns>\n</API>\n\n### `isVoid`\n\nCheck if an element is void.\n\n<API name=\"isVoid\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check for void status.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element is void, false otherwise.\n</APIReturns>\n</API>\n\n### `markableVoid`\n\nCheck if an element is a markable void element.\n\n<API name=\"markableVoid\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"ElementIn<V>\">\n    The element to check for markable void status.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the element is a markable void element, false otherwise.\n</APIReturns>\n</API>\n\n## Ref\n\n### `pathRef`\n\nCreate a mutable ref for a `Path`.\n\n<API name=\"pathRef\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to reference.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorPathRefOptions\" optional>\n    Options for the path reference.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorPathRefOptions\">\n  <APIItem name=\"affinity\" type=\"TextDirection | null\" optional>\n    The direction to resolve the ref when ambiguous:\n    - `'forward'`: Resolve to the next valid position\n    - `'backward'`: Resolve to the previous valid position\n    - `null`: Do not resolve to any position\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"PathRef\">\n  A mutable reference that updates its path as operations are applied to the editor.\n</APIReturns>\n</API>\n\n### `pathRefs`\n\nGet the set of currently tracked path refs of the editor.\n\n<API name=\"pathRefs\">\n<APIReturns type=\"Set<PathRef>\">\n  The set of tracked path refs.\n</APIReturns>\n</API>\n\n### `pointRef`\n\nCreate a mutable ref for a `Point`.\n\n<API name=\"pointRef\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to reference.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorPointRefOptions\" optional>\n    Options for the point reference.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorPointRefOptions\">\n  <APIItem name=\"affinity\" type=\"TextDirection | null\" optional>\n    The direction to resolve the ref when ambiguous:\n    - `'forward'`: Resolve to the next valid position\n    - `'backward'`: Resolve to the previous valid position\n    - `null`: Do not resolve to any position\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"PointRef\">\n  A mutable reference that updates its point as operations are applied to the editor.\n</APIReturns>\n</API>\n\n### `pointRefs`\n\nGet the set of currently tracked point refs of the editor.\n\n<API name=\"pointRefs\">\n<APIReturns type=\"Set<PointRef>\">\n  The set of tracked point refs.\n</APIReturns>\n</API>\n\n### `rangeRef`\n\nCreate a mutable ref for a `Range`.\n\n<API name=\"rangeRef\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to reference.\n  </APIItem>\n  <APIItem name=\"options\" type=\"EditorRangeRefOptions\" optional>\n    Options for the range reference.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"EditorRangeRefOptions\">\n  <APIItem name=\"affinity\" type=\"RangeDirection | null\" optional>\n    The direction to resolve the ref when ambiguous:\n    - `'forward'`: Resolve both points forward\n    - `'backward'`: Resolve both points backward\n    - `'outward'`: Resolve start backward and end forward\n    - `'inward'`: Resolve start forward and end backward\n    - `null`: Do not resolve to any position\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"RangeRef\">\n  A mutable reference that updates its range as operations are applied to the editor.\n</APIReturns>\n</API>\n\n### `rangeRefs`\n\nGet the set of currently tracked range refs of the editor.\n\n<API name=\"rangeRefs\">\n<APIReturns type=\"Set<RangeRef>\">\n  The set of tracked range refs.\n</APIReturns>\n</API>\n\n## DOM\n\n### `findDocumentOrShadowRoot`\n\nFind the document or shadow root from the editor.\n\n<API name=\"findDocumentOrShadowRoot\">\n<APIReturns type=\"Document | ShadowRoot\">\n  The document or shadow root containing the editor.\n</APIReturns>\n</API>\n\n### `findEventRange`\n\nGet the target range from a DOM event.\n\n<API name=\"findEventRange\">\n<APIParameters>\n  <APIItem name=\"event\" type=\"Event\">\n    The DOM event to get the range from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TRange | null\">\n  The range at the event target, or null if no valid range found.\n</APIReturns>\n</API>\n\n### `findKey`\n\nFind a key for a Plate node. Returns an instance of `Key` which looks like `{ id: string }`.\n\n<API name=\"findKey\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to find the key for.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Key\">\n  The key associated with the node.\n</APIReturns>\n</API>\n\n### `getWindow`\n\nGet the window object from the editor.\n\n<API name=\"getWindow\">\n<APIReturns type=\"Window\">\n  The window object associated with the editor.\n</APIReturns>\n</API>\n\n### `hasDOMNode`\n\nCheck if a DOM node is within the editor.\n\n<API name=\"hasDOMNode\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"Node\">\n    The DOM node to check.\n  </APIItem>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Options for checking the DOM node.\n  </APIItem>\n</APIParameters>\n<APIOptions type=\"object\">\n  <APIItem name=\"editable\" type=\"boolean\" optional>\n    Whether to check if the node is in an editable element.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  True if the DOM node is within the editor, false otherwise.\n</APIReturns>\n</API>\n\n### `hasEditableTarget`\n\nCheck if a DOM target is editable.\n\n<API name=\"hasEditableTarget\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"EventTarget | null\">\n    The DOM target to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"target is Node\">\n  True if the target is editable, false otherwise.\n</APIReturns>\n</API>\n\n### `hasRange`\n\nCheck if the editor has a range.\n\n<API name=\"hasRange\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the editor has the specified range, false otherwise.\n</APIReturns>\n</API>\n\n### `hasSelectableTarget`\n\nCheck if a DOM target is selectable.\n\n<API name=\"hasSelectableTarget\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"EventTarget | null\">\n    The DOM target to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"target is Node\">\n  True if the target is selectable, false otherwise.\n</APIReturns>\n</API>\n\n### `hasTarget`\n\nCheck if a DOM target exists.\n\n<API name=\"hasTarget\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"EventTarget | null\">\n    The DOM target to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"target is Node\">\n  True if the target exists, false otherwise.\n</APIReturns>\n</API>\n\n### `isComposing`\n\nCheck if the user is currently composing inside the editor.\n\n<API name=\"isComposing\">\n<APIReturns type=\"boolean\">\n  True if the user is currently composing text, false otherwise.\n</APIReturns>\n</API>\n\n### `isFocused`\n\nCheck if the editor is focused.\n\n<API name=\"isFocused\">\n<APIReturns type=\"boolean\">\n  True if the editor has focus, false otherwise.\n</APIReturns>\n</API>\n\n### `isReadOnly`\n\nCheck if the editor is in read-only mode.\n\n<API name=\"isReadOnly\">\n<APIReturns type=\"boolean\">\n  True if the editor is read-only, false otherwise.\n</APIReturns>\n</API>\n\n### `toDOMNode`\n\nFind the native DOM element from a Plate node.\n\n<API name=\"toDOMNode\">\n<APIOptions type=\"TNode\">\n  <APIItem name=\"node\" type=\"TNode\">\n    The Plate node to convert to a DOM element.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"HTMLElement\">\n  The corresponding DOM element for the Plate node.\n</APIReturns>\n</API>\n\n### `toDOMPoint`\n\nFind a native DOM selection point from a Plate point.\n\n<API name=\"toDOMPoint\">\n<APIOptions type=\"Point\">\n  <APIItem name=\"point\" type=\"Point\">\n    The Plate point to convert to a DOM point.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"DOMPoint\">\n  A tuple of [node, offset] representing the DOM point.\n</APIReturns>\n</API>\n\n### `toDOMRange`\n\nFind a native DOM range from a Plate range.\n\n<API name=\"toDOMRange\">\n<APIOptions type=\"TRange\">\n  <APIItem name=\"range\" type=\"TRange\">\n    The Plate range to convert to a DOM range.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"DOMRange\">\n  The corresponding DOM range for the Plate range.\n</APIReturns>\n</API>\n\n### `toSlateNode`\n\nFind a Plate node from a native DOM element.\n\n<API name=\"toSlateNode\">\n<APIOptions type=\"DOMNode\">\n  <APIItem name=\"domNode\" type=\"DOMNode\">\n    The DOM node to convert to a Plate node.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"TNode | undefined\">\n  The corresponding Plate node if found, undefined otherwise.\n</APIReturns>\n</API>\n\n### `toSlatePoint`\n\nFind a Plate point from a DOM selection point.\n\n<API name=\"toSlatePoint\">\n<APIOptions type=\"DOMPoint\">\n  <APIItem name=\"domPoint\" type=\"DOMPoint\">\n    The DOM point to convert to a Plate point.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Point | undefined\">\n  The corresponding Plate point if found, undefined otherwise.\n</APIReturns>\n</API>\n\n### `toSlateRange`\n\nFind a Plate range from a DOM range.\n\n<API name=\"toSlateRange\">\n<APIOptions type=\"DOMRange\">\n  <APIItem name=\"domRange\" type=\"DOMRange\">\n    The DOM range to convert to a Plate range.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"TRange | undefined\">\n  The corresponding Plate range if found, undefined otherwise.\n</APIReturns>\n</API>\n\n## Callback\n\n### `onChange`\n\nCalled when there is a change in the editor.\n\n<API name=\"onChange\">\n<APIOptions type=\"object\">\n  <APIItem name=\"operation\" type=\"Operation\" optional>\n    The operation that triggered the change.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Core\n\n### `getDirtyPaths`\n\nGet the paths that need to be normalized after an operation.\n\n<API name=\"getDirtyPaths\">\n<APIParameters>\n  <APIItem name=\"operation\" type=\"Operation<N extends DescendantIn<V>>\">\n    The operation that triggered normalization.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path[]\">\n  An array of paths that need to be normalized after the operation.\n</APIReturns>\n</API>\n\n### `shouldMergeNodes`\n\nDecide whether two adjacent node entries should merge. Plate calls this before\n`editor.tf.mergeNodes()` applies a merge operation.\n\n<API name=\"shouldMergeNodes\">\n<APIParameters>\n  <APIItem name=\"prevNodeEntry\" type=\"NodeEntry\">\n    Previous node entry at the merge boundary.\n  </APIItem>\n  <APIItem name=\"nextNodeEntry\" type=\"NodeEntry\">\n    Next node entry at the merge boundary.\n  </APIItem>\n  <APIItem name=\"options\" type=\"{ reverse?: boolean }\" optional>\n    Merge direction metadata.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True when the nodes should merge, false when the merge should stop.\n</APIReturns>\n</API>\n\n### `shouldNormalizeNode`\n\nOverride this method to prevent normalizing a specific node. Defaults to returning `true`.\n\n<API name=\"shouldNormalizeNode\">\n<APIParameters>\n  <APIItem name=\"entry\" type=\"NodeEntry\">\n    The node entry (node and path) to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if the node should be normalized, false otherwise.\n</APIReturns>\n</API>\n\n### `setNormalizing`\n\nManually control the editor's normalizing state.\n\n<API name=\"setNormalizing\">\n<APIOptions type=\"boolean\">\n  <APIItem name=\"isNormalizing\" type=\"boolean\">\n    Whether the editor should normalize after each operation.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `shouldNormalize`\n\nControls whether the editor should normalize after an operation. Override this method to prevent normalizing in certain situations.\n\n<API name=\"shouldNormalize\">\n<APIOptions type=\"object\">\n  <APIItem name=\"dirtyPaths\" type=\"Path[]\">\n    The paths that need to be normalized.\n  </APIItem>\n  <APIItem name=\"initialDirtyPathsLength\" type=\"number\">\n    The initial number of dirty paths before normalization started.\n  </APIItem>\n  <APIItem name=\"iteration\" type=\"number\">\n    The current normalization iteration count.\n  </APIItem>\n  <APIItem name=\"operation\" type=\"Operation\" optional>\n    The operation that triggered the normalization.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  True if the editor should normalize, false otherwise.\n</APIReturns>\n</API>\n\n## History\n\n### `isMerging`\n\nGet the merge flag's current value.\n\n<API name=\"isMerging\">\n<APIReturns type=\"boolean\">\n  True if the editor is currently merging operations, false otherwise.\n</APIReturns>\n</API>\n\n### `isSaving`\n\nGet the saving flag's current value.\n\n<API name=\"isSaving\">\n<APIReturns type=\"boolean\">\n  True if the editor is currently saving, false otherwise.\n</APIReturns>\n</API>\n\n### `isSplittingOnce`\n\nGet the splitting flag's current value.\n\n<API name=\"isSplittingOnce\">\n<APIReturns type=\"boolean\">\n  True if the editor is currently performing a single split operation, false otherwise.\n</APIReturns>\n</API>\n\n## Utils\n\n### `create.block`\n\nDefault block factory for creating new block elements.\n\n<API name=\"create.block\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"Partial<TElement>\" optional>\n    Partial element properties to merge into the new block.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\" optional>\n    Path for the new block.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TElement\">\n  A new block element.\n</APIReturns>\n</API>\n\n### `create.value`\n\nDefault value factory for creating new editor values.\n\n<API name=\"create.value\">\n<APIReturns type=\"Value\">\n  A new editor value.\n</APIReturns>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/editor-api.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-editor-transforms-docs",
      "title": "Editor Transforms",
      "description": "API reference for editor transformation operations in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/editor-transforms.mdx",
          "content": "---\ntitle: Editor Transforms\ndescription: API reference for editor transformation operations in Plate.\n---\n\nTransforms are helper functions that manipulate a Plate document.\n\n## Node Operations\n\n### `duplicateNodes`\n\nDuplicates nodes at a location and inserts them after that location.\n\n<API name=\"duplicateNodes\">\n<APIOptions type=\"DuplicateNodesOptions\">\n  <APIItem name=\"...options\" type=\"InsertNodesOptions\" optional>\n    `insertNodes` options.\n  </APIItem>\n  <APIItem name=\"at\" type=\"At\" optional>\n    Location to duplicate from and insert after. Defaults to selection.\n  </APIItem>\n  <APIItem name=\"block\" type=\"boolean\" optional>\n    If true, duplicates blocks above location. Ignored if `nodes` provided.\n  </APIItem>\n  <APIItem name=\"nodes\" type=\"NodeEntry[]\" optional>\n    Specific nodes to duplicate. Takes precedence over `block`.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertFragment`\n\nInsert a fragment of nodes at a location.\n\n<API name=\"insertFragment\">\n<APIParameters>\n  <APIItem name=\"fragment\" type=\"N[]\">\n    Fragment of nodes to insert.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertFragmentOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"InsertFragmentOptions\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    Location to insert at. Defaults to selection.\n  </APIItem>\n  <APIItem name=\"hanging\" type=\"boolean\" optional>\n    Whether range is hanging.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Allow insertion in void nodes.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertNode`\n\nInsert a single node atomically.\n\n<API name=\"insertNode\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"N\">\n    Node to insert.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertNodesOptions\" optional />\n</APIParameters>\n</API>\n\n### `insertNodes`\n\nInsert one or more nodes atomically.\n\n<API name=\"insertNodes\">\n<APIParameters>\n  <APIItem name=\"nodes\" type=\"N | N[]\">\n    Node(s) to insert.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertNodesOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"InsertNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional>\n    Common query options.\n  </APIItem>\n  <APIItem name=\"batchDirty\" type=\"boolean\" optional />\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"nextBlock\" type=\"boolean\" optional>\n    Insert after the current block if `removeEmpty` caused it to be removed.\n  </APIItem>\n  <APIItem name=\"removeEmpty\" type=\"QueryNodeOptions | boolean\" optional>\n    Remove the current block if empty. Defaults to removing an empty paragraph, but can be customized.\n  </APIItem>\n  <APIItem name=\"select\" type=\"boolean\" optional>\n    Select inserted nodes.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Allow insertion in void nodes.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `liftNodes`\n\nLift nodes at the specified location upwards in the document tree. If necessary, the parent node is split.\n\n<API name=\"liftNodes\">\n<APIOptions type=\"LiftNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `mergeNodes`\n\nMerge a node at the specified location with the previous node at the same depth. Resulting empty container nodes are removed.\n\n<API name=\"mergeNodes\">\n<APIOptions type=\"MergeNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `moveNodes`\n\nMove the nodes from an origin to a destination.\n\n<API name=\"moveNodes\">\n<APIOptions type=\"MoveNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"to\" type=\"Path\">\n    Destination path.\n  </APIItem>\n  <APIItem name=\"children\" type=\"boolean\" optional>\n    Move only children of the node at the location.\n  </APIItem>\n  <APIItem name=\"fromIndex\" type=\"number\" optional>\n    Start index of the children to move. Default is 0.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `removeNodes`\n\nRemove nodes at a location.\n\n<API name=\"removeNodes\">\n<APIOptions type=\"RemoveNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"children\" type=\"boolean\" optional>\n    When true, remove all children of the node at the specified location.\n  </APIItem>\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"previousEmptyBlock\" type=\"boolean\" optional>\n    Remove the previous empty block if it exists.\n  </APIItem>\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `replaceNodes`\n\nReplace nodes at a location with new nodes.\n\n<API name=\"replaceNodes\">\n<APIParameters>\n  <APIItem name=\"nodes\" type=\"N | N[]\">\n    The new node(s) to insert.\n  </APIItem>\n  <APIItem name=\"options\" type=\"ReplaceNodesOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"ReplaceNodesOptions\">\n  <APIItem name=\"...options\" type=\"InsertNodesOptions\" optional>\n    `insertNodes` options.\n  </APIItem>\n  <APIItem name=\"children\" type=\"boolean\" optional>\n    Replace all children of the node at the specified location instead of the node itself.\n  </APIItem>\n  <APIItem name=\"removeNodes\" type=\"Omit<RemoveNodesOptions, 'at'>\" optional>\n    Options for removing nodes before the replacement.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `reset`\n\nReset the editor state including history, selection and children.\n\n<API name=\"reset\">\n<APIOptions type=\"ResetOptions\">\n  <APIItem name=\"...options\" type=\"ReplaceNodesOptions\" optional>\n    `replaceNodes` options.\n  </APIItem>\n  <APIItem name=\"children\" type=\"boolean\" optional>\n    When true, only reset the children without clearing history/operations.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `setNodes`\n\nSet properties on nodes.\n\n<API name=\"setNodes\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"Partial<NodeProps<N>>\">\n    Properties to set. Use `undefined` to unset.\n  </APIItem>\n  <APIItem name=\"options\" type=\"SetNodesOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"SetNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"compare\" type=\"(prop: Partial<Descendant>, node: Partial<Descendant>) => boolean\" optional />\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"marks\" type=\"boolean\" optional>\n    When true, only apply to text nodes in non-void or markable void nodes.\n  </APIItem>\n  <APIItem name=\"merge\" type=\"(prop: Partial<Descendant>, node: Partial<Descendant>) => object\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"split\" type=\"boolean\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `splitNodes`\n\nSplit nodes at a location.\n\n<API name=\"splitNodes\">\n<APIOptions type=\"SplitNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"always\" type=\"boolean\" optional />\n  <APIItem name=\"height\" type=\"number\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `toggleBlock`\n\nToggle the block type at a location.\n\n<API name=\"toggleBlock\">\n<APIParameters>\n  <APIItem name=\"type\" type=\"string\">\n    The block type to toggle.\n  </APIItem>\n  <APIItem name=\"options\" type=\"ToggleBlockOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"ToggleBlockOptions\">\n  <APIItem name=\"...options\" type=\"SetNodesOptions\" optional>\n    Options to pass to `setNodes`.\n  </APIItem>\n  <APIItem name=\"defaultType\" type=\"string\" optional>\n    The default block type when untoggling. Defaults to paragraph.\n  </APIItem>\n  <APIItem name=\"someOptions\" type=\"EditorNodesOptions\" optional>\n    Options for determining if the block is active.\n  </APIItem>\n  <APIItem name=\"wrap\" type=\"boolean\" optional>\n    If true, toggles wrapping with `type`. Otherwise, sets the block type directly.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `unsetNodes`\n\nRemove properties from nodes.\n\n<API name=\"unsetNodes\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"string | string[]\">\n    Property key(s) to remove.\n  </APIItem>\n  <APIItem name=\"options\" type=\"UnsetNodesOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"UnsetNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"split\" type=\"boolean\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `unwrapNodes`\n\nUnwrap a node at a location. If necessary, the parent node is split.\n\n<API name=\"unwrapNodes\">\n<APIOptions type=\"UnwrapNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"split\" type=\"boolean\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `wrapNodes`\n\nWrap nodes at a location in the `element` container.\n\n<API name=\"wrapNodes\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"N\">\n    The wrapper element.\n  </APIItem>\n  <APIItem name=\"options\" type=\"WrapNodesOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"WrapNodesOptions\">\n  <APIItem name=\"...options\" type=\"QueryOptions\" optional />\n  <APIItem name=\"children\" type=\"boolean\" optional>\n    When true, wrap all children into a single container element.\n  </APIItem>\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"mode\" type=\"'highest' | 'lowest'\" optional />\n  <APIItem name=\"split\" type=\"boolean\" optional />\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n## Text Operations\n\n### `delete`\n\nDelete text at a location.\n\n<API name=\"delete\">\n<APIOptions type=\"DeleteTextOptions\">\n  <APIItem name=\"at\" type=\"At\" optional />\n  <APIItem name=\"distance\" type=\"number\" optional>\n    Number of characters (or other unit) to delete. Default is 1.\n  </APIItem>\n  <APIItem name=\"hanging\" type=\"boolean\" optional />\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, delete backward.\n  </APIItem>\n  <APIItem name=\"unit\" type=\"'character' | 'word' | 'line' | 'block'\" optional>\n    Unit to delete by.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional />\n</APIOptions>\n</API>\n\n### `deleteBackward`\n\nDelete text backward.\n\n<API name=\"deleteBackward\">\n<APIParameters>\n  <APIItem name=\"unit\" type=\"'character' | 'word' | 'line' | 'block'\" optional>\n    Defaults to `'character'`.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `deleteForward`\n\nDelete text forward.\n\n<API name=\"deleteForward\">\n<APIParameters>\n  <APIItem name=\"unit\" type=\"'character' | 'word' | 'line' | 'block'\" optional>\n    Defaults to `'character'`.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `deleteFragment`\n\nDelete a fragment of nodes.\n\n<API name=\"deleteFragment\">\n<APIOptions type=\"EditorFragmentDeletionOptions\">\n  <APIItem name=\"direction\" type=\"'forward' | 'backward'\" optional>\n    Direction to delete.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertText`\n\nInsert text at a location, optionally with marks. The behavior depends on the provided options:\n\n1. If `at` is specified in options, inserts at that location regardless of selection\n2. Otherwise, if there's a selection:\n   - If `marks` is true (default) and editor has marks, inserts text with those marks\n   - If no marks, inserts plain text\n3. If neither `at` nor selection exists, no text is inserted\n\n<API name=\"insertText\">\n<APIParameters>\n  <APIItem name=\"text\" type=\"string\">\n    Text to insert.\n  </APIItem>\n  <APIItem name=\"options\" type=\"InsertTextOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"InsertTextOptions\">\n  <APIItem name=\"at\" type=\"TLocation\" optional>\n    Location to insert text at. Takes precedence over current selection.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to allow insertion in void nodes.\n  </APIItem>\n  <APIItem name=\"marks\" type=\"boolean\" optional>\n    - **Default:** `true`\n    When true and editor has marks, the inserted text will include those marks.\n    When false, inserts plain text without marks.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertBreak`\n\nInsert a block break at the current selection.\n\n### `insertSoftBreak`\n\nInsert a soft break at the current selection. A soft break is a new line in the current block.\n\n### `deselect`\n\nUnset the selection.\n\n### `move`\n\nMove the selection's point forward or backward.\n\n<API name=\"move\">\n<APIOptions type=\"object\">\n  <APIItem name=\"distance\" type=\"number\" optional>\n    How many units to move. Defaults to 1.\n  </APIItem>\n  <APIItem name=\"unit\" type=\"'offset' | 'character' | 'word' | 'line'\" optional>\n    Defaults to `'character'`.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    Move backward if true.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"'anchor' | 'focus' | 'start' | 'end'\" optional>\n    Which edge to move.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Mark Operations\n\n### `addMark`\n\nAdd a custom property to the leaf text nodes within non-void nodes or void nodes that `editor.markableVoid()` allows in the current selection. If the selection is currently collapsed, the marks will be added to the `editor.marks` property instead, and applied when text is inserted next.\n\n<API name=\"addMark\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"string\">\n    Mark key to add.\n  </APIItem>\n  <APIItem name=\"value\" type=\"any\">\n    Value for the mark.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `addMarks`\n\nAdd multiple marks to the current selection.\n\n```ts\neditor.tf.addMarks({ bold: true, italic: true })\neditor.tf.addMarks({ bold: subscript }, { remove: 'superscript' })\neditor.tf.addMarks({ bold: true }, { remove: ['italic', 'underline'] })\n```\n\n<API name=\"addMarks\">\n<APIParameters>\n  <APIItem name=\"marks\" type=\"Record<string, any>\">\n    Key-value pairs of mark props.\n  </APIItem>\n  <APIItem name=\"options\" type=\"AddMarksOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"AddMarksOptions\">\n  <APIItem name=\"remove\" type=\"string[] | string\" optional>\n    Mark keys to remove first. For mutually exclusive marks, e.g. subscript/superscript.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `removeMark`\n\nRemove a mark from text in the selection.\n\n<API name=\"removeMark\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"string\">\n    Mark key to remove.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `removeMarks`\n\nRemove marks from text nodes in the current selection or from `editor.marks`. The behavior depends on the selection state and options:\n\n1. If selection is expanded or is in a markable void node:\n   - Remove specified mark keys from text nodes\n2. If selection is collapsed and no custom range provided:\n   - Remove specified keys from `editor.marks`\n   - If no keys specified, clear all marks from `editor.marks`\n3. If custom range provided (`at` option):\n   - Only remove marks from text nodes in that range\n\n```ts\neditor.tf.removeMarks()             // remove all marks\neditor.tf.removeMarks('bold')       // remove the 'bold' mark\neditor.tf.removeMarks(['bold','italic'])\neditor.tf.removeMarks('bold', { at: range })\n```\n\n<API name=\"removeMarks\">\n<APIParameters>\n  <APIItem name=\"keys\" type=\"string | string[]\" optional>\n    Mark key(s) to remove. If not provided and selection is collapsed, clears all marks from `editor.marks`.\n  </APIItem>\n  <APIItem name=\"options\" type=\"RemoveMarksOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"RemoveMarksOptions\">\n  <APIItem name=\"...options\" type=\"UnsetNodesOptions\" optional />\n  <APIItem name=\"at\" type=\"TRange\" optional>\n    Custom range to remove marks from. Takes precedence over current selection.\n  </APIItem>\n  <APIItem name=\"shouldChange\" type=\"boolean\" optional>\n    - **Default:** `true`\n    Whether to trigger onChange when modifying editor.marks.\n  </APIItem>\n  <APIItem name=\"split\" type=\"boolean\" optional>\n    Whether to split nodes when removing marks.\n  </APIItem>\n  <APIItem name=\"match\" type=\"(node: Node, path: Path) => boolean\" optional>\n    Custom function to filter which nodes to remove marks from.\n  </APIItem>\n  <APIItem name=\"voids\" type=\"boolean\" optional>\n    Whether to allow removing marks from void nodes.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `toggleMark`\n\nToggle a mark on or off in the current selection. If the mark exists, removes it. If it doesn't exist:\n1. Removes any specified marks in the `remove` option \n2. Adds the mark with value `true`\n\n```ts\neditor.tf.toggleMark('bold')                                // Toggle bold on/off\neditor.tf.toggleMark('subscript', { remove: 'superscript'}) // Remove superscript before adding subscript\n```\n\n<API name=\"toggleMark\">\n<APIParameters>\n  <APIItem name=\"key\" type=\"string\">\n    The mark key to toggle.\n  </APIItem>\n  <APIItem name=\"options\" type=\"ToggleMarkOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"ToggleMarkOptions\">\n  <APIItem name=\"remove\" type=\"string[] | string\" optional>\n    Mark key(s) to remove before adding the mark. Useful for mutually exclusive marks like subscript/superscript.\n    The specified mark key is always removed in addition to these marks.\n  </APIItem>\n</APIOptions>\n</API>\n\n## Selection\n\n### `collapse`\n\nCollapse the selection to a point.\n\n<API name=\"collapse\">\n<APIOptions type=\"object\">\n  <APIItem name=\"edge\" type=\"'anchor' | 'focus' | 'start' | 'end'\" optional>\n    Edge to collapse to. Defaults to `'anchor'`.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `deselect`\n\nUnset the current selection.\n\n### `move`\n\nMove the selection's point.\n\n<API name=\"move\">\n<APIOptions type=\"object\">\n  <APIItem name=\"distance\" type=\"number\" optional>\n    Defaults to 1.\n  </APIItem>\n  <APIItem name=\"unit\" type=\"'offset' | 'character' | 'word' | 'line'\" optional>\n    Defaults to `'character'`.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, move backward.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"'anchor' | 'focus' | 'start' | 'end'\" optional>\n    Which edge to move.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `select`\n\nSet the selection to a new value specified by `at`. When a selection already exists, this method just calls `setSelection`.\n\n```ts\neditor.tf.select(at)\neditor.tf.select(at, { edge: 'end' })\neditor.tf.select(at, { edge: 'start' })\n```\n\n<API name=\"select\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At\">\n    Location to select.\n  </APIItem>\n  <APIItem name=\"options\" type=\"SelectOptions\" optional />\n</APIParameters>\n\n<APIOptions type=\"SelectOptions\">\n  <APIItem name=\"edge\" type=\"'start' | 'end'\" optional>\n    Select the start or end edge above `at`.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"boolean\" optional>\n    Focus the editor before selecting.\n  </APIItem>\n  <APIItem name=\"next\" type=\"boolean\" optional>\n    Select the start of the next sibling.\n  </APIItem>\n  <APIItem name=\"previous\" type=\"boolean\" optional>\n    Select the end of the previous sibling.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `setPoint`\n\nSet new properties on one of the selection's points.\n\n<API name=\"setPoint\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"Partial<Point>\">\n    Point properties to update.\n  </APIItem>\n  <APIItem name=\"options\" type=\"object\" optional />\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"edge\" type=\"'anchor' | 'focus' | 'start' | 'end'\" optional>\n    Which edge of the selection to set.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `setSelection`\n\nSet new properties on an active selection. Since the value is a `Partial<Range>`, this method can only handle updates to an existing selection. If there is no active selection the operation will be void. Use `select` if you'd like to create a selection when there is none.\n\n<API name=\"setSelection\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"Partial<TRange>\">\n    A partial range to update existing selection properties.\n  </APIItem>\n</APIParameters>\n</API>\n\n## DOM Operations\n\n### `blur`\n\nBlur the editor.\n\n### `deselectDOM`\n\nDeselect the editor's DOM selection in addition to `deselect`.\n\n### `focus`\n\nFocus the editor.\n\n```ts\neditor.tf.focus()\neditor.tf.focus({ edge: 'end' })\neditor.tf.focus({ edge: 'endEditor' })\n```\n\n<API name=\"focus\">\n<APIOptions type=\"FocusOptions\">\n  <APIItem name=\"at\" type=\"At\" optional>\n    Select this location before focusing.\n  </APIItem>\n  <APIItem name=\"edge\" type=\"'start' | 'startEditor' | 'end' | 'endEditor'\" optional>\n    Focus at the edge of the location or the editor.\n  </APIItem>\n  <APIItem name=\"retries\" type=\"number\" optional>\n    Number of attempts to refocus.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `insertData`\n\nInsert data from a `DataTransfer` into the editor. Calls:\n\n1. `insertFragmentData(editor: ReactEditor, data: DataTransfer)`\n2. `insertTextData(editor: ReactEditor, data: DataTransfer)`\n\n<API name=\"insertData\">\n<APIParameters>\n  <APIItem name=\"data\" type=\"DataTransfer\">\n    Data to insert from clipboard or drag event.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `insertFragmentData`\n\nInsert fragment data from a `DataTransfer` into the editor.\n\n<API name=\"insertFragmentData\">\n<APIParameters>\n  <APIItem name=\"data\" type=\"DataTransfer\">\n    Data to parse as fragment.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\" />\n</API>\n\n### `insertTextData`\n\nInsert text data from a `DataTransfer` into the editor.\n\n<API name=\"insertTextData\">\n<APIParameters>\n  <APIItem name=\"data\" type=\"DataTransfer\">\n    Text data to insert.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\" />\n</API>\n\n### `setFragmentData`\n\nSets data from the currently selected fragment on a `DataTransfer`.\n\n<API name=\"setFragmentData\">\n<APIParameters>\n  <APIItem name=\"data\" type=\"DataTransfer\">\n    DataTransfer to store the fragment.\n  </APIItem>\n</APIParameters>\n</API>\n\n## History Operations\n\n### `redo`\n\nRedo to the next saved state.\n\n### `undo`\n\nUndo to the previous saved state.\n\n### `setSplittingOnce`\n\n<API name=\"setSplittingOnce\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"boolean\">\n    Whether the next operation should split into a new batch in history.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withMerging`\n\nApply a series of changes inside a synchronous `fn`, These operations will\nbe merged into the previous history.\n\n<API name=\"withMerging\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    Batched changes to merge into the previous history point.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withNewBatch`\n\nApply a series of changes inside a synchronous `fn`, ensuring that the first\noperation starts a new batch in the history. Subsequent operations will be\nmerged as usual.\n\n<API name=\"withNewBatch\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    Batched changes in a new history point.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withoutMerging`\n\nApply a series of changes inside a synchronous `fn`, without merging any of\nthe new operations into previous save point in the history.\n\n<API name=\"withoutMerging\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    Changes not merged into any existing history point.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `withoutSaving`\n\nApply a series of changes inside a synchronous `fn`, without saving any of\ntheir operations into the history.\n\n<API name=\"withoutSaving\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    Changes not saved into history at all.\n  </APIItem>\n</APIParameters>\n</API>\n\n## Core Operations\n\n### `apply`\n\nApply an operation in the editor.\n\n<API name=\"apply\">\n<APIParameters>\n  <APIItem name=\"operation\" type=\"Operation<N>\">\n    Operation to apply.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `normalizeNode`\n\nNormalize a node according to the editor's schema.\n\n<API name=\"normalizeNode\">\n<APIParameters>\n  <APIItem name=\"entry\" type=\"NodeEntry<N>\">\n    The node entry to normalize.\n  </APIItem>\n  <APIItem name=\"options\" type=\"{ operation?: Operation }\" optional />\n</APIParameters>\n\n<APIOptions type=\"{ operation?: Operation }\">\n  <APIItem name=\"operation\" type=\"Operation\" optional>\n    The triggering operation.\n  </APIItem>\n</APIOptions>\n</API>\n\n### `normalize`\n\nNormalize dirty nodes in the editor.\n\n<API name=\"normalize\">\n<APIOptions type=\"EditorNormalizeOptions\">\n  <APIItem name=\"force\" type=\"boolean\" optional>\n    When true, forcibly re-normalize all nodes.\n  </APIItem>\n  <APIItem name=\"operation\" type=\"Operation\" optional />\n</APIOptions>\n</API>\n\n### `withoutNormalizing`\n\nCall a function, deferring normalization until after it completes.\n\n<API name=\"withoutNormalizing\">\n<APIParameters>\n  <APIItem name=\"fn\" type=\"() => void\">\n    A synchronous function to execute without normalization in between operations.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  True if normalization was performed afterwards.\n</APIReturns>\n</API>\n\n## Keyboard Shortcuts\n\n### `moveLine`\n\nHandle `ArrowUp` and `ArrowDown` keyboard events.\n\n<API name=\"moveLine\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ reverse: boolean }\">\n    - `reverse: true` for `ArrowUp`\n    - `reverse: false` for `ArrowDown`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean | void\">\n  Return `true` to prevent default browser behavior, `false` to allow it.\n</APIReturns>\n</API>\n\n**Default behavior:** Returns `false` (allows Plate's default line movement).\n\n**Usage:**\n```ts\nconst plugin = createPlatePlugin({\n  key: 'myPlugin',\n}).overrideEditor(() => ({\n  transforms: {\n    moveLine: ({ reverse }) => {\n      // Custom line movement logic\n      if (reverse) {\n        // Handle ArrowUp\n      } else {\n        // Handle ArrowDown  \n      }\n      return true; // Prevent default\n    },\n  },\n}));\n```\n\n### `tab`\n\nHandle `Tab` and `Shift+Tab` keyboard events.\n\n<API name=\"tab\">\n<APIParameters>\n  <APIItem name=\"options\" type=\"{ reverse: boolean }\">\n    - `reverse: false` for `Tab`\n    - `reverse: true` for `Shift+Tab`\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean | void\">\n  Return `true` to prevent default browser behavior, `false` to allow it.\n</APIReturns>\n</API>\n\n**Default behavior:** Returns `false` (allows default browser tab navigation).\n\n**Usage:**\n```ts\nconst plugin = createPlatePlugin({\n  key: 'myPlugin',\n}).overrideEditor(() => ({\n  transforms: {\n    tab: ({ reverse }) => {\n      if (reverse) {\n        // Handle Shift+Tab (usually outdent)\n        editor.tf.outdent();\n      } else {\n        // Handle Tab (usually indent)\n        editor.tf.indent();\n      }\n      return true; // Prevent default\n    },\n  },\n}));\n```\n\n### `selectAll`\n\nHandle `Cmd+A` / `Ctrl+A` keyboard events.\n\n<API name=\"selectAll\">\n<APIReturns type=\"boolean | void\">\n  Return `true` to prevent default browser behavior, `false` to allow it.\n</APIReturns>\n</API>\n\n**Default behavior:** Returns `false` (allows default browser select all).\n\n**Usage:**\n```ts\nconst plugin = createPlatePlugin({\n  key: 'myPlugin',\n}).overrideEditor(() => ({\n  transforms: {\n    selectAll: () => {\n      // Custom select all logic\n      const blockEntry = editor.api.block();\n      if (blockEntry) {\n        editor.tf.select(blockEntry[1]);\n        return true; // Prevent default\n      }\n      return false; // Allow default\n    },\n  },\n}));\n```\n\n### `escape`\n\nHandle `Escape` keyboard events.\n\n<API name=\"escape\">\n<APIReturns type=\"boolean | void\">\n  Return `true` to prevent default browser behavior, `false` to allow it.\n</APIReturns>\n</API>\n\n**Default behavior:** Returns `false` (allows default browser escape handling).\n\n**Usage:**\n```ts\nconst plugin = createPlatePlugin({\n  key: 'myPlugin',\n}).overrideEditor(() => ({\n  transforms: {\n    escape: () => {\n      // Custom escape logic (e.g., exit special mode)\n      if (editor.api.inSpecialMode()) {\n        editor.tf.exitSpecialMode();\n        return true; // Prevent default\n      }\n      return false; // Allow default\n    },\n  },\n}));\n```\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/editor-transforms.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-element-docs",
      "title": "Element",
      "description": "API reference for elements in Slate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/element.mdx",
          "content": "---\ntitle: Element\ndescription: API reference for elements in Slate.\n---\n\n`TElement` objects are a type of `Node` in a Plate document that contain other `TElement` nodes or `Text` nodes.\n\n```typescript\ninterface TElement {\n  children: Descendant[]\n  type: string\n  [key: string]: unknown\n}\n```\n\n## Element Behavior\n\nElements can have different behaviors depending on the editor's configuration:\n\n### Block vs Inline\n\nElements can be either \"block\" or \"inline\" as defined by plugin `node.isInline`:\n\n- Block elements can only be siblings with other block elements\n- Inline elements can be siblings with Text nodes or other inline elements\n\n### Void vs Non-void\n\nElements can be either \"void\" or \"non-void\" as defined by plugin `node.isVoid`:\n\n- Non-void elements: Slate handles rendering of children (e.g., paragraph with Text and Inline children)\n- Void elements: Children are rendered by the Element's render code\n\n### Markable Voids\n\nSome void elements can support marks through plugin `node.markableVoid`. For example, a mention element might need to support bold or italic formatting.\n\n## `ElementAPI`\n\n### `isElementType`\n\nCheck if a value implements the `TElement` interface and has `elementKey` matching a specified value. Defaults to checking the `'type'` key.\n\n<API name=\"isElementType\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n  <APIItem name=\"elementVal\" type=\"string\">\n    The value to match against.\n  </APIItem>\n  <APIItem name=\"elementKey\" type=\"string\" optional>\n    The key to check. Defaults to `'type'`.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an element with the specified key matching `elementVal`.\n</APIReturns>\n</API>\n\n### `isAncestor`\n\nCheck if a value implements the `Ancestor` interface.\n\n<API name=\"isAncestor\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an ancestor node.\n</APIReturns>\n</API>\n\n### `isElement`\n\nCheck if a value implements the `TElement` interface.\n\n<API name=\"isElement\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a Plate element.\n</APIReturns>\n</API>\n\n### `isElementList`\n\nCheck if a value is an array of `TElement` objects.\n\n<API name=\"isElementList\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an array of elements.\n</APIReturns>\n</API>\n\n### `isElementProps`\n\nCheck if a set of props is a partial of `TElement`.\n\n<API name=\"isElementProps\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"any\">\n    The props to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the props match element properties.\n</APIReturns>\n</API>\n\n### `matches`\n\nCheck if an element matches a set of properties.\n\n<API name=\"matches\">\n<APIParameters>\n  <APIItem name=\"element\" type=\"TElement\">\n    The element to check.\n  </APIItem>\n  <APIItem name=\"props\" type=\"Partial<TElement>\">\n    The properties to match against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the element matches all provided properties.\n</APIReturns>\n</API>\n\n## Types\n\n### `TElement`\n\n`TElement` objects are a type of node in a Plate document that contain other element nodes or text nodes. They can be either \"blocks\" or \"inlines\" depending on the editor's configuration.\n\n`Element` is a type alias for `TElement`.\n\n<API name=\"TElement\">\n<APIAttributes>\n  <APIItem name=\"children\" type=\"Descendant[]\">\n    An array of child nodes that can be either elements or text nodes.\n  </APIItem>\n  <APIItem name=\"type\" type=\"string\">\n    A string identifier that defines the element's type (e.g., 'paragraph', 'heading', etc.).\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `ElementEntry`\n\nElement entries represent an Element node and its path.\n\n<API name=\"ElementEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"Element\">\n    The Element node.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the element.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `ElementOrTextOf`\n\n```ts\ntype ElementOrTextOf<E extends Editor> = ElementOf<E> | TextOf<E>;\n```\n\nThe `ElementOrTextOf` type represents either an element or a text node from a specific editor type.\n\n### `ElementOrTextIn`\n\n```ts\ntype ElementOrTextIn<V extends Value> = ElementIn<V> | TextIn<V>;\n```\n\nThe `ElementOrTextIn` type represents either an element or a text node from a specific value type.\n\n### `ElementOf`\n\n`ElementOf` is a utility type to get all the element node types from a given root node type.\n\n### `ElementIn`\n\n```ts\ntype ElementIn<V extends Value> = ElementOf<V[number]>;\n```\n\n`ElementIn` is a utility type to get an element type from a Plate `Value` type.\n```",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/element.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-location-ref-docs",
      "title": "Location Ref",
      "description": "API reference for location references in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/location-ref.mdx",
          "content": "---\ntitle: Location Ref\ndescription: API reference for location references in Plate.\n---\n\nLocation references are objects that keep specific locations (paths, points, or ranges) in a document synced over time as new operations are applied to the editor. You can access their `current` property at any time for the up-to-date location value. You can access their `current` property at any time for the up-to-date location value.\n\n## Types\n\n### `PathRef`\n\nPath reference objects keep a specific path in a document synced over time. Created using `editor.api.pathRef`.\n\n<API name=\"PathRef\">\n<APIAttributes>\n  <APIItem name=\"current\" type=\"Path | null\">\n    The current path value, updated as operations are applied.\n  </APIItem>\n  <APIItem name=\"affinity\" type=\"'forward' | 'backward' | null\">\n    The direction to prefer when transforming the path:\n    - `'forward'`: Prefer the position after inserted content\n    - `'backward'`: Prefer the position before inserted content\n    - `null`: No preference\n  </APIItem>\n  <APIItem name=\"unref()\" type=\"() => Path | null\">\n    Call this when you no longer need to sync this path. Returns the final path value.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `PointRef`\n\nPoint reference objects keep a specific point in a document synced over time. Created using `editor.api.pointRef`.\n\n<API name=\"PointRef\">\n<APIAttributes>\n  <APIItem name=\"current\" type=\"Point | null\">\n    The current point value, updated as operations are applied.\n  </APIItem>\n  <APIItem name=\"affinity\" type=\"'forward' | 'backward' | null\">\n    The direction to prefer when transforming the point:\n    - `'forward'`: Prefer the position after inserted content\n    - `'backward'`: Prefer the position before inserted content\n    - `null`: No preference\n  </APIItem>\n  <APIItem name=\"unref()\" type=\"() => Point | null\">\n    Call this when you no longer need to sync this point. Returns the final point value.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `RangeRef`\n\nRange reference objects keep a specific range in a document synced over time. Created using `editor.api.rangeRef`.\n\n<API name=\"RangeRef\">\n<APIAttributes>\n  <APIItem name=\"current\" type=\"TRange | null\">\n    The current range value, updated as operations are applied.\n  </APIItem>\n  <APIItem name=\"affinity\" type=\"'forward' | 'backward' | 'inward' | 'outward' | null\">\n    The direction to prefer when transforming the range:\n    - `'forward'`: Both points prefer after inserted content\n    - `'backward'`: Both points prefer before inserted content\n    - `'inward'`: Range tends to stay same size when content is inserted at edges\n    - `'outward'`: Range tends to grow when content is inserted at edges\n    - `null`: No preference\n  </APIItem>\n  <APIItem name=\"unref()\" type=\"() => TRange | null\">\n    Call this when you no longer need to sync this range. Returns the final range value.\n  </APIItem>\n</APIAttributes>\n</API>\n\nExample usage of a RangeRef:\n\n```typescript\nconst selectionRef = editor.api.rangeRef(editor.selection, {\n  affinity: 'inward',\n})\n// Operations that might change the selection\nTransforms.unwrapNodes(editor)\n// Restore the selection using the ref\nTransforms.select(editor, selectionRef.unref())\n```\n\n\n## `PathRefApi`\n\n### `transform`\n\nTransform a path reference by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"ref\" type=\"PathRef\">\n    The path reference to transform.\n  </APIItem>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to apply. The editor calls this automatically as needed.\n  </APIItem>\n</APIParameters>\n</API>\n\n## `PointRefApi`\n\n### `transform`\n\nTransform a point reference by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"ref\" type=\"PointRef\">\n    The point reference to transform.\n  </APIItem>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to apply. The editor calls this automatically as needed.\n  </APIItem>\n</APIParameters>\n</API>\n\n## `RangeRefApi`\n\n### `transform`\n\nTransform a range reference by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"ref\" type=\"RangeRef\">\n    The range reference to transform.\n  </APIItem>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to apply. The editor calls this automatically as needed.\n  </APIItem>\n</APIParameters>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/location-ref.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-location-docs",
      "title": "Location",
      "description": "API reference for locations in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/location.mdx",
          "content": "---\ntitle: Location\ndescription: API reference for locations in Plate.\n---\n\nA Location is either a Path, Point or Range. Methods will often accept a Location instead of requiring only a Path, Point or Range.\n\n```typescript\ntype TLocation = Path | Point | TRange\n```\n\n- [Path](/docs/plate/api/slate/path)\n- [PathRef](/docs/plate/api/slate/location-ref#pathref)\n- [Point](/docs/plate/api/slate/point)\n- [PointRef](/docs/plate/api/slate/location-ref#pointref)\n- [Range](/docs/plate/api/slate/range)\n- [RangeRef](/docs/plate/api/slate/location-ref#rangeref)\n\n## `LocationApi`\n\n### `isAt`\n\nCheck if a value implements the `At` interface.\n\n<API name=\"isAt\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is either a location or a node.\n</APIReturns>\n</API>\n\n### `isLocation`\n\nCheck if a value implements the `TLocation` interface.\n\n<API name=\"isLocation\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a `Path`, `Point`, or `TRange`.\n</APIReturns>\n</API>\n\n### `isSpan`\n\nCheck if a value implements the `Span` interface.\n\n<API name=\"isSpan\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a valid `Span`.\n</APIReturns>\n</API>\n\n## Types\n\n### `TLocation`\n\n`Location` is a type alias for `TLocation`.\n\n<API name=\"TLocation\">\n<APIAttributes>\n  <APIItem name=\"Path\" type=\"Path\">\n    An array of numbers representing a node's position.\n  </APIItem>\n  <APIItem name=\"Point\" type=\"Point\">\n    An object with `path` and `offset`.\n  </APIItem>\n  <APIItem name=\"TRange\" type=\"TRange\">\n    An object with `anchor` and `focus`.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `Span`\n\n<API name=\"Span\">\n<APIAttributes>\n  <APIItem name=\"[0]\" type=\"Path\">\n    The start path.\n  </APIItem>\n  <APIItem name=\"[1]\" type=\"Path\">\n    The end path.\n  </APIItem>\n</APIAttributes>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/location.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-node-docs",
      "title": "Node",
      "description": "API reference for nodes in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/node.mdx",
          "content": "---\ntitle: Node\ndescription: API reference for nodes in Plate.\n---\n\nNodes are the building blocks of Plate documents. It can either be the Editor root node (highest), an Element node, or a Text node (lowest). This API provides utilities for interacting with nodes, including traversing, querying, and extracting data.\n\n```ts\ntype TNode = Editor | TElement | TText;\n\ntype Descendant = Element | Text\ntype Ancestor = Editor | Element\n```\n\n- [Editor](/docs/plate/api/slate/editor-api)\n- [Element](/docs/plate/api/slate/element)\n- [Text](/docs/plate/api/slate/text)\n\n## `NodeAPI`\n\n### `ancestor`\n\nGet the node at a specific path, asserting that it's an ancestor node.\n\n<API name=\"ancestor\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node to start from.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the ancestor node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Ancestor | undefined\">\n  The ancestor node if found, or `undefined` if not found.\n</APIReturns>\n</API>\n\n### `ancestors`\n\nReturn a generator of all the ancestor nodes above a specific path.\n\n<API name=\"ancestors\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node to start from.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to get ancestors for.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeAncestorsOptions\" optional>\n    Options for ancestor retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"NodeAncestorsOptions\">\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, returns ancestors top-down instead of bottom-up.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<Ancestor>, void, undefined>\">\n  A generator of ancestor node entries.\n</APIReturns>\n</API>\n\n### `child`\n\nGet the child of a node at a specific index.\n\n<API name=\"child\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The parent node.\n  </APIItem>\n  <APIItem name=\"index\" type=\"number\">\n    The index of the child.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TNode | undefined\">\n  The child node if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `children`\n\nIterate over the children of a node at a specific path.\n\n<API name=\"children\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the parent node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeChildrenOptions\" optional>\n    Options for iterating over children.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"NodeChildrenOptions\">\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, iterates in reverse order.\n  </APIItem>\n  <APIItem name=\"from\" type=\"number\" optional>\n    Start index (inclusive).\n  </APIItem>\n  <APIItem name=\"to\" type=\"number\" optional>\n    End index (exclusive).\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<TNode>, void, undefined>\">\n  A generator of child node entries.\n</APIReturns>\n</API>\n\n### `common`\n\nGet an entry for the common ancestor node of two paths.\n\n<API name=\"common\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    First path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    Second path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The common ancestor entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `descendant`\n\nGet the node at a specific path, asserting that it's a descendant node.\n\n<API name=\"descendant\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the descendant.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Descendant | undefined\">\n  The descendant node if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `descendants`\n\nReturn a generator of all the descendant node entries inside a root node.\n\n<API name=\"descendants\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeDescendantsOptions\" optional>\n    Options for descendant retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"NodeDescendantsOptions\">\n  <APIItem name=\"from\" type=\"Path\" optional>\n    Starting path.\n  </APIItem>\n  <APIItem name=\"to\" type=\"Path\" optional>\n    Ending path.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, iterates in reverse order.\n  </APIItem>\n  <APIItem name=\"pass\" type=\"(node: Descendant) => boolean\" optional>\n    A function to filter descendants.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<Descendant>, void, undefined>\">\n  A generator of descendant node entries.\n</APIReturns>\n</API>\n\n### `elements`\n\nReturn a generator of all the element nodes inside a root node.\n\n<API name=\"elements\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeElementsOptions\" optional>\n    Options for element retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"NodeElementsOptions\">\n  <APIItem name=\"pass\" type=\"(node: Element) => boolean\" optional>\n    A function to filter elements.\n  </APIItem>\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, iterates in reverse order.\n  </APIItem>\n  <APIItem name=\"from\" type=\"Path\" optional>\n    Starting path.\n  </APIItem>\n  <APIItem name=\"to\" type=\"Path\" optional>\n    Ending path.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Generator<NodeEntry<Element>, void, undefined>\">\n  A generator of element entries.\n</APIReturns>\n</API>\n\n### `first`\n\nGet the first node entry in a root node from a path.\n\n<API name=\"first\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The first node entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `firstChild`\n\nGet the first child node entry of a node.\n\n<API name=\"firstChild\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The parent node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the parent node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The first child node entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `firstText`\n\nGet the first text node entry of a node.\n\n<API name=\"firstText\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The parent node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the parent node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The first text node entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `fragment`\n\nGet the sliced fragment represented by a range inside a root node.\n\n<API name=\"fragment\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to slice.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"N[]\">\n  The sliced fragment.\n</APIReturns>\n</API>\n\n### `get`\n\nGet the descendant node referred to by a specific path.\n\n<API name=\"get\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TNode | undefined\">\n  The node if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `last`\n\nGet the last node entry in a root node from a path.\n\n<API name=\"last\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The last node entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `lastChild`\n\nGet the last child node entry of a node.\n\n<API name=\"lastChild\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The parent node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the parent node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeEntry<N> | undefined\">\n  The last child node entry if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `leaf`\n\nGet the node at a specific path, ensuring it's a leaf text node.\n\n<API name=\"leaf\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"N | undefined\">\n  The leaf node if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `levels`\n\nReturn a generator of the in a branch of the tree, from a specific path.\n\n<API name=\"levels\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Generator<NodeEntry<N>, void, undefined>\">\n  A generator of node entries in a branch of the tree from a specific path.\n</APIReturns>\n</API>\n\n### `nodes`\n\nReturn a generator of all the node entries of a root node.\n\n<API name=\"nodes\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeTextsOptions\" optional>\n    Similar options to `descendants`.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Generator<NodeEntry<N>, void, undefined>\">\n  A generator of node entries.\n</APIReturns>\n</API>\n\n### `parent`\n\nGet the parent of a node at a specific path.\n\n<API name=\"parent\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Ancestor | undefined\">\n  The parent node if found, or `undefined` otherwise.\n</APIReturns>\n</API>\n\n### `texts`\n\nReturn a generator of all leaf text nodes in a root node.\n\n<API name=\"texts\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"options\" type=\"NodeTextsOptions\" optional>\n    Options for text node retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Generator<NodeEntry<Text>, void, undefined>\">\n  A generator of text node entries.\n</APIReturns>\n</API>\n\n### `extractProps`\n\nGet the props of a node.\n\n<API name=\"extractProps\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to extract props from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeProps<N>\">\n  The props of the node.\n</APIReturns>\n</API>\n\n### `has`\n\nCheck if a descendant node exists at a specific path.\n\n<API name=\"has\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if a node exists at the specified path, `false` otherwise.\n</APIReturns>\n</API>\n\n### `hasSingleChild`\n\nCheck if a node has a single child.\n\n<API name=\"hasSingleChild\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the node has a single child.\n</APIReturns>\n</API>\n\n### `isAncestor`\n\nCheck if a value implements the `Ancestor` interface.\n\n<API name=\"isAncestor\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value implements the `Ancestor` interface.\n</APIReturns>\n</API>\n\n### `isDescendant`\n\nCheck if a value implements the `Descendant` interface.\n\n<API name=\"isDescendant\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value implements the `Descendant` interface.\n</APIReturns>\n</API>\n\n### `isLastChild`\n\nCheck if a node is the last child of its parent.\n\n<API name=\"isLastChild\">\n<APIParameters>\n  <APIItem name=\"root\" type=\"TNode\">\n    The root node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the node is the last child of its parent.\n</APIReturns>\n</API>\n\n### `isNode`\n\nCheck if a value implements the `TNode` interface.\n\n<API name=\"isNode\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value implements the `TNode` interface.\n</APIReturns>\n</API>\n\n### `isNodeList`\n\nCheck if a value is a list of `Descendant` objects.\n\n<API name=\"isNodeList\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a list of `Descendant` objects.\n</APIReturns>\n</API>\n\n### `matches`\n\nCheck if a node matches a set of props.\n\n<API name=\"matches\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"Descendant\">\n    The node to check.\n  </APIItem>\n  <APIItem name=\"props\" type=\"Partial<Descendant>\">\n    The properties to match against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the node matches the provided properties.\n</APIReturns>\n</API>\n\n### `string`\n\nGet the concatenated text string of a node's content.\n\n<API name=\"string\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to get text from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"string\">\n  The concatenated text content.\n</APIReturns>\n</API>\n\n## Types\n\n### `TNode`\n\n`Node` is a type alias for `TNode`.\n\n```ts\ntype TNode = Editor | TElement | TText;\n```\n\n### `NodeEntry`\n\n`NodeEntry` objects are returned when iterating over the nodes in a Plate document tree. They consist of an array with two elements: the `TNode` and its `Path` relative to the root node in the document.\n\n<API name=\"NodeEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"TNode\">\n    The node itself.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the node.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `Descendant`\n\nThe `Descendant` union type represents nodes that are descendants in the tree.\n\n```ts\ntype Descendant = TElement | TText;\n```\n\n\n### `Ancestor`\n\nThe `Ancestor` union type represents nodes that are ancestors in the tree.\n\n```ts\ntype Ancestor = Editor | TElement;\n```\n\n### `NodeOf<N>`\n\n<API name=\"NodeOf\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TNode\">\n    The node to get the type of.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"N\">\n  The node type.\n</APIReturns>\n</API>\n\n### `NodeIn<V>`\n\n<API name=\"NodeIn\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"Value\">\n    The value to get node types from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"NodeOf<V[number]>\">\n  All possible node types from the specified value.\n</APIReturns>\n</API>\n\n### `TNodeMatch<N>`\n\n<API name=\"TNodeMatch\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"N\">\n    The node to match.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the node matches the predicate.\n</APIReturns>\n</API>\n\n### `DescendantOf<N>`\n\n<API name=\"DescendantOf\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"N\">\n    The node to get descendant types from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"DescendantOf<N>\">\n  All possible descendant node types from the specified root node.\n</APIReturns>\n</API>\n\n### `DescendantIn<V>`\n\n<API name=\"DescendantIn\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"Value\">\n    The value to get descendant types from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"DescendantIn<V>\">\n  All possible descendant node types from the specified value.\n</APIReturns>\n</API>\n\n### `ChildOf<N>`\n\n<API name=\"ChildOf\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"N\">\n    The node to get the child type from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"ChildOf<N>\">\n  The child node type.\n</APIReturns>\n</API>\n\n### `AncestorOf<N>`\n\n<API name=\"AncestorOf\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"N\">\n    The node to get ancestor types from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"AncestorOf<N>\">\n  All possible ancestor node types from the specified root node.\n</APIReturns>\n</API>\n\n### `AncestorIn<V>`\n\n<API name=\"AncestorIn\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"Value\">\n    The value to get ancestor types from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"AncestorIn<V>\">\n  All possible ancestor node types from the specified value.\n</APIReturns>\n</API>\n\n### `AncestorEntry`\n\nAncestor entries represent an ancestor node (Editor or Element) and its path.\n\n<API name=\"AncestorEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"Ancestor\">\n    The Editor or Element node.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the ancestor.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `DescendantEntry`\n\nDescendant entries represent a descendant node (Element or Text) and its path.\n\n<API name=\"DescendantEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"Descendant\">\n    The Element or Text node.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the descendant.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `NodeChildEntry`\n\nNode child entries represent a child node and its path relative to its parent.\n\n<API name=\"NodeChildEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"TNode\">\n    The child node.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the child.\n  </APIItem>\n</APIAttributes>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/node.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-operation-docs",
      "title": "Operation",
      "description": "API reference for operations in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/operation.mdx",
          "content": "---\ntitle: Operation\ndescription: API reference for operations in Plate.\n---\n\nAn Operation is the lowest-level instructions that Plate editors use to apply changes to their internal state. Representing all changes as operations is what allows Plate editors to easily implement history, collaboration, and other features.\n\n```typescript\nexport type Operation<N extends Descendant = Descendant> =\n  | NodeOperation<N>\n  | SelectionOperation\n  | TextOperation;\n```\n\n## `OperationApi`\n\n### `isNodeOperation`\n\nCheck if a value is a `NodeOperation` object.\n\n<API name=\"isNodeOperation\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a node operation.\n</APIReturns>\n</API>\n\n### `inverse`\n\nInvert an operation, returning a new operation that will exactly undo the original when applied.\n\n<API name=\"inverse\">\n<APIParameters>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to invert.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Operation\">\n  A new operation that undoes the original operation.\n</APIReturns>\n</API>\n\n### `isOperation`\n\nCheck if a value is an `Operation` object.\n\n<API name=\"isOperation\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an operation.\n</APIReturns>\n</API>\n\n### `isOperationList`\n\nCheck if a value is a list of `Operation` objects.\n\n<API name=\"isOperationList\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an array of operations.\n</APIReturns>\n</API>\n\n### `isSelectionOperation`\n\nCheck if a value is a `SelectionOperation` object.\n\n<API name=\"isSelectionOperation\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a selection operation.\n</APIReturns>\n</API>\n\n### `isTextOperation`\n\nCheck if a value is a `TextOperation` object.\n\n<API name=\"isTextOperation\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a text operation.\n</APIReturns>\n</API>\n\n## Types\n\n### `Operation`\n\n```ts\nexport type Operation<N extends Descendant = Descendant> =\n  | NodeOperation<N>\n  | SelectionOperation\n  | TextOperation;\n```\n\n### `NodeOperation`\n\nA node operation modifies a node.\n\n```ts\nexport type NodeOperation<N extends Descendant = Descendant> =\n  | InsertNodeOperation<N>\n  | MergeNodeOperation<N>\n  | MoveNodeOperation\n  | RemoveNodeOperation<N>\n  | SetNodeOperation<N>\n  | SplitNodeOperation<N>;\n```\n\n### `SelectionOperation`\n\nA selection operation modifies the selection.\n\n```ts\nexport type SelectionOperation = SetSelectionOperation;\n```\n\n### `TextOperation`\n\nA text operation modifies text content.\n\n```ts\nexport type TextOperation = InsertTextOperation | RemoveTextOperation;\n```\n\n### `InsertNodeOperation`\n\n<API name=\"InsertNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"node\" type=\"N\">\n    The node to insert.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to insert at.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'insert_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `MergeNodeOperation`\n\n<API name=\"MergeNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the node to merge.\n  </APIItem>\n  <APIItem name=\"position\" type=\"number\">\n    The position to merge at.\n  </APIItem>\n  <APIItem name=\"properties\" type=\"Partial<NodeProps<N>>\">\n    The properties of the merged node.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'merge_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `MoveNodeOperation`\n\n<API name=\"MoveNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"newPath\" type=\"Path\">\n    The new path to move to.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the node to move.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'move_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `RemoveNodeOperation`\n\n<API name=\"RemoveNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"node\" type=\"N\">\n    The node to remove.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the node.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'remove_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `SetNodeOperation`\n\n<API name=\"SetNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"newProperties\" type=\"Partial<NodeProps<N1>>\">\n    The new properties to set.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the node.\n  </APIItem>\n  <APIItem name=\"properties\" type=\"Partial<NodeProps<N2>>\">\n    The old properties.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'set_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `SplitNodeOperation`\n\n<API name=\"SplitNodeOperation\">\n<APIAttributes>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the node to split.\n  </APIItem>\n  <APIItem name=\"position\" type=\"number\">\n    The position to split at.\n  </APIItem>\n  <APIItem name=\"properties\" type=\"Partial<NodeProps<N>>\">\n    The properties of the new split node.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'split_node'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `SetSelectionOperation`\n\n<API name=\"SetSelectionOperation\">\n<APIAttributes>\n  <APIItem name=\"newProperties\" type=\"Partial<TRange> | TRange | null\">\n    The new selection properties.\n  </APIItem>\n  <APIItem name=\"properties\" type=\"Partial<TRange> | TRange | null\">\n    The old selection properties.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'set_selection'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `InsertTextOperation`\n\n<API name=\"InsertTextOperation\">\n<APIAttributes>\n  <APIItem name=\"offset\" type=\"number\">\n    The offset to insert at.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the text node.\n  </APIItem>\n  <APIItem name=\"text\" type=\"string\">\n    The text to insert.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'insert_text'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `RemoveTextOperation`\n\n<API name=\"RemoveTextOperation\">\n<APIAttributes>\n  <APIItem name=\"offset\" type=\"number\">\n    The offset to remove from.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path of the text node.\n  </APIItem>\n  <APIItem name=\"text\" type=\"string\">\n    The text being removed.\n  </APIItem>\n  <APIItem name=\"type\" type=\"'remove_text'\">\n    The operation type.\n  </APIItem>\n</APIAttributes>\n</API>\n\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/operation.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-path-docs",
      "title": "Path",
      "description": "API reference for paths in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/path.mdx",
          "content": "---\ntitle: Path\ndescription: API reference for paths in Plate.\n---\n\nA Path is a list of indexes that describe a node's exact position in a Plate node tree. Although they are usually relative to the root `Editor` object, they can be relative to any `Node` object.\n\n```ts\ntype Path = number[];\n```\n\n## `PathApi`\n\n### `operationCanTransformPath`\n\nCheck if an operation can affect paths (used as an optimization for dirty-path updates during normalization).\n\n<API name=\"operationCanTransformPath\">\n<APIParameters>\n  <APIItem name=\"operation\" type=\"Operation<N>\">\n    The operation to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the operation is an insert, merge, move, remove, or split operation.\n</APIReturns>\n</API>\n\n### `transform`\n\nTransform a path by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to transform.\n  </APIItem>\n  <APIItem name=\"operation\" type=\"Operation\">\n    The operation to apply.\n  </APIItem>\n  <APIItem name=\"options\" type=\"PathTransformOptions\" optional>\n    Options for transforming a path.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"PathTransformOptions\">\n  <APIItem name=\"affinity\" type=\"TextDirection | null\" optional>\n    The affinity of the transform.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Path | null\">\n  The transformed path, or `null` if the path was deleted.\n</APIReturns>\n</API>\n\n### `ancestors`\n\nGet a list of ancestor paths for a given path.\n\n<API name=\"ancestors\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to get ancestors for.\n  </APIItem>\n  <APIItem name=\"options\" type=\"PathAncestorsOptions\" optional>\n    Options for ancestor retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"PathAncestorsOptions\">\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, returns paths in reverse (deepest to shallowest).\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Path[]\">\n  An array of paths sorted from shallowest to deepest ancestor (unless reversed).\n</APIReturns>\n</API>\n\n### `child`\n\nGet a path to a child at the given index.\n\n<API name=\"child\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The parent path.\n  </APIItem>\n  <APIItem name=\"index\" type=\"number\">\n    The child index.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The path to the child node.\n</APIReturns>\n</API>\n\n### `common`\n\nGet the common ancestor path of two paths.\n\n<API name=\"common\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The first path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The second path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The common ancestor path.\n</APIReturns>\n</API>\n\n### `compare`\n\nCompare a path to another, returning an integer indicating whether the path was before, at, or after the other.\n\n<API name=\"compare\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The first path to compare.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The second path to compare.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"-1 | 0 | 1\">\n  `-1` if before, `0` if at the same location, `1` if after.\n</APIReturns>\n</API>\n\n### `endsAfter`\n\nCheck if a path ends after one of the indexes in another.\n\n<API name=\"endsAfter\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` ends after `another`.\n</APIReturns>\n</API>\n\n### `endsAt`\n\nCheck if a path ends at one of the indexes in another.\n\n<API name=\"endsAt\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` ends at the same index as `another`.\n</APIReturns>\n</API>\n\n### `endsBefore`\n\nCheck if a path ends before one of the indexes in another.\n\n<API name=\"endsBefore\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` ends before `another`.\n</APIReturns>\n</API>\n\n### `equals`\n\nCheck if a path is exactly equal to another.\n\n<API name=\"equals\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The first path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The second path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the paths are exactly equal.\n</APIReturns>\n</API>\n\n### `firstChild`\n\nGet a path to the first child of a path.\n\n<API name=\"firstChild\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The parent path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The path to the first child node.\n</APIReturns>\n</API>\n\n### `hasPrevious`\n\nCheck if the path of a previous sibling node exists.\n\n<API name=\"hasPrevious\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if a previous sibling exists.\n</APIReturns>\n</API>\n\n### `isAfter`\n\nCheck if a path is after another.\n\n<API name=\"isAfter\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the first path is after the second.\n</APIReturns>\n</API>\n\n### `isAncestor`\n\nCheck if a path is an ancestor of another.\n\n<API name=\"isAncestor\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The potential ancestor path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The potential descendant path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` is an ancestor of `another`.\n</APIReturns>\n</API>\n\n### `isBefore`\n\nCheck if a path is before another.\n\n<API name=\"isBefore\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the first path is before the second.\n</APIReturns>\n</API>\n\n### `isChild`\n\nCheck if a path is a child of another.\n\n<API name=\"isChild\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The potential child path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The potential parent path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` is a child of `another`.\n</APIReturns>\n</API>\n\n### `isCommon`\n\nCheck if a path is equal to or an ancestor of another.\n\n<API name=\"isCommon\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` is equal to or an ancestor of `another`.\n</APIReturns>\n</API>\n\n### `isDescendant`\n\nCheck if a path is a descendant of another.\n\n<API name=\"isDescendant\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The potential descendant path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The potential ancestor path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` is a descendant of `another`.\n</APIReturns>\n</API>\n\n### `isParent`\n\nCheck if a path is the parent of another.\n\n<API name=\"isParent\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The potential parent path.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The potential child path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if `path` is the parent of `another`.\n</APIReturns>\n</API>\n\n### `isPath`\n\nCheck if a value implements the `Path` interface.\n\n<API name=\"isPath\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a path.\n</APIReturns>\n</API>\n\n### `isSibling`\n\nCheck if a path is a sibling of another.\n\n<API name=\"isSibling\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Path\">\n    The path to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the paths share the same parent.\n</APIReturns>\n</API>\n\n### `lastIndex`\n\nGet the last index of a path.\n\n<API name=\"lastIndex\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"number\">\n  The last index, or -1 if the path is empty.\n</APIReturns>\n</API>\n\n### `levels`\n\nGet a list of paths at every level down to a path.\n\n<API name=\"levels\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to get levels for.\n  </APIItem>\n  <APIItem name=\"options\" type=\"PathLevelsOptions\" optional>\n    Options for levels retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"PathLevelsOptions\">\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, returns paths in reverse (deepest to shallowest).\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Path[]\">\n  An array of paths including the path itself and all its ancestors.\n</APIReturns>\n</API>\n\n### `next`\n\nGet the path to the next sibling node.\n\n<API name=\"next\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The current path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The path to the next sibling.\n</APIReturns>\n</API>\n\n### `parent`\n\nGet the path to the parent node.\n\n<API name=\"parent\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The current path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The path to the parent node.\n</APIReturns>\n</API>\n\n### `previous`\n\nGet the path to the previous sibling node.\n\n<API name=\"previous\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The current path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path | undefined\">\n  The path to the previous sibling, or `undefined` if there is none.\n</APIReturns>\n</API>\n\n### `relative`\n\nGet a path relative to an ancestor.\n\n<API name=\"relative\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to make relative.\n  </APIItem>\n  <APIItem name=\"ancestor\" type=\"Path\">\n    The ancestor path.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Path\">\n  The relative path.\n</APIReturns>\n</API>\n\n## Types\n\n### `Path`\n\nAn array of numbers representing the indexes to traverse to reach a specific node in the document tree.",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/path.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-point-docs",
      "title": "Point",
      "description": "API reference for points in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/point.mdx",
          "content": "---\ntitle: Point\ndescription: API reference for points in Plate.\n---\n\nA Point represents a specific location in a Plate document. It consists of a path to a text node and an offset within that text node's content.\n\n```typescript\ntype Point = {\n  path: Path\n  offset: number\n}\n```\n\n- [Path](/docs/plate/api/slate/path)\n\n## `PointAPI`\n\n### `get`\n\nGet a point from a location.\n\n<API name=\"get\">\n<APIParameters>\n  <APIItem name=\"at\" type=\"At | null\" optional>\n    The location to get the point from. Can be a `TRange`, `Point`, or `Path`.\n  </APIItem>\n  <APIItem name=\"options\" type=\"object\" optional>\n    Additional options for point retrieval.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"object\">\n  <APIItem name=\"focus\" type=\"boolean\" optional>\n    If true and the location is a range, returns the focus point instead of the anchor point.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Point | undefined\">\n  The point at the specified location, or `undefined` if not found.\n</APIReturns>\n</API>\n\n### `transform`\n\nTransform a point by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to transform.\n  </APIItem>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to apply.\n  </APIItem>\n  <APIItem name=\"options\" type=\"PointTransformOptions\" optional>\n    Options for transforming the point.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"PointTransformOptions\">\n  <APIItem name=\"affinity\" type=\"TextDirection | null\" optional>\n    The direction to prefer when transforming the point.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"Point | null\">\n  The transformed point, or `null` if the point was deleted.\n</APIReturns>\n</API>\n\n### `compare`\n\nCompare a point to another.\n\n<API name=\"compare\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The first point to compare.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Point\">\n    The second point to compare.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"-1 | 0 | 1\">\n  `-1` if before, `0` if at the same location, `1` if after.\n</APIReturns>\n</API>\n\n### `equals`\n\nCheck if two points are exactly equal.\n\n<API name=\"equals\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The first point to compare.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Point\">\n    The second point to compare.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the points are equal, `false` otherwise.\n</APIReturns>\n</API>\n\n### `isAfter`\n\nCheck if a point is after another.\n\n<API name=\"isAfter\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Point\">\n    The point to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the first point is after the second.\n</APIReturns>\n</API>\n\n### `isBefore`\n\nCheck if a point is before another.\n\n<API name=\"isBefore\">\n<APIParameters>\n  <APIItem name=\"point\" type=\"Point\">\n    The point to check.\n  </APIItem>\n  <APIItem name=\"another\" type=\"Point\">\n    The point to compare against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the first point is before the second.\n</APIReturns>\n</API>\n\n### `isPoint`\n\nCheck if a value implements the `Point` interface.\n\n<API name=\"isPoint\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a point.\n</APIReturns>\n</API>\n\n## Types\n\n### `Point`\n\nA point represents a specific location in a Plate document.\n\n<API name=\"Point\">\n<APIAttributes>\n  <APIItem name=\"offset\" type=\"number\">\n    The index of the character in the text node.\n  </APIItem>\n  <APIItem name=\"path\" type=\"Path\">\n    The path to the text node.\n  </APIItem>\n</APIAttributes>\n</API>\n### `PointEntry`\n\nA point entry is returned when iterating over `Point` objects that belong to a range.\n\n<API name=\"PointEntry\">\n<APIAttributes>\n  <APIItem name=\"[0]\" type=\"Point\">\n    The point location.\n  </APIItem>\n  <APIItem name=\"[1]\" type=\"'anchor' | 'focus'\">\n    Indicates whether this point is the anchor or focus of a range.\n  </APIItem>\n</APIAttributes>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/point.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-range-docs",
      "title": "Range",
      "description": "API reference for ranges in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/range.mdx",
          "content": "---\ntitle: Range\ndescription: API reference for ranges in Plate.\n---\n\nA Range is a set of points that refer to a specific span of a Plate document. They can define a span inside a single node or span across multiple nodes. A range consists of two points: an anchor (start) and a focus (end).\n\n```typescript\ntype TRange = {\n  anchor: Point\n  focus: Point\n}\n```\n\n- [Point](/docs/plate/api/slate/point)\n\n## `RangeAPI`\n\n### `transform`\n\nTransform a range by an operation.\n\n<API name=\"transform\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to transform.\n  </APIItem>\n  <APIItem name=\"op\" type=\"Operation\">\n    The operation to apply to the range.\n  </APIItem>\n  <APIItem name=\"options\" type=\"RangeTransformOptions\" optional>\n    Options for transforming the range.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"RangeTransformOptions\">\n  <APIItem name=\"affinity\" type=\"RangeDirection | null\" optional>\n    The direction to prefer when transforming the range.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"TRange | null\">\n  The transformed range, or `null` if the range was deleted.\n</APIReturns>\n</API>\n\n### `edges`\n\nGet the start and end points of a range.\n\n<API name=\"edges\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to get edges from.\n  </APIItem>\n  <APIItem name=\"options\" type=\"RangeEdgesOptions\" optional>\n    Options for retrieving edges.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"RangeEdgesOptions\">\n  <APIItem name=\"reverse\" type=\"boolean\" optional>\n    If true, returns points in reverse order.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"[Point, Point]\">\n  A tuple of points representing the start and end points.\n</APIReturns>\n</API>\n\n### `end`\n\nGet the end point of a range.\n\n<API name=\"end\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to get the end point from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Point\">\n  The end point of the range.\n</APIReturns>\n</API>\n\n### `equals`\n\nCheck if two ranges are exactly equal.\n\n<API name=\"equals\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The first range to compare.\n  </APIItem>\n  <APIItem name=\"another\" type=\"TRange\">\n    The second range to compare.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the ranges are exactly equal.\n</APIReturns>\n</API>\n\n### `includes`\n\nCheck if a range includes a path, point, or part of another range.\n\n<API name=\"includes\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to check.\n  </APIItem>\n  <APIItem name=\"target\" type=\"Path | Point | TRange\">\n    The target to check for inclusion.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range includes the target.\n</APIReturns>\n</API>\n\n### `intersection`\n\nGet the intersection of two ranges.\n\n<API name=\"intersection\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The first range.\n  </APIItem>\n  <APIItem name=\"another\" type=\"TRange\">\n    The second range.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"TRange | null\">\n  The intersecting range, or `null` if there is no intersection.\n</APIReturns>\n</API>\n\n### `isBackward`\n\nCheck if a range is backward (anchor point appears after focus point).\n\n<API name=\"isBackward\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range is backward.\n</APIReturns>\n</API>\n\n### `isCollapsed`\n\nCheck if a range is collapsed (both points refer to the same position).\n\n<API name=\"isCollapsed\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange | null\" optional>\n    The range to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range exists and is collapsed.\n</APIReturns>\n</API>\n\n### `isExpanded`\n\nCheck if a range is expanded (not collapsed).\n\n<API name=\"isExpanded\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange | null\" optional>\n    The range to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range exists and is expanded.\n</APIReturns>\n</API>\n\n### `isForward`\n\nCheck if a range is forward (anchor point appears before focus point).\n\n<API name=\"isForward\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range is forward.\n</APIReturns>\n</API>\n\n### `isRange`\n\nCheck if a value implements the `TRange` interface.\n\n<API name=\"isRange\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a range.\n</APIReturns>\n</API>\n\n### `points`\n\nIterate through all point entries in a range.\n\n<API name=\"points\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to iterate through.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Generator<PointEntry, void, undefined>\">\n  A generator that yields point entries.\n</APIReturns>\n</API>\n\n### `start`\n\nGet the start point of a range.\n\n<API name=\"start\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range to get the start point from.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"Point\">\n  The start point of the range.\n</APIReturns>\n</API>\n\n### `surrounds`\n\nCheck if a range completely surrounds another range.\n\n<API name=\"surrounds\">\n<APIParameters>\n  <APIItem name=\"range\" type=\"TRange\">\n    The range that might surround the target.\n  </APIItem>\n  <APIItem name=\"target\" type=\"TRange\">\n    The target range that might be surrounded.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the range surrounds the target.\n</APIReturns>\n</API>\n\n## Types\n\n### `TRange`\n\n`TRange` objects are a set of points that refer to a specific span of a Plate document. They can define a span inside a \nsingle node or span across multiple nodes.\n\n`Range` is a type alias for `TRange`.\n\n<API name=\"TRange\">\n<APIAttributes>\n  <APIItem name=\"anchor\" type=\"Point\">\n    The start point of the range.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"Point\">\n    The end point of the range.\n  </APIItem>\n</APIAttributes>\n</API>\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/range.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-text-docs",
      "title": "Text",
      "description": "API reference for text nodes in Plate.",
      "files": [
        {
          "path": "../../content/docs/api/slate/text.mdx",
          "content": "---\ntitle: Text\ndescription: API reference for text nodes in Plate.\n---\n\nA Text node contains the actual text content of a Plate document along with any formatting properties. They are always leaf nodes in the document tree as they cannot contain any children.\n\n```ts\ntype TText = {\n  text: string\n  [key: string]: unknown\n}\n```\n\n## `TextApi`\n\n### `decorations`\n\n<API name=\"decorations\">\n<APIParameters>\n  <APIItem name=\"node\" type=\"TText\">\n    The text node to get leaves from.\n  </APIItem>\n  <APIItem name=\"decorations\" type=\"DecoratedRange[]\">\n    The array of decorated ranges to apply.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"{ leaf: TText, position?: LeafPosition }[]\">\n  An array of leaves and their positions. The position is undefined if there is a single leaf.\n</APIReturns>\n</API>\n\n### `equals`\n\n<API name=\"equals\">\n<APIParameters>\n  <APIItem name=\"text\" type=\"TText\">\n    The first text node to compare.\n  </APIItem>\n  <APIItem name=\"another\" type=\"TText\">\n    The second text node to compare.\n  </APIItem>\n  <APIItem name=\"options\" type=\"TextEqualsOptions\" optional>\n    Additional comparison options.\n  </APIItem>\n</APIParameters>\n\n<APIOptions type=\"TextEqualsOptions\">\n  <APIItem name=\"loose\" type=\"boolean\" optional>\n    If `true`, the text content is not compared. This is used to check\n    whether sibling text nodes can be merged based only on their\n    formatting properties.\n  </APIItem>\n</APIOptions>\n\n<APIReturns type=\"boolean\">\n  `true` if the text nodes are equal according to the comparison rules.\n</APIReturns>\n</API>\n\n### `isText`\n\n<API name=\"isText\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is a valid text node.\n</APIReturns>\n</API>\n\n### `isTextList`\n\n<API name=\"isTextList\">\n<APIParameters>\n  <APIItem name=\"value\" type=\"any\">\n    The value to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the value is an array of text nodes.\n</APIReturns>\n</API>\n\n### `isTextProps`\n\n<API name=\"isTextProps\">\n<APIParameters>\n  <APIItem name=\"props\" type=\"any\">\n    The props to check.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the props match a partial text node structure.\n</APIReturns>\n</API>\n\n### `matches`\n\n<API name=\"matches\">\n<APIParameters>\n  <APIItem name=\"text\" type=\"TText\">\n    The text node to check.\n  </APIItem>\n  <APIItem name=\"props\" type=\"Partial<TText>\">\n    The properties to match against.\n  </APIItem>\n</APIParameters>\n\n<APIReturns type=\"boolean\">\n  `true` if the text node matches the properties.\n</APIReturns>\n</API>\n\n## Types\n\n### `TText`\n\n`Text` is a type alias for `TText`.\n\n<API name=\"TText\">\n<APIAttributes>\n  <APIItem name=\"text\" type=\"string\">\n    The text content of the node.\n  </APIItem>\n  <APIItem name=\"[key: string]\" type=\"unknown\">\n    Additional formatting properties that can be added to the text node.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `TextEntry` \n\nText entries represent a Text node and its path.\n\n<API name=\"TextEntry\">\n<APIAttributes>\n  <APIItem name=\"0\" type=\"TText\">\n    The Text node.\n  </APIItem>\n  <APIItem name=\"1\" type=\"Path\">\n    The path to the text node.\n  </APIItem>\n</APIAttributes>\n</API>\n\n### `DecoratedRange`\n\nA range object that includes decoration information. Used to apply formatting or styling to specific ranges of text within a document.\n\n### `TextOf<N>`\n\nA utility type that extracts all possible text node types from a root node type.\n\n<API name=\"TextOf\">\n<APIParameters>\n  <APIItem name=\"N\" type=\"TNode\">\n    The root node type to extract text types from.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `TextIn<V>`\n\nA utility type that extracts all text node types from a `Value` type.\n\n<API name=\"TextIn\">\n<APIParameters>\n  <APIItem name=\"V\" type=\"Value\">\n    The `Value` type to extract text types from.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `MarksOf<N>`\n\nA utility type that extracts all possible mark types from a root node type. Marks are the formatting properties that can be applied to text nodes.\n\n<API name=\"MarksOf\">\n<APIParameters>\n  <APIItem name=\"N\" type=\"TNode\">\n    The root node type to extract mark types from.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `MarksIn<V>`\n\nA utility type that extracts all mark types from a `Value` type. Similar to `MarksOf` but works specifically with editor `Value` types.\n\n<API name=\"MarksIn\">\n<APIParameters>\n  <APIItem name=\"V\" type=\"Value\">\n    The `Value` type to extract mark types from.\n  </APIItem>\n</APIParameters>\n</API>\n\n### `MarkKeysOf<N>`\n\nA utility type that extracts all possible mark property keys from a node type.\n\n<API name=\"MarkKeysOf\">\n<APIParameters>\n  <APIItem name=\"N\" type=\"TNode\">\n    The node type to extract mark keys from.\n  </APIItem>\n</APIParameters>\n</API>",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate/text.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-slate-docs",
      "title": "Slate",
      "description": "API reference for @platejs/slate.",
      "files": [
        {
          "path": "../../content/docs/api/slate.mdx",
          "content": "---\ntitle: Slate\ndescription: API reference for @platejs/slate.\n---\n\n`@platejs/slate` is Plate's framework-free Slate layer. It exports the editor\nfactory, editor APIs, transforms, node/location types, DOM helpers from\n`slate-dom`, history helpers, and small query utilities.\n\n## Installation\n\n```bash\nnpm install @platejs/slate\n```\n\nUse `@platejs/slate` in package code that should not import React or Plate core.\nUse `platejs` in app code when the umbrella package is already available.\n\n## Quick Use\n\n```ts title=\"Create a framework-free editor\"\nimport { createEditor, withHistory } from '@platejs/slate';\n\nconst editor = withHistory(\n  createEditor({\n    children: [{ children: [{ text: 'Hello' }], type: 'p' }],\n  })\n);\n\neditor.tf.insertText(' world');\n\nconst text = editor.api.string([]);\n```\n\n`createEditor` returns an editor with `api`, `tf`, and legacy direct methods.\n`withHistory` replaces `undo`, `redo`, `apply`, and `writeHistory` with real\nhistory behavior.\n\n## Package Surface\n\n| Surface | Export | Use |\n| --- | --- | --- |\n| Editor factory | `createEditor` | Creates a Slate editor and binds Plate's `api` and `tf` namespaces. |\n| Editor types | `Editor`, `Value`, `ValueOf` | Type editor instances and document values. |\n| Editor API | `EditorApi` | Read/query editor state through `editor.api`. |\n| Editor transforms | `EditorTransforms` | Change editor state through `editor.tf`. |\n| Interfaces | `ElementApi`, `NodeApi`, `TextApi`, `PathApi`, `PointApi`, `RangeApi`, `OperationApi` | Typed helpers for Slate data structures. |\n| Refs | `PathRef`, `PointRef`, `RangeRef` | Mutable location refs that track editor operations. |\n| History | `HistoryApi`, `withHistory` | Undo/redo stacks and history batching flags. |\n| DOM helpers | `slate-dom` re-exports | DOM selection, browser, and diff helpers used by editable surfaces. |\n| Utilities | `getAt`, `match`, `queryEditor`, `queryNode`, `deleteMerge` | Shared query and transform predicates. |\n\n## Editor Shape\n\nAn `Editor` contains the Slate document state plus Plate's namespaced helpers.\n\n| Field | Type | Use |\n| --- | --- | --- |\n| `children` | `Value` | Document nodes. |\n| `selection` | `TRange \\| null` | Current selection. |\n| `operations` | `Operation[]` | Operations applied since the last change flush. |\n| `marks` | `Record<string, any> \\| null` | Marks applied to the next inserted text. |\n| `history` | `History` | Undo and redo stacks. |\n| `meta` | `UnknownObject & { isNormalizing?: boolean }` | Custom metadata and normalization state. |\n| `api` | `EditorApi` | Query methods. |\n| `tf` | `EditorTransforms` | Transform methods. |\n| `transforms` | `EditorTransforms` | Alias of `tf` for compatibility. |\n\n`createEditor` also syncs legacy direct methods, so existing Slate-style calls\nsuch as `editor.insertText()` continue to route to the current transform\nimplementation.\n\n## API Map\n\n| Page | Covers |\n| --- | --- |\n| [Editor API](/docs/plate/api/slate/editor-api) | Query methods on `editor.api`, including locations, DOM lookup, marks, blocks, and selection predicates. |\n| [Editor Transforms](/docs/plate/api/slate/editor-transforms) | Mutation methods on `editor.tf`, including text, node, mark, selection, history, and keyboard transforms. |\n| [Node](/docs/plate/api/slate/node) | Node, descendant, ancestor, and node-entry helpers. |\n| [Element](/docs/plate/api/slate/element) | Element types, element props, and element guards. |\n| [Text](/docs/plate/api/slate/text) | Text node types, mark objects, and text guards. |\n| [Path](/docs/plate/api/slate/path) | Path comparison, movement, ancestry, and transform helpers. |\n| [Point](/docs/plate/api/slate/point) | Point comparison, edge checks, and point transforms. |\n| [Range](/docs/plate/api/slate/range) | Range checks, edge helpers, intersection, inclusion, and transforms. |\n| [Location](/docs/plate/api/slate/location) | `Path`, `Point`, `Range`, and `Span` location unions. |\n| [Location Ref](/docs/plate/api/slate/location-ref) | `PathRef`, `PointRef`, and `RangeRef` affinity behavior. |\n| [Operation](/docs/plate/api/slate/operation) | Slate operation types, operation lists, and operation inversion. |\n\n## History\n\n```ts title=\"Batch history\"\neditor.tf.withNewBatch(() => {\n  editor.tf.insertText('Title');\n  editor.tf.insertBreak();\n});\n\neditor.tf.withoutSaving(() => {\n  editor.tf.select([]);\n});\n```\n\n`withHistory` stores undo batches in `history.undos` and redo batches in\n`history.redos`. Selection-only operations are not saved. Adjacent text inserts\nor removals on the same path merge into one batch unless a history transform\nsets a different batching mode.\n\n## DOM Helpers\n\n`@platejs/slate` re-exports selected `slate-dom` helpers, including browser\nflags, DOM point/range types, DOM selection helpers, string diff helpers, and\n`withDOM`.\n\nUse these exports when a package already depends on `@platejs/slate`; import\nfrom `slate-dom` directly only for code that is intentionally independent of\nPlate's Slate layer.\n\n## Utilities\n\n| Utility | Use |\n| --- | --- |\n| `getAt` | Normalizes editor locations and node inputs before query/transform calls. |\n| `match` | Builds node predicates from match options. |\n| `queryEditor` | Checks editor state before running a transform. |\n| `queryNode` | Checks a node against type, path, and predicate options. |\n| `deleteMerge` | Shared delete/merge behavior for text deletion transforms. |\n| `assignLegacyTransforms` / `syncLegacyMethods` | Keeps legacy direct editor methods wired to `editor.tf`. |\n\n## Related APIs\n\n- [Plate](/docs/plate/api/plate) shows where `@platejs/slate` is re-exported from the\n  `platejs` umbrella package.\n- [Plate Editor](/docs/plate/api/core/plate-editor) covers the React/Core editor layer\n  built on top of this package.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/slate.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "api-utils-docs",
      "title": "Plate Utils",
      "description": "API reference for @platejs/utils.",
      "files": [
        {
          "path": "../../content/docs/api/utils.mdx",
          "content": "---\ntitle: Plate Utils\ndescription: API reference for @platejs/utils.\n---\n\n`@platejs/utils` contains Plate's shared key constants, editor value types, and\nsmall utility plugins. `@platejs/utils/react` adds React hooks and the block\nplaceholder plugin used by registry UI.\n\n## Installation\n\n```bash\nnpm install @platejs/utils\n```\n\nApplication code usually imports this surface from `platejs` and `platejs/react`.\nDirect package imports are useful inside packages that should not depend on the\numbrella `platejs` package.\n\n## Import Paths\n\n| Import | Re-exports | Use |\n| --- | --- | --- |\n| `@platejs/utils` | `plate-keys`, `plate-types`, utility plugins | Shared node keys, Plate element/mark types, and headless utility plugins. |\n| `@platejs/utils/react` | React hooks, `BlockPlaceholderPlugin` | Registry controls and React-only utility behavior. |\n| `platejs` | `@platejs/utils` | App-level imports for headless constants, types, and utility plugins. |\n| `platejs/react` | `@platejs/utils/react` | App-level imports for React hooks and `BlockPlaceholderPlugin`. |\n\n## Key Constants\n\n`KEYS` is the canonical key map used by Plate packages, registry components, and\nplugin configuration.\n\n| Export | Contains | Notes |\n| --- | --- | --- |\n| `NODES` | Element and mark node keys such as `p`, `blockquote`, `codeBlock`, `table`, `bold`, and `link` | `link` maps to the same node type as `a`. |\n| `STYLE_KEYS` | Style property keys such as `color`, `fontSize`, `indent`, and `textAlign` | Used by style plugins and registry controls. |\n| `KEYS` | `NODES`, `STYLE_KEYS`, and plugin keys such as `exitBreak`, `normalizeTypes`, `singleBlock`, and `trailingBlock` | Also includes grouped values such as `heading`. |\n| `NodeKey` | Union of values from `NODES` | Use for node-type values. |\n| `StyleKey` | Union of values from `STYLE_KEYS` | Use for style keys. |\n| `PlateKey` | Union of values from `KEYS` | Includes string values and grouped key arrays. |\n\n```ts title=\"Use Plate keys\"\nimport { KEYS, TrailingBlockPlugin } from 'platejs';\n\nexport const trailingBlock = TrailingBlockPlugin.configure({\n  options: {\n    type: KEYS.p,\n  },\n});\n```\n\n## Shared Types\n\n`plate-types` exports common element, prop, media, list, table, mark, and\nsuggestion shapes used across feature packages.\n\n| Type group | Examples | Use |\n| --- | --- | --- |\n| Block elements | `TCalloutElement`, `TCodeBlockElement`, `TColumnElement`, `TDateElement`, `TEquationElement` | Typed element props for feature packages and registry nodes. |\n| Media elements | `TImageElement`, `TAudioElement`, `TFileElement`, `TVideoElement`, `TMediaEmbedElement` | Media nodes with `url`, `id`, upload, provider, and source metadata. |\n| Table elements | `TTableElement`, `TTableRowElement`, `TTableCellElement`, `TTableCellBorder` | Table structure, spans, sizes, backgrounds, and borders. |\n| Shared props | `TIdProps`, `TCaptionProps`, `TIndentProps`, `TResizableProps`, `TListProps` | Reusable node property contracts. |\n| Marks | `TBasicMarks`, `TFontMarks`, `TCommentText`, `TSuggestionText` | Text marks and collaboration text state. |\n| Suggestions | `TSuggestionData`, `TInsertSuggestionData`, `TRemoveSuggestionData`, `TUpdateSuggestionData` | Suggestion metadata stored on elements or text. |\n\n```ts title=\"Type a media element\"\nimport type { TImageElement } from 'platejs';\n\nexport function getImageUrl(element: TImageElement) {\n  return element.url;\n}\n```\n\n## Utility Plugins\n\n| Plugin | Key | Behavior |\n| --- | --- | --- |\n| `ExitBreakPlugin` | `KEYS.exitBreak` | Adds `editor.tf.insert` and `editor.tf.insertBefore` wrappers around `insertExitBreak`. |\n| `NormalizeTypesPlugin` | `KEYS.normalizeTypes` | Normalizes configured root paths to a required `type` or `strictType`. |\n| `SingleBlockPlugin` | `KEYS.singleBlock` | Forces the editor value into one block and turns hard breaks into soft breaks. |\n| `SingleLinePlugin` | `KEYS.singleLine` | Forces one block and strips line-break characters from text nodes. |\n| `TrailingBlockPlugin` | `KEYS.trailingBlock` | Ensures a trailing block exists at the configured level and type. |\n| `withTrailingBlock` | Override editor helper | Implements the trailing block normalization logic used by `TrailingBlockPlugin`. |\n\nUse the plugin guide pages for options and examples:\n[Exit Break](/docs/plate/exit-break), [Forced Layout](/docs/plate/forced-layout),\n[Single Block](/docs/plate/single-block), and [Trailing Block](/docs/plate/trailing-block).\n\n## React Hooks\n\n| Hook | Returns | Use |\n| --- | --- | --- |\n| `useEditorString()` | `string` | Reads `editor.api.string([])` through `useEditorSelector`. |\n| `useFormInputProps(options?)` | `{ props }` | Adds an optional `onKeyDownCapture` handler that prevents Enter from submitting a wrapper form. |\n| `useMarkToolbarButtonState({ nodeType, clear? })` | `{ clear, nodeType, pressed }` | Reads whether a mark is active. |\n| `useMarkToolbarButton(state)` | `{ props }` | Provides `pressed`, `onClick`, and `onMouseDown` props that toggle a mark and focus the editor. |\n| `useRemoveNodeButton({ element })` | `{ props }` | Provides button props that remove an element by path. |\n| `useSelectionCollapsed()` | `boolean` | Selection is collapsed. |\n| `useSelectionExpanded()` | `boolean` | Selection is expanded. |\n| `useSelectionWithinBlock()` | `boolean` | Selection is inside one block. |\n| `useSelectionAcrossBlocks()` | `boolean` | Selection spans blocks. |\n| `useSelectionFragment()` | `Descendant[]` | Reads the selected fragment while unwrapping container types. |\n| `useSelectionFragmentProp(options?)` | `unknown` | Reads a property from the selected fragment. |\n\n```tsx title=\"registry/ui/mark-toolbar-button.tsx\"\nimport {\n  useMarkToolbarButton,\n  useMarkToolbarButtonState,\n} from 'platejs/react';\n\nexport function MarkToolbarButton({ nodeType }: { nodeType: string }) {\n  const state = useMarkToolbarButtonState({ nodeType });\n  const { props } = useMarkToolbarButton(state);\n\n  return <button type=\"button\" aria-pressed={props.pressed} {...props} />;\n}\n```\n\n## React Plugins\n\n| Plugin | Key | Behavior |\n| --- | --- | --- |\n| `BlockPlaceholderPlugin` | `KEYS.blockPlaceholder` | Tracks the current empty block and injects `placeholder` and optional `className` props into matching block components. |\n\n`BlockPlaceholderPlugin` defaults to paragraph placeholders and only targets a\nfocused, editable, collapsed selection. Configure `placeholders` by plugin key\nand `query` by node/path.\n\n```tsx title=\"components/editor/plugins/block-placeholder-kit.tsx\"\nimport { KEYS } from 'platejs';\nimport { BlockPlaceholderPlugin } from 'platejs/react';\n\nexport const blockPlaceholderPlugin = BlockPlaceholderPlugin.configure({\n  options: {\n    placeholders: {\n      [KEYS.p]: 'Type something...',\n    },\n    query: ({ path }) => path.length === 1,\n  },\n});\n```\n\n## Related APIs\n\n- [Plate](/docs/plate/api/plate) covers the umbrella package that re-exports these APIs.\n- [Plate Core](/docs/plate/api/core) covers editor creation, plugin contracts, and stores.\n- [React Utils](/docs/plate/api/react-utils) covers `@udecode/react-utils`, which is re-exported through `platejs/react`.\n- [Toolbar](/docs/plate/toolbar) covers registry controls that use the React hook helpers.\n",
          "type": "registry:file",
          "target": "content/docs/plate/api/utils.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-collaboration-example-docs",
      "title": "Collaboration Demo",
      "description": "Real-time Plate collaboration example with Yjs providers and remote cursors.",
      "files": [
        {
          "path": "../../content/docs/examples/collaboration-example.mdx",
          "content": "---\ntitle: Collaboration Demo\ndescription: Real-time Plate collaboration example with Yjs providers and remote cursors.\n---\n\nThis example shows a Plate editor bound to a shared Yjs document. It uses the\n`collaboration-demo` registry example with `YjsPlugin`, Hocuspocus, WebRTC, room\nstate, connection controls, and remote cursor rendering.\n\n## Demo\n\n<ComponentPreview name=\"collaboration-demo\" />\n\n## Source\n\nThe demo creates a room id in `localStorage`, gives each tab a random user name\nand cursor color, then initializes Yjs after mount.\n\n<ComponentSource name=\"collaboration-demo\" />\n\n## Runtime Shape\n\n| Piece | Owner | Notes |\n| --- | --- | --- |\n| `YjsPlugin` | `@platejs/yjs/react` | Binds Plate to a shared `Y.Doc`, provider list, awareness data, and cursor data. |\n| Hocuspocus provider | App infrastructure | The demo points to `ws://localhost:8888`; run your own server for durable collaboration. |\n| WebRTC provider | `y-webrtc` | The demo uses `ws://localhost:4444` in dev and `wss://signaling.yjs.dev` in production. |\n| Room state | Registry example | The room id is stored in `localStorage` and passed to every provider plus `yjs.init`. |\n| Connection controls | Registry example | The status bar reads `_providers` and `_isConnected`, then calls `connect` or `disconnect`. |\n\nSet `skipInitialization: true` on the editor. Yjs owns the initial document\nload, and `editor.getApi(YjsPlugin).yjs.init()` seeds the value only when the\nshared document is empty.\n\n## Remote Cursors\n\nThe demo renders remote selections with the registry `remote-cursor-overlay`\ncomponent.\n\n<ComponentSource name=\"remote-cursor-overlay\" />\n\n`RemoteCursorOverlay` waits for `_isSynced`, reads cursor positions with\n`useRemoteCursorOverlayPositions`, then paints selection rectangles and a named\ncaret.\n\n## Try It\n\nOpen the same demo in two tabs and use the same room id. Each tab receives a\ndifferent random user name and cursor color, so typing and selections are easy\nto distinguish.\n\n## Related\n\n- [Yjs](/docs/plate/yjs) covers provider setup, lifecycle methods, and API reference.\n- [Remote Cursor Overlay](/docs/plate/components/remote-cursor-overlay) covers the reusable cursor UI component.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/collaboration-example.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-editable-voids-docs",
      "title": "Editable Voids",
      "description": "Nest editable controls and a Plate editor inside a void element.",
      "files": [
        {
          "path": "../../content/docs/examples/editable-voids.mdx",
          "content": "---\ntitle: Editable Voids\ndescription: Nest editable controls and a Plate editor inside a void element.\n---\n\nThis example renders form controls and a nested Plate editor inside a void\nelement. It is app-local demo code built with `createPlatePlugin`, not a packaged\nfeature plugin.\n\n## Demo\n\n<ComponentPreview name=\"editable-voids-demo\" />\n\n## Source\n\nThe demo registers an `editable-void` element plugin and renders custom React UI\nfor that node.\n\n<ComponentSource name=\"editable-voids-demo\" />\n\nThe initial value contains a normal paragraph, one `editable-void` element, and\nan empty paragraph after it.\n\n<ComponentSource src=\"examples/values/editable-voids-value.tsx\" />\n\n## Runtime Shape\n\n| Piece | Owner | Notes |\n| --- | --- | --- |\n| `EditableVoidPlugin` | Registry example | Uses `createPlatePlugin` with `node.isElement: true` and `node.isVoid: true`. |\n| `EditableVoidElement` | Registry example | Renders inputs, radio controls, and a nested `<Plate>` instance. |\n| Outer editor | Registry example | Loads `EditorKit` plus `EditableVoidPlugin` and the `editableVoidsValue`. |\n| Inner editor | Registry example | Creates a separate `usePlateEditor({ plugins: EditorKit })` inside the void element. |\n| Value shape | Slate | Keeps an empty text child inside the void node so Slate can select it. |\n\n## Void Element Rules\n\nSet `contentEditable={false}` on the custom void wrapper. Without it, browser\nediting behavior can leak into the nested controls; the demo notes Firefox input\nissues specifically.\n\nRender `{children}` after the non-editable UI. Slate still needs the hidden void\nchild mounted even when your visible element is fully custom React.\n\nThe nested editor is a separate Plate editor. It does not share the outer\neditor's value, selection, plugins, or undo history.\n\n## Related\n\n- [Plate Plugin](/docs/plate/api/core/plate-plugin) covers `node.isVoid`, `node.component`, and plugin rendering.\n- [Plugin Components](/docs/plate/plugin-components) covers custom node components.\n- [Plate Components](/docs/plate/api/core/plate-components) covers `<Plate>`, editor providers, and node primitives.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/editable-voids.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-export-docs",
      "title": "Export",
      "description": "Export a Plate document to HTML, PDF, image, Markdown, or Word.",
      "files": [
        {
          "path": "../../content/docs/examples/export.mdx",
          "content": "---\ntitle: Export\ndescription: Export a Plate document to HTML, PDF, image, Markdown, or Word.\ndocs:\n  - route: https://pro.platejs.org/docs/examples/export\n    title: Plus\n---\n\nThis example exports the current Plate editor value from the browser. The\nregistry `export-toolbar-button` owns the client-side download menu for HTML,\nPDF, image, Markdown, and Word output.\n\n## Demo\n\n<ComponentPreview name=\"playground-demo\" id=\"basic-blocks\" />\n\n## Toolbar Source\n\nInstall the [Export Toolbar Button](/docs/plate/components/export-toolbar-button)\ncomponent to add the menu to an editor toolbar.\n\n<ComponentSource name=\"export-toolbar-button\" />\n\n## Export Formats\n\n| Format | Implementation | Output |\n| --- | --- | --- |\n| HTML | `serializeHtml` from `platejs/static` with `BaseEditorKit` and `EditorStatic` | `plate.html` |\n| PDF | `html2canvas-pro` snapshot converted with `pdf-lib` | `plate.pdf` |\n| Image | `html2canvas-pro` snapshot | `plate.png` |\n| Markdown | `editor.getApi(MarkdownPlugin).markdown.serialize()` | `plate.md` |\n| Word | `exportToDocx(editor.children, { editorPlugins })` from `@platejs/docx-io` | `plate.docx` |\n\nThe PDF and image exporters snapshot the current editable DOM. Use them for a\nquick client-side download, not for paginated print layout.\n\n## DOCX Static Kit\n\nWord export passes the base static editor kit plus `DocxExportKit` so custom\nnodes can render with DOCX-friendly components.\n\n<ComponentSource name=\"docx-export-kit\" />\n\n`DocxExportKit` overrides code blocks, columns, equations, callouts, and table\nof contents rendering for the DOCX conversion path.\n\n## Plus Export\n\nPlate Plus includes a server-side export flow for PDF output with page settings.\n\n<ComponentPreviewPro name=\"export-pro\" />\n\n## Related\n\n- [DOCX Import/Export](/docs/plate/docx-io) covers `@platejs/docx-io` options and plugin APIs.\n- [Markdown](/docs/plate/markdown) covers Markdown serialization.\n- [Static Rendering](/docs/plate/static) covers static editor rendering and HTML serialization.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/export.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-huge-document-docs",
      "title": "Huge Document",
      "description": "Documentation for Huge Document",
      "files": [
        {
          "path": "../../content/docs/examples/huge-document.mdx",
          "content": "---\ntitle: Huge Document\n---\n\n<ComponentPreview name=\"huge-document-demo\" />\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/huge-document.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-hundreds-editors-docs",
      "title": "Hundreds Editors",
      "description": "Render many independent Plate editors on one page.",
      "files": [
        {
          "path": "../../content/docs/examples/hundreds-editors.mdx",
          "content": "---\ntitle: Hundreds Editors\ndescription: Render many independent Plate editors on one page.\n---\n\nThis example mounts 300 separate Plate editors in a single React tree. Each\neditor gets its own id and value, so the page stresses editor creation, store\nisolation, and repeated editable surfaces.\n\n## Demo\n\n<ComponentPreview name=\"hundreds-editors-demo\" />\n\n## Source\n\nThe demo maps generated values to `<WithPlate id={idx + 1} value={value} />`.\n\n<ComponentSource name=\"hundreds-editors-demo\" />\n\n## Value Generator\n\nEach generated editor value contains one heading and two paragraphs.\n\n<ComponentSource src=\"examples/values/multi-editors-value.tsx\" />\n\n## Runtime Shape\n\n| Surface | Owner | Notes |\n| --- | --- | --- |\n| `createMultiEditorsValue()` | Registry example | Creates 300 editor values with JSX test-utils syntax. |\n| `WithPlate` | Registry example | Calls `usePlateEditor({ id, value })` for each editor. |\n| `<Plate>` | `platejs/react` | Creates one editor store provider per editor instance. |\n| `<EditorContainer>` / `<Editor>` | Registry UI | Renders the shared editor shell for every instance. |\n\nUse this demo to inspect many-editor mounting cost, store isolation, and focus\nbehavior. It is a browser stress page, not a production layout recommendation.\n\n## Related\n\n- [Plate Components](/docs/plate/api/core/plate-components) covers `<Plate>` and editor store providers.\n- [Plate Controller](/docs/plate/api/core/plate-controller) covers cross-editor coordination.\n- [Plate Store](/docs/plate/api/core/plate-store) covers scoped editor store access.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/hundreds-editors.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-preview-markdown-docs",
      "title": "Preview Markdown",
      "description": "Decorate text ranges so Markdown syntax previews inline.",
      "files": [
        {
          "path": "../../content/docs/examples/preview-markdown.mdx",
          "content": "---\ntitle: Preview Markdown\ndescription: Decorate text ranges so Markdown syntax previews inline.\n---\n\nThis example previews Markdown syntax with Slate decorations. It does not\ndeserialize Markdown into Plate nodes; it keeps the text as text and styles\nmatching ranges with a custom leaf renderer.\n\n## Demo\n\n<ComponentPreview name=\"preview-markdown-demo\" />\n\n## Source\n\nThe demo tokenizes each text node with Prism's Markdown grammar and returns\ndecoration ranges for token types such as title, bold, italic, blockquote, list,\nhorizontal rule, and code.\n\n<ComponentSource name=\"preview-markdown-demo\" />\n\n## Initial Value\n\nThe value is plain Plate content whose paragraph text includes Markdown\ncharacters.\n\n<ComponentSource src=\"examples/values/preview-md-value.tsx\" />\n\n## Runtime Shape\n\n| Surface | Owner | Notes |\n| --- | --- | --- |\n| `decoratePreview` | Registry example | Reads text nodes, tokenizes `node.text`, and returns Slate ranges with token-type flags. |\n| `PreviewLeaf` | Registry example | Applies CSS classes when a decorated leaf has `bold`, `italic`, `title`, `list`, `hr`, `blockquote`, or `code`. |\n| `preview-markdown` plugin | Registry example | Local `createSlatePlugin({ decorate })` plugin. |\n| `BasicNodesKit` | Registry kit | Supplies the editor's normal paragraph and heading plugins. |\n| `prismjs` | Dependency | Supplies the Markdown tokenizer used by the decoration function. |\n\nUse this pattern when the editor should keep raw Markdown characters visible.\nUse [Markdown](/docs/plate/markdown) when the editor should convert Markdown text into\nPlate nodes.\n\n## Related\n\n- [Markdown](/docs/plate/markdown) covers Markdown deserialization and serialization.\n- [Plate Plugin](/docs/plate/api/core/plate-plugin) covers `decorate`.\n- [Text](/docs/plate/api/slate/text) covers text decorations and decorated leaves.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/preview-markdown.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "examples-version-history-docs",
      "title": "Version History",
      "description": "Save Plate value snapshots and render a diff against the live editor.",
      "files": [
        {
          "path": "../../content/docs/examples/version-history.mdx",
          "content": "---\ntitle: Version History\ndescription: Save Plate value snapshots and render a diff against the live editor.\n---\n\nThis example stores immutable Plate value snapshots in local React state. It compares the selected revision with the current value through `@platejs/diff`.\n\n## Demo\n\n<ComponentPreview name=\"version-history-demo\" />\n\n## Source\n\n<ComponentSource name=\"version-history-demo\" />\n\n## Runtime Shape\n\n| Piece | Owner | Role |\n| --- | --- | --- |\n| `createVersionSnapshot` | registry example | Clones each `Value` with `cloneDeep` before saving or diffing. |\n| `revisions` | registry example | Stores the saved Plate values and feeds the revision picker. |\n| `computeDiff` | `@platejs/diff` | Compares the selected revision and live value, then returns diff-marked nodes. |\n| `DiffPlugin` | registry example | Renders diff leaves and wraps diff elements with insert, delete, or update styling. |\n| `InlinePlugin` and `InlineVoidPlugin` | registry example | Keep inline and inline-void nodes in the demo value so the diff path covers mixed content. |\n| `withGetFragmentExcludeDiff` | `@platejs/diff` | Removes `diff` and `diffOperation` metadata from copied fragments. |\n\n## Snapshot Flow\n\n1. The editor starts from `initialValue` and keeps the live value in local state.\n2. `Save revision` clones the live value and appends it to `revisions`.\n3. The select menu chooses one saved revision for comparison.\n4. `computeDiff` receives the selected revision, current value, `editor.api.isInline`, and `lineBreakChar: '¶'`.\n5. The read-only diff editor renders the returned value with the same inline plugins plus `DiffPlugin`.\n\n## Product Preview\n\nPotion uses the same document-history idea in a full app surface with stored versions, restore actions, and visual comparison.\n\n<ComponentPreview name=\"potion-iframe-demo\" />\n\n## Related\n\n- [Controlled Value](/docs/plate/controlled) for owning editor value from React state.\n- [Plate Plugin](/docs/plate/api/core/plate-plugin) for render hooks like `render.node` and `render.aboveNodes`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/examples/version-history.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "index-docs",
      "title": "Introduction",
      "description": "Build rich-text editors with Plate, Plate UI, AI, MCP, and shadcn/ui.",
      "files": [
        {
          "path": "../../content/docs/index.mdx",
          "content": "---\ntitle: Introduction\ndescription: Build rich-text editors with Plate, Plate UI, AI, MCP, and shadcn/ui.\n---\n\nPlate is a React framework for building rich-text editors. It gives you a headless editor runtime, composable plugins, and optional Plate UI components that you copy into your app. Start with Plate UI for a complete editor, or use the packages directly when you need a headless setup.\n\n## Choose a Path\n\n<Cards>\n  <Card\n    href=\"/docs/plate/installation/plate-ui\"\n    title=\"Install Plate UI\"\n    description=\"Use the shadcn CLI to add the editor UI, kits, and starter components.\"\n  />\n  <Card\n    href=\"/docs/plate/installation\"\n    title=\"Pick an install guide\"\n    description=\"Choose Next.js, React, RSC, Node.js, or manual setup.\"\n  />\n  <Card\n    href=\"/docs/plate/plugin\"\n    title=\"Learn plugins\"\n    description=\"Understand the headless plugin model before customizing behavior.\"\n  />\n  <Card\n    href=\"/editors\"\n    title=\"Start from a template\"\n    description=\"Copy a complete editor configuration and adapt it in your app.\"\n  />\n</Cards>\n\n## What Plate Owns\n\n| Layer | What it owns | Start here |\n| --- | --- | --- |\n| `platejs` | Core editor runtime, React bindings, and editor APIs. | [Installation](/docs/plate/installation) |\n| `@platejs/*` packages | Headless plugins for nodes, marks, serialization, collaboration, AI, and editor behavior. | [Plugin guide](/docs/plate/plugin) |\n| Plate UI registry | App-local UI components, kits, editor templates, and API routes installed through the shadcn CLI. | [Plate UI](/docs/plate/installation/plate-ui) |\n| Your app | The copied component code, styling, routing, data model, and product-specific editor behavior. | [Plugin Components](/docs/plate/plugin-components) |\n\n## Why Plate UI?\n\nPlate UI follows the shadcn/ui model: you copy the code, own it, and keep editing it in your app.\n\n- **Open code:** The UI layer is app-local code, not a sealed component package.\n- **Composition:** Components use predictable React and shadcn/ui conventions.\n- **CLI distribution:** Add kits, templates, components, API routes, and docs through the shadcn CLI.\n- **AI-ready:** Open component code and consistent registry metadata give agents real context.\n- **MCP-ready:** The [MCP setup](/docs/plate/installation/mcp) lets AI tools inspect and apply Plate registry items.\n\nDone. You can install the full UI path, drop down to headless packages, or start from an editor template.\n\n## FAQ\n\n<Accordions>\n  <Accordion title=\"Which frameworks are Plate compatible with?\">\n    Plate works in React environments. Use the [Next.js guide](/docs/plate/installation/next) for server-rendered apps, the [React guide](/docs/plate/installation/react) for Vite and client-side apps, [RSC](/docs/plate/installation/rsc) for server components, and [Node.js](/docs/plate/installation/node) for backend-only processing.\n  </Accordion>\n\n  <Accordion title=\"What's the difference between Plate and Plate UI?\">\n    **Plate** is the headless editor framework: runtime, plugins, transforms, APIs, and React bindings. **Plate UI** is the copied UI layer: components, kits, editor templates, and API routes installed into your app.\n  </Accordion>\n\n  <Accordion title=\"Can I use Plate / Plate UI in my project?\">\n    Yes. Plate and Plate UI are free for personal and commercial projects. Attribution is not required.\n  </Accordion>\n\n  <Accordion title=\"Is Plate UI a replacement for shadcn/ui?\">\n    No. Plate UI uses [shadcn/ui](https://ui.shadcn.com/) conventions for editor-specific UI. Use shadcn/ui for the rest of your application.\n  </Accordion>\n\n  <Accordion title=\"How do I manage updates with an Open Code approach?\">\n    Package behavior updates through `platejs` and `@platejs/*` dependencies. Copied UI code is yours, so update it by comparing the Plate registry source with your local files or by using the [MCP setup](/docs/plate/installation/mcp).\n  </Accordion>\n\n  <Accordion title=\"Why copy/paste components instead of using an npm package?\">\n    The copied component layer lets you change markup, styles, toolbar behavior, and product details without waiting for a package API to expose every option.\n  </Accordion>\n\n  <Accordion title=\"Will Plate UI be published as an npm package?\">\n    No. Plate UI is distributed as registry code through the shadcn CLI so the UI layer stays owned by your app.\n  </Accordion>\n</Accordions>\n\n## Credits\n\n- [shadcn/ui](https://ui.shadcn.com/) - For UI inspiration, documentation, and the CLI.\n- [Radix UI](https://radix-ui.com) - For the unstyled, accessible primitives.\n- [Vercel](https://vercel.com) - For hosting.\n- [Shu Ding](https://shud.in) - Typography style adapted from [Nextra](https://nextra.site).\n- [cmdk](https://cmdk.paco.me) - For the `<Command />` component.\n",
          "type": "registry:file",
          "target": "content/docs/plate/index.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-docs-docs",
      "title": "Local Docs",
      "description": "Set up Plate's documentation locally for version-controlled, AI-enhanced development.",
      "files": [
        {
          "path": "../../content/docs/installation/docs.mdx",
          "content": "---\ntitle: Local Docs\ndescription: Set up Plate's documentation locally for version-controlled, AI-enhanced development.\n---\n\nHost Plate's documentation locally to integrate it directly with your project. This setup ensures your team works with documentation that matches your Plate version while enabling AI tools to better understand and assist with your codebase.\n\n## Why Host Documentation Locally?\n\nLocal documentation provides distinct advantages over external sites:\n\n*   **Version Lock-In:** Keep documentation synchronized with your project's Plate version. Avoid confusion from features or APIs in newer, unadopted Plate releases.\n*   **AI-Ready Development:**\n    *   **Better than `llms.txt`:** While dumping docs into a single text file is common for LLM context, this breaks down with large documentation (exceeding typical 100k token limits). Our structured, local setup lets AI tools access exactly what they need.\n    *   **Direct Access for AI Tools:** Your AI-assisted IDE gets direct access to version-specific documentation, enabling accurate code generation and contextual help with your Plate setup.\n    *   **Structured for AI Tasks:** AI tools can help with tasks like translating docs, creating summaries, or updating documentation between Plate versions.\n*   **Customization & Control:** Modify documentation appearance and structure to match your project needs.\n*   **Easy Updates:** Treat documentation like code - review, update, and version control it with your codebase.\n*   **Fast Access:** Get reliable, quick access to documentation.\n*   **Central Hub:** Keep Plate docs alongside documentation for other libraries in one place.\n\n## Setup Guide\n\nThere are two ways to set up local documentation:\n\n### Option 1: Docs App\n\nThis option sets up a complete documentation site using Fumadocs, providing a searchable, navigable interface.\n\n<Steps>\n\n#### Create Fumadocs App\n\nSet up a Fumadocs app which provides the Next.js-based framework for your documentation site:\n\n```bash\npnpm create fumadocs-app\n```\n\nDuring setup:\n- **Name:** Enter `docs` when prompted\n- **Choose your preferred options** (defaults work well)\n- The wizard will create a `docs` directory with all necessary files\n\nNavigate to your newly created `docs` directory:\n\n```bash\ncd docs\n```\n\nFor detailed Fumadocs setup, see the [Fumadocs UI documentation](https://fumadocs.dev/docs/ui).\n\n#### Create `components.json`\n\nIn your docs directory, create a `components.json` file. You have two options:\n\n**Option A: Command line**\n\n```bash\necho '{\\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\\n  \"style\": \"new-york\",\\n  \"rsc\": true,\\n  \"tsx\": true,\\n  \"tailwind\": {\\n    \"config\": \"\",\\n    \"css\": \"app/global.css\",\\n    \"baseColor\": \"neutral\",\\n    \"cssVariables\": true,\\n    \"prefix\": \"\"\\n  },\\n  \"aliases\": {\\n    \"components\": \"@/components\",\\n    \"utils\": \"@/lib/utils\",\\n    \"ui\": \"@/components/ui\",\\n    \"lib\": \"@/lib\",\\n    \"hooks\": \"@/hooks\"\\n  },\\n  \"iconLibrary\": \"lucide\"\\n}' > components.json\n```\n\n**Option B: Copy and paste**\n\nCreate a new file called `components.json` in your docs directory with this content:\n\n```json\n{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"app/global.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}\n```\n\n#### Add Plate Documentation\n\nFetch the Plate documentation files and necessary MDX components from the `@plate` registry.\n\n```bash\nnpx shadcn@latest add @plate/fumadocs\n```\n\n<Callout type=\"warning\" title=\"Version-Specific Documentation\">\n  The command above installs the current Plate documentation from the live registry.\n  For older Plate versions, raw GitHub URLs are safe only for leaf registry\n  items that declare no `registryDependencies`.\n\n  Do not install aggregate items such as `fumadocs.json` or `docs.json` from a\n  raw GitHub tag: their transitive registry dependencies can resolve from the\n  live registry. For full version-pinned docs, generate or serve the registry\n  from the matching Plate checkout so every transitive item comes from the same\n  version.\n</Callout>\n\n#### Run Fumadocs App\n\nStart the development server:\n\n```bash\npnpm run dev\n```\n\nYour documentation site will be available at:\n- Plate docs: `http://localhost:3000/docs/plate`\n\n#### Customize\n\nEnhance your docs with [Fumadocs features](https://fumadocs.dev/docs/ui#writing-content).\n\n</Steps>\n\n### Option 2: MDX Files Only\n\nIf you just want the documentation files without setting up a full site, you can add them directly to your existing project:\n\n```bash\n# Run from your project root (wherever you want the docs)\nnpx shadcn@latest add @plate/docs\n```\n\nThis will:\n- Install MDX documentation files in your project (typically in a `docs/` or similar directory)\n- Skip the Fumadocs setup entirely\n- Give you raw MDX files to use however you need\n\nUse cases:\n- Reference documentation directly in your codebase\n- Integrate with your existing documentation setup\n- Make docs available to AI tools for better context\n\n## Advanced Integration\n\n### MCP Integration\n\nEnable AI tools to work with your local documentation by adding the Plate server to your `.cursor/mcp.json` (or equivalent).\n\n```json\n{\n  \"mcpServers\": {\n    \"shadcn\": {\n      \"description\": \"Shadcn and Plate MCP\",\n      \"command\": \"npx\",\n      \"args\": [\n        \"shadcn@latest\",\n        \"mcp\"\n      ]\n    }\n  }\n}\n```\n\nYour AI tools can then:\n*   Access documentation context for better code assistance\n*   Help manage and update documentation\n*   Generate code with proper imports and configurations\n*   Assist with editor setup and feature integration\n\nSee the [MCP Guide](/docs/plate/installation/mcp) for more details.\n\n<Callout>\n  Once configured, try asking your AI:\n  ```bash\n  \"Help me understand how the Plate AI plugin works\"\n  \"How to create a custom plugin?\"\n  \"What's new in the latest Plate version?\"\n  ```\n</Callout>\n\n### Centralizing Multiple Documentations\n\nYour `content/docs/` directory can host documentation for multiple libraries. Replicate the process for Plate to add docs for other internal or external tools:\n\n```plaintext\ncontent/\n└── docs/\n    ├── plate/         # Plate documentation\n    │   └── ...\n    ├── other-library/ # Documentation for another library\n    │   └── ...\n    └── index.mdx      # Main landing page for all docs\n```\n\nThis creates a unified, version-controlled knowledge base for your project stack.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/docs.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-manual-docs",
      "title": "Manual Installation",
      "description": "Install and configure Plate in your React project without relying on UI component libraries.",
      "files": [
        {
          "path": "../../content/docs/installation/manual.mdx",
          "content": "---\ntitle: Manual Installation\ndescription: Install and configure Plate in your React project without relying on UI component libraries.\n---\n\nThis guide walks you through setting up Plate from scratch, giving you full control over styling and component rendering. This approach is ideal if you're not using a UI library like shadcn/ui or Tailwind CSS.\n\n<Steps>\n\n### Create Project\n\n<Callout type=\"info\">\nThis guide uses **Vite** for demonstrating the initial project setup. Plate is framework-agnostic and integrates seamlessly with other React environments like Next.js or Remix. You can adapt the general setup principles to your chosen framework.\n</Callout>\n\nTo begin with Vite, create a new project and select the **React + TypeScript** template:\n\n```bash\nnpm create vite@latest\n```\n\n### Install Core Dependencies\n\nFirst, install the necessary Plate packages. These packages provide the core editor functionality, React integration, and basic plugins for marks and elements.\n\n```bash\nnpm install platejs @platejs/basic-nodes\n```\n\n-   `platejs`: The core Plate engine and React components.\n-   `@platejs/basic-nodes`: Plugin for basic nodes like headings, bold, italic, underline, etc.\n\n### TypeScript Configuration\n\nPlate provides ESM packages. If you're using TypeScript, ensure your `tsconfig.json` is configured correctly. The recommended setup for Plate requires TypeScript 5.0+ with the `\"moduleResolution\": \"bundler\"` setting:\n\n```jsonc\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    // ... other options\n    \"module\": \"esnext\", // or commonjs if your setup requires it and handles ESM interop\n    \"moduleResolution\": \"bundler\",\n    // ... other options\n  },\n}\n```\n\n<Callout type=\"info\">\n  If you cannot use `\"moduleResolution\": \"bundler\"` or are on an older TypeScript version, please see our [full TypeScript guide](/docs/plate/typescript) for alternative configurations using path aliases.\n</Callout>\n\n### Create Your First Editor\n\nStart by creating a basic editor component. This example sets up a simple editor.\n\n```tsx title=\"src/App.tsx\"\nimport React from 'react';\nimport type { Value } from 'platejs';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nexport default function App() {\n  const editor = usePlateEditor();\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent \n        style={{ padding: '16px 64px', minHeight: '100px' }}\n        placeholder=\"Type your amazing content here...\"\n      />\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"info\">\n  `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor` from `platejs/react`.\n</Callout>\n\n<ComponentPreview name=\"installation-next-01-editor-demo\" height=\"200px\" />\n\nAt this point, you'll have a very basic editor capable of displaying and editing plain text.\n\n### Adding Basic Marks\n\nLet's add support for basic text formatting like bold, italic, and underline.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {4-8,32}\nimport React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BoldPlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  PlateContent,\n  usePlateEditor,\n} from 'platejs/react';\n\nconst initialValue: Value = [\n  {\n    type: 'p',\n    children: [\n      { text: 'Hello! Try out the ' },\n      { text: 'bold', bold: true },\n      { text: ', ' },\n      { text: 'italic', italic: true },\n      { text: ', and ' },\n      { text: 'underline', underline: true },\n      { text: ' formatting.' },\n    ],\n  },\n];\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin],\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      {/* You would typically add a toolbar here to toggle marks */}\n      <PlateContent style={{ padding: '16px 64px', minHeight: '100px' }} />\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"info\" title=\"Default Components\">\n  Mark plugins like `BoldPlugin`, `ItalicPlugin`, and `UnderlinePlugin` come with default components that render as `<strong>`, `<em>`, and `<u>` elements respectively. You don't need to register custom components unless you want to customize their appearance.\n</Callout>\n\n<ComponentPreview name=\"installation-next-02-marks-demo\" height=\"200px\" />\n\nYou'll need to implement your own toolbar to apply these marks. For example, to toggle bold: `editor.tf.bold.toggle()`.\n\n### Adding Basic Elements\n\nNow, let's add support for block-level elements like headings, and blockquotes.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {5,7-9,76-79}\nimport React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  PlateContent,\n  PlateElement,\n  usePlateEditor,\n  type PlateElementProps,\n} from 'platejs/react';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\n// Define element components\nfunction H1Element(props: PlateElementProps) {\n  return <PlateElement as=\"h1\" {...props} />;\n}\n\nfunction H2Element(props: PlateElementProps) {\n  return <PlateElement as=\"h2\" {...props} />;\n}\n\nfunction H3Element(props: PlateElementProps) {\n  return <PlateElement as=\"h3\" {...props} />;\n}\n\nfunction BlockquoteElement(props: PlateElementProps) {\n  return (\n    <PlateElement\n      as=\"blockquote\"\n      style={{\n        borderLeft: '2px solid #eee',\n        marginLeft: 0,\n        marginRight: 0,\n        paddingLeft: '24px',\n        color: '#666',\n        fontStyle: 'italic',\n      }}\n      {...props}\n    />\n  );\n}\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      {/* You would typically add a toolbar here to toggle elements and marks */}\n      <PlateContent style={{ padding: '16px 64px', minHeight: '100px' }} />\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"note\">\n  Notice how we use `Plugin.withComponent(Component)` to register components with block element plugins like headings and blockquotes. This is the recommended approach for associating React components with Plate plugins when you need custom styling or behavior.\n</Callout>\n\n<ComponentPreview name=\"installation-next-03-elements-demo\" height=\"200px\" />\n\n### Handling Editor Value\n\nTo make the editor's content persistent, let's integrate state management to save and load the editor's value.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {81-84,90-92}\nimport React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  PlateContent,\n  PlateElement,\n  usePlateEditor,\n  type PlateElementProps,\n} from 'platejs/react';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\n// Define element components\nfunction H1Element(props: PlateElementProps) {\n  return <PlateElement as=\"h1\" {...props} />;\n}\n\nfunction H2Element(props: PlateElementProps) {\n  return <PlateElement as=\"h2\" {...props} />;\n}\n\nfunction H3Element(props: PlateElementProps) {\n  return <PlateElement as=\"h3\" {...props} />;\n}\n\nfunction BlockquoteElement(props: PlateElementProps) {\n  return (\n    <PlateElement\n      as=\"blockquote\"\n      style={{\n        borderLeft: '2px solid #eee',\n        marginLeft: 0,\n        marginRight: 0,\n        paddingLeft: '24px',\n        color: '#666',\n        fontStyle: 'italic',\n      }}\n      {...props}\n    />\n  );\n}\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: () => {\n      const savedValue = localStorage.getItem('plate-manual-demo');\n      return savedValue ? JSON.parse(savedValue) : initialValue;\n    },\n  });\n\n  return (\n    <Plate\n      editor={editor}\n      onChange={({ value }) => {\n        localStorage.setItem('plate-manual-demo', JSON.stringify(value));\n      }}\n    >\n      {/* Toolbar would go here */}\n      <div style={{ padding: '8px 0' }}>\n        <button\n          onClick={() => editor.tf.setValue(initialValue)}\n          style={{\n            padding: '4px 8px',\n            margin: '0 4px',\n            border: '1px solid #ccc',\n            borderRadius: '4px',\n            cursor: 'pointer',\n          }}\n        >\n          Reset\n        </button>\n      </div>\n      <PlateContent\n        style={{\n          padding: '16px 64px',\n          minHeight: '100px',\n          border: '1px solid #eee',\n          borderRadius: '4px',\n        }}\n        placeholder=\"Type your amazing content here...\"\n      />\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"info\" title=\"Value Management\">\n  The example above demonstrates a basic pattern for managing editor value:\n  - Initial value is set through the `value` option in `usePlateEditor`\n  - Changes can be handled via the `onChange` prop on `<Plate>`\n  - The reset button uses `editor.tf.setValue()` to restore the initial value\n  - To control the value, see [Controlled Value](/docs/plate/controlled)\n</Callout>\n\n<ComponentPreview name=\"installation-next-demo\" />\n\n### Next Steps\n\nYou've now set up a basic Plate editor manually! From here, you can:\n\n*   **Add Styling:**\n    *   For a quick start with pre-built components, consider using [Plate UI](/docs/plate/installation/plate-ui)\n    *   Or continue styling manually using CSS, CSS-in-JS libraries, or your preferred styling solution\n*   **[Add Plugins](/docs/plate/plugins):** Plate has a rich ecosystem of plugins for features like tables, mentions, images, lists, and more. Install their packages (e.g., `@platejs/table`) and add them to your `plugins` array.\n*   **[Build a Toolbar](/docs/plate/toolbar):** Create React components for toolbar buttons that use the [Editor Transforms](/docs/plate/transforms) to apply formatting (e.g., `editor.tf.bold.toggle()`, `editor.tf.h1.toggle()`). You can also use the editor state with the [Editor API](/docs/plate/api).\n*   **Learn More:**\n    *   [Editor Configuration](/docs/plate/editor)\n    *   [Plugin Configuration](/docs/plate/plugin)\n    *   [Plugin Components](/docs/plate/plugin-components)\n\n</Steps>\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/manual.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-mcp-docs",
      "title": "MCP Server",
      "description": "Use the Model Context Protocol with Plate.",
      "files": [
        {
          "path": "../../content/docs/installation/mcp.mdx",
          "content": "---\ntitle: MCP Server\ndescription: Use the Model Context Protocol with Plate.\n---\n\nPlate has an official MCP server. This lets AI tools understand and work with our rich ecosystem of editor templates, plugin configurations, UI components, and documentation.\n\n## What is MCP?\n\nThe Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs. This is useful for Plate because you can now give your AI-assisted IDE direct access to hundreds of Plate resources.\n\n## Using MCP with Plate\n\nYour AI can now:\n\n- Access to all our editor templates, plugins, and UI components\n- Access our complete documentation, including guides and API references\n- Generate code with the right imports and configurations\n- Help with setting up full editor instances or specific features\n- Keep your Plate configurations and components up to date\n\nTry asking your AI:\n\n```bash\n\"Set up a Plate editor with basic formatting and table support\"\n\"Help me understand how the Plate AI plugin works\"\n\"Update my editor components to the latest version\"\n```\n\n## How it works\n\nThe Plate ecosystem provides structured information that MCP-enabled tools can read from a unified registry that includes:\n\n- Editor templates and plugin configurations\n- UI components and their dependencies\n- Documentation files and migration guides\n- API references and examples\n\nThis comprehensive registry ensures AI tools have complete context for both code generation and understanding Plate's features.\n\n## Local Documentation\n\nFor teams working with Plate, integrating local documentation is key to maximizing the benefits of MCP. We recommend following our [Local Docs](/docs/plate/installation/docs) guide to set this up. This approach offers several advantages for AI-powered development:\n\n-   **Version-Specific Context:** AI tools gain direct access to documentation that precisely matches your project's Plate version, ensuring relevant and accurate assistance.\n-   **Superior to `llms.txt`:** Unlike simply dumping documentation into a text file (which can struggle with large volumes and context limits), a structured local setup allows AI to efficiently access the specific information it needs.\n-   **Integrated Workflow:** Documentation becomes a part of your codebase, simplifying updates, version control, and team collaboration.\n-   **AI-Ready:** A well-structured local documentation allows AI to more effectively assist with tasks such as generating code, creating summaries, or explaining complex Plate features within the context of your project.\n\n## Setup MCP\n\nFollow these steps to set up MCP with Plate:\n\n### Step 1: Install Plate\n\nFor an existing shadcn project, add the Plate registry to `components.json`:\n\n```json\n{\n  \"registries\": {\n    \"@plate\": \"https://platejs.org/r/{name}.json\"\n  }\n}\n```\n\nThen add the basic editor from the `@plate` registry:\n\n```bash\nnpx shadcn@latest add @plate/editor-basic\n```\n\nFor a new project, initialize with the Plate preset:\n\n```bash\nnpx shadcn@latest init --preset https://platejs.org/init\n```\n\n### Step 2: Configure your IDE\n\nChoose the configuration for your IDE:\n\n#### Cursor\n\nCopy and paste into `.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"plate\": {\n      \"description\": \"Plate editors, plugins and components\",\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\n        \"shadcn@latest\",\n        \"mcp\"\n      ]\n    }\n  }\n}\n```\n\n#### VS Code\n\nCopy and paste into `.vscode/mcp.json` in your workspace:\n\n```json\n{\n  \"servers\": {\n    \"plate\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\n        \"shadcn@latest\",\n        \"mcp\"\n      ]\n    }\n  }\n}\n```\n\n#### Claude\n\nCopy and paste into `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"plate\": {\n      \"description\": \"Plate editors, plugins and components\",\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\n        \"shadcn@latest\",\n        \"mcp\"\n      ]\n    }\n  }\n}\n```\n\n#### CodeX\n\n1. Open or create the file `~/.codex/config.toml`\n2. Add the following configuration:\n\n```toml\n[mcp_servers.plate]\ncommand = \"npx\"\nargs = [\"shadcn@latest\", \"mcp\"]\n```\n\n3. Restart CodeX to load the MCP server\n\n## Best Practices\n\n1.  **Local Documentation:** Set up local documentation to give AI tools version-specific context. This ensures more accurate assistance, especially for larger projects.\n2.  **AI-Assisted Development:** Let AI handle editor setup, plugin integration, and component additions.\n3.  **Manual Fallback:** Use the [shadcn CLI](/docs/plate/components/cli) for manual additions when needed (e.g., with small models or outdated documentation).\n4.  **Stay Updated:** Keep both your Plate components and local documentation in sync. Use [Sync copied files](/docs/plate/installation/plate-ui#sync-copied-files) when your AI needs to sync copied registry files.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/mcp.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-next-docs",
      "title": "Next.js",
      "description": "Install and configure Plate UI for Next.js",
      "files": [
        {
          "path": "../../content/docs/installation/next.mdx",
          "content": "---\ntitle: Next.js\ndescription: Install and configure Plate UI for Next.js\n---\n\n<Callout type=\"warning\" title=\"Prerequisites\">\n  Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation/next) and [Plate UI](/docs/plate/installation/plate-ui).\n</Callout>\n\nThis guide walks you through incrementally building a Plate editor in your Next.js application.\n\n<Steps>\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/plate/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add @plate/editor\n```\n\nNext, create a basic editor page. This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\"\n'use client';\n\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function MyEditorPage() {\n  const editor = usePlateEditor(); // Initializes the editor instance\n\n  return (\n    <Plate editor={editor}>      {/* Provides editor context */}\n      <EditorContainer>         {/* Styles the editor area */}\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"info\">\n  `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n</Callout>\n\n<ComponentPreview name=\"installation-next-01-editor-demo\" height=\"200px\" />\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/plate/components/fixed-toolbar) and [MarkToolbarButton](/docs/plate/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\n<Callout type=\"info\">\n  The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n</Callout>\n\nUpdate your editor page to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {4,6-10,17-18,20-33,37-38,43-47}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BoldPlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n  {\n    type: 'p',\n    children: [\n      { text: 'Hello! Try out the ' },\n      { text: 'bold', bold: true },\n      { text: ', ' },\n      { text: 'italic', italic: true },\n      { text: ', and ' },\n      { text: 'underline', underline: true },\n      { text: ' formatting.' },\n    ],\n  },\n];\n\nexport default function MyEditorPage() {\n  const editor = usePlateEditor({\n    plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n    value: initialValue,         // Set initial content\n  });\n\n  return (\n    <Plate editor={editor}>\n      <FixedToolbar className=\"justify-start rounded-t-lg\">\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-02-marks-demo\" height=\"200px\" />\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {7,9-11,20,23,25,28-35,52-55,63-67}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\nexport default function MyEditorPage() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      <FixedToolbar className=\"flex justify-start gap-1 rounded-t-lg\">\n        {/* Element Toolbar Buttons */}\n        <ToolbarButton onClick={() => editor.tf.h1.toggle()}>H1</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h2.toggle()}>H2</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h3.toggle()}>H3</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.blockquote.toggle()}>Quote</ToolbarButton>\n        {/* Mark Toolbar Buttons */}\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-03-elements-demo\" height=\"200px\" />\n\n<Callout type=\"info\" title=\"Component Registration\">\n  Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n  For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n  ```bash\n  npx shadcn@latest add @plate/editor-basic\n  ```\n  This handles much of the boilerplate for you.\n</Callout>\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value client-side.\n\n```tsx showLineNumbers title=\"app/editor/page.tsx\" {57-60,66-68,78-84}\n'use client';\n\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\nexport default function MyEditorPage() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: () => {\n      const savedValue = localStorage.getItem('installation-next-demo');\n      return savedValue ? JSON.parse(savedValue) : initialValue;\n    },\n  });\n\n  return (\n    <Plate\n      editor={editor}\n      onChange={({ value }) => {\n        localStorage.setItem('installation-next-demo', JSON.stringify(value));\n      }}\n    >\n      <FixedToolbar className=\"flex justify-start gap-1 rounded-t-lg\">\n        <ToolbarButton onClick={() => editor.tf.h1.toggle()}>H1</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h2.toggle()}>H2</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h3.toggle()}>H3</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.blockquote.toggle()}>Quote</ToolbarButton>\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n        <div className=\"flex-1\" />\n        <ToolbarButton\n          className=\"px-2\"\n          onClick={() => editor.tf.setValue(initialValue)}\n        >\n          Reset\n        </ToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-demo\" />\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in Next.js.\n\nTo further enhance your editor:\n\n*   **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/plate/components), and more.\n*   **Add Plugins:** Integrate features like [Tables](/docs/plate/table), [Mentions](/docs/plate/mention), [AI](/docs/plate/ai), or [Markdown](/docs/plate/markdown).\n*   **Use Editor Blocks:** Quickly set up pre-configured editors:\n    *   Basic editor: `npx shadcn@latest add @plate/editor-basic`\n    *   AI-powered editor: `npx shadcn@latest add @plate/editor-ai`\n*   **Learn More:**\n    *   [Editor Configuration](/docs/plate/editor)\n    *   [Plugin Configuration](/docs/plate/plugin)\n    *   [Plugin Components](/docs/plate/plugin-components)\n\n</Steps>\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/next.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-node-docs",
      "title": "Node.js",
      "description": "Install and configure Plate for Node.js.",
      "files": [
        {
          "path": "../../content/docs/installation/node.mdx",
          "content": "---\ntitle: Node.js\ndescription: Install and configure Plate for Node.js.\n---\n\nUse Plate in Node.js when you need to read, validate, transform, or serialize editor values outside the browser. Node scripts use the base runtime imports, while React editors use `/react` subpaths. This guide walks through a server-safe editor, Markdown IO, and a content transform.\n\n## Node.js Setup\n\n<Callout type=\"warning\" title=\"Use base imports\">\n  Do not import from `platejs/react` or `@platejs/*/react` in Node.js scripts.\n  Use `createSlateEditor` from `platejs` and base plugins from `@platejs/*`\n  packages.\n</Callout>\n\n<Steps>\n\n### Install Packages\n\nInstall the core runtime and the packages your pipeline needs.\n\n```bash\nnpm install platejs @platejs/basic-nodes @platejs/markdown\n```\n\n| Package | Owns |\n| --- | --- |\n| `platejs` | `createSlateEditor`, core editor APIs, core paragraph behavior. |\n| `@platejs/basic-nodes` | Base headings, blockquotes, horizontal rules, and text marks. |\n| `@platejs/markdown` | Markdown serialization, deserialization, and the `MarkdownPlugin` API. |\n\n### Create a Server Editor\n\nCreate the editor with base plugins only. The editor exposes the same `editor.api`\nand `editor.tf` surfaces without mounting a React tree.\n\n```ts showLineNumbers title=\"scripts/process-content.ts\"\nimport type { Value } from 'platejs';\n\nimport {\n  BaseBasicBlocksPlugin,\n  BaseBasicMarksPlugin,\n} from '@platejs/basic-nodes';\nimport { createSlateEditor } from 'platejs';\n\nconst value: Value = [\n  {\n    children: [{ text: 'Document Title' }],\n    type: 'h1',\n  },\n  {\n    children: [\n      { text: 'With ' },\n      { bold: true, text: 'bold' },\n      { text: ' text.' },\n    ],\n    type: 'p',\n  },\n];\n\nconst editor = createSlateEditor({\n  plugins: [BaseBasicBlocksPlugin, BaseBasicMarksPlugin],\n  value,\n});\n\nconst plainText = editor.api.string([]);\n\nconsole.info(plainText);\n```\n\n### Read and Write Markdown\n\nAdd `MarkdownPlugin` when the script needs Markdown helpers through\n`editor.getApi(MarkdownPlugin)`. You can also call `deserializeMd` and\n`serializeMd` directly.\n\n```ts showLineNumbers title=\"scripts/markdown-io.ts\"\nimport {\n  BaseBasicBlocksPlugin,\n  BaseBasicMarksPlugin,\n} from '@platejs/basic-nodes';\nimport {\n  MarkdownPlugin,\n  deserializeMd,\n  serializeMd,\n} from '@platejs/markdown';\nimport { createSlateEditor } from 'platejs';\n\nconst editor = createSlateEditor({\n  plugins: [BaseBasicBlocksPlugin, BaseBasicMarksPlugin, MarkdownPlugin],\n});\n\nconst value = deserializeMd(\n  editor,\n  [\n    '# Migration Note',\n    '',\n    'Move legacy content into **Plate** format.',\n  ].join('\\n')\n);\n\nconst markdown = serializeMd(editor, { value });\n\nconsole.info(markdown);\n```\n\n### Transform Content\n\nUse transforms for migrations and bulk cleanup. Pass `at: []` when the operation\nshould scan the whole document.\n\n```ts showLineNumbers title=\"scripts/normalize-headings.ts\"\nimport type { Value } from 'platejs';\n\nimport {\n  BaseBasicBlocksPlugin,\n  BaseBasicMarksPlugin,\n} from '@platejs/basic-nodes';\nimport { MarkdownPlugin, serializeMd } from '@platejs/markdown';\nimport { createSlateEditor } from 'platejs';\n\nexport function normalizeHeadings(value: Value) {\n  const editor = createSlateEditor({\n    plugins: [BaseBasicBlocksPlugin, BaseBasicMarksPlugin, MarkdownPlugin],\n    value,\n  });\n\n  editor.tf.setNodes(\n    { type: 'h2' },\n    {\n      at: [],\n      match: (node) => 'type' in node && node.type === 'h1',\n    }\n  );\n\n  editor.tf.insertNodes(\n    [{ children: [{ text: 'Imported from the legacy CMS.' }], type: 'p' }],\n    { at: [editor.children.length] }\n  );\n\n  return {\n    markdown: serializeMd(editor),\n    text: editor.api.string([]),\n    value: editor.children,\n  };\n}\n```\n\n</Steps>\n\n## Runtime Boundaries\n\n| Runtime | Import from | Use for |\n| --- | --- | --- |\n| Node.js scripts | `platejs`, `@platejs/*` | Migration, validation, serialization, search indexing. |\n| React editors | `platejs/react`, `@platejs/*/react` | Editable UI, hooks, rendered components, toolbar behavior. |\n| Static rendering | `platejs/static` | Server-rendered read-only content. |\n\n<Callout type=\"info\">\n  Plugin packages can expose both base and React entrypoints. In Node.js, choose\n  the base entrypoint even when the same feature has React components for the\n  browser editor.\n</Callout>\n\n## API Reference\n\n| API | Package | Notes |\n| --- | --- | --- |\n| `createSlateEditor` | `platejs` | Creates a non-React editor instance. |\n| `editor.api.string([])` | `platejs` | Reads text from the whole document. |\n| `editor.tf.setNodes` | `platejs` | Updates matching nodes. Use `at: []` for document-wide transforms. |\n| `editor.tf.insertNodes` | `platejs` | Inserts nodes at a path. |\n| `deserializeMd` | `@platejs/markdown` | Converts Markdown into a Plate value. |\n| `serializeMd` | `@platejs/markdown` | Converts the editor value or an explicit `value` option to Markdown. |\n\n## Next Steps\n\n| Task | Guide |\n| --- | --- |\n| Serialize to Markdown | [Markdown](/docs/plate/markdown) |\n| Serialize to HTML | [HTML](/docs/plate/html) |\n| Render read-only content | [Static Rendering](/docs/plate/static) |\n| Query editor state | [Editor API](/docs/plate/api/slate/editor-api) |\n| Apply transforms | [Editor Transforms](/docs/plate/api/slate/editor-transforms) |\n\nDone. You now have a server-safe Plate runtime that can power migration scripts,\nvalidation jobs, and content serialization.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/node.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-plate-ui-docs",
      "title": "Plate UI",
      "description": "Install, customize, and sync copied Plate UI components.",
      "files": [
        {
          "path": "../../content/docs/installation/plate-ui.mdx",
          "content": "---\ntitle: Plate UI\ndescription: Install, customize, and sync copied Plate UI components.\n---\n\nPlate UI is the copied component layer for Plate. Use the shadcn CLI for the\nfastest setup, or install components manually when you need more control.\n\n## CLI Installation (Recommended)\n\nThis is the fastest way to integrate Plate UI's core dependencies and base styles.\n\n<Steps>\n\n### Install Plate UI\n\n<Callout type=\"info\">\n  If you are not using Next.js, refer to the official [shadcn/ui installation guides](https://ui.shadcn.com/docs/installation) for your specific framework before proceeding.\n</Callout>\n\n```bash\nnpx shadcn@latest add @plate/plate-ui\n```\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n*   **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n*   **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n</Steps>\n\n## Manual Installation\n\nIf you prefer a step-by-step approach or are not using the shadcn CLI, follow these steps:\n\n<Steps>\n\n### Install Plate\n\n```bash\nnpm install platejs\n```\n\n<Callout type=\"note\" title=\"Plugin Dependencies\">\n  When manually installing Plate, you'll need to add the specific packages for each plugin you want to use. For example, if you want to use the `AIMenu` component, you would need `@platejs/ai` and its dependencies. Check each component's documentation for its required packages, or use the CLI to automatically install the required packages.\n</Callout>\n\n### Configure CSS Variables\n\nAdd the following CSS variables to your global stylesheet:\n\n```css title=\"app/globals.css\"\n:root {\n  /* Base brand color for Plate UI components */\n  --brand: oklch(0.623 0.214 259.815);\n}\n\n.dark {\n  --brand: oklch(0.707 0.165 254.624);\n}\n```\n\n### Add Components\n\nWith Plate UI's core installed, you can now add individual Plate UI components to build your editor interface. For example, to add the `FixedToolbar` and a `MarkToolbarButton`:\n\n```bash\nnpx shadcn@latest add @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\nImport and use them in your editor:\n\n```tsx showLineNumbers title=\"components/editor.tsx\"\nimport { Plate } from \"platejs/react\";\nimport { FixedToolbar } from \"@/components/ui/fixed-toolbar\";\nimport { MarkToolbarButton } from \"@/components/ui/mark-toolbar-button\";\n// ... other imports\n\nexport function MyEditor() {\n  // ... editor setup\n  return (\n    <Plate editor={editor}>\n      <FixedToolbar>\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold\">B</MarkToolbarButton>\n        {/* ... other toolbar buttons ... */}\n      </FixedToolbar>\n      {/* ... Editor component ... */}\n    </Plate>\n  );\n}\n```\n\nExplore the available [UI Components](/docs/plate/components) and [Plugin Components](/docs/plate/plugin-components) to customize your editor nodes (like paragraphs, headings, etc.) and build a feature-rich editing experience.\n\n### Basic Usage \n\nWith Plate UI's core installed, proceed to the guide specific to your framework:\n\n*   **[Next.js Guide](./next)** - For server-side rendered applications (Next, Remix, etc.)\n*   **[React Guide](./react)** - For single-page applications (Vite, React Router, etc.)\n\n</Steps>\n\n## Sync copied files\n\nPlate UI components live in your app after installation. Use this workflow when\nan agent needs to merge upstream registry file changes into local copies while\nkeeping app-specific edits.\n\nFor release review, use [Releases](/docs/plate/releases). For scoped sync data, use\nthe raw registry JSON.\n\n<Steps>\n\n### Install the skill\n\n```bash\nnpx skills add sync-plate-ui\n```\n\n### Pick the JSON source\n\nUse the index for ordered update events. Use the component map when you need the\nevents for one copied registry item.\n\n- [Index JSON](/registry/changelog/index.json)\n- [Components JSON](/registry/changelog/components.json)\n\n### Prompt your agent\n\n```text\nUse sync-plate-ui to sync code-block-node from:\nhttps://platejs.org/registry/changelog/index.json\n```\n\n</Steps>\n\n### Agent contract\n\n- Use `/registry/changelog/index.json` for ordered Plate UI update events.\n- Use `/registry/changelog/components.json` to find events by registry item.\n- Open event JSON for the PR, release dependency, target files, and migration\n  notes.\n- Merge upstream file changes into the app copy; keep intentional local changes.\n\n## Component Types\n\nWhen installing Plate UI components, you'll encounter different types of components with consistent naming patterns. Here's what they mean:\n\n### Feature Kits\n\nFeature kits are the easiest way to add complete functionality to your editor. They bundle all necessary plugin configurations, UI components (including node renderers and toolbar items), and their underlying npm dependencies for a specific feature.\n\n```bash\n# Install the complete AI feature suite, including configuration and UI\nnpx shadcn@latest add @plate/ai-kit\n\n# Install drag and drop functionality with all its parts\nnpx shadcn@latest add @plate/dnd-kit\n```\n\n<Callout type=\"note\">\n  When in doubt about what individual components you need, start with a feature kit. It's designed to provide a working feature out-of-the-box. You can always customize or remove parts later.\n</Callout>\n\n### Components\n\n#### Node Components\n\nThese components are responsible for rendering specific types of content (elements or leaves) in your editor. If you need to create your own or customize existing ones, see the [Plugin Components guide](/docs/plate/plugin-components).\n\n```bash\n# Install the component for rendering blockquotes\nnpx shadcn@latest add @plate/blockquote-node\n\n# Install the component for rendering code text\nnpx shadcn@latest add @plate/code-node\n```\n\n#### Toolbar Components\n\nToolbar components add interactive controls like buttons and dropdowns to your editor's toolbar.\n\n```bash\n# Add an alignment dropdown for your toolbar\nnpx shadcn@latest add @plate/align-toolbar-button\n\n# Add a toolbar button for AI features\nnpx shadcn@latest add @plate/ai-toolbar-button\n\n# Add a color picker dropdown for your toolbar\nnpx shadcn@latest add @plate/font-color-toolbar-button\n```\n\n#### Other Components\n\nFinally, Plate UI includes more advanced editor components as overlays, menus, block wrappers, etc.\n\n```bash\n# Install a menu for AI features\nnpx shadcn@latest add @plate/ai-menu\n\n# Install a draggable component for reordering blocks\nnpx shadcn@latest add @plate/block-draggable\n```\n\nThese components are typically included as part of their respective Feature Kits but can be installed individually for custom setups.\n\n### Editor Templates\n\nThese are pre-configured editor setups, often tailored for specific use cases or as a comprehensive starting point. You can explore all available [Editor Templates](/editors).\n\n```bash\n# Install an AI-enabled editor template\nnpx shadcn@latest add @plate/editor-ai\n\n# Install a basic editor template\nnpx shadcn@latest add @plate/editor-basic\n```\n\n### API Routes\n\nThese items provide server-side components or API route handlers necessary for certain features.\n\n```bash\n# Install AI-related API routes\nnpx shadcn@latest add @plate/ai-api\n\n# Install file upload API routes (e.g., for UploadThing)\nnpx shadcn@latest add @plate/media-uploadthing-api\n```\n\n### Documentation\n\nDocumentation files for various Plate features, guides, and API references can also be added to your local project. This is especially useful for keeping version-specific documentation alongside your code and for providing context to AI tools through MCP. Learn more about [setting up local documentation](/docs/plate/installation/docs).\n\n```bash\n# Add AI documentation\nnpx shadcn@latest add @plate/ai-docs\n\n# Add Plate Plugin documentation\nnpx shadcn@latest add @plate/plugin-docs\n```\n\nThese items consists of Markdown files, which can be integrated into your own documentation system or made available for local search and AI consumption.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/plate-ui.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-react-docs",
      "title": "React",
      "description": "Install and configure Plate UI for React",
      "files": [
        {
          "path": "../../content/docs/installation/react.mdx",
          "content": "---\ntitle: React\ndescription: Install and configure Plate UI for React\n---\n\n<Callout type=\"warning\" title=\"Prerequisites\">\n  Before you begin, ensure you have installed and configured [shadcn/ui](https://ui.shadcn.com/docs/installation) (adapted for your framework, e.g., Vite) and [Plate UI](/docs/plate/installation/plate-ui).\n</Callout>\n\nThis guide walks you through incrementally building a Plate editor in your project.\n\n<Steps>\n\n### Create Your First Editor\n\nStart by adding the core [Editor](/docs/plate/components/editor) component to your project:\n\n```bash\nnpx shadcn@latest add @plate/editor\n```\n\nNext, create a basic editor in your main application file (e.g. `src/App.tsx`). This example sets up a simple editor within an `EditorContainer`.\n\n```tsx showLineNumbers title=\"src/App.tsx\"\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport default function App() {\n  const editor = usePlateEditor(); // Initializes the editor instance\n\n  return (\n    <Plate editor={editor}>      {/* Provides editor context */}\n      <EditorContainer>         {/* Styles the editor area */}\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<Callout type=\"info\">\n  `usePlateEditor` creates a memoized editor instance, ensuring stability across re-renders. For a non-memoized version, use `createPlateEditor`.\n</Callout>\n\n<ComponentPreview name=\"installation-next-01-editor-demo\" height=\"200px\" />\n\n### Adding Basic Marks\n\nEnhance your editor with text formatting. Add the **Basic Nodes Kit**, [FixedToolbar](/docs/plate/components/fixed-toolbar) and [MarkToolbarButton](/docs/plate/components/mark-toolbar-button) components:\n\n```bash\nnpx shadcn@latest add @plate/basic-nodes-kit @plate/fixed-toolbar @plate/mark-toolbar-button\n```\n\n<Callout type=\"info\">\n  The `basic-nodes-kit` includes all the basic plugins (bold, italic, underline, headings, blockquotes, etc.) and their components that we'll use in the following steps.\n</Callout>\n\nUpdate your `src/App.tsx` to include these components and the basic mark plugins.\nThis example adds bold, italic, and underline functionality.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {2,4-8,15-16,18-31,35-36,41-45}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BoldPlugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\n\nconst initialValue: Value = [\n  {\n    type: 'p',\n    children: [\n      { text: 'Hello! Try out the ' },\n      { text: 'bold', bold: true },\n      { text: ', ' },\n      { text: 'italic', italic: true },\n      { text: ', and ' },\n      { text: 'underline', underline: true },\n      { text: ' formatting.' },\n    ],\n  },\n];\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [BoldPlugin, ItalicPlugin, UnderlinePlugin], // Add the mark plugins\n    value: initialValue,         // Set initial content\n  });\n\n  return (\n    <Plate editor={editor}>\n      <FixedToolbar className=\"justify-start rounded-t-lg\">\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-02-marks-demo\" height=\"200px\" />\n\n### Adding Basic Elements\n\nIntroduce block-level elements like headings and blockquotes with custom components.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {5,7-9,18,21,23,26-33,50-53,61-65}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar'; // Generic toolbar button\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      <FixedToolbar className=\"flex justify-start gap-1 rounded-t-lg\">\n        {/* Element Toolbar Buttons */}\n        <ToolbarButton onClick={() => editor.tf.h1.toggle()}>H1</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h2.toggle()}>H2</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h3.toggle()}>H3</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.blockquote.toggle()}>Quote</ToolbarButton>\n        {/* Mark Toolbar Buttons */}\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-03-elements-demo\" height=\"200px\" />\n\n<Callout type=\"info\" title=\"Component Registration\">\n  Notice how we use `Plugin.withComponent(Component)` to register components with their respective plugins. This is the recommended approach for associating React components with Plate plugins.\n\n  For a quicker start with common plugins and components pre-configured, use the `editor-basic` block:\n  ```bash\n  npx shadcn@latest add @plate/editor-basic\n  ```\n  This handles much of the boilerplate for you.\n</Callout>\n\n### Handling Editor Value\n\nTo make the editor content persistent, let's integrate `localStorage` to save and load the editor's value.\n\n```tsx showLineNumbers title=\"src/App.tsx\" {55-58,64-66,76-82}\nimport * as React from 'react';\nimport type { Value } from 'platejs';\n\nimport {\n  BlockquotePlugin,\n  BoldPlugin,\n  H1Plugin,\n  H2Plugin,\n  H3Plugin,\n  ItalicPlugin,\n  UnderlinePlugin,\n} from '@platejs/basic-nodes/react';\nimport {\n  Plate,\n  usePlateEditor,\n} from 'platejs/react';\n\nimport { BlockquoteElement } from '@/components/ui/blockquote-node';\nimport { Editor, EditorContainer } from '@/components/ui/editor';\nimport { FixedToolbar } from '@/components/ui/fixed-toolbar';\nimport { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';\nimport { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';\nimport { ToolbarButton } from '@/components/ui/toolbar';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Title' }],\n    type: 'h3',\n  },\n  {\n    children: [\n      {\n        children: [{ text: 'This is a quote.' }],\n        type: 'p',\n      },\n    ],\n    type: 'blockquote',\n  },\n  {\n    children: [\n      { text: 'With some ' },\n      { bold: true, text: 'bold' },\n      { text: ' text for emphasis!' },\n    ],\n    type: 'p',\n  },\n];\n\nexport default function App() {\n  const editor = usePlateEditor({\n    plugins: [\n      BoldPlugin,\n      ItalicPlugin,\n      UnderlinePlugin,\n      H1Plugin.withComponent(H1Element),\n      H2Plugin.withComponent(H2Element),\n      H3Plugin.withComponent(H3Element),\n      BlockquotePlugin.withComponent(BlockquoteElement),\n    ],\n    value: () => {\n      const savedValue = localStorage.getItem('installation-react-demo');\n      return savedValue ? JSON.parse(savedValue) : initialValue;\n    },\n  });\n\n  return (\n    <Plate\n      editor={editor}\n      onChange={({ value }) => {\n        localStorage.setItem('installation-react-demo', JSON.stringify(value));\n      }}\n    >\n      <FixedToolbar className=\"flex justify-start gap-1 rounded-t-lg\">\n        <ToolbarButton onClick={() => editor.tf.h1.toggle()}>H1</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h2.toggle()}>H2</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.h3.toggle()}>H3</ToolbarButton>\n        <ToolbarButton onClick={() => editor.tf.blockquote.toggle()}>Quote</ToolbarButton>\n        <MarkToolbarButton nodeType=\"bold\" tooltip=\"Bold (⌘+B)\">B</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"italic\" tooltip=\"Italic (⌘+I)\">I</MarkToolbarButton>\n        <MarkToolbarButton nodeType=\"underline\" tooltip=\"Underline (⌘+U)\">U</MarkToolbarButton>\n        <div className=\"flex-1\" />\n        <ToolbarButton\n          className=\"px-2\"\n          onClick={() => editor.tf.setValue(initialValue)}\n        >\n          Reset\n        </ToolbarButton>\n      </FixedToolbar>\n      <EditorContainer>\n        <Editor placeholder=\"Type your amazing content here...\" />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n<ComponentPreview name=\"installation-next-demo\" />\n\n### Next Steps\n\nCongratulations! You've built a foundational Plate editor in your project.\n\nTo further enhance your editor:\n\n*   **Explore Components:** Discover [Toolbars, Menus, Node components](/docs/plate/components), and more.\n*   **Add Plugins:** Integrate features like [Tables](/docs/plate/plugins/table), [Mentions](/docs/plate/plugins/mention), [AI](/docs/plate/plugins/ai), or [Markdown](/docs/plate/plugins/markdown).\n*   **Use Editor Blocks:** Quickly set up pre-configured editors:\n    *   Basic editor: `npx shadcn@latest add @plate/editor-basic`\n    *   AI-powered editor: `npx shadcn@latest add @plate/editor-ai`\n*   **Learn More:**\n    *   [Editor Configuration](/docs/plate/editor)\n    *   [Plugin Configuration](/docs/plate/plugin)\n    *   [Plugin Components](/docs/plate/plugin-components)\n\n</Steps>\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/react.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-rsc-docs",
      "title": "RSC",
      "description": "Install and configure Plate for React Server Components.",
      "files": [
        {
          "path": "../../content/docs/installation/rsc.mdx",
          "content": "---\ntitle: RSC\ndescription: Install and configure Plate for React Server Components.\n---\n\nUse Plate in React Server Components when you need to render read-only editor content or generate HTML on the server. Interactive editors still belong in client components with `platejs/react`. This guide shows the server-safe static editor path, HTML serialization, and the boundary with Node-only processing.\n\n## RSC Setup\n\n<Callout type=\"warning\" title=\"Use static imports\">\n  Do not import `platejs/react` or `@platejs/*/react` from a Server Component.\n  Use `platejs/static`, `platejs`, and base `@platejs/*` entrypoints.\n</Callout>\n\n<Steps>\n\n### Install the Base Kit\n\nInstall the copied base editor kit when you want Plate UI static components and\nthe default serializer plugins.\n\n```bash\nnpx shadcn@latest add @plate/editor-base-kit\n```\n\nThe `editor-base-kit` item installs `BaseEditorKit`, static node components, and\nthe `editor-static` UI component used by server rendering.\n\n### Render Static Content\n\nCreate a static editor in the Server Component and render it with\n`<PlateStatic>`.\n\n```tsx title=\"app/documents/page.tsx\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { createStaticEditor, PlateStatic } from 'platejs/static';\n\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\n\nconst value: Value = [\n  {\n    children: [{ text: 'Server-rendered document' }],\n    type: 'h1',\n  },\n  {\n    children: [{ text: 'This content is rendered without editor state.' }],\n    type: 'p',\n  },\n];\n\nexport default function DocumentsPage() {\n  const editor = createStaticEditor({\n    plugins: BaseEditorKit,\n    value,\n  });\n\n  return <PlateStatic editor={editor} />;\n}\n```\n\n### Serialize HTML\n\nUse `serializeHtml` when a route needs an HTML string instead of React output.\n\n```tsx title=\"app/api/document-html/route.ts\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { createStaticEditor, serializeHtml } from 'platejs/static';\n\nimport { BaseEditorKit } from '@/components/editor/editor-base-kit';\nimport { EditorStatic } from '@/components/ui/editor-static';\n\nconst value: Value = [\n  {\n    children: [{ text: 'Exported document' }],\n    type: 'h1',\n  },\n  {\n    children: [{ text: 'This HTML is generated on the server.' }],\n    type: 'p',\n  },\n];\n\nexport async function GET() {\n  const editor = createStaticEditor({\n    plugins: BaseEditorKit,\n    value,\n  });\n\n  const html = await serializeHtml(editor, {\n    editorComponent: EditorStatic,\n  });\n\n  return new Response(html, {\n    headers: { 'content-type': 'text/html; charset=utf-8' },\n  });\n}\n```\n\n### Use Base Packages Without Plate UI\n\nIf you only need data transforms or Markdown IO, use the Node.js path instead\nof static React rendering.\n\n```bash\nnpm install platejs @platejs/basic-nodes @platejs/markdown\n```\n\nThat path uses `createSlateEditor` from `platejs` and does not need copied UI\ncomponents.\n\n</Steps>\n\n## Runtime Boundaries\n\n| Runtime | Import from | Use for |\n| --- | --- | --- |\n| Server Component | `platejs/static` | Read-only React output with `<PlateStatic>`. |\n| Route handler | `platejs/static` | HTML export with `serializeHtml`. |\n| Node script | `platejs`, `@platejs/*` | Validation, migration, Markdown IO. |\n| Client editor | `platejs/react`, `@platejs/*/react` | Editable UI, hooks, toolbar interactions. |\n\n<Callout type=\"info\">\n  `createStaticEditor` includes the static `ViewPlugin` before your plugins.\n  Use it for rendered output. Use `createSlateEditor` when the script only\n  reads or transforms a value.\n</Callout>\n\n## Next Steps\n\n| Task | Guide |\n| --- | --- |\n| Learn the static renderer. | [Static Rendering](/docs/plate/static) |\n| Generate HTML. | [HTML](/docs/plate/html) |\n| Read or write Markdown. | [Markdown](/docs/plate/markdown) |\n| Transform content without React. | [Node.js](/docs/plate/installation/node) |\n\nDone. Your Server Component can render Plate content without shipping the\ninteractive editor runtime.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation/rsc.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "installation-docs",
      "title": "Getting Started",
      "description": "Choose the right Plate installation path for your React project.",
      "files": [
        {
          "path": "../../content/docs/installation.mdx",
          "content": "---\ntitle: Getting Started\ndescription: Choose the right Plate installation path for your React project.\n---\n\nPlate has different setup paths for UI-first editors, headless React editors, server components, and backend processing. Start with Plate UI when you want a complete editor surface. Use the lower-level guides when you need to own the rendering, runtime environment, or server-only pipeline.\n\n## Choose a Path\n\n<Cards>\n  <Card\n    href=\"/docs/plate/installation/plate-ui\"\n    title=\"Plate UI\"\n    description=\"Recommended. Install the shadcn-based editor UI, kits, and starter components.\"\n  />\n  <Card\n    href=\"/docs/plate/installation/next\"\n    title=\"Next.js\"\n    description=\"Use Plate in a server-rendered React app.\"\n  />\n  <Card\n    href=\"/docs/plate/installation/react\"\n    title=\"React\"\n    description=\"Use Plate in Vite, React Router, or another client-side React app.\"\n  />\n  <Card\n    href=\"/docs/plate/installation/manual\"\n    title=\"Manual\"\n    description=\"Build a headless editor with direct package installs.\"\n  />\n</Cards>\n\n<Steps>\n\n### Install Plate UI\n\nUse Plate UI when you want the fastest working editor path.\n\n```bash\nnpx shadcn@latest add @plate/plate-ui\n```\n\n### Pick the Runtime Guide\n\nAfter Plate UI is installed, choose the runtime guide that matches your app:\n\n- [Next.js](/docs/plate/installation/next) for server-rendered React apps.\n- [React](/docs/plate/installation/react) for Vite, React Router, and other client-side apps.\n\n### Add Features\n\nUse feature kits when you want complete plugin + UI wiring. Use manual plugin docs when you want only the headless package behavior.\n\n</Steps>\n\n## Other Environments\n\n| Environment | Use it when | Guide |\n| --- | --- | --- |\n| Manual React | You do not want Plate UI or shadcn/ui. | [Manual Installation](/docs/plate/installation/manual) |\n| React Server Components | You need static content generation or server-side editor processing without client-side interactivity. | [RSC](/docs/plate/installation/rsc) |\n| Node.js | You need backend migration, validation, or serialization scripts. | [Node.js](/docs/plate/installation/node) |\n\n<Callout type=\"note\">\n  Server-only environments use base package imports like `platejs` and `@platejs/basic-nodes`. Do not import from `/react` subpaths in RSC or Node.js.\n</Callout>\n\n## Next Steps\n\nOnce the editor runs, use these docs to customize it:\n\n- [Editor](/docs/plate/editor) explains the editor instance.\n- [Plugin](/docs/plate/plugin) explains plugin configuration.\n- [Plugin Components](/docs/plate/plugin-components) explains node and mark rendering.\n- [Troubleshooting](/docs/plate/troubleshooting) covers common setup issues.\n\nDone. You now have the right install path for your app surface.\n",
          "type": "registry:file",
          "target": "content/docs/plate/installation.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "migration-slate-to-plate-docs",
      "title": "From Slate to Plate",
      "description": "Move a Slate React editor to Plate's editor, plugin, and rendering model.",
      "files": [
        {
          "path": "../../content/docs/migration/slate-to-plate.mdx",
          "content": "---\ntitle: From Slate to Plate\ndescription: Move a Slate React editor to Plate's editor, plugin, and rendering model.\n---\n\nPlate keeps Slate's document model and moves editor setup, rendering, handlers,\nand command wiring into plugins. Migrate the editor shell first, then move\ncustom rendering and behavior into plugins.\n\n## Install\n\n```bash\nnpm install platejs\n```\n\nUse feature packages only for the nodes, marks, or behavior you add to the\neditor. Plate UI users should start with [Plate UI](/docs/plate/installation/plate-ui)\ninstead of rebuilding every component by hand.\n\n## Migration Map\n\n| Slate surface | Plate surface |\n| --- | --- |\n| `createEditor()` plus `withReact()` | `usePlateEditor()` in React components, or `createPlateEditor()` in factories and tests. |\n| `<Slate>` plus `<Editable>` | `<Plate>` plus `<PlateContent>`. |\n| `renderElement` / `renderLeaf` switch statements | Plugin components through `.withComponent()` or `node.component`. |\n| `withX(editor)` plugin functions | `.overrideEditor()` for wrappers, `.extend*()` for new APIs and transforms. |\n| Top-level event handlers on `Editable` | Plugin `handlers` or `shortcuts`. |\n| `Transforms.*` imports | `editor.tf.*` transforms. |\n| `Editor.*` imports | `editor.api.*` queries. |\n\n## Editor Shell\n\nMove the editor value into the editor creation call and render the editable with `PlateContent`.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers\n'use client';\n\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nconst initialValue = [\n  {\n    children: [{ text: 'Hello Plate.' }],\n    type: 'p',\n  },\n];\n\nexport function Editor() {\n  const editor = usePlateEditor({\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent className=\"p-4\" />\n    </Plate>\n  );\n}\n```\n\nUse `createPlateEditor()` when the editor is created outside React memoization.\n\n```ts title=\"lib/create-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  value: [\n    {\n      children: [{ text: 'Draft' }],\n      type: 'p',\n    },\n  ],\n});\n```\n\n## Custom Elements\n\nReplace `renderElement` branches with node plugins. Use `.withComponent()` when the only change is the React component.\n\n```tsx title=\"components/editor/paragraph-plugin.tsx\" showLineNumbers\nimport {\n  ParagraphPlugin,\n  PlateElement,\n  type PlateElementProps,\n} from 'platejs/react';\n\nexport function ParagraphElement({\n  children,\n  ...props\n}: PlateElementProps) {\n  return (\n    <PlateElement className=\"m-0 px-0 py-1\" {...props}>\n      {children}\n    </PlateElement>\n  );\n}\n\nexport const AppParagraphPlugin = ParagraphPlugin.withComponent(\n  ParagraphElement\n);\n```\n\nIf your Slate document stores a custom type like `paragraph`, keep that type on the plugin.\n\n```tsx title=\"components/editor/paragraph-plugin.tsx\" showLineNumbers\nexport const AppParagraphPlugin = ParagraphPlugin.configure({\n  node: { type: 'paragraph' },\n}).withComponent(ParagraphElement);\n```\n\n## Custom Behavior\n\nUse `.overrideEditor()` when the Slate plugin wrapped an existing editor method.\n\n```tsx title=\"components/editor/limit-exclamation-plugin.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const LimitExclamationPlugin = createPlatePlugin({\n  key: 'limitExclamation',\n}).overrideEditor(({ tf: { insertText } }) => ({\n  transforms: {\n    insertText(text, options) {\n      insertText(text === '!' ? '.' : text, options);\n    },\n  },\n}));\n```\n\nUse `.extendEditorApi()` or `.extendEditorTransforms()` when the plugin adds a new method.\n\n```tsx title=\"components/editor/signature-plugin.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const SignaturePlugin = createPlatePlugin({\n  key: 'signature',\n}).extendEditorTransforms(({ editor }) => ({\n  insertSignature() {\n    editor.tf.insertText(' - Plate');\n  },\n}));\n```\n\n## Handlers And Shortcuts\n\nMove editor events into the plugin that owns the behavior.\n\n```tsx title=\"components/editor/tab-plugin.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const TabPlugin = createPlatePlugin({\n  key: 'tab',\n  handlers: {\n    onKeyDown: ({ event }) => {\n      if (event.key !== 'Tab') return false;\n\n      event.preventDefault();\n\n      return true;\n    },\n  },\n});\n```\n\nUse `shortcuts` when the key combination should call a plugin API, transform, or explicit handler.\n\n```tsx title=\"components/editor/save-plugin.tsx\" showLineNumbers\nimport { createPlatePlugin } from 'platejs/react';\n\nexport const SavePlugin = createPlatePlugin({\n  key: 'save',\n}).extend({\n  shortcuts: {\n    draft: {\n      keys: 'mod+s',\n      handler: ({ event }) => {\n        event.preventDefault();\n\n        return true;\n      },\n    },\n  },\n});\n```\n\n## API Calls\n\nPlate keeps Slate-style direct methods for compatibility, but plugin code should use the namespaced API and transform surfaces.\n\n```ts title=\"editor-commands.ts\"\neditor.tf.toggleMark('bold');\neditor.tf.insertText('Hello');\n\nconst text = editor.api.string([]);\n\nif (editor.selection) {\n  const isStart = editor.api.isStart(editor.selection.anchor, []);\n}\n```\n\n## Headless Code\n\nUse `createSlateEditor` from `platejs` for non-React importers, serializers, transforms, and tests.\n\n```ts title=\"lib/headless-editor.ts\"\nimport { createSlateEditor } from 'platejs';\n\nexport const editor = createSlateEditor({\n  value: [\n    {\n      children: [{ text: 'Headless document.' }],\n      type: 'p',\n    },\n  ],\n});\n```\n\n## Related\n\n- [Editor](/docs/plate/editor) for editor creation options.\n- [Plugin Components](/docs/plate/plugin-components) for replacing `renderElement` and `renderLeaf`.\n- [Plugin Methods](/docs/plate/plugin-methods) for `.configure()`, `.extend*()`, and `.overrideEditor()`.\n- [Plugin Shortcuts](/docs/plate/plugin-shortcuts) for keyboard command wiring.\n",
          "type": "registry:file",
          "target": "content/docs/plate/migration/slate-to-plate.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "migration-v48-docs",
      "title": "Major Releases up to v48",
      "description": "Historical upgrade notes for Plate apps on v48 and earlier.",
      "files": [
        {
          "path": "../../content/docs/migration/v48.mdx",
          "content": "---\ntitle: Major Releases up to v48\ndescription: Historical upgrade notes for Plate apps on v48 and earlier.\n---\n\nThis is the compatibility archive for apps still on `@udecode/*` packages\nthrough Plate v48. Use it to identify the breaking-change family, then follow\nthe current docs for the target API.\n\n## Start Here\n\n<Callout type=\"info\" title=\"Current release notes\">\n  Use [Releases](/docs/plate/releases) for v49 and later. Use\n  [From Slate to Plate](/docs/plate/migration/slate-to-plate) when the source editor\n  is plain Slate rather than an older Plate app.\n</Callout>\n\n# 48.0.0\n\n## @udecode/plate-core@48.0.0\n\n- [#4281](https://github.com/udecode/plate/pull/4281) by [@zbeyens](https://github.com/zbeyens) –\n\n  - `PlateElement`, `PlateLeaf` and `PlateText` HTML attributes are moved from top-level props to `attributes` prop, except `className`, `style` and `as`. Migration:\n\n  ```tsx\n  // From\n  <PlateElement\n    {...props}\n    ref={ref}\n    contentEditable={false}\n  >\n    {children}\n  </PlateElement>\n\n  // To\n  <PlateElement\n    {...props}\n    ref={ref}\n    attributes={{\n      ...props.attributes,\n      contentEditable: false,\n    }}\n  >\n    {children}\n  </PlateElement>\n  ```\n\n  - Remove `nodeProps` prop from `PlateElement`, `PlateLeaf`, `PlateText`. It has been merged into `attributes` prop.\n  - Plugin `node.props` should return the props directly instead of inside `nodeProps` object. Migration:\n\n  ```ts\n  // From\n  node: {\n    props: ({ element }) => ({\n      nodeProps: {\n        colSpan: element?.attributes?.colspan,\n        rowSpan: element?.attributes?.rowspan,\n      },\n    });\n  }\n\n  // To\n  node: {\n    props: ({ element }) => ({\n      colSpan: element?.attributes?.colspan,\n      rowSpan: element?.attributes?.rowspan,\n    });\n  }\n  ```\n\n  - Remove `asChild` prop from `PlateElement`, `PlateLeaf`, `PlateText`. Use `as` prop instead.\n  - Remove `elementToAttributes`, `leafToAttributes`, `textToAttributes` props from `PlateElement`, `PlateLeaf`, `PlateText`.\n  - Remove `DefaultElement`, `DefaultLeaf`, `DefaultText`. Use `PlateElement`, `PlateLeaf`, `PlateText` instead.\n  - Types: remove `PlateRenderElementProps`, `PlateRenderLeafProps`, `PlateRenderTextProps`. Use `PlateElementProps`, `PlateLeafProps`, `PlateTextProps` instead.\n\n## @udecode/plate-utils@48.0.0\n\n- [#4281](https://github.com/udecode/plate/pull/4281) by [@zbeyens](https://github.com/zbeyens) –\n  - Moved `PlateElement`, `PlateLeaf`, `PlateText` to `@udecode/plate-core`. No migration needed if you're importing from `@udecode/plate`.\n\n## @udecode/plate-yjs@48.0.0\n\n- [#4225](https://github.com/udecode/plate/pull/4225) by [@bbyiringiro](<https://github.com/[bbyiringiro](https://github.com/bbyiringiro)>) –\n\n  - Add multi-provider support for improved collaboration: now supports both Hocuspocus and WebRTC simultaneously using a shared Y.Doc.\n    - **Migration**: Replace `hocuspocusProviderOptions` with the new `providers` array. See examples below.\n\n  **Before:**\n\n  ```tsx\n  YjsPlugin.configure({\n    options: {\n      cursorOptions: {\n        /* ... */\n      },\n      hocuspocusProviderOptions: {\n        url: 'wss://hocuspocus.example.com',\n        name: 'document-1',\n        // ... other Hocuspocus options\n      },\n    },\n  });\n  ```\n\n  **After (Hocuspocus only):**\n\n  ```tsx\n  YjsPlugin.configure({\n    options: {\n      cursors: {\n        /* ... */\n      },\n      providers: [\n        {\n          type: 'hocuspocus',\n          options: {\n            url: 'wss://hocuspocus.example.com',\n            name: 'document-1',\n            // ... other Hocuspocus options\n          },\n        },\n      ],\n    },\n  });\n  ```\n\n  **After (Hocuspocus + WebRTC):**\n\n  ```tsx\n  YjsPlugin.configure({\n    options: {\n      cursors: {\n        /* ... */\n      },\n      providers: [\n        {\n          type: 'hocuspocus',\n          options: {\n            url: 'wss://hocuspocus.example.com',\n            name: 'document-1',\n          },\n        },\n        {\n          type: 'webrtc',\n          options: {\n            roomName: 'document-1',\n            // signaling: ['wss://signaling.example.com'], // Optional\n          },\n        },\n      ],\n    },\n  });\n  ```\n\n  - Introduces `UnifiedProvider` interface that enables custom provider implementations (e.g., IndexedDB for offline persistence).\n  - Renamed `cursorOptions` to `cursors`.\n  - Merged `yjsOptions` into `options`.\n    - **Migration**: Move options previously under `yjsOptions` directly into the main `options` object.\n  - Removed `YjsAboveEditable`. You should now call `init` and `destroy` manually:\n\n  ```tsx\n  React.useEffect(() => {\n    if (!mounted) return;\n\n    // Initialize Yjs connection and sync\n    editor.getApi(YjsPlugin).yjs.init({\n      id: roomName, // Or your document identifier\n      value: INITIAL_VALUE, // Your initial editor content\n    });\n\n    // Destroy connection on component unmount\n    return () => {\n      editor.getApi(YjsPlugin).yjs.destroy();\n    };\n  }, [editor, mounted, roomName]); // Add relevant dependencies\n  ```\n\n# 47.0.0\n\n## @udecode/plate-markdown\n\n- [#4174](https://github.com/udecode/plate/pull/4174) by [@felixfeng33](https://github.com/felixfeng33) – #### New Features\n\n  - Added support for math type deserialization\n  - Added default underline mark serialization as `<u>underline</u>`\n  - Improved serialization process:\n    - Now uses a two-step process: `slate nodes => MDAST nodes => markdown string`\n    - Previously: direct conversion from Slate nodes to markdown string\n    - Results in more reliable and robust serialization\n  - New node filtering options:\n    - `allowedNodes`: Whitelist specific nodes\n    - `disallowedNodes`: Blacklist specific nodes\n    - `allowNode`: Custom function to filter nodes\n  - New `rules` option for customizing serialization and deserialization rules, including **custom mdx** support\n  - New `remarkPlugins` option to use [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)\n\n  #### Breaking Changes\n\n  **Plugin Options**\n\n  Removed options:\n\n  - `elementRules` use `rules` instead\n  - `textRules` use `rules` instead\n  - `indentList` now automatically detects if the IndentList plugin is used\n  - `splitLineBreaks` deserialize only\n\n  ##### Deserialization\n\n  - Removed `elementRules` and `textRules` options\n    - Use `rules.key.deserialize` instead\n    - See [nodes documentation](https://platejs.org/docs/markdown)\n\n  Example migration:\n\n  ```tsx\n  export const markdownPlugin = MarkdownPlugin.configure({\n    options: {\n      disallowedNodes: [SuggestionPlugin.key],\n      rules: {\n        // For textRules\n        [BoldPlugin.key]: {\n          mark: true,\n          deserialize: (mdastNode) => ({\n            bold: true,\n            text: node.value || '',\n          }),\n        },\n        // For elementRules\n        [EquationPlugin.key]: {\n          deserialize: (mdastNode, options) => ({\n            children: [{ text: '' }],\n            texExpression: node.value,\n            type: EquationPlugin.key,\n          }),\n        },\n      },\n      remarkPlugins: [remarkMath, remarkGfm],\n    },\n  });\n  ```\n\n  - Removed processor in `editor.api.markdown.deserialize`\n    - Use `remarkPlugins` instead\n\n  ##### Serialization\n\n  - Removed `serializeMdNodes`\n    - Use `editor.markdown.serialize({ value: nodes })` instead\n  - Removed `SerializeMdOptions` due to new serialization process\n    - Previous process: `slate nodes => md`\n    - New process: `slate nodes => md-ast => md`\n  - Removed options:\n    - `nodes`\n    - `breakTag`\n    - `customNodes`\n    - `ignoreParagraphNewline`\n    - `listDepth`\n    - `markFormats`\n    - `ulListStyleTypes`\n    - `ignoreSuggestionType`\n\n  Migration example for `SerializeMdOptions.customNodes` and `SerializeMdOptions.nodes`:\n\n  ```tsx\n  export const markdownPlugin = MarkdownPlugin.configure({\n    options: {\n      rules: {\n        // Ignore all `insert` type suggestions\n        [SuggestionPlugin.key]: {\n          mark: true,\n          serialize: (slateNode: TSuggestionText, options): mdast.Text => {\n            const suggestionData = options.editor\n              .getApi(SuggestionPlugin)\n              .suggestion.suggestionData(node);\n\n            return suggestionData?.type === 'insert'\n              ? { type: 'text', value: '' }\n              : { type: 'text', value: node.text };\n          },\n        },\n        // For elementRules\n        [EquationPlugin.key]: {\n          serialize: (slateNode) => ({\n            type: 'math',\n            value: node.texExpression,\n          }),\n        },\n      },\n      remarkPlugins: [remarkMath, remarkGfm],\n    },\n  });\n  ```\n\n# 46.0.0\n\n## @udecode/plate-code-block@46.0.0\n\n- [#4122](https://github.com/udecode/plate/pull/4122) by [@zbeyens](https://github.com/zbeyens) – Migrated from `prismjs` to `highlight.js` + `lowlight` for syntax highlighting.\n\n  - Fix highlighting multi-lines tokens. Before, line tokens were computed line by line. Now, it's computed once for the whole block.\n  - Bundle size much lower.\n  - `CodeBlockPlugin`: remove `prism` option. Use `lowlight` option instead:\n\n  ```tsx\n  import { all, createLowlight } from 'lowlight';\n\n  const lowlight = createLowlight(all);\n\n  CodeBlockPlugin.configure({\n    options: {\n      lowlight,\n    },\n  });\n  ```\n\n  - New option: `defaultLanguage`\n  - Remove `syntax` option. Just omit `lowlight` option to disable syntax highlighting.\n  - Remove `syntaxPopularFirst` option. Control this behavior in your own components.\n  - Fix pasting code inside code blocks.\n  - Remove `useCodeBlockCombobox`, `useCodeBlockElement`, `useCodeSyntaxLeaf`, `useToggleCodeBlockButton`. The logic has been moved to the components.\n\n# 45.0.0\n\n## @udecode/plate-comments@45.0.0\n\n- [#4064](https://github.com/udecode/plate/pull/4064) by [@felixfeng33](https://github.com/felixfeng33) – This is a rewrite of the comments plugin removing UI logic (headless).\n\n  **Plugin Options**\n\n  - Removed configuration options from plugin options in favor of component-level control:\n    - `options.comments`\n    - `options.myUserId`\n    - `options.users`\n\n  **Components**\n\n  - Removed legacy components:\n    - `CommentDeleteButton`\n    - `CommentEditActions`\n    - `CommentEditButton`\n    - `CommentEditCancelButton`\n    - `CommentEditSaveButton`\n    - `CommentEditTextarea`\n    - `CommentNewSubmitButton`\n    - `CommentNewTextarea`\n    - `CommentResolveButton`\n    - `CommentsPositioner`\n    - `CommentUserName`\n\n  **API**\n\n  - Removed functions in favor of new API methods:\n    - `findCommentNode` → `api.comment.node()`\n    - `findCommentNodeById` → `api.comment.node({ id })`\n    - `getCommentNodeEntries` → `api.comment.nodes()`\n    - `getCommentNodesById` → `api.comment.nodes({ id })`\n    - `removeCommentMark` → `tf.comment.remove()`\n    - `unsetCommentNodesById` → `tf.comment.unsetMark({ id })`\n  - Removed unused functions:\n    - `getCommentFragment`\n    - `getCommentUrl`\n    - `getElementAbsolutePosition`\n    - `getCommentPosition`\n  - Updated `getCommentCount` to exclude draft comments\n\n  **State Management**\n\n  - Removed `CommentProvider` - users should implement their own state management – `block-discussion.tsx`\n  - Moved `useHooksComments` to UI registry – `comments-plugin.tsx`\n  - Removed hooks no longer needed with new UI:\n    - `useActiveCommentNode`\n    - `useCommentsResolved`\n    - `useCommentAddButton`\n    - `useCommentItemContent`\n    - `useCommentLeaf`\n    - `useCommentsShowResolvedButton`\n    - `useFloatingCommentsContentState`\n    - `useFloatingCommentsState`\n\n  **Types**\n\n  - Removed `CommentUser`\n  - Moved `TComment` to UI registry – `comment.tsx`\n\n## @udecode/plate-suggestion@45.0.0\n\n- [#4064](https://github.com/udecode/plate/pull/4064) by [@felixfeng33](https://github.com/felixfeng33) – Note: This plugin is currently in an experimental phase and breaking changes may be introduced without a major version bump.\n\n  - Add Suggestion UI\n  - Remove: `findSuggestionNode` use `findSuggestionProps.ts` instead\n  - Remove `addSuggestionMark.ts`\n  - Remove `useHooksSuggestion.ts` as we've updated the activeId logic to no longer depend on useEditorSelector\n\n# 44.0.1\n\n## @udecode/plate-core@44.0.0\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) –\n\n  - Support React 19\n  - Upgraded to `zustand-x@6`\n    - `eventEditorSelectors` -> `EventEditorStore.get`\n    - `eventEditorActions` -> `EventEditorStore.set`\n    - `useEventEditorSelectors` -> `useEventEditorValue(key)`\n  - Upgraded to `jotai-x@2`\n    - `usePlateEditorStore` -> `usePlateStore`\n    - `usePlateActions` -> `usePlateSet`\n    - Remove `editor.setPlateState`, use `usePlateSet` instead\n    - `usePlateSelectors` -> `usePlateValue`\n    - `usePlateStates` -> `usePlateState`\n  - Moving plugin options hooks into standalone hooks to be compatible with React Compiler\n    - `editor.useOption`, `ctx.useOption` -> `usePluginOption(plugin, key, ...args)`\n    - `editor.useOptions`, `ctx.useOptions` -> `usePluginOption(plugin, 'state')`\n    - New hook `usePluginOptions(plugin, selector)` to select plugin options (Zustand way).\n  - We were supporting adding selectors to plugins using `extendOptions`. Those were mixed up with the options state, leading to potential conflicts and confusion.\n    - The plugin method is renamed to `extendSelectors`\n    - Selectors are now internally stored in `plugin.selectors` instead of `plugin.options`, but this does not change how you access those: using `editor.getOption(plugin, 'selectorName')`, `ctx.getOption('selectorName')` or above hooks.\n    - Selector types are no longer in the 2nd generic type of `PluginConfig`, we're adding a 5th generic type for it.\n\n  ```ts\n  // Before:\n  export type BlockSelectionConfig = PluginConfig<\n    'blockSelection',\n    { selectedIds?: Set<string>; } & BlockSelectionSelectors,\n  >;\n\n  // After:\n  export type BlockSelectionConfig = PluginConfig<\n    'blockSelection',\n    { selectedIds?: Set<string>; },\n    {}, // API\n    {}, // Transforms\n    BlockSelectionSelectors, // Selectors\n  }>\n  ```\n\n## @udecode/plate-comments@44.0.0\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Upgrade to `jotai-x@2`. [Migration](https://github.com/udecode/jotai-x/blob/main/packages/jotai-x/CHANGELOG.md#211) needed only if you use `useCommentStore`\n\n## @udecode/plate-media@44.0.0\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Upgrade to `zustand-x@2`. [Migration](https://github.com/udecode/zustand-x/blob/main/packages/zustand-x/CHANGELOG.md#600) needed only if you use one of these stores:\n\n  - `ImagePreviewStore`\n  - `FloatingMediaStore`\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Upgrade to `jotai-x@2`. [Migration](https://github.com/udecode/jotai-x/blob/main/packages/jotai-x/CHANGELOG.md#211) needed only if you use `usePlaceholderStore`\n\n## @udecode/plate-resizable@44.0.0\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Upgrade to `jotai-x@2`. [Migration](https://github.com/udecode/jotai-x/blob/main/packages/jotai-x/CHANGELOG.md#211) needed only if you use `useResizableStore`\n\n## @udecode/plate-table@44.0.0\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Move store state `selectedCells` and `selectedTables` from `useTableStore` to `TablePlugin` options store. This fixes the issue to get access to those state outside a table element (e.g. the toolbar)\n\n- [#4048](https://github.com/udecode/plate/pull/4048) by [@zbeyens](https://github.com/zbeyens) – Upgrade to `jotai-x@2`. [Migration](https://github.com/udecode/jotai-x/blob/main/packages/jotai-x/CHANGELOG.md#211) needed only if you use `useTableStore`\n\n# 43.0.0\n\nNo breaking changes. Upgraded all dependencies to the latest version.\n\n# 42.0.1\n\n## @udecode/plate-ai@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – AI plugins are now experimental: pin the dependency to avoid breaking changes. No breaking changes for this release.\n\n## @udecode/plate-common@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – This package is now deprecated and will be renamed to `@udecode/plate`. Migration:\n\n  - Remove `@udecode/plate-common` and install `@udecode/plate`\n  - Replace all `'@udecode/plate-common'` with `'@udecode/plate'`,\n\n## @udecode/plate-core@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) –\n\n  - **Plugin `normalizeInitialValue`** now returns `void` instead of `Value`. When mutating nodes, keep their references (e.g., use `Object.assign` instead of spread).\n  - **Editor methods have moved** to `editor.tf` and `editor.api`. They still exist at the top level for **slate backward compatibility**, but are no longer redundantly typed. If you truly need the top-level method types, extend your editor type with `LegacyEditorMethods` (e.g. `editor as Editor & LegacyEditorMethods`). Since these methods can be overridden by `extendEditor`, `with...`, or slate plugins, consider migrating to the following approaches:\n\n    ```tsx\n    // For overriding existing methods only:\n    overrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({\n      transforms: {\n        deleteForward(options) {\n          // ...conditional override\n          deleteForward(options);\n        },\n      },\n      api: {\n        isInline(element) {\n          // ...conditional override\n          return isInline(element);\n        },\n      },\n    }));\n    ```\n\n  This was previously done in `extendEditor` using top-level methods, which still works but now throws a type error due to the move to `editor.tf/editor.api`. A workaround is to extend your editor with `LegacyEditorMethods`.\n\n  **Why?** Having all methods at the top-level (next to `children`, `marks`, etc.) would clutter the editor interface. Slate splits transforms in three places (`editor`, `Editor`, and `Transforms`), which is also confusing. We've reorganized them into `tf` and `api` for better DX, but also to support transform-only middlewares in the future. This also lets us leverage `extendEditorTransforms`, `extendEditorApi`, and `overrideEditor` to modify those methods.\n\n  Migration example:\n\n  ```tsx\n  // From:\n  export const withInlineVoid: ExtendEditor = ({ editor }) => {\n    const { isInline, isSelectable, isVoid, markableVoid } = editor;\n\n    const voidTypes: string[] = [];\n    const inlineTypes: string[] = [];\n\n    editor.pluginList.forEach((plugin) => {\n      if (plugin.node.isInline) {\n        inlineTypes.push(plugin.node.type);\n      }\n      if (plugin.node.isVoid) {\n        voidTypes.push(plugin.node.type);\n      }\n    });\n\n    editor.isInline = (element) => {\n      return inlineTypes.includes(element.type as any)\n        ? true\n        : isInline(element);\n    };\n\n    editor.isVoid = (element) => {\n      return voidTypes.includes(element.type as any) ? true : isVoid(element);\n    };\n\n    return editor;\n  };\n\n  export const InlineVoidPlugin = createSlatePlugin({\n    key: 'inlineVoid',\n    extendEditor: withInlineVoid,\n  });\n\n  // After (using overrideEditor since we're only overriding existing methods):\n  export const withInlineVoid: OverrideEditor = ({\n    api: { isInline, isSelectable, isVoid, markableVoid },\n    editor,\n  }) => {\n    const voidTypes: string[] = [];\n    const inlineTypes: string[] = [];\n\n    editor.pluginList.forEach((plugin) => {\n      if (plugin.node.isInline) {\n        inlineTypes.push(plugin.node.type);\n      }\n      if (plugin.node.isVoid) {\n        voidTypes.push(plugin.node.type);\n      }\n    });\n\n    return {\n      api: {\n        isInline(element) {\n          return inlineTypes.includes(element.type as any)\n            ? true\n            : isInline(element);\n        },\n        isVoid(element) {\n          return voidTypes.includes(element.type as any)\n            ? true\n            : isVoid(element);\n        },\n      },\n    };\n  };\n\n  export const InlineVoidPlugin = createSlatePlugin({\n    key: 'inlineVoid',\n  }).overrideEditor(withInlineVoid);\n  ```\n\n  - Move `editor.redecorate` to `editor.api.redecorate`\n\n  Types:\n\n  - Rename `TRenderElementProps` to `RenderElementProps`\n  - Rename `TRenderLeafProps` to `RenderLeafProps`\n  - Rename `TEditableProps` to `EditableProps`\n\n## @udecode/plate@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – **This package is now the new common package**, so all plugin packages are being removed. **Migration**:\n\n  - Add the following dependencies:\n\n  ```json\n  \"@udecode/plate-alignment\": \"42.0.0\",\n  \"@udecode/plate-autoformat\": \"42.0.0\",\n  \"@udecode/plate-basic-elements\": \"42.0.0\",\n  \"@udecode/plate-basic-marks\": \"42.0.0\",\n  \"@udecode/plate-block-quote\": \"42.0.0\",\n  \"@udecode/plate-break\": \"42.0.0\",\n  \"@udecode/plate-code-block\": \"42.0.0\",\n  \"@udecode/plate-combobox\": \"42.0.0\",\n  \"@udecode/plate-comments\": \"42.0.0\",\n  \"@udecode/plate-csv\": \"42.0.0\",\n  \"@udecode/plate-diff\": \"42.0.0\",\n  \"@udecode/plate-docx\": \"42.0.0\",\n  \"@udecode/plate-find-replace\": \"42.0.0\",\n  \"@udecode/plate-floating\": \"42.0.0\",\n  \"@udecode/plate-font\": \"42.0.0\",\n  \"@udecode/plate-heading\": \"42.0.0\",\n  \"@udecode/plate-highlight\": \"42.0.0\",\n  \"@udecode/plate-horizontal-rule\": \"42.0.0\",\n  \"@udecode/plate-indent\": \"42.0.0\",\n  \"@udecode/plate-indent-list\": \"42.0.0\",\n  \"@udecode/plate-kbd\": \"42.0.0\",\n  \"@udecode/plate-layout\": \"42.0.0\",\n  \"@udecode/plate-line-height\": \"42.0.0\",\n  \"@udecode/plate-link\": \"42.0.0\",\n  \"@udecode/plate-list\": \"42.0.0\",\n  \"@udecode/plate-markdown\": \"42.0.0\",\n  \"@udecode/plate-media\": \"42.0.0\",\n  \"@udecode/plate-mention\": \"42.0.0\",\n  \"@udecode/plate-node-id\": \"42.0.0\",\n  \"@udecode/plate-normalizers\": \"42.0.0\",\n  \"@udecode/plate-reset-node\": \"42.0.0\",\n  \"@udecode/plate-resizable\": \"42.0.0\",\n  \"@udecode/plate-select\": \"42.0.0\",\n  \"@udecode/plate-selection\": \"42.0.0\",\n  \"@udecode/plate-slash-command\": \"42.0.0\",\n  \"@udecode/plate-suggestion\": \"42.0.0\",\n  \"@udecode/plate-tabbable\": \"42.0.0\",\n  \"@udecode/plate-table\": \"42.0.0\",\n  \"@udecode/plate-toggle\": \"42.0.0\",\n  \"@udecode/plate-trailing-block\": \"42.0.0\"\n  ```\n\n  - Either replace all `@udecode/plate` imports with the individual package imports, or export the following in a new file (e.g. `src/plate.ts`):\n\n  ```ts\n  export * from '@udecode/plate-alignment';\n  export * from '@udecode/plate-autoformat';\n  export * from '@udecode/plate-basic-elements';\n  export * from '@udecode/plate-basic-marks';\n  export * from '@udecode/plate-block-quote';\n  export * from '@udecode/plate-break';\n  export * from '@udecode/plate-code-block';\n  export * from '@udecode/plate-combobox';\n  export * from '@udecode/plate-comments';\n  export * from '@udecode/plate-diff';\n  export * from '@udecode/plate-find-replace';\n  export * from '@udecode/plate-font';\n  export * from '@udecode/plate-heading';\n  export * from '@udecode/plate-highlight';\n  export * from '@udecode/plate-horizontal-rule';\n  export * from '@udecode/plate-indent';\n  export * from '@udecode/plate-indent-list';\n  export * from '@udecode/plate-kbd';\n  export * from '@udecode/plate-layout';\n  export * from '@udecode/plate-line-height';\n  export * from '@udecode/plate-link';\n  export * from '@udecode/plate-list';\n  export * from '@udecode/plate-media';\n  export * from '@udecode/plate-mention';\n  export * from '@udecode/plate-node-id';\n  export * from '@udecode/plate-normalizers';\n  export * from '@udecode/plate-reset-node';\n  export * from '@udecode/plate-select';\n  export * from '@udecode/plate-csv';\n  export * from '@udecode/plate-docx';\n  export * from '@udecode/plate-markdown';\n  export * from '@udecode/plate-slash-command';\n  export * from '@udecode/plate-suggestion';\n  export * from '@udecode/plate-tabbable';\n  export * from '@udecode/plate-table';\n  export * from '@udecode/plate-toggle';\n  export * from '@udecode/plate-trailing-block';\n  export * from '@udecode/plate-alignment/react';\n  export * from '@udecode/plate-autoformat/react';\n  export * from '@udecode/plate-basic-elements/react';\n  export * from '@udecode/plate-basic-marks/react';\n  export * from '@udecode/plate-block-quote/react';\n  export * from '@udecode/plate-break/react';\n  export * from '@udecode/plate-code-block/react';\n  export * from '@udecode/plate-combobox/react';\n  export * from '@udecode/plate-comments/react';\n  export * from '@udecode/plate-floating';\n  export * from '@udecode/plate-font/react';\n  export * from '@udecode/plate-heading/react';\n  export * from '@udecode/plate-highlight/react';\n  export * from '@udecode/plate-layout/react';\n  export * from '@udecode/plate-slash-command/react';\n  export * from '@udecode/plate-indent/react';\n  export * from '@udecode/plate-indent-list/react';\n  export * from '@udecode/plate-kbd/react';\n  export * from '@udecode/plate-line-height/react';\n  export * from '@udecode/plate-link/react';\n  export * from '@udecode/plate-list/react';\n  export * from '@udecode/plate-media/react';\n  export * from '@udecode/plate-reset-node/react';\n  export * from '@udecode/plate-selection';\n  export * from '@udecode/plate-suggestion/react';\n  export * from '@udecode/plate-tabbable/react';\n  export * from '@udecode/plate-table/react';\n  export * from '@udecode/plate-toggle/react';\n  export * from '@udecode/plate-resizable';\n  ```\n\n  - Replace all `'@udecode/plate'` and `'@udecode/plate/react'` with `'@/plate'` in your codebase.\n\n## @udecode/plate-utils@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) –\n  - Removed unused `moveSelectionByOffset`, `getLastBlockDOMNode`, `useLastBlock`, `useLastBlockDOMNode`\n\n## @udecode/plate-selection@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – Remove first parameter of editor.api.blockSelection.duplicate\n\n## @udecode/slate@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) –\n\n  - Remove `slate`, `slate-dom`, `slate-react`, `slate-history` and `slate-hyperscript` from your dependencies. It's now part of this package and `@udecode/plate`. All exports remain the same or have equivalents (see below).\n  - Renamed `createTEditor` to `createEditor`.\n  - `createEditor` now returns an editor (`Editor`) with all queries under `editor.api` and transforms under `editor.tf`. You can see or override them at a glance. For example, we now use `editor.tf.setNodes` instead of importing `setNodes`. This marks the completion of generic typing and the removal of error throws from `slate`, `slate-dom`, and `slate-history` queries/transforms, without forking implementations. We’ve also reduced the number of queries/transforms by merging a bunch of them.\n\n  The following interfaces from `slate` and `slate-dom` are now part of `Editor`:\n\n  - `Editor`, `EditorInterface`\n\n  - `Transforms`\n\n  - `HistoryEditor` (noop, unchanged), `HistoryEditorInterface`\n\n  - `DOMEditor` (noop, unchanged), `DOMEditorInterface`\n\n  - `editor.findPath` now returns `DOMEditor.findPath` (memo) and falls back to `findNodePath` (traversal, less performant) if not found.\n\n  - Removed the first parameter (`editor`) from:\n\n    - `editor.hasEditableTarget`\n    - `editor.hasSelectableTarget`\n    - `editor.isTargetInsideNonReadonlyVoid`\n    - `editor.hasRange`\n    - `editor.hasTarget`\n\n  - `editor.api.node(options)` (previously `findNode`) `at` option is now `at ?? editor.selection` instead of `at ?? editor.selection ?? []`. That means if you want to lookup the entire document, you need to pass `[]` explicitly.\n\n  - Removed `setNode` in favor of `setNodes` (you can now pass a `TNode` to `at` directly).\n\n  - Removed `setElements` in favor of `setNodes`.\n\n  - Removed unused `isWordAfterTrigger`, `setBlockAboveNode`, `setBlockAboveTexts`, `setBlockNodes`, `getPointNextToVoid`.\n\n  - Replaced `Path` from slate with `Path` (type) and `PathApi` (static methods).\n\n  - Replaced `Operation` from slate with `Operation` (type) and `OperationApi` (static methods).\n\n  - Replaced `Point` from slate with `Point` (type) and `PointApi` (static methods).\n\n  - Replaced `Text` from slate with `TText` (type) and `TextApi` (static methods). We also export `Text` type like `slate` but we don't recommend it as it's conflicting with the DOM type.\n\n  - Replaced `Range` from slate with `TRange` (type) and `RangeApi` (static methods). We also export `Range` type like `slate` but we don't recommend it as it's conflicting with the DOM type.\n\n  - Replaced `Location` from slate with `TLocation` (type) and `LocationApi` (static methods). We also export `Location` type like `slate` but we don't recommend it as it's conflicting with the DOM type.\n\n  - Replaced `Span` from slate with `Span` (type) and `SpanApi` (static methods).\n\n  - Replaced `Node` from slate with `TNode` (type) and `NodeApi` (static methods). We also export `Node` type like `slate` but we don't recommend it as it's conflicting with the DOM type.\n\n  - Replaced `Element` from slate with `TElement` (type) and `ElementApi` (static methods). We also export `Element` type like `slate` but we don't recommend it as it's conflicting with the DOM type.\n\n  - Signature change:\n\n    - `editor.tf.toggle.block({ type, ...options })` -> `editor.tf.toggleBlock(type, options)`\n    - `editor.tf.toggle.mark({ key, clear })` -> `editor.tf.toggleMark(key, { remove: clear })`\n\n  - Moved editor functions:\n\n    - `addMark` -> `editor.tf.addMark`\n    - `addRangeMarks` -> `editor.tf.setNodes(props, { at, marks: true })`\n    - `blurEditor` -> `editor.tf.blur`\n    - `collapseSelection` -> `editor.tf.collapse`\n    - `createDocumentNode` -> `editor.api.create.value` (core)\n    - `createNode` -> `editor.api.create.block`\n    - `createPathRef` -> `editor.api.pathRef`\n    - `createPointRef` -> `editor.api.pointRef`\n    - `createRangeRef` -> `editor.api.rangeRef`\n    - `deleteBackward({ unit })` -> `editor.tf.deleteBackward(unit)`\n    - `deleteForward({ unit })` -> `editor.tf.deleteForward(unit)`\n    - `deleteFragment` -> `editor.tf.deleteFragment`\n    - `deleteText` -> `editor.tf.delete`\n    - `deselect` -> `editor.tf.deselect`\n    - `deselectEditor` -> `editor.tf.deselectDOM`\n    - `duplicateBlocks` -> `editor.tf.duplicateNodes({ nodes })`\n    - `findDescendant` -> `editor.api.descendant`\n    - `findEditorDocumentOrShadowRoot` -> `editor.api.findDocumentOrShadowRoot`\n    - `findEventRange` -> `editor.api.findEventRange`\n    - `findNode(options)` -> `editor.api.node(options)`\n    - `findNodeKey` -> `editor.api.findKey`\n    - `findNodePath` -> `editor.api.findPath`\n    - `findPath` -> `editor.api.findPath`\n    - `focusEditor` -> `editor.tf.focus({ at })`\n    - `focusEditorEdge` -> `editor.tf.focus({ at, edge: 'startEditor' | 'endEditor' })`\n    - `getAboveNode` -> `editor.api.above`\n    - `getAncestorNode` -> `editor.api.block({ highest: true })`\n    - `getBlockAbove` -> `editor.api.block({ at, above: true })` or `editor.api.block()` if `at` is not a path\n    - `getBlocks` -> `editor.api.blocks`\n    - `getEdgeBlocksAbove` -> `editor.api.edgeBlocks`\n    - `getEdgePoints` -> `editor.api.edges`\n    - `getEditorString` -> `editor.api.string`\n    - `getEditorWindow` -> `editor.api.getWindow`\n    - `getEndPoint` -> `editor.api.end`\n    - `getFirstNode` -> `editor.api.first`\n    - `getFragment` -> `editor.api.fragment`\n    - `getFragmentProp(fragment, options)` -> `editor.api.prop({ nodes, ...options})`\n    - `getLastNode` -> `editor.api.last`\n    - `getLastNodeByLevel(level)` -> `editor.api.last([], { level })`\n    - `getLeafNode` -> `editor.api.leaf`\n    - `getLevels` -> `editor.api.levels`\n    - `getMark` -> `editor.api.mark`\n    - `getMarks` -> `editor.api.marks`\n    - `getNextNode` -> `editor.api.next`\n    - `getNextNodeStartPoint` -> `editor.api.start(at, { next: true })`\n    - `getNodeEntries` -> `editor.api.nodes`\n    - `getNodeEntry` -> `editor.api.node(at, options)`\n    - `getNodesRange` -> `editor.api.nodesRange`\n    - `getParentNode` -> `editor.api.parent`\n    - `getPath` -> `editor.api.path`\n    - `getPathRefs` -> `editor.api.pathRefs`\n    - `getPoint` -> `editor.api.point`\n    - `getPointAfter` -> `editor.api.after`\n    - `getPointBefore` -> `editor.api.before`\n    - `getPointBeforeLocation` -> `editor.api.before`\n    - `getPointRefs` -> `editor.api.pointRefs`\n    - `getPositions` -> `editor.api.positions`\n    - `getPreviousBlockById` -> `editor.api.previous({ id, block: true })`\n    - `getPreviousNode` -> `editor.api.previous`\n    - `getPreviousNodeEndPoint` -> `editor.api.end({ previous: true })`\n    - `getPreviousSiblingNode` -> `editor.api.previous({ at, sibling: true })`\n    - `getRange` -> `editor.api.range`\n    - `getRangeBefore` -> `editor.api.range('before', to, { before })`\n    - `getRangeFromBlockStart` -> `editor.api.range('start', to)`\n    - `getRangeRefs` -> `editor.api.rangeRefs`\n    - `getSelectionFragment` -> `editor.api.fragment(editor.selection, { structuralTypes })`\n    - `getSelectionText` -> `editor.api.string()`\n    - `getStartPoint` -> `editor.api.start`\n    - `getVoidNode` -> `editor.api.void`\n    - `hasBlocks` -> `editor.api.hasBlocks`\n    - `hasEditorDOMNode` -> `editor.api.hasDOMNode`\n    - `hasEditorEditableTarget` -> `editor.api.hasEditableTarget`\n    - `hasEditorSelectableTarget` -> `editor.api.hasSelectableTarget`\n    - `hasEditorTarget` -> `editor.api.hasTarget`\n    - `hasInlines` -> `editor.api.hasInlines`\n    - `hasTexts` -> `editor.api.hasTexts`\n    - `insertBreak` -> `editor.tf.insertBreak`\n    - `insertData` -> `editor.tf.insertData`\n    - `insertElements` -> `editor.tf.insertNodes<TElement>`\n    - `insertEmptyElement` -> `editor.tf.insertNodes(editor.api.create.block({ type }))`\n    - `insertFragment` -> `editor.tf.insertFragment`\n    - `insertNode` -> `editor.tf.insertNode`\n    - `insertNodes` -> `editor.tf.insertNodes`\n    - `insertText` -> `editor.tf.insertText({ at })` or `editor.tf.insertText({ marks: false })` without `at`\n    - `isAncestorEmpty` -> `editor.api.isEmpty`\n    - `isBlock` -> `editor.api.isBlock`\n    - `isBlockAboveEmpty` -> `editor.api.isEmpty(editor.selection, { block: true })`\n    - `isBlockTextEmptyAfterSelection` -> `editor.api.isEmpty(editor.selection, { after: true })`\n    - `isCollapsed(editor.selection)` -> `editor.api.isCollapsed()`\n    - `isComposing` -> `editor.api.isComposing`\n    - `isDocumentEnd` -> `editor.api.isEditorEnd`\n    - `isEdgePoint` -> `editor.api.isEdge`\n    - `isEditorEmpty` -> `editor.api.isEmpty()`\n    - `isEditorFocused` -> `editor.api.isFocused`\n    - `isEditorNormalizing` -> `editor.api.isNormalizing`\n    - `isEditorReadOnly` -> `editor.api.isReadOnly`\n    - `isElementEmpty` -> `editor.api.isEmpty`\n    - `isElementReadOnly` -> `editor.api.elementReadOnly`\n    - `isEndPoint` -> `editor.api.isEnd`\n    - `isExpanded(editor.selection)` -> `editor.api.isCollapsed()`\n    - `isInline` -> `editor.api.isInline`\n    - `isMarkableVoid` -> `editor.api.markableVoid`\n    - `isMarkActive` -> `editor.api.hasMark(key)`\n    - `isPointAtWordEnd` -> `editor.api.isAt({ at, word: true, end: true })`\n    - `isRangeAcrossBlocks` -> `editor.api.isAt({ at, blocks: true })`\n    - `isRangeInSameBlock` -> `editor.api.isAt({ at, block: true })`\n    - `isRangeInSingleText` -> `editor.api.isAt({ at, text: true })`\n    - `isSelectionAtBlockEnd` -> `editor.api.isAt({ end: true })`\n    - `isSelectionAtBlockStart` -> `editor.api.isAt({ start: true })`\n    - `isSelectionCoverBlock` -> `editor.api.isAt({ block: true, start: true, end: true })`\n    - `isSelectionExpanded` -> `editor.api.isExpanded()`\n    - `isStartPoint` -> `editor.api.isStart`\n    - `isTargetinsideNonReadonlyVoidEditor` -> `editor.api.isTargetInsideNonReadonlyVoid`\n    - `isTextByPath` -> `editor.api.isText(at)`\n    - `isVoid` -> `editor.api.isVoid`\n    - `liftNodes` -> `editor.tf.liftNodes`\n    - `mergeNodes` -> `editor.tf.mergeNodes`\n    - `moveChildren` -> `editor.tf.moveNodes({ at, to, children: true, fromIndex, match: (node, path) => boolean })`\n    - `moveNodes` -> `editor.tf.moveNodes`\n    - `moveSelection` -> `editor.tf.move`\n    - `normalizeEditor` -> `editor.tf.normalize`\n    - `removeEditorMark` -> `editor.tf.removeMark`\n    - `removeEditorText` -> `editor.tf.removeNodes({ text: true, empty: false })`\n    - `removeEmptyPreviousBlock` -> `editor.tf.removeNodes({ previousEmptyBlock: true })`\n    - `removeMark(options)` -> `editor.tf.removeMarks(keys, options)`\n    - `removeNodeChildren` -> `editor.tf.removeNodes({ at, children: true })`\n    - `removeNodes` -> `editor.tf.removeNodes`\n    - `removeSelectionMark` -> `editor.tf.removeMarks()`\n    - `replaceNode(editor, { nodes, insertOptions, removeOptions })` -> `editor.tf.replaceNodes(nodes, { removeNodes, ...insertOptions })`\n    - `select` -> `editor.tf.select`\n    - `selectEndOfBlockAboveSelection` -> `editor.tf.select(editor.selection, { edge: 'end' })`\n    - `selectNodes` -> `editor.tf.select(editor.api.nodesRange(nodes))`\n    - `setFragmentData` -> `editor.tf.setFragmentData`\n    - `setMarks(marks, clear)` -> `editor.tf.addMarks(marks, { remove: string | string[] })`\n    - `setNodes` -> `editor.tf.setNodes`\n    - `setPoint` -> `editor.tf.setPoint`\n    - `setSelection` -> `editor.tf.setSelection`\n    - `someNode` -> `editor.api.some(options)`\n    - `splitNodes` -> `editor.tf.splitNodes`\n    - `toDOMNode` -> `editor.api.toDOMNode`\n    - `toDOMPoint` -> `editor.api.toDOMPoint`\n    - `toDOMRange` -> `editor.api.toDOMRange`\n    - `toggleWrapNodes` -> `editor.tf.toggleBlock(type, { wrap: true })`\n    - `toSlateNode` -> `editor.api.toSlateNode`\n    - `toSlatePoint` -> `editor.api.toSlatePoint`\n    - `toSlateRange` -> `editor.api.toSlateRange`\n    - `unhangCharacterRange` -> `editor.api.unhangRange(range, { character: true })`\n    - `unhangRange` -> `editor.api.unhangRange`\n    - `unsetNodes` -> `editor.tf.unsetNodes`\n    - `unwrapNodes` -> `editor.tf.unwrapNodes`\n    - `withoutNormalizing` -> `editor.tf.withoutNormalizing`\n    - `wrapNodeChildren` -> `editor.tf.wrapNodes(element, { children: true })`\n    - `wrapNodes` -> `editor.tf.wrapNodes`\n    - `replaceNodeChildren` -> `editor.tf.replaceNodes({ at, children: true })`\n    - `resetEditor` -> `editor.tf.reset`\n    - `resetEditorChildren` -> `editor.tf.reset({ children: true })`\n    - `selectEditor` -> `editor.tf.select([], { focus, edge })`\n    - `selectSiblingNodePoint` -> `editor.tf.select(at, { next, previous })`\n\n  - Moved to `NodeApi.`:\n\n    - `getNextSiblingNodes(parentEntry, path)` -> `NodeApi.children(editor, path, { from: path.at(-1) + 1 })`\n    - `getFirstNodeText` -> `NodeApi.firstText`\n    - `getFirstChild([node, path])` -> `NodeApi.firstChild(editor, path)`\n    - `getLastChild([node, path])` -> `NodeApi.lastChild(editor, path)`\n    - `getLastChildPath([node, path])` -> `NodeApi.lastChild(editor, path)`\n    - `isLastChild([node, path], childPath)` -> `NodeApi.isLastChild(editor, childPath)`\n    - `getChildren([node, path])` -> `Array.from(NodeApi.children(editor, path))`\n    - `getCommonNode` -> `NodeApi.common`\n    - `getNode` -> `NodeApi.get`\n    - `getNodeAncestor` -> `NodeApi.ancestor`\n    - `getNodeAncestors` -> `NodeApi.ancestors`\n    - `getNodeChild` -> `NodeApi.child`\n    - `getNodeChildren` -> `NodeApi.children`\n    - `getNodeDescendant` -> `NodeApi.descendant`\n    - `getNodeDescendants` -> `NodeApi.descendants`\n    - `getNodeElements` -> `NodeApi.elements`\n    - `getNodeFirstNode` -> `NodeApi.first`\n    - `getNodeFragment` -> `NodeApi.fragment`\n    - `getNodeLastNode` -> `NodeApi.last`\n    - `getNodeLeaf` -> `NodeApi.leaf`\n    - `getNodeLevels` -> `NodeApi.levels`\n    - `getNodeParent` -> `NodeApi.parent`\n    - `getNodeProps` -> `NodeApi.extractProps`\n    - `getNodes` -> `NodeApi.nodes`\n    - `getNodeString` -> `NodeApi.string`\n    - `getNodeTexts` -> `NodeApi.texts`\n    - `hasNode` -> `NodeApi.has`\n    - `hasSingleChild` -> `NodeApi.hasSingleChild`\n    - `isAncestor` -> `NodeApi.isAncestor`\n    - `isDescendant` -> `NodeApi.isDescendant`\n    - `isEditor` -> `NodeApi.isEditor`\n    - `isNode` -> `NodeApi.isNode`\n    - `isNodeList` -> `NodeApi.isNodeList`\n    - `nodeMatches` -> `NodeApi.matches`\n\n  - Moved to `ElementApi.`:\n\n    - `elementMatches` -> `ElementApi.matches`\n    - `isElement` -> `ElementApi.isElement`\n    - `isElementList` -> `ElementApi.isElementList`\n\n  - Moved to `TextApi.`:\n\n    - `isText` -> `TextApi.isText(at)`\n\n  - Moved to `RangeApi.`:\n\n    - `isCollapsed` -> `RangeApi.isCollapsed`\n    - `isExpanded` -> `RangeApi.isExpanded`\n\n  - Moved to `PathApi.`:\n\n    - `isFirstChild` -> `!PathApi.hasPrevious`\n    - `getPreviousPath` -> `PathApi.previous`\n\n  - Moved to `PointApi.`:\n\n    - `getPointFromLocation({ at, focus })` -> `PointApi.get(at, { focus })`\n\n  - Moved from `@udecode/plate/react` to `@udecode/plate`:\n\n    - `Hotkeys`\n\n  - Upgraded to `zustand@5` and `zustand-x@5`:\n    - Replace `createZustandStore('name')(initialState)` with `createZustandStore(initialState, { mutative: true, name: 'name' })`\n    - All plugin stores now use [zustand-mutative](https://github.com/mutativejs/zustand-mutative) for immutable state updates, which is faster than `immer`.\n\n  Types:\n\n  - Rename the following types:\n    - `TEditor` -> `Editor`\n    - `TOperation` -> `Operation`\n    - `TPath` -> `Path`\n    - `TNodeProps` -> `NodeProps`\n    - `TNodeChildEntry` -> `NodeChildEntry`\n    - `TNodeEntry` -> `NodeEntry`\n    - `TDescendant` -> `Descendant`\n    - `TDescendantEntry` -> `DescendantEntry`\n    - `TAncestor` -> `Ancestor`\n    - `TAncestorEntry` -> `AncestorEntry`\n    - `TElementEntry` -> `ElementEntry`\n    - `TTextEntry` -> `TextEntry`\n  - Query/transform options now use generic `V extends Value` instead of `E extends Editor`.\n  - `getEndPoint`, `getEdgePoints`, `getFirstNode`, `getFragment`, `getLastNode`, `getLeafNode`, `getPath`, `getPoint`, `getStartPoint` can return `undefined` if not found (suppressing error throws).\n  - `NodeApi.ancestor`, `NodeApi.child`, `NodeApi.common`, `NodeApi.descendant`, `NodeApi.first`, `NodeApi.get`, `NodeApi.last`, `NodeApi.leaf`, `NodeApi.parent`, `NodeApi.getIf`, `PathApi.previous` return `undefined` if not found instead of throwing\n  - Replace `NodeOf` type with `DescendantOf` in `editor.tf.setNodes` `editor.tf.unsetNodes`, `editor.api.previous`, `editor.api.node`, `editor.api.nodes`, `editor.api.last`\n  - Enhanced `editor.tf.setNodes`:\n    - Added `marks` option to handle mark-specific operations\n    - When `marks: true`:\n      - Only applies to text nodes in non-void nodes or markable void nodes\n      - Automatically sets `split: true` and `voids: true`\n      - Handles both expanded ranges and collapsed selections in markable voids\n    - Replaces `addRangeMarks` functionality\n\n## @udecode/slate-utils@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – This package is now deprecated. Use `@udecode/slate` or `@udecode/plate` instead.\n\n## @udecode/slate-react@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – This package is now deprecated. Use `@udecode/slate` or `@udecode/plate` instead.\n\n## @udecode/plate-table@42.0.0\n\n- [#3920](https://github.com/udecode/plate/pull/3920) by [@zbeyens](https://github.com/zbeyens) – **Major performance improvement**: all table cells were re-rendering when a single cell changed. This is now fixed.\n\n  - `TablePlugin` now depends on `NodeIdPlugin`.\n  - Table merging is now enabled by default:\n    - Renamed `enableMerging` to `disableMerge`.\n    - **Migration**:\n      - `enableMerging: true` → remove the option.\n      - otherwise → `TablePlugin.configure({ options: { disableMerge: true } })`\n  - Renamed `unmergeTableCells` to `splitTableCell`.\n  - Renamed `editor.api.create.cell` to `editor.api.create.tableCell`.\n  - In `useTableMergeState`, renamed `canUnmerge` to `canSplit`.\n  - `insertTableRow` and `insertTableColumn`: removed `disableSelect` in favor of `select`. **Migration**: replace it with the opposite boolean.\n  - `getTableCellBorders`: params `(element, options)` → `(editor, options)`; removed `isFirstCell` and `isFirstRow`.\n  - Merged `useTableCellElementState` into `useTableCellElement`:\n    - Removed its parameter.\n    - Removed `hovered` and `hoveredLeft` returns (use CSS instead).\n    - Renamed `rowSize` to `minHeight`.\n    - Computes column sizes and returns `width`.\n  - Merged `useTableCellElementResizableState` into `useTableCellElementResizable`:\n    - Removed `onHover` and `onHoverEnd` props (use CSS instead).\n  - Merged `useTableElementState` into `useTableElement`:\n    - Removed its parameter.\n    - No longer computes and returns `colSizes`, `minColumnWidth`, and `colGroupProps`.\n\n# 41.0.2\n\n## @udecode/slate-react@41.0.0\n\n- [#3830](https://github.com/udecode/plate/pull/3830) by [@felixfeng33](https://github.com/felixfeng33) – Rename `findNodePath` to `findPath` since the addition of `findNodePath` in the headless lib.\n\n  We recommend using `findPath` mostly when subscribing to its value (e.g. in a React component) as it has O(path.length) complexity, compared to O(n) for the traversal-based `findNodePath`. This optimization is particularly important in:\n\n  - Render functions of Plate components where using `findNodePath` would increase the initial render time by O(n²)\n  - Key press handlers where using `findNodePath` would increase the handling time by O(n)\n\n  where n is the number of nodes in the editor.\n\n## @udecode/plate-dnd@41.0.2\n\n- [#3861](https://github.com/udecode/plate/pull/3861) by [@zbeyens](https://github.com/zbeyens) –\n\n  - Removed `useDndBlock`, `useDragBlock`, and `useDropBlock` hooks in favor of `useDndNode`, `useDragNode`, and `useDropNode`.\n  - Removed `DndProvider` and `useDraggableStore`. Drop line state is now managed by `DndPlugin` as a single state object `dropTarget` containing both `id` and `line`.\n  - `useDropNode`: removed `onChangeDropLine` and `dropLine` options\n\n  Migration steps:\n\n  - Remove `DndProvider` from your draggable component (e.g. `draggable.tsx`)\n  - Replace `useDraggableStore` with `useEditorPlugin(DndPlugin).useOption`\n  - Remove `useDraggableState`. Use `const { isDragging, previewRef, handleRef } = useDraggable`\n  - Remove `useDraggableGutter`. Set `contentEditable={false}` to your gutter element\n  - Remove `props` from `useDropLine`. Set `contentEditable={false}` to your drop line element\n  - Remove `withDraggable`, `useWithDraggable`. Use [`DraggableAboveNodes`](https://github.com/udecode/plate/pull/3878/files#diff-493c12ebed9c3ef9fd8c3a723909b18ad439a448c0132d2d93e5341ee0888ad2) instead\n\n## @udecode/plate-indent-list@41.0.0\n\n- [#3830](https://github.com/udecode/plate/pull/3830) by [@felixfeng33](https://github.com/felixfeng33) –\n  - Move `render.belowNodes` from `IndentListPlugin` to `BaseIndentListPlugin`. Props type for `listStyleTypes.liComponent` and `listStyleTypes.markerComponent` options is now `SlateRenderElementProps` instead of `PlateRenderElementProps`\n  - Move `someIndentList`, `someIndentTodo` from `@udecode/plate-indent-list/react` to `@udecode/plate-indent-list`\n\n## @udecode/plate-layout@41.0.2\n\n- [#3878](https://github.com/udecode/plate/pull/3878) by [@zbeyens](https://github.com/zbeyens) –\n\n  - `insertColumnGroup`: rename `layout` to `columns`\n  - Remove `setColumnWidth`, `useColumnState`. Use `setColumns` instead\n\n## @udecode/plate-table@41.0.0\n\n- [#3830](https://github.com/udecode/plate/pull/3830) by [@felixfeng33](https://github.com/felixfeng33) – Move from `@udecode/plate-table/react` to `@udecode/plate-table`:\n\n  - `deleteColumn`\n  - `deleteColumnWhenExpanded`\n  - `deleteRow`\n  - `deleteRowWhenExpanded`\n  - `getTableColumn`\n  - `getTableGridAbove`\n  - `getTableGridByRange`\n  - `getTableRow`\n  - `insertTable`\n  - `mergeTableCells`\n  - `moveSelectionFromCell`\n  - `overrideSelectionFromCell`\n  - `unmergeTableCells`\n  - `withDeleteTable`\n  - `withGetFragmentlable`\n  - `withInsertFragmentTable`\n  - `withInsertTextTable`\n  - `withMarkTable`\n  - `withSelectionTable`\n  - `withSetFragmentDataTable`\n  - `withTable`\n\n# 40.0.0\n\n## @udecode/slate-react@40.0.0\n\n- [#3744](https://github.com/udecode/plate/pull/3744) by [@zbeyens](https://github.com/zbeyens) –\n  - Add `slate-dom` as a peer dependency.\n  - Update `slate-react` peer dependency to `>=0.111.0`\n\n## @udecode/plate-ai@40.0.0\n\n- [#3744](https://github.com/udecode/plate/pull/3744) by [@zbeyens](https://github.com/zbeyens) –\n  - Remove `scrollContainerSelector` option in favor of `useEditorContainerRef`\n\n## @udecode/plate-heading@40.0.0\n\n- [#3744](https://github.com/udecode/plate/pull/3744) by [@zbeyens](https://github.com/zbeyens) –\n  - Remove `scrollContainerSelector` option in favor of `useEditorContainerRef`\n\n## @udecode/plate-layout@40.0.0\n\n- [#3744](https://github.com/udecode/plate/pull/3744) by [@zbeyens](https://github.com/zbeyens) –\n  - Remove `toggleColumns` in favor of `toggleColumnGroup`\n  - Remove `insertEmptyColumn` in favor of `insertColumn`\n\n# 39.0.0\n\n## @udecode/plate-dnd@39.0.0\n\n- [#3597](https://github.com/udecode/plate/pull/3597) by [@zbeyens](https://github.com/zbeyens) – The following changes were made to improve performance:\n\n  - Refactored `useDraggable` hook to focus on core dragging functionality:\n    - Removed `dropLine`. Use `useDropLine().dropLine` instead.\n    - Removed `groupProps` from the returned object – `isHovered`, and `setIsHovered` from the returned state. Use CSS instead.\n    - Removed `droplineProps`, and `gutterLeftProps` from the returned object. Use `useDropLine().props`, `useDraggableGutter().props` instead.\n\n## @udecode/plate-selection@39.0.0\n\n- [#3597](https://github.com/udecode/plate/pull/3597) by [@zbeyens](https://github.com/zbeyens) – The following changes were made to improve performance:\n\n  - Removed `useHooksBlockSelection` in favor of `BlockSelectionAfterEditable`\n  - Removed `slate-selected` class from `BlockSelectable`. You can do it on your components using `useBlockSelected()` instead, or by using our new `block-selection.tsx` component.\n  - Introduced `useBlockSelectableStore` for managing selectable state.\n\n# 38.0.1\n\n## @udecode/plate-core@38.0.1\n\n- [#3506](https://github.com/udecode/plate/pull/3506) by [@zbeyens](https://github.com/zbeyens) –\n\n  - Change `plugin.options` merging behavior from deep merge to shallow merge.\n  - This affects `.extend()`, `.configure()`, and other methods that modify plugin options.\n  - This update addresses a **performance regression** introduced in v37 that affected editor creation.\n\n  Before:\n\n  ```ts\n  const plugin = createSlatePlugin({\n    key: 'test',\n    options: { nested: { a: 1 } },\n  }).extend({\n    options: { nested: { b: 1 } },\n  });\n\n  // Result: { nested: { a: 1, b: 1 } }\n  ```\n\n  After:\n\n  ```ts\n  const plugin = createSlatePlugin({\n    key: 'test',\n    options: { nested: { a: 1 } },\n  }).extend(({ getOptions }) => ({\n    options: {\n      ...getOptions(),\n      nested: { ...getOptions().nested, b: 1 },\n    },\n  }));\n\n  // Result: { nested: { a: 1, b: 1 } }\n  ```\n\n  Migration:\n\n  - If you're using nested options and want to preserve the previous behavior, you need to manually spread both the top-level options and the nested objects.\n  - If you're not using nested options, no changes are required.\n\n- Rename all base plugins that have a React plugin counterpart to be prefixed with `Base`. This change improves clarity and distinguishes base implementations from potential React extensions. Use base plugins only for server-side environments or to extend your own DOM layer.\n- Import the following plugins from `/react` entry: `AlignPlugin`, `CalloutPlugin`, `EquationPlugin`, `FontBackgroundColorPlugin`, `FontColorPlugin`, `FontFamilyPlugin`, `FontSizePlugin`, `FontWeightPlugin`, `InlineEquationPlugin`, `LineHeightPlugin`, `TextIndentPlugin`, `TocPlugin`\n\n# 37.0.0\n\nMigration example: https://github.com/udecode/plate/pull/3480\n\nWe recommend to upgrade to `@udecode/plate-core@38.1.0` in one-go.\n\n## @udecode/plate-alignment@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createAlignPlugin` -> `AlignPlugin`\n  - `setAlign`: remove `key` option\n\n## @udecode/plate-autoformat@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createAutoformatPlugin` -> `AutoformatPlugin`\n\n## @udecode/plate-basic-elements@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createBasicElementPlugins` -> `BasicElementsPlugin`\n  - `createBlockquotePlugin` -> `BlockquotePlugin`\n  - `createCodeBlockPlugin` -> `CodeBlockPlugin`\n  - `createHeadingPlugin` -> `HeadingPlugin`\n  - Move paragraph plugin to `@udecode/plate-core`\n\n## @udecode/plate-basic-marks@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createBasicMarksPlugins` -> `BasicMarksPlugin`\n  - `createBoldPlugin` -> `BoldPlugin`\n  - `createCodePlugin` -> `CodePlugin`\n  - `createItalicPlugin` -> `ItalicPlugin`\n  - `createStrikethroughPlugin` -> `StrikethroughPlugin`\n  - `createSubscriptPlugin` -> `SubscriptPlugin`\n  - `createSuperscriptPlugin` -> `SuperscriptPlugin`\n  - `createUnderlinePlugin` -> `UnderlinePlugin`\n  - All mark plugins removed `hotkey` option. Use `plugin.shortcuts` instead (see plate-core)\n\n## @udecode/plate-block-quote@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createBlockquotePlugin` -> `BlockquotePlugin`\n\n## @udecode/plate-break@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createSoftBreakPlugin` -> `SoftBreakPlugin`\n  - `createExitBreakPlugin` -> `ExitBreakPlugin`\n  - `createSingleLinePlugin` -> `SingleLinePlugin`\n\n## @udecode/plate-caption@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createCaptionPlugin` -> `CaptionPlugin`\n  - `CaptionPlugin` options:\n    - Rename `pluginKeys` to `plugins`\n    - Rename `focusEndCaptionPath` to `focusEndPath`\n    - Rename `focusStartCaptionPath` to `focusStartPath`\n    - Rename `showCaptionId` to `visibleId`\n    - Rename `isShow` to `isVisible`\n  - Move `captionGlobalStore` to `CaptionPlugin`\n\n## @udecode/plate-cloud@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createCloudPlugin` -> `CloudPlugin`\n\n## @udecode/plate-code-block@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createCodeBlockPlugin` -> `CodeBlockPlugin`\n  - NEW `CodeLinePlugin`\n  - NEW `CodeSyntaxPlugin`\n  - Remove `getCodeLineType`, use `editor.getType(CodeLinePlugin)` instead\n\n## @udecode/plate-combobox@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Split build into `@udecode/plate-combobox` and `@udecode/plate-combobox/react`.\n\n## @udecode/plate-comments@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createCommentsPlugin` -> `CommentsPlugin`\n  - Move `commentsStore` to `CommentsPlugin`\n  - Remove `CommentsProvider` and its hooks\n  - Remove `useCommentsStates` (replaced by direct option access)\n  - Remove `useCommentsSelectors` (replaced by option selectors)\n  - Remove `useCommentsActions` (replaced by api methods)\n  - Replace `useUpdateComment` with `api.comment.updateComment`\n  - Replace `useAddRawComment` with `api.comment.addRawComment`\n  - Replace `useAddComment` with `api.comment.addComment`\n  - Replace `useRemoveComment` with `api.comment.removeComment`\n  - Replace `useResetNewCommentValue` with `api.comment.resetNewCommentValue`\n  - Replace `useNewCommentText` with `options.newText`\n  - Replace `useMyUser` with `options.myUser`\n  - Replace `useUserById` with `options.userById`\n  - Replace `useCommentById` with `options.commentById`\n  - Replace `useActiveComment` with `options.activeComment`\n  - Replace `useAddCommentMark` with `insert.comment`\n\n## @udecode/plate-common@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Split build into `@udecode/plate-common` and `@udecode/plate-common/react`.\n  - NEW `/react` exports `@udecode/react-hotkeys`\n\n## @udecode/plate-core@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) – **Plugin System**:\n\n  Decoupling React in all packages:\n\n  - Split build into `@udecode/plate-core` and `@udecode/plate-core/react`\n  - NEW `SlatePlugin` as the foundation for all plugins\n  - `PlatePlugin` extends `SlatePlugin` with React-specific plugin features\n\n  **Plugin Creation**:\n\n  - Remove `createPluginFactory`\n  - NEW `createSlatePlugin`: vanilla\n  - NEW `createTSlatePlugin`: vanilla explicitly typed\n  - NEW `createPlatePlugin`: React\n  - NEW `createTPlatePlugin`: React explicitly typed\n  - NEW `toPlatePlugin`: extend a vanilla plugin into a React plugin\n  - NEW `toTPlatePlugin`: extend a vanilla plugin into a React plugin explicitly typed\n  - Rename all plugins starting with `createNamePlugin()` to `NamePlugin`\n\n  Before:\n\n  ```typescript\n  const MyPluginFactory = createPluginFactory({\n    key: 'myPlugin',\n    isElement: true,\n    component: MyComponent,\n  });\n  const plugin = MyPluginFactory();\n  ```\n\n  After:\n\n  ```typescript\n  const plugin = createSlatePlugin({\n    key: 'myPlugin',\n    node: {\n      isElement: true,\n      component: MyComponent,\n    },\n  });\n  const reactPlugin = toPlatePlugin(plugin);\n  ```\n\n  **Plugin Configuration**:\n\n  - Remove all `NamePlugin` option types, use `NameConfig` instead.\n  - `NameConfig` as the new naming convention for plugin configurations.\n\n  Before:\n\n  ```typescript\n  createPluginFactory<HotkeyPlugin>({\n    handlers: {\n      onKeyDown: onKeyDownToggleElement,\n    },\n    options: {\n      hotkey: ['mod+opt+0', 'mod+shift+0'],\n    },\n  });\n  ```\n\n  After:\n\n  ```typescript\n  export const ParagraphPlugin = createPlatePlugin({\n    key: 'p',\n    node: { isElement: true },\n  }).extend({ editor, type }) => ({\n    shortcuts: {\n      toggleParagraph: {\n        handler: () => {\n          editor.tf.toggle.block({ type });\n        },\n        keys: [\n          [Key.Mod, Key.Alt, '0'],\n          [Key.Mod, Key.Shift, '0'],\n        ],\n        preventDefault: true,\n      },\n    },\n  })\n  ```\n\n  - `toggleParagraph` is now a shortcut for `editor.tf.toggle.block({ type: 'p' })` for the given keys\n  - Multiple shortcuts can be defined per plugin, and any shortcut can be disabled by setting `shortcuts.toggleParagraph = null`\n  - Note the typing support using `Key`\n\n  **Plugin Properties**:\n\n  Rename `SlatePlugin` / `PlatePlugin` properties:\n\n  - `type` -> `node.type`\n  - `isElement` -> `node.isElement`\n  - `isLeaf` -> `node.isLeaf`\n  - `isInline` -> `node.isInline`\n  - `isMarkableVoid` -> `node.isMarkableVoid`\n  - `isVoid` -> `node.isVoid`\n  - `component` -> `node.component` or `render.node`\n  - `props` -> `node.props`\n  - `overrideByKey` -> `override.plugins`\n  - `renderAboveEditable` -> `render.aboveEditable`\n  - `renderAboveSlate` -> `render.aboveSlate`\n  - `renderAfterEditable` -> `render.afterEditable`\n  - `renderBeforeEditable` -> `render.beforeEditable`\n  - `inject.props` -> `inject.nodeProps`\n  - `inject.props.validTypes` -> `inject.targetPlugins`\n  - `inject.aboveComponent` -> `render.aboveNodes`\n  - `inject.belowComponent` -> `render.belowNodes`\n  - `inject.pluginsByKey` -> `inject.plugins`\n  - `editor.insertData` -> `parser`\n    - NEW `parser.format` now supports `string[]`\n    - NEW `parser.mimeTypes: string[]`\n  - `deserializeHtml` -> `parsers.html.deserializer`\n  - `deserializeHtml.getNode` -> `parsers.html.deserializer.parse`\n  - `serializeHtml` -> `parsers.htmlReact.serializer`\n  - `withOverride` -> `extendEditor`\n  - All methods now have a single parameter: `SlatePluginContext<C>` or `PlatePluginContext<C>`, in addition to the method specific options. Some of the affected methods are:\n    - `decorate`\n    - `handlers`, including `onChange`. Returns `({ event, ...ctx }) => void` instead of `(editor, plugin) => (event) => void`\n    - `handlers.onChange`: `({ value, ...ctx }) => void` instead of `(editor, plugin) => (value) => void`\n    - `normalizeInitialValue`\n    - `editor.insertData.preInsert`\n    - `editor.insertData.transformData`\n    - `editor.insertData.transformFragment`\n    - `deserializeHtml.getNode`\n    - `deserializeHtml.query`\n    - `inject.props.query`\n    - `inject.props.transformProps`\n    - `useHooks`\n    - `withOverrides`\n\n  NEW `SlatePlugin` properties:\n\n  - `api`: API methods provided by this plugin\n  - `dependencies`: An array of plugin keys that this plugin depends on\n  - `node`: Node-specific configuration for this plugin\n  - `parsers`: Now accept `string` keys to add custom parsers\n  - `priority`: Plugin priority for registration and execution order\n  - `shortcuts`: Plugin-specific hotkeys\n  - `inject.targetPluginToInject`: Function to inject plugin config into other plugins specified by `inject.targetPlugins`\n\n  Before:\n\n  ```typescript\n  export const createAlignPlugin = createPluginFactory({\n    key: KEY_ALIGN,\n    inject: {\n      props: {\n        defaultNodeValue: 'start',\n        nodeKey: KEY_ALIGN,\n        styleKey: 'textAlign',\n        validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],\n        validTypes: ['p'],\n      },\n    },\n    then: (_, plugin) =>\n      mapInjectPropsToPlugin(editor, plugin, {\n        deserializeHtml: {\n          getNode: (el, node) => {\n            if (el.style.textAlign) {\n              node[plugin.key] = el.style.textAlign;\n            }\n          },\n        },\n      }),\n  });\n  ```\n\n  After:\n\n  ```typescript\n  export const AlignPlugin = createSlatePlugin({\n    inject: {\n      nodeProps: {\n        defaultNodeValue: 'start',\n        nodeKey: 'align',\n        styleKey: 'textAlign',\n        validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],\n      },\n      targetPluginToInject: ({ editor, plugin }) => ({\n        parsers: {\n          html: {\n            deserializer: {\n              parse: ({ element, node }) => {\n                if (element.style.textAlign) {\n                  node[editor.getType(plugin)] = element.style.textAlign;\n                }\n              },\n            },\n          },\n        },\n      }),\n      targetPlugins: [ParagraphPlugin.key],\n    },\n    key: 'align',\n  });\n  ```\n\n  **Plugin Shortcuts**:\n\n  - NEW `shortcuts` to add custom hotkeys to a plugin.\n  - Remove `hotkey` option from all plugins\n\n  Before:\n\n  ```typescript\n  type LinkPlugin = {\n    hotkey?: string;\n  };\n  ```\n\n  After:\n\n  ```typescript\n  type LinkConfig = PluginConfig<\n    // key\n    'p',\n    // options\n    { defaultLinkAttributes?: any },\n    // api\n    { link: { getAttributes: (editor: PlateEditor) => LinkAttributes } },\n    // transforms\n    { floatingLink: { hide: () => void } }\n  >;\n  ```\n\n  Shortcuts API:\n\n  - `handler` is called with the editor, event, and event details.\n  - `keys` is an array of keys to trigger the shortcut.\n  - `priority` is the priority of the shortcut over other shortcuts.\n  - `...HotkeysOptions` from `@udecode/react-hotkeys`\n\n  **Plugin Types**:\n\n  - Update `SlatePlugin`, `PlatePlugin` generics. `P, V, E` -> `C extends AnyPluginConfig = PluginConfig`\n  - Remove `PluginOptions`\n  - Remove `PlatePluginKey`\n  - Remove `HotkeyPlugin`, `ToggleMarkPlugin` in favor of `plugin.shortcuts`\n  - `WithPlatePlugin` -> `EditorPlugin`, `EditorPlatePlugin`\n  - `PlatePluginComponent` -> `NodeComponent`\n  - `InjectComponent*` -> `NodeWrapperComponent*`\n  - `PlatePluginInsertData` -> `Parser`\n  - `PlatePluginProps` -> `NodeProps`\n  - `RenderAfterEditable` -> `EditableSiblingComponent`\n  - `WithOverride` -> `ExtendEditor`\n  - `SerializeHtml` -> `HtmlReactSerializer`\n\n  **Plugin Store**:\n\n  - NEW each plugin has its own store, accessible via `plugin.optionsStore` and `plugin.useOptionsStore`\n  - `editor` has many methods to get, set and subscribe to plugin options\n\n  **Plugin Methods**:\n\n  - All plugin methods return a new plugin instance with the extended types.\n  - Remove `then`, use `extend` instead\n  - NEW `extend` method to deep merge a plugin configuration\n    - If you pass an object, it will be directly merged with the plugin config.\n    - If you pass a function, it will be called with the plugin config once the editor is resolved and should return the new plugin config.\n    - Object extensions always have the priority over function extensions.\n    - Extend multiple times to derive from the result of the previous extension.\n  - NEW `configure` method to configure the properties of existing plugins. The difference with `extend` is that `configure` with not add new properties to the plugin, it will only modify existing ones.\n  - NEW `extendPlugin` method to extend a nested plugin configuration.\n  - NEW `configurePlugin` method to configure the properties of a nested plugin.\n  - NEW `extendApi` method to extend the plugin API. The API is then merged into `editor.api[plugin.key]`.\n  - NEW `extendTransforms` method to extend the plugin transforms. The transforms is then merged into `editor.transforms[plugin.key]`.\n  - NEW `extendEditorApi` method to extend the editor API. The API is then merged into `editor.api`. Use this to add or override top-level methods to the editor.\n  - NEW `extendEditorTransforms` method to extend the editor transforms. The transforms is then merged into `editor.transforms`.\n  - NEW `extendOptions` method to extend the plugin options with selectors. Use `editor.useOption(plugin, 'optionKey')` to subscribe to an (extended) option.\n  - NEW `withComponent` to replace `plugin.node.component`\n\n  **Plugin Context**\n\n  Each plugin method now receive the plugin context created with `getEditorPlugin(editor, plugin)` as parameter:\n\n  - `api`\n  - `editor`\n  - `getOption`\n  - `getOptions`\n  - `plugin`\n  - `setOption`\n  - `setOptions`\n  - `tf`\n  - `type`\n  - `useOption`\n\n  **Core Plugins**:\n\n  - NEW `ParagraphPlugin` is now part of `core`\n  - NEW `DebugPlugin` is now part of `core`\n    - NEW `api.debug.log`, `api.debug.info`, `api.debug.warn`, `api.debug.error` methods\n    - `options.isProduction` to control logging in production environments\n    - `options.logLevel` to set the minimum log level\n    - `options.logger` to customize logging behavior\n    - `options.throwErrors` to control error throwing behavior, by default a `PlateError` will be thrown on `api.debug.error`\n  - NEW - You can now override a core plugin by adding it to `editor.plugins`. Last plugin wins.\n  - `createDeserializeHtmlPlugin` -> `HtmlPlugin`\n    - NEW `api.html.deserialize`\n  - `createEventEditorPlugin` -> `EventEditorPlugin`\n    - `eventEditorStore` -> `EventEditorStore`\n  - `createDeserializeAstPlugin` -> `AstPlugin`\n  - `createEditorProtocolPlugin` -> `SlateNextPlugin`\n    - NEW `editor.tf.toggle.block`\n    - NEW `editor.tf.toggle.mark`\n    - Remove `createNodeFactoryPlugin`, included in `SlateNextPlugin`.\n    - Remove `createPrevSelectionPlugin`, included in `SlateNextPlugin`.\n  - `createHistoryPlugin` -> `HistoryPlugin`\n  - `createInlineVoidPlugin` -> `InlineVoidPlugin`\n  - `createInsertDataPlugin` -> `ParserPlugin`\n  - `createLengthPlugin` -> `LengthPlugin`\n  - `createReactPlugin` -> `ReactPlugin`\n\n  **Editor Creation**:\n\n  NEW `withSlate`:\n\n  - Extends an editor into a vanilla Plate editor\n  - NEW `rootPlugin` option for configuring the root plugin\n\n  NEW `withPlate`:\n\n  - Extends an editor into a React Plate editor\n  - Now extends `withSlate` with React-specific enhancements\n  - NEW `useOptions` and `useOption` methods to the editor\n\n  NEW `createSlateEditor`:\n\n  - Create a vanilla Plate editor with server-side support\n\n  `createPlateEditor`:\n\n  - Plugin replacement mechanism: using `plugins`, any plugin with the same key that a previous plugin will **replace** it. That means you can now override core plugins that way, like `ReactPlugin`\n  - `root` plugin is now created from `createPlateEditor` option as a quicker way to configure the editor than passing `plugins`. Since plugins can have nested plugins (think as a recursive tree), `plugins` option will be passed to `root` plugin `plugins` option.\n  - Centralized editor resolution. Before, both `createPlateEditor` and `Plate` component were resolving the editor. Now, only `createPlateEditor` takes care of that. That means `id`, `value`, and other options are now controlled by `createPlateEditor`.\n  - Remove `createPlugins`, pass plugins directly:\n\n    - `components` -> `override.components`\n    - `overrideByKey` -> `override.plugins`\n\n  `createPlateEditor` options:\n\n  - Rename `normalizeInitialValue` option to `shouldNormalizeEditor`\n  - Move `components` to `override.components` to override components by key\n  - Move `overrideByKey` to `override.plugins` to override plugins by key\n  - Remove `disableCorePlugins`, use `override.enabled` instead\n  - NEW `value` to set the initial value of the editor.\n  - NEW `autoSelect?: 'end' | 'start' | boolean` to auto select the start of end of the editor. This is decoupled from `autoFocus`.\n  - NEW `selection` to control the initial selection.\n  - NEW `override.enabled` to disable plugins by key\n  - NEW `rootPlugin?: (plugin: AnyPlatePlugin) => AnyPlatePlugin` to configure the root plugin. From here, you can for example call `configurePlugin` to configure any plugin.\n  - NEW `api`, `decorate`, `extendEditor`, `handlers`, `inject`, `normalizeInitialValue`, `options`, `override`, `priority`, `render`, `shortcuts`, `transforms`, `useHooks`. These options will be passed to the very first `rootPlugin`.\n\n  NEW `usePlateEditor()` hook to create a `PlateEditor` in a React component:\n\n  - Uses `createPlateEditor` and `useMemo` to avoid re-creating the editor on every render.\n  - Dependencies can be added to the hook to re-create the editor on demand. `id` option is always used as dependency.\n\n  **Editor Methods**:\n\n  `editor: PlateEditor`:\n\n  - Move `redecorate` to `editor.api.redecorate`\n  - Move `reset` to `editor.tf.reset`\n  - Move `plate.set` to `editor.setPlateState`\n  - Move `blockFactory` to `editor.api.create.block`\n  - Move `childrenFactory` to `editor.api.create.value`\n  - Rename `plugins` to `pluginList`\n  - Rename `pluginsByKey` to `plugins`\n  - NEW `getApi()` to get the editor API\n  - NEW `getTransforms()` to get the editor transforms\n  - Remove `getPlugin(editor, key)`, use `editor.getPlugin(plugin) or editor.getPlugin({ key })`\n  - Remove `getPluginType`, use `editor.getType(plugin)` to get node type\n  - Remove `getPluginInjectProps(editor, key)`, use `editor.getPlugin(plugin).inject.props`\n  - NEW `getOptionsStore()` to get a plugin options store\n  - Remove `getPluginOptions`, use `getOptions()`\n  - NEW `getOption()` to get a plugin option\n  - NEW `setOption()` to set a plugin option\n  - NEW `setOptions()` to set multiple plugin options. Pass a function to use Immer. Pass an object to merge the options.\n  - NEW `useOption` to subscribe to a plugin option in a React component\n  - NEW `useOptions` to subscribe to a plugin options store in a React component\n  - Remove `getPlugins`, use `editor.pluginList`\n  - Remove `getPluginsByKey`, use `editor.plugins`\n  - Remove `mapInjectPropsToPlugin`\n\n  **Editor Types**:\n\n  The new generic types are:\n\n  - `V extends Value = Value`, `P extends AnyPluginConfig = PlateCorePlugin`\n  - That means this function will **infer all plugin configurations** from the options passed to it:\n    - `key`\n    - `options`\n    - `api`\n    - `transforms`\n  - Can't infer for some reason? Use `createTPlateEditor` for explicit typing.\n\n  ```ts\n  const editor = createPlateEditor({ plugins: [TablePlugin] });\n  editor.api.htmlReact.serialize(); // core plugin is automatically inferred\n  editor.tf.insert.tableRow(); // table plugin is automatically inferred\n  ```\n\n  **Plate Component**\n\n  `PlateProps`:\n\n  - `editor` is now required. If `null`, `Plate` will not render anything. As before, `Plate` remounts on `id` change.\n  - Remove `id`, `plugins`, `maxLength`, pass these to `createPlateEditor` instead\n  - Remove `initialValue`, `value`, pass `value` to `createPlateEditor` instead\n  - Remove `editorRef`\n  - Remove `disableCorePlugins`, override `plugins` in `createPlateEditor` instead\n\n  Utils:\n\n  - Remove `useReplaceEditor` since `editor` is now always controlled\n  - NEW `useEditorPlugin` to get the editor and the plugin context.\n\n  Types:\n\n  - `PlateRenderElementProps`, `PlateRenderLeafProps` generics: `V, N` -> `N, C`\n\n  **Plate Store**:\n\n  - Remove `plugins` and `rawPlugins`, use `useEditorRef().plugins` instead, or listen to plugin changes with `editor.useOption(plugin, <optionKey>)`\n  - Remove `value`, use `useEditorValue()` instead\n  - Remove `editorRef`, use `useEditorRef()` instead\n\n  **Miscellaneous Changes**\n\n  - `slate >=0.103.0` peer dependency\n  - `slate-react >=0.108.0` peer dependency\n  - New dependency `@udecode/react-hotkeys`\n  - Remove `ELEMENT_`, `MARK_` and `KEY_` constants. Use `NamePlugin.key` instead.\n  - Replace `ELEMENT_DEFAULT` with `ParagraphPlugin.key`.\n  - Remove `getTEditor`\n  - Rename `withTReact` to `withPlateReact`\n  - Rename `withTHistory` to `withPlateHistory`\n  - Rename `usePlateId` to `useEditorId`\n  - Remove `usePlateSelectors().id()`, `usePlateSelectors().value()`, `usePlateSelectors().plugins()`, use instead `useEditorRef().<key>`\n  - Rename `toggleNodeType` to `toggleBlock`\n  - `toggleBlock` options:\n    - Rename `activeType` to `type`\n    - Rename `inactiveType` to `defaultType`\n  - Remove `react-hotkeys-hook` re-exports. Use `@udecode/react-hotkeys` instead.\n\n  Types:\n\n  - Move `TEditableProps`, `TRenderElementProps` to `@udecode/slate-react`\n  - Remove `<V extends Value>` generic in all functions where not used\n  - Remove `PlatePluginKey`\n  - Remove `OverrideByKey`\n  - Remove `PlateId`\n\n## @udecode/plate-csv@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDeserializeCsvPlugin` -> `CsvPlugin`\n\n## @udecode/plate-cursor@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createCursorPlugin` -> `CursorPlugin`\n\n## @udecode/plate-date@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDatePlugin` -> `DatePlugin`\n\n## @udecode/plate-diff@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDiffPlugin` -> `DiffPlugin`\n\n## @udecode/plate-dnd@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDndPlugin` -> `DndPlugin`\n  - Remove `editor.isDragging`, use `editor.getOptions(DndPlugin).isDragging` instead\n  - Move `dndStore` to `DndPlugin`\n\n## @udecode/plate-docx@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDeserializeDocxPlugin` -> `DocxPlugin`\n\n## @udecode/plate-emoji@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createEmojiPlugin` -> `EmojiPlugin`\n  - NEW `EmojiInputPlugin`\n\n## @udecode/plate-excalidraw@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createExcalidrawPlugin` -> `ExcalidrawPlugin`\n  - `insertExcalidraw` remove `key` option\n\n## @udecode/plate-find-replace@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createFindReplacePlugin` -> `FindReplacePlugin`\n\n## @udecode/plate-floating@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Remove unused generics\n\n## @udecode/plate-font@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createFontBackgroundColorPlugin` -> `FontBackgroundColorPlugin`\n  - `createFontColorPlugin` -> `FontColorPlugin`\n  - `createFontSizePlugin` -> `FontSizePlugin`\n  - `createFontFamilyPlugin` -> `FontFamilyPlugin`\n  - `createFontWeightPlugin` -> `FontWeightPlugin`\n\n## @udecode/plate-heading@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createHeadingPlugin` -> `HeadingPlugin`\n  - Replace `ELEMENT_H1` with `HEADING_KEYS.H1`\n  - Replace `KEYS_HEADING` with `HEADING_LEVELS`\n\n## @udecode/plate-highlight@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createHighlightPlugin` -> `HighlightPlugin`\n\n## @udecode/plate-horizontal-rule@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createHorizontalRulePlugin` -> `HorizontalRulePlugin`\n\n## @udecode/plate-html@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDeserializeHtmlPlugin` -> `HtmlPlugin`\n  - Rename `deserializeHtml` plugin to `html`\n  - Rename `deserializeHtml.getNode` to `parse`\n\n## @udecode/plate-indent@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createIndentPlugin` -> `IndentPlugin`\n\n## @udecode/plate-indent-list@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createIndentListPlugin` -> `IndentListPlugin`\n  - Rename `injectIndentListComponent` to `renderIndentListBelowNodes`\n  - Replace `normalizeIndentList` with `withNormalizeIndentList`\n  - Replace `deleteBackwardIndentList` with `withDeleteBackwardIndentList`\n  - Replace `insertBreakIndentList` with `withInsertBreakIndentList`\n  - Remove types: `LiFC` (use `PlateRenderElementProps`), `MarkerFC` (use `Omit<PlateRenderElementProps, 'children'>`)\n\n## @udecode/plate-juice@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createJuicePlugin` -> `JuicePlugin`\n\n## @udecode/plate-kbd@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createKbdPlugin` -> `KbdPlugin`\n\n## @udecode/plate-layout@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createColumnPlugin` -> `ColumnPlugin`\n  - NEW `ColumnItemPlugin`\n\n## @udecode/plate-line-height@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createLineHeightPlugin` -> `LineHeightPlugin`\n\n## @udecode/plate-link@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createLinkPlugin` -> `LinkPlugin`\n  - Move `floatingLinkStore` to `LinkPlugin`\n\n## @udecode/plate-list@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createListPlugin` -> `ListPlugin`\n  - NEW `BulletedListPlugin`\n  - NEW `NumberedListPlugin`\n  - NEW `ListItemPlugin`\n  - NEW `ListItemContentPlugin`\n  - NEW list transforms: `toggle.list`, `toggle.bulletedList`, `toggle.numberedList`\n  - Remove type utils: `getListItemType`, `getUnorderedListType`, `getOrderedListType`, `getListItemContentType`\n  - Replace `insertBreakList(editor)` with `withInsertBreakList(ctx)`\n  - Replace `insertFragmentList(editor)` with `withInsertFragmentList(ctx)`\n  - Replace `insertBreakTodoList(editor)` with `withInsertBreakTodoList(ctx)`\n  - Replace `deleteForwardList(editor)` with `withdeleteForwardList(ctx)`\n  - Replace `deleteBackwardList(editor)` with `withdeleteBackwardList(ctx)`\n  - Move list options from `ul` and `ol` to `list` plugin\n  - `toggleList` options are now `{ type: string }`\n\n## @udecode/plate-markdown@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createDeserializeMdPlugin` -> `MarkdownPlugin`\n\n## @udecode/plate-math@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createMathPlugin` -> `MathPlugin`\n\n## @udecode/plate-media@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createMediaPlugin` -> `MediaPlugin`\n  - `FloatingMediaUrlInput`, `submitFloatingMedia` rename option `pluginKey` -> `plugin`\n  - `insertMediaEmbed` remove `key` option\n\n## @udecode/plate-mention@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createMentionPlugin` -> `MentionPlugin`\n  - NEW `MentionInputPlugin`\n  - Remove `createMentionNode` option, override `api.insert.mention` instead\n\n## @udecode/plate-node-id@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createNodeIdPlugin` -> `NodeIdPlugin`\n\n## @udecode/plate-normalizers@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createNormalizersPlugin` -> `NormalizersPlugin`\n\n## @udecode/plate@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - NEW `@udecode/plate-layout`\n  - NEW `/react` exports `@udecode/react-hotkeys`\n  - Split build into `@udecode/plate` and `@udecode/plate/react`.\n  - Remove `@udecode/plate-paragraph`\n  - All stores now start with a capital letter\n\n## @udecode/plate-utils@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Remove `onKeyDownToggleElement`, use shortcuts instead.\n  - Remove `onKeyDownToggleMark`, use shortcuts instead.\n\n## @udecode/plate-playwright@37.0.0\n\n- [#3473](https://github.com/udecode/plate/pull/3473) by [@12joan](https://github.com/12joan) – New package for integrating Plate with Playwright tests\n\n## @udecode/plate-reset-node@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createResetNodePlugin` -> `ResetNodePlugin`\n\n## @udecode/plate-resizable@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Peer dependencies updated\n\n## @udecode/plate-select@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createSelectOnBackspacePlugin` -> `SelectOnBackspacePlugin`\n  - `createDeletePlugin` -> `DeletePlugin`\n\n## @udecode/plate-selection@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - Rename `createSelectionPlugin` to `BlockSelectionPlugin`\n  - Remove `isNodeBlockSelected`, `isBlockSelected`, `hasBlockSelected`, `useBlockSelected` functions\n    - Use `editor.getOptions(BlockSelectionPlugin)` or `editor.useOptions(BlockSelectionPlugin)` instead\n  - Remove `addSelectedRow` function\n    - Use `editor.api.blockSelection.addSelectedRow` instead\n  - Remove `withSelection` HOC\n  - Rename `onCloseBlockSelection` to `onChangeBlockSelection`\n  - Moved `blockSelectionStore` to `BlockSelectionPlugin`\n  - Moved `blockContextMenuStore` to `BlockContextMenuPlugin`\n  - Remove `BlockStartArea` and `BlockSelectionArea` components\n    - Use `areaOptions` in `BlockSelectionPlugin` for configuration instead\n  - Remove dependency on `@viselect/vanilla` package\n    - Forked and integrated selection functionality locally\n  - Add `BlockContextMenuPlugin`, which is now used by `BlockSelectionPlugin`\n    - No need to add it manually\n  - Fix scroll-related bugs in the selection functionality\n  - Improve performance and reliability of block selection\n\n## @udecode/plate-slash-command@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createSlashPlugin` -> `SlashPlugin`\n  - NEW `SlashInputPlugin`\n\n## @udecode/slate@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) – `createTEditor`:\n\n  - Implement default methods for `slate-react` and `slate-history` in `createTEditor`\n  - Add `noop` function to provide default implementations for unimplemented editor methods\n\n  Types:\n\n  - Merge `ReactEditor` and `HistoryEditor` interfaces into `TEditor`, removing `TReactEditor` and `THistoryEditor`\n  - Remove `Value` generic type parameter from function signatures and type definitions\n  - Replace `V extends Value` with `E extends TEditor` for improved type inference\n  - Simplify `TEditor<V>` to `TEditor` in many places\n  - Refactor element-related types, where `E extends TEditor` in all cases:\n    - `EElement<V>` to `ElementOf<E>`\n    - `EText<V>` to `TextOf<E>`\n    - `ENode<V>` to `NodeOf<E>`\n    - `EDescendant<V>` to `DescendantOf<E>`\n    - `EAncestor<V>` to `AncestorOf<E>`\n    - `EElementOrText<V>` to `ElementOrTextOf<E>`\n  - Update `TNodeEntry` related types:\n    - `ENodeEntry<V>` to `NodeEntryOf<E>`\n    - `EElementEntry<V>` to `ElementEntryOf<E>`\n    - `ETextEntry<V>` to `TextEntryOf<E>`\n    - `EAncestorEntry<V>` to `AncestorEntryOf<E>`\n    - `EDescendantEntry<V>` to `DescendantEntryOf<E>`\n  - Remove unused types:\n    - `EElementEntry<V>`\n    - `ETextEntry<V>`\n    - `EDescendantEntry<V>`\n\n## @udecode/slate-react@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) – Types:\n\n  - Remove `TReactEditor` type, as it's now integrated into the main `TEditor` type in `@udecode/slate`. Use `TEditor` instead.\n  - Replace `V extends Value` with `E extends TEditor` for improved type inference\n  - NEW `TEditableProps`, `TRenderElementProps`\n\n## @udecode/slate-utils@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) – Types:\n\n  - Replace `V extends Value` with `E extends TEditor` for improved type inference\n\n## @udecode/plate-suggestion@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createSuggestionPlugin` -> `SuggestionPlugin`\n  - Move `suggestionStore` to `SuggestionPlugin`\n  - Remove `SuggestionProvider` and its hooks\n  - Remove `useSuggestionStates` (replaced by direct option access)\n  - Remove `useSuggestionSelectors` (replaced by option selectors)\n  - Remove `useSuggestionActions` (replaced by api methods)\n  - Replace `useUpdateSuggestion` with `api.suggestion.updateSuggestion`\n  - Replace `useAddSuggestion` with `api.suggestion.addSuggestion`\n  - Replace `useRemoveSuggestion` with `api.suggestion.removeSuggestion`\n  - Replace `useSuggestionById` with `options.suggestionById`\n  - Replace `useSuggestionUserById` with `options.suggestionUserById`\n  - Replace `useCurrentSuggestionUser` with `options.currentSuggestionUser`\n  - Remove `editor.activeSuggestionId`, use plugin option\n  - Remove `useSetIsSuggesting`, use `editor.setOption`\n  - Remove `useSetActiveSuggestionId`, use `editor.setOption`\n  - Remove `editor.isSuggesting`, use plugin option\n  - Remove `SuggestionEditorProps` type\n\n## @udecode/plate-tabbable@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createTabbablePlugin` -> `TabbablePlugin`\n  - `TabbablePlugin` option `isTabbable`: remove first `editor` parameter\n\n## @udecode/plate-table@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createTablePlugin` -> `TablePlugin`\n  - NEW `TableRowPlugin`, `TableCellPlugin`, `TableCellHeaderPlugin`\n  - Replace `insertTableColumn` with `editor.insert.tableColumn`\n  - Replace `insertTableRow` with `editor.insert.tableRow`\n  - Move `cellFactory` option to `create.cell` api\n  - Move `getCellChildren` option to `table.getCellChildren` api\n\n## @udecode/plate-toggle@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createTogglePlugin` -> `TogglePlugin`\n  - Move `toggleControllerStore` to `TogglePlugin`\n  - Remove `setOpenIds` option\n  - Replace `isToggleOpen` with option `isOpen`\n  - Rename `injectToggle` to `renderToggleAboveNodes`\n\n## @udecode/plate-trailing-block@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createTrailingBlockPlugin` -> `TrailingBlockPlugin`\n\n## @udecode/plate-yjs@37.0.0\n\n- [#3420](https://github.com/udecode/plate/pull/3420) by [@zbeyens](https://github.com/zbeyens) –\n  - `createYjsPlugin` -> `YjsPlugin`\n  - Move `yjsStore` to `YjsPlugin`\n  - Move `editor.yjs.provider` to `options.provider`\n  - Rename `RenderAboveEditableYjs` to `YjsAboveEditable`\n\n# 36.0.0\n\nNo breaking changes\n\n# 35.0.0\n\n## @udecode/plate-code-block@35.0.0\n\n- [#3282](https://github.com/udecode/plate/pull/3282) by [@12joan](https://github.com/12joan) – Make the dependency on prismjs optional\n\n  New usage:\n\n  ```ts\n  // Import Prism with your supported languages\n  import Prism from 'prismjs';\n\n  import 'prismjs/components/prism-antlr4.js';\n  import 'prismjs/components/prism-bash.js';\n  import 'prismjs/components/prism-c.js';\n  // ...\n\n  const plugins = createPlugins([\n    createCodeBlockPlugin({\n      options: {\n        prism: Prism,\n      },\n    }),\n  ]);\n  ```\n\n# 34.0.0\n\n## @udecode/plate-selection@34.0.0\n\n- Breaking Change: The `selectedColor` option for `BlockSelectable` has been deprecated. Please use `useBlockSelected` to customize the style of each node component.\n\n- [#3241](https://github.com/udecode/plate/pull/3241) by [@felixfeng33](https://github.com/felixfeng33) – Add logic for the `block-context-menu` and improved the user experience for `block-selection`, such as interactions related to keyboard shortcuts, bug fixes.\n- Starting from this version, a single Cmd+A will no longer select the entire document but will select the entire block instead. Double Cmd+A will use the blockSelection plugin to select all blocks. To disable this behavior, pass handlers: `{ onKeyDown: null }`.\n\n# 33.0.0\n\n## @udecode/plate-serializer-md@33.0.0\n\n- [#3125](https://github.com/udecode/plate/pull/3125) by [@zbeyens](https://github.com/zbeyens) –\n  - `serializeMd`: remove `nodes` option. `editor.children` is now serialized\n\n# 32.0.0\n\nNone (CI release issue)\n\n# 31.0.0\n\nNone (CI release issue)\n\n# 30.0.0\n\n## @udecode/plate-table@30.0.0\n\n- [#2867](https://github.com/udecode/plate/pull/2867) by [@12joan](https://github.com/12joan) – Fix: in v28, `TableProvider` was incorrectly shared by all tables in the editor. `TableProvider` must now be rendered as part of `TableElement`.\n\n# 29.0.0\n\n## @udecode/plate-utils@29.0.0\n\n- [#2829](https://github.com/udecode/plate/pull/2829) by [@zbeyens](https://github.com/zbeyens) –\n  - Moved `withProps` to `@udecode/cn`\n  - Moved `PortalBody`, `Text`, `Box`, `createPrimitiveComponent`, `createSlotComponent`, `withProviders` to `@udecode/react-utils`\n  - Removed `getRootProps` (unused)\n\n# 28.0.0\n\n## @udecode/plate-core@28.0.0\n\n- [`822f6f56b`](https://github.com/udecode/plate/commit/822f6f56be526a6e26f904b9e767c0bc09f1e28b) by [@12joan](https://github.com/12joan) –\n  - Remove `{ fn: ... }` workaround for jotai stores that contain functions\n  - Breaking change: `usePlateSelectors`, `usePlateActions` and `usePlateStates` no longer accept generic type arguments. If custom types are required, cast the resulting values at the point of use, or use hooks like `useEditorRef` that still provide generics.\n\n# 27.0.0\n\n## @udecode/plate-comments@27.0.0\n\n- [#2763](https://github.com/udecode/plate/pull/2763) by [@12joan](https://github.com/12joan) –\n  - Migrate store to `jotai@2`\n  - Revert the breaking changes to `@udecode/plate-comments` made in 26.0.0\n\n## @udecode/plate-core@27.0.0\n\n- [#2763](https://github.com/udecode/plate/pull/2763) by [@12joan](https://github.com/12joan) –\n  - Migrate store from `jotai@1` to `jotai@2`\n    - New dependency: `jotai-x`. See https://github.com/udecode/jotai-x\n    - Accessing a store without an explicit provider component is no longer supported. Attempting to do so will result in a warning in the console: `Tried to access jotai store '${storeName}' outside of a matching provider.`\n  - Upgraded from `zustand@3` to `zustand@4`\n    - See https://github.com/udecode/zustand-x\n  - Rename `zustand-x` exports\n    - `StateActions` -> `ZustandStateActions`\n    - `StoreApi` -> `ZustandStoreApi`\n    - `createStore` -> `createZustandStore`\n    - Note that these exports are deprecated and should not be used in new code. They may be removed in a future version of Plate.\n\n## @udecode/plate-resizable@27.0.0\n\n- [#2763](https://github.com/udecode/plate/pull/2763) by [@12joan](https://github.com/12joan) –\n  - Migrate store to `jotai@2`\n  - Resizable components must now be wrapped inside a `ResizableProvider`\n\n# 26.0.0\n\n## @udecode/plate-comments@26.0.0\n\n- [#2760](https://github.com/udecode/plate/pull/2760) by [@12joan](https://github.com/12joan) –\n  - Renamed the `comments` prop on CommentsProvider to `initialComments` to reflect the fact that updating its value after the initial render has no effect\n  - Removed the following props from CommentsProvider, since they represent the internal state of the comments plugin and should not be controlled externally:\n    - `activeCommentId`\n    - `addingCommentId`\n    - `newValue`\n    - `focusTextarea`\n  - The following props on CommentsProvider can now be updated after the initial render (whereas prior to this version, doing so had no effect):\n    - `myUserId`\n    - `users`\n    - `onCommentAdd`\n    - `onCommentUpdate`\n    - `onCommentDelete`\n\n## @udecode/plate-serializer-html@26.0.0\n\n- [#2733](https://github.com/udecode/plate/pull/2733) by [@dimaanj](https://github.com/dimaanj) –\n  - [Breaking] `serializeHtml`: replaced option `slateProps` by `plateProps`.\n\n# 25.0.1\n\n## @udecode/plate-core@25.0.1\n\n- [#2729](https://github.com/udecode/plate/pull/2729) by [@12joan](https://github.com/12joan) – **This is a breaking change meant to be part of v25, hence the patch.**\n  On `deserializeHtml`, replace `stripWhitespace` with `collapseWhiteSpace`, defaulting to true. The `collapseWhiteSpace` option aims to parse white space in HTML according to the HTML specification, ensuring greater accuracy when pasting HTML from browsers.\n\n## @udecode/plate-comments@25.0.0\n\n- [#2725](https://github.com/udecode/plate/pull/2725) by [@EandrewJones](https://github.com/EandrewJones) – Remove `useCommentValue`, which was redundant with the hooks applied automatically in `CommentEditTextarea.tsx`.\n\n# 24.0.0\n\n## @udecode/plate-core@24.0.0\n\n- [#2629](https://github.com/udecode/plate/pull/2629) by [@zbeyens](https://github.com/zbeyens) –\n\n  - [**Breaking**] Rename `Plate` to `PlateContent`.\n  - [**Breaking**] Rename `PlateProvider` to `Plate`.\n  - [**Breaking**] Rendering `PlateContent` is now required in `Plate`. This allows you to choose where to render the editor next to other components like toolbar. Example:\n\n  ```tsx\n  // Before\n  <Plate />\n  // or\n  <PlateProvider>\n    <Plate />\n  </PlateProvider>\n\n  // After\n  <Plate>\n    <PlateContent />\n  </Plate>\n  ```\n\n  - [**Breaking**] Remove provider props such as `plugins` from `PlateContent`. These props should be passed to `Plate`.\n  - [**Breaking**] Remove `editableProps` prop from `PlateContent`. Move these as`PlateContent` props.\n  - [**Breaking**] Remove `children` prop from `PlateContent`. Render instead these components after `PlateContent`.\n  - [**Breaking**] Remove `firstChildren` prop from `PlateContent`. Render instead these components before `PlateContent`.\n  - [**Breaking**] Remove `editableRef` prop from `PlateContent`. Use `ref` instead.\n  - [**Breaking**] Remove `withPlateProvider`.\n  - [**Breaking**] Rename `usePlateEditorRef` to `useEditorRef`.\n  - [**Breaking**] Rename `usePlateEditorState` to `useEditorState`.\n  - [**Breaking**] Rename `usePlateReadOnly` to `useEditorReadOnly`. This hook can be used below `Plate` while `useReadOnly` can only be used in node components.\n  - [**Breaking**] Rename `usePlateSelection` to `useEditorSelection`.\n  - [**Breaking**] Rename store attributes `keyDecorate`, `keyEditor` and `keySelection` to `versionDecorate`, `versionEditor` and `versionSelection`. These are now numbers incremented on each change.\n  - [**Breaking**] Rename store attribute `isRendered` to `isMounted`.\n\n# 23.0.0\n\n## @udecode/plate-media@23.0.0\n\n- [#2537](https://github.com/udecode/plate/pull/2537) by [@haydencarlson](https://github.com/haydencarlson) – `MediaEmbedElement` is now more headless with a smaller bundle size.\n  Update the following components:\n\n  - `npx shadcn@canary add media-embed-element`\n    - now uses `react-lite-youtube-embed` for YouTube videos.\n    - now uses `react-tweet` for Twitter tweets.\n  - `npx shadcn@canary add image-element`\n\n  Breaking changes:\n\n  - Moved `Resizable` to `@udecode/plate-resizable`\n  - Moved `Caption`, `CaptionTextarea` to `@udecode/plate-caption`\n  - Removed `useMediaEmbed`, `MediaEmbedVideo`, `MediaEmbedTweet`, `Tweet`, `parseMediaUrl`, `mediaStore`\n  - Removed `@udecode/resizable`, `scriptjs`, `react-textarea-autosize` dependencies\n  - `MediaPlugin`\n    - removed `rules`. Use `parsers` option instead.\n    - removed `disableCaption`. Use `createCaptionPlugin` instead.\n  - Caption is now a separate plugin. Install `@udecode/plate-caption` and add it to your plugins:\n\n  ```ts\n  import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED } from '@udecode/plate-media';\n\n  createCaptionPlugin({\n    options: { pluginKeys: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] },\n  });\n  ```\n\n## @udecode/plate-resizable@23.0.0\n\n- [#2541](https://github.com/udecode/plate/pull/2541) by [@zbeyens](https://github.com/zbeyens) –\n  - Package renamed to `@udecode/plate-resizable`.\n  - `ResizeHandle` is now fully headless: no style is applied by default. Add your own `Resizable`, `ResizeHandle` components:\n    - `npx shadcn@canary add resizable`\n\n## @udecode/plate-table@23.0\n\n- Removed `TableCellElementResizable`. Use `useTableCellElementResizableState` and `useTableCellElementResizable` instead.\n\n# 22.0.0\n\nHeadless UI.\n\n## @udecode/plate-ui@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – This package is now a CLI to generate components. Install it as a dev dependency. See https://platejs.org/docs/components/cli.\n\nMigration:\n\n- [Manual installation](https://platejs.org/docs/components/installation/manual).\n- For each unresolved import not listed in the following major changes (components from `@udecode/plate-ui-x`), generate the component using the [CLI](https://platejs.org/docs/components/cli).\n\n## @udecode/plate-comments@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `AccountCircleIcon`\n  - `CheckIcon`\n  - `MoreVertIcon`\n  - `RefreshIcon`\n  - `AvatarImage`\n  - `CommentLinkButton`\n  - `CommentLinkDialog`\n  - `CommentLinkDialogCloseButton`\n  - `CommentLinkDialogCopyLink`\n  - `CommentLinkDialogInput`\n  - `PlateCommentLeaf` for `useCommentLeafState`\n\n## @udecode/plate-dnd@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `Draggable`\n  - `DraggableBlock`\n  - `DraggableBlockToolbar`\n  - `DraggableBlockToolbarWrapper`\n  - `DraggableDropline`\n  - `DraggableGutterLeftProps`\n  - `DraggableRoot`\n  - `DragHandle`\n\n## @udecode/plate-link@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `FloatingLink`\n  - `FloatingLinkEditButton`\n  - `FloatingLinkTextInput`\n  - `UnlinkButton`\n  - `LaunchIcon`\n  - `Link`\n  - `LinkIcon`\n  - `LinkOffIcon`\n  - `ShortTextIcon`\n\n## @udecode/plate-media@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `MediaEmbed`\n\n## @udecode/plate@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Plate 2.0 – Headless UI.\n  Read the docs about the new UI pattern: https://platejs.org/docs/components.\n\n  - Removed `@udecode/plate-ui` dependency.\n  - Removed `@udecode/plate-emoji` dependency. You can install it separately.\n  - Removed `styled-components` peerDependency.\n\n  Replaced `@udecode/plate-headless` dependency (deprecated) by:\n\n  - `@udecode/plate-alignment`\n  - `@udecode/plate-autoformat`\n  - `@udecode/plate-basic-elements`\n  - `@udecode/plate-basic-marks`\n  - `@udecode/plate-block-quote`\n  - `@udecode/plate-break`\n  - `@udecode/plate-code-block`\n  - `@udecode/plate-combobox`\n  - `@udecode/plate-comments`\n  - `@udecode/plate-common`\n  - `@udecode/plate-find-replace`\n  - `@udecode/plate-floating`\n  - `@udecode/plate-font`\n  - `@udecode/plate-heading`\n  - `@udecode/plate-highlight`\n  - `@udecode/plate-horizontal-rule`\n  - `@udecode/plate-indent`\n  - `@udecode/plate-indent-list`\n  - `@udecode/plate-kbd`\n  - `@udecode/plate-line-height`\n  - `@udecode/plate-link`\n  - `@udecode/plate-list`\n  - `@udecode/plate-media`\n  - `@udecode/plate-mention`\n  - `@udecode/plate-node-id`\n  - `@udecode/plate-normalizers`\n  - `@udecode/plate-paragraph`\n  - `@udecode/plate-reset-node`\n  - `@udecode/plate-select`\n  - `@udecode/plate-serializer-csv`\n  - `@udecode/plate-serializer-docx`\n  - `@udecode/plate-serializer-html`\n  - `@udecode/plate-serializer-md`\n  - `@udecode/plate-suggestion`\n  - `@udecode/plate-tabbable`\n  - `@udecode/plate-table`\n  - `@udecode/plate-trailing-block`\n  - `@udecode/resizable`\n\n## @udecode/plate-utils@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Upgraded peer dependencies:\n  - `slate-react: >=0.95.0`\n    Removed:\n  - `useElementPrpos`\n  - `useWrapElement`\n  - `createComponentAs`\n  - `createElementAs`\n\n## @udecode/plate-table@22.0.0\n\n- [#2471](https://github.com/udecode/plate/pull/2471) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `TableCellElement`\n  - `TableCellElementResizableWrapper`\n  - `TableCellElementRoot`\n  - `TableElement`\n  - `TableElementCol`\n  - `TableElementColGroup`\n  - `TableElementRoot`\n  - `TableElementTBody`\n  - `TableRowElement`\n  - `ArrowDropDownCircleIcon`\n  - `BorderAllIcon`\n  - `BorderBottomIcon`\n  - `BorderLeftIcon`\n  - `BorderNoneIcon`\n  - `BorderOuterIcon`\n  - `BorderRightIcon`\n  - `BorderTopIcon`\n\n# 21.0.0\n\n## @udecode/slate@21.0.0\n\n- [#2369](https://github.com/udecode/plate/pull/2369) by [@zbeyens](https://github.com/zbeyens) – Support `slate@0.94.0`, `slate-react@0.94.0` and `slate-history@0.93.0` by upgrading the peer dependencies.\n\n# 20.0.0\n\n## @udecode/plate-core@20.0.0\n\n- [`0077402`](https://github.com/udecode/plate/commit/00774029236d37737abdadf49b074e613e290792) by [@zbeyens](https://github.com/zbeyens) –\n  - This package has been split into multiple packages for separation of concerns and decoupled versioning:\n    - `@udecode/utils` is a collection of miscellaneous utilities. Can be used by any project.\n    - `@udecode/slate` is a collection of `slate` experimental features and bug fixes that may be moved into `slate` one day. It's essentially composed of the generic types. Can be used by vanilla `slate` consumers without plate.\n    - `@udecode/slate-react` is a collection of `slate-react` experimental features and bug fixes that that may be moved into `slate-react` one day. It's essentially composed of the generic types. Can be used by vanilla `slate-react` consumers without plate.\n    - `@udecode/plate-core` is the minimalistic core of plate. It essentially includes `Plate`, `PlateProvider` and their dependencies. Note this package is not dependent on the `*-utils` packages.\n    - `@udecode/slate-utils` is a collection of utils depending on `@udecode/slate`. Can be used by vanilla `slate` consumers without plate.\n    - `@udecode/plate-utils` is a collection of utils depending on `@udecode/slate-react` and `@udecode/plate-core`\n    - `@udecode/plate-common` re-exports the 6 previous packages and is a dependency of all the other packages. It's basically replacing `@udecore/plate-core` as a bundle.\n  - Removed `getPreventDefaultHandler` since it is no longer needed.\n    **Migration**:\n    - If using `@udecode/plate` or `@udecode/plate-headless`: none\n    - Else: find & replace `@udecode/plate-core` by `@udecode/plate-common`\n\n## @udecode/plate-link@20.0.0\n\n- [#2240](https://github.com/udecode/plate/pull/2240) by [@OliverWales](https://github.com/OliverWales) –\n  - Add `allowedSchemes` plugin option\n    - Any URL schemes other than `http(s)`, `mailto` and `tel` must be added to `allowedSchemes`, otherwise they will not be included in links\n\n## @udecode/plate-table@20.0.0\n\n- [#2251](https://github.com/udecode/plate/pull/2251) by [@zbeyens](https://github.com/zbeyens) –\n  - `TablePlugin` option `disableUnsetSingleColSize` has been renamed and inverted into `enableUnsetSingleColSize`. New default is disabled. **Migration**:\n    - if using `disableUnsetSingleColSize: true`, the option can be removed\n    - if using `disableUnsetSingleColSize: false`, use `enableUnsetSingleColSize: true`\n  - `getTableColumnIndex` second parameter type is now: `cellNode: TElement`\n\n## @udecode/plate-ui-dnd@20.0.0\n\n- [#2237](https://github.com/udecode/plate/pull/2237) by [@tmorane](https://github.com/tmorane) – Unstyled logic has been moved to `@udecode/plate-dnd`\n\n  ```ts\n  // before\n  import { createDndPlugin } from '@udecode/plate-ui-dnd';\n\n  // after\n  import { createDndPlugin } from '@udecode/plate-dnd';\n  ```\n\n  Only `withPlateDraggable`, `withPlateDraggables` and `PlateDraggable` are left in `@udecode/plate-ui-dnd`.\n  Renamed:\n\n  - `withDraggables` -> `withPlateDraggables`. In the second parameter, draggable props options have been moved under `draggableProps`:\n\n  ```tsx\n  // before\n  {\n    onRenderDragHandle: () => {}\n    styles,\n  }\n\n  // after\n  {\n    draggableProps: {\n      onRenderDragHandle: () => {}\n      styles,\n    },\n  }\n  ```\n\n## @udecode/plate-ui-table@20.0.0\n\n- [#2251](https://github.com/udecode/plate/pull/2251) by [@zbeyens](https://github.com/zbeyens) – Headless components and hooks moved to `@udecode/plate-table`, so the following components have been renamed:\n  - `TableElement` -> `PlateTableElement`\n    - removed table border to set it at the cell level\n    - `margin-left: 1px` to support cell borders\n    - if all columns have a fixed size, the table will have a dynamic width instead of always 100%\n  - `TableRowElement` -> `PlateTableRowElement`\n  - `TableCellElement` -> `PlateTableCellElement`\n    - removed td border in favor of td::before. The latter is responsible of having the border and the selected background color.\n    - z-index: td is 0, td::before is 10, td::before in selected state is 20, handle is 30, handle resize is 40.\n    - removed `selectedCell` div in favor of `::before`\n  - `TablePopover` -> `PlateTablePopover`\n    Styled props have been removed.\n\n# 19.0.0\n\n## @udecode/plate-core@19.0.0\n\n- [#2097](https://github.com/udecode/plate/pull/2097) by [@zbeyens](https://github.com/zbeyens) –\n  - upgrade deps, including typescript support for the new editor methods:\n  ```json\n  // from\n  \"slate\": \"0.78.0\",\n  \"slate-history\": \"0.66.0\",\n  \"slate-react\": \"0.79.0\"\n  // to\n  \"slate\": \"0.87.0\",\n  \"slate-history\": \"0.86.0\",\n  \"slate-react\": \"0.88.0\"\n  ```\n\n## @udecode/plate@19.0.0\n\n- [#2097](https://github.com/udecode/plate/pull/2097) by [@zbeyens](https://github.com/zbeyens) –\n  - due to esm issues, dnd plugin is not part of plate package anymore. To use it, install `@udecode/plate-ui-dnd`\n  ```ts\n  // before\n  import { createDndPlugin } from '@udecode/plate';\n  // after\n  import { createDndPlugin } from '@udecode/plate-ui-dnd';\n  ```\n  - upgrade peerDeps:\n  ```json\n  // from\n  \"slate\": \">=0.78.0\",\n  \"slate-history\": \">=0.66.0\",\n  \"slate-react\": \">=0.79.0\"\n  // to\n  \"slate\": \">=0.87.0\",\n  \"slate-history\": \">=0.86.0\",\n  \"slate-react\": \">=0.88.0\"\n  ```\n\n# 18.0.0\n\n## @udecode/plate-headless@18.0.0\n\n- [#1889](https://github.com/udecode/plate/pull/1889) by [@zbeyens](https://github.com/zbeyens) –\n  - `@udecode/plate-selection` package moved out from `@udecode/plate` because of https://github.com/Simonwep/selection/issues/124\n  - Migration:\n    - If not using `createBlockSelectionPlugin`, no migration is needed.\n    - Otherwise, install `@udecode/plate-selection` and import `createBlockSelectionPlugin` from that package.\n\n# 17.0.0\n\n## @udecode/plate-core@17.0.0\n\n- [#1871](https://github.com/udecode/plate/pull/1871) by [@zbeyens](https://github.com/zbeyens) –\n\n  - `usePlateStore`:\n    - Plate no longer has a global store containing all the editor states (zustand). Each editor store is now defined in a React context tree ([jotai](https://github.com/pmndrs/jotai)). If you need to access all the editor states at once (as you could do before), you'll need to build that layer yourself.\n    - Plate store is now accessible only below `PlateProvider` or `Plate` (provider-less mode). It means it's no longer accessible outside of a Plate React tree. If you have such use-case, you'll need to build your own layer to share the state between your components.\n    - You can nest many `PlateProvider` with different scopes (`id` prop). Default scope is `PLATE_SCOPE`\n    - Hook usage:\n      - `const value = usePlateSelectors(id).value()`\n      - `const setValue = usePlateActions(id).value()`\n      - `const [value, setValue] = usePlateStates(id).value()`\n    - removed from the store:\n      - `editableProps`, use the props instead\n      - `enabled`, use conditional rendering instead\n      - `isReady`, no point anymore as it's now directly ready\n    - `useEventPlateId` is still used to get the last focused editor id.\n    - Functions are stored in an object `{ fn: <here> }`\n      - `const setOnChange = usePlateActions(id).onChange()`\n      - `setOnChange({ fn: newOnChange })`\n  - `Plate`\n    - if rendered below `PlateProvider`, it will render `PlateSlate > PlateEditable`\n    - if rendered without `PlateProvider`, it will render `PlateProvider > PlateSlate > PlateEditable`\n    - default `id` is no longer `main`, it's now `PLATE_SCOPE`\n  - `PlateProvider`\n    - Each provider has an optional `scope`, so you can have multiple providers in the same React tree and use the plate hooks with the corresponding `scope`.\n    - Plate effects are now run in `PlateProvider`\n      - `initialValue, value, editor, normalizeInitialValue, normalizeEditor` are no longer defined in an effect (SSR support)\n    - Props:\n      - now extends the previous `Plate` props\n      - if using `PlateProvider`, set the provider props on it instead of `Plate`. `Plate` would only need `editableProps` and `PlateEditableExtendedProps`\n      - if not using it, set the provider props on `Plate`\n\n  ```tsx\n  // Before\n  <PlateProvider>\n    <Toolbar>\n      <AlignToolbarButtons />\n    </Toolbar>\n\n    <Plate<MyValue> editableProps={editableProps} <MyValue> initialValue={alignValue} plugins={plugins} />\n  </PlateProvider>\n\n  // After\n  <PlateProvider<MyValue> initialValue={alignValue} plugins={plugins}>\n    <Toolbar>\n      <AlignToolbarButtons />\n    </Toolbar>\n\n    <Plate<MyValue> editableProps={editableProps} />\n  </PlateProvider>\n\n  // After (provider-less mode)\n  <Plate<MyValue> editableProps={editableProps} initialValue={alignValue} plugins={plugins} />\n  ```\n\n  - types:\n    - store `editor` is no longer nullable\n    - store `value` is no longer nullable\n    - `id` type is now `PlateId`\n  - renamed:\n    - `SCOPE_PLATE` to `PLATE_SCOPE`\n    - `getEventEditorId` to `getEventPlateId`\n    - `getPlateActions().resetEditor` to `useResetPlateEditor()`\n  - removed:\n    - `plateIdAtom`\n    - `usePlateId` for `usePlateSelectors().id()`\n    - `EditablePlugins` for `PlateEditable`\n    - `SlateChildren`\n    - `PlateEventProvider` for `PlateProvider`\n    - `withPlateEventProvider` for `withPlateProvider`\n    - `usePlate`\n    - `usePlatesStoreEffect`\n    - `useEventEditorId` for `useEventPlateId`\n    - `platesStore, platesActions, platesSelectors, usePlatesSelectors`\n    - `getPlateActions` for `usePlateActions`\n    - `getPlateSelectors` for `usePlateSelectors`\n    - `getPlateEditorRef` for `usePlateEditorRef`\n    - `getPlateStore, usePlateStore`\n    - `EditorId` for `PlateId`\n\n## @udecode/plate-code-block@17.0.0\n\n- [#1871](https://github.com/udecode/plate/pull/1871) by [@zbeyens](https://github.com/zbeyens) –\n  - Removed these imports because of build errors:\n    - `prismjs/components/prism-django`\n    - `prismjs/components/prism-ejs`\n    - `prismjs/components/prism-php`\n\n## @udecode/plate-ui@17.0.0\n\n- [#1871](https://github.com/udecode/plate/pull/1871) by [@zbeyens](https://github.com/zbeyens) –\n  - Removed `[ELEMENT_CODE_BLOCK]: CodeBlockElement` from Plate UI. You can define it in your app.\n\n# 16.0.0\n\n## @udecode/plate@16.0.0\n\n## @udecode/plate-headless@16.0.0\n\n- [#1721](https://github.com/udecode/plate/pull/1721) by [@zbeyens](https://github.com/zbeyens) –\n  - deprecate `@udecode/plate-image` and `@udecode/plate-media-embed`, those got merged into `@udecode/plate-media`\n\n## @udecode/plate-media@16.0.0\n\n- [#1721](https://github.com/udecode/plate/pull/1721) by [@zbeyens](https://github.com/zbeyens) –\n  - removed:\n    - `useImageElement` for `useElement`\n    - `MediaEmbedUrlInput` for `FloatingMediaUrlInput`\n    - `parseEmbedUrl` for `parseMediaUrl`\n    - `EmbedProviders`\n  - renamed:\n    - `ImageImg` to `Image`\n    - `ImageCaptionTextarea` to `CaptionTextarea`\n    - `useImageCaptionString` to `useCaptionString`\n    - `ImageResizable` to `Resizable`\n\n## @udecode/plate-ui-table@16.0.0\n\n- [#1721](https://github.com/udecode/plate/pull/1721) by [@zbeyens](https://github.com/zbeyens) –\n- `TableElementBase` props:\n  - replace `onRenderContainer` by `floatingOptions` or by replacing `ELEMENT_TABLE` in the `createPlateUI` function.\n- `TablePopover` is now a floating instead of tippy\n- deps:\n  - replaced `plate-ui-popover` by `plate-floating`\n\n## @udecode/plate-ui@16.0.0\n\n- [#1721](https://github.com/udecode/plate/pull/1721) by [@zbeyens](https://github.com/zbeyens) –\n- deprecate `@udecode/plate-ui-popover` for `@udecode/plate-floating`\n\n# 15.0.0\n\n## @udecode/plate-combobox@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n  - deps:\n    - replaced `@udecode/plate-ui-popper` by `@udecode/plate-floating`\n  - `comboboxStore`:\n    - removed `popperContainer`, use `floatingOptions` instead\n    - removed `popperOptions`, use `floatingOptions` instead\n\n## @udecode/plate-link@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n  - `createLinkPlugin`\n    - removed `onKeyDownLink` for floating link\n    - removed `hotkey` for `triggerFloatingLinkHotkeys`\n  - removed:\n    - `getAndUpsertLink` for `upsertLink`\n    - `upsertLinkAtSelection` for `upsertLink`\n  - `LinkToolbarButton`:\n    - `onClick` now calls `triggerFloatingLink`\n\n## @udecode/plate-table@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n- remove `addRow` for `insertTableRow`\n- remove `addColumn` for `insertTableColumn`\n\n## @udecode/plate@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n  - remove `@udecode/plate-ui-popper` dep for `@udecode/plate-floating`\n\n## @udecode/plate-ui-button@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n  - moved `Button` to `@udecode/plate-button`\n  - `Button` is now unstyled\n\n## @udecode/plate-ui-popper@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n- deprecated, use instead `@udecode/plate-floating`\n\n## @udecode/plate-ui-toolbar@15.0.0\n\n- [#1677](https://github.com/udecode/plate/pull/1677) by [@zbeyens](https://github.com/zbeyens) –\n- remove `@udecode/plate-ui-popper` and `react-popper` deps for `@udecode/plate-floating`\n- `BalloonToolbarProps`:\n  - removed `popperOptions` for `floatingOptions`\n- remove `useBalloonToolbarPopper` for `useFloatingToolbar`\n\n# 14.0.0\n\n## @udecode/plate-core@14.0.0\n\n- [#1633](https://github.com/udecode/plate/pull/1633) by [@tjramage](https://github.com/tjramage) – Moved `serializeHtml` and its utils to `@udecode/plate-serializer-html` as it has a new dependency: [html-entities](https://www.npmjs.com/package/html-entities).\n  - If you're using `@udecode/plate`, no migration is needed\n  - Otherwise, import it from `@udecode/plate-serializer-html`\n\n# 13.0.0\n\n## @udecode/plate-core@13.1.0\n\n- `Plate` children are now rendered as last children of `Slate` (previously first children). To reproduce the previous behavior, move `children` to `firstChildren`\n\n## @udecode/plate@13.0.0\n\n## @udecode/plate-headless@13.0.0\n\n- [#1585](https://github.com/udecode/plate/pull/1585) by [@zbeyens](https://github.com/zbeyens) – Removed `@udecode/plate-juice` from `@udecode/plate`. Install it if using `@udecode/plate-serializer-docx`:\n  ```bash\n  pnpm install @udecode/plate-juice\n  ```\n\n## @udecode/plate@13.0.0\n\n## @udecode/plate-ui@13.0.0\n\n## @udecode/plate-ui-dnd@13.0.0\n\n- [#1585](https://github.com/udecode/plate/pull/1585) by [@zbeyens](https://github.com/zbeyens) – Moved `react-dnd react-dnd-html5-backend` deps to peer-dependencies. Install these if using `@udecode/plate-ui-dnd`:\n  ```bash\n  pnpm install react-dnd react-dnd-html5-backend\n  ```\n\n# 12.0.0\n\n## @udecode/plate-ui-dnd@12.0.0\n\n- [#1579](https://github.com/udecode/plate/pull/1579) by [@zbeyens](https://github.com/zbeyens) – renamed:\n- `useDndBlock` options:\n  - `blockRef` -> `nodeRef`\n  - `removePreview` -> `preview.disable`\n- `useDropBlockOnEditor` -> `useDropBlock`\n- `useDropBlock` options:\n  - `blockRef` -> `nodeRef`\n  - `setDropLine` -> `onChangeDropLine`\n    signature change:\n- `getHoverDirection`:\n\n```tsx\n// before\n(\n  dragItem: DragItemBlock,\n  monitor: DropTargetMonitor,\n  ref: any,\n  hoverId: string\n)\n// after\n{\n  dragItem,\n  id,\n  monitor,\n  nodeRef,\n}: GetHoverDirectionOptions\n```\n\n# 11.0.0\n\n## @udecode/plate-core@11.0.6\n\n- [#1500](https://github.com/udecode/plate/pull/1500) by [@zbeyens](https://github.com/zbeyens) – Thanks @ianstormtaylor for the initial work on https://github.com/ianstormtaylor/slate/pull/4177.\n\n  This release includes major changes to plate and slate types:\n\n  - Changing the `TEditor` type to be `TEditor<V>` where `V` represents the \"value\" being edited by Slate. In the most generic editor, `V` would be equivalent to `TElement[]` (since that is what is accepted as children of the editor). But in a custom editor, you might have `TEditor<Array<Paragraph | Quote>>`.\n  - Other `TEditor`-and-`TNode`-related methods have been also made generic, so for example if you use `getLeafNode(editor, path)` it knows that the return value is a `TText` node. But more specifically, it knows that it is the text node of the type you've defined in your custom elements (with any marks you've defined).\n  - This replaces the declaration merging approach, and provides some benefits. One of the drawbacks to declaration merging was that it was impossible to know whether you were dealing with an \"unknown\" or \"known\" element, since the underlying type was changed. Similarly, having two editors on the page with different schemas wasn't possible to represent. Hopefully this approach with generics will be able to smoothly replace the declaration merging approach. (While being easy to migrate to, since you can pass those same custom element definitions into `TEditor` still.)\n\n**Define your custom types**\n\n- Follow https://platejs.org/docs/typescript example.\n\n**Slate types**\n\nThose Slate types should be replaced by the new types:\n\n- `Editor` -> `TEditor<V extends Value = Value>`\n  - Note that `TEditor` methods are not typed based on `Value` as it would introduce a circular dependency. You can use `getTEditor(editor)` to get the editor with typed methods.\n- `ReactEditor` -> `TReactEditor<V extends Value = Value>`\n- `HistoryEditor` -> `THistoryEditor<V extends Value = Value>`\n- `EditableProps` -> `TEditableProps<V extends Value = Value>`\n- `Node` -> `TNode`\n- `Element` -> `TElement`\n- `Text` -> `TText`\n- `NodeEntry` -> `TNodeEntry`\n- `NodeProps` -> `TNodeProps`\n\n**Slate functions**\n\nThose Slate functions should be replaced by the new typed ones:\n\n- As the new editor type is not matching the slate ones, all `Transforms`, `Editor`, `Node`, `Element`, `Text`, `HistoryEditor`, `ReactEditor` functions should be replaced: The whole API has been typed into Plate core. See https://github.com/udecode/plate/packages/core/src/slate\n- `createEditor` -> `createTEditor`\n- `withReact` -> `withTReact`\n- `withHistory` -> `withTHistory`\n\n**Generic types**\n\n- `<T = {}>` could be used to extend the editor type. It is now replaced by `<E extends PlateEditor<V> = PlateEditor<V>>` to customize the whole editor type.\n- When the plugin type is customizable, these generics are used: `<P = PluginOptions, V extends Value = Value, E extends PlateEditor<V> = PlateEditor<V>>`, where `P` is the plugin options type.\n- `Editor` functions are using `<V extends Value>` generic, where `V` can be a custom editor value type used in `PlateEditor<V>`.\n- `Editor` functions returning a node are using `<N extends ENode<V>, V extends Value = Value>` generics, where `N` can be a custom returned node type.\n- `Editor` callbacks (e.g. a plugin option) are using `<V extends Value = Value, E extends PlateEditor<V> = PlateEditor<V>>` generics, where `E` can be a custom editor type.\n- `Node` functions returning a node are using `<N extends Node, R extends TNode = TNode>` generics.\n- These generics are used by `<V extends Value, K extends keyof EMarks<V>>`: `getMarks`, `isMarkActive`, `removeMark`, `setMarks`, `ToggleMarkPlugin`, `addMark`, `removeEditorMark`\n- `WithOverride` is a special type case as it can return a new editor type:\n\n  ```tsx\n  // before\n  export type WithOverride<T = {}, P = {}> = (\n    editor: PlateEditor<T>,\n    plugin: WithPlatePlugin<T, P>\n  ) => PlateEditor<T>;\n\n  // after - where E is the Editor type (input), and EE is the Extended Editor type (output)\n  export type WithOverride<\n    P = PluginOptions,\n    V extends Value = Value,\n    E extends PlateEditor<V> = PlateEditor<V>,\n    EE extends E = E,\n  > = (editor: E, plugin: WithPlatePlugin<P, V, E>) => EE;\n  ```\n\n- `type TEditor<V extends Value>`\n- `type PlateEditor<V extends Value>`\n\n**Renamed functions**\n\n- `getAbove` -> `getAboveNode`\n- `getParent` -> `getParentNode`\n- `getText` -> `getEditorString`\n- `getLastNode` -> `getLastNodeByLevel`\n- `getPointBefore` -> `getPointBeforeLocation`\n- `getNodes` -> `getNodeEntries`\n- `isStart` -> `isStartPoint`\n- `isEnd` -> `isEndPoint`\n\n**Replaced types**\n\nRemoving node props types in favor of element types (same props + extends `TElement`). You can use `TNodeProps` to get the node data (props).\n\n- `LinkNodeData` -> `TLinkElement`\n- `ImageNodeData` -> `TImageElement`\n- `TableNodeData` -> `TTableElement`\n- `MentionNodeData` -> `TMentionElement`\n- `MentionNode` -> `TMentionElement`\n- `MentionInputNodeData` -> `TMentionInputElement`\n- `MentionInputNode` -> `TMentionInputElement`\n- `CodeBlockNodeData` -> `TCodeBlockElement`\n- `MediaEmbedNodeData` -> `TMediaEmbedElement`\n- `TodoListItemNodeData` -> `TTodoListItemElement`\n- `ExcalidrawNodeData` -> `TExcalidrawElement`\n\n**Utils**\n\n- `match` signature change:\n\n```\n<T extends TNode>(\n  obj: T,\n  path: TPath,\n  predicate?: Predicate<T>\n)\n```\n\n- `deleteFragment` is now using `Editor.deleteFragment`\n\n## @udecode/plate-table@11.0.0\n\n- `getEmptyTableNode` default options changed. Migration:\n\n```tsx\n// From (0 row count and col count, previously it was 2)\ngetEmptyTableNode(editor);\n// To\ngetEmptyTableNode(editor, { rowCount: 2, colCount: 2 });\n```\n\n## @udecode/plate-styled-components@11.0.0\n\n**Generic types**\n\n- `type StyledElementProps<V extends Value, N extends TElement = EElement<V>, TStyles = {}>`\n\n# 10.0.0\n\n## @udecode/plate-ui-toolbar@10.0.0\n\n- [#1377](https://github.com/udecode/plate/pull/1377) by [@zbeyens](https://github.com/zbeyens) – Before, `BalloonToolbar` could be outside `Plate`. Now, `BallonToolbar` should be a child of `Plate` to support multiple editors.\n\n# 9.0.0\n\n## @udecode/plate-core@9.0.0\n\n- [#1303](https://github.com/udecode/plate/pull/1303) by [@zbeyens](https://github.com/zbeyens) –\n  - `Plate`\n    - `editor` prop can now be fully controlled: Plate is not applying `withPlate` on it anymore\n  - `PlatePlugin.deserializeHtml`\n    - can't be an array anymore\n    - moved `validAttribute`, `validClassName`, `validNodeName`, `validStyle` to `deserializeHtml.rules` property\n  - renamed `plateStore` to `platesStore`\n  - `platesStore` is now a zustood store\n  - `eventEditorStore` is now a zustood store\n  - `getPlateId` now gets the last editor id if not focused or blurred\n    - used by `usePlateEditorRef` and `usePlateEditorState`\n  - removed:\n    - `usePlateEnabled` for `usePlateSelectors(id).enabled()`\n    - `usePlateValue` for `usePlateSelectors(id).value()`\n    - `usePlateActions`:\n      - `resetEditor` for `getPlateActions(id).resetEditor()`\n      - `clearState` for `platesActions.unset()`\n      - `setInitialState` for `platesActions.set(id)`\n      - `setEditor` for `getPlateActions(id).editor(value)`\n      - `setEnabled` for `getPlateActions(id).enabled(value)`\n      - `setValue` for `getPlateActions(id).value(value)`\n    - `getPlateState`\n    - `usePlateState`\n    - `usePlateKey`\n\n## @udecode/plate@9.0.0\n\n- [#1303](https://github.com/udecode/plate/pull/1303) by [@zbeyens](https://github.com/zbeyens) –\n- renamed `plate-x-ui` to `plate-ui-x`: all packages depending on `styled-components` has `plate-ui` prefix\n- renamed `plate-x-serializer` to `plate-serializer-x`\n- is now exporting only these (new) packages:\n  - `@udecode/plate-headless`: all unstyled packages\n  - `@udecode/plate-ui`: all styled packages\n- renamed `PlateState` to `PlateStoreState`\n\n# 8.0.0\n\n## @udecode/plate-indent-list@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Removed:\n\n  - `IndentListPluginOptions` for `PlatePlugin`\n\n  Rename:\n\n  - `getIndentListInjectComponent` to `injectIndentListComponent`\n\n## @udecode/plate-core@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Breaking changes:\n\n  ### `Plate`\n\n  - removed `components` prop:\n\n  ```tsx\n  // Before\n  <Plate plugins={plugins} components={components} />;\n\n  // After\n  // option 1: use the plugin factory\n  let plugins = [\n    createParagraphPlugin({\n      component: ParagraphElement,\n    }),\n  ];\n\n  // option 2: use createPlugins\n  plugins = createPlugins(plugins, {\n    components: {\n      [ELEMENT_PARAGRAPH]: ParagraphElement,\n    },\n  });\n\n  <Plate plugins={plugins} />;\n  ```\n\n  - removed `options` prop:\n\n  ```tsx\n  // Before\n  <Plate plugins={plugins} options={options} />;\n\n  // After\n  // option 1: use the plugin factory\n  let plugins = [\n    createParagraphPlugin({\n      type: 'paragraph',\n    }),\n  ];\n\n  // option 2: use createPlugins\n  plugins = createPlugins(plugins, {\n    overrideByKey: {\n      [ELEMENT_PARAGRAPH]: {\n        type: 'paragraph',\n      },\n    },\n  });\n\n  <Plate plugins={plugins} />;\n  ```\n\n  ### `PlatePlugin`\n\n  - `key`\n    - replacing `pluginKey`\n    - is now required: each plugin needs a key to be retrieved by key.\n  - all handlers have `plugin` as a second parameter:\n\n  ```tsx\n  // Before\n  export type X<T = {}> = (editor: PlateEditor<T>) => Y;\n\n  // After\n  export type X<T = {}, P = {}> = (\n    editor: PlateEditor<T>,\n    plugin: WithPlatePlugin<T, P>\n  ) => Y;\n  ```\n\n  - `serialize` no longer has `element` and `leaf` properties:\n\n  ```ts\n  type SerializeHtml = RenderFunction<\n    PlateRenderElementProps | PlateRenderLeafProps\n  >;\n  ```\n\n  Renamed:\n\n  - `injectParentComponent` to `inject.aboveComponent`\n  - `injectChildComponent` to `inject.belowComponent`\n  - `overrideProps` to `inject.props`\n    - `transformClassName`, `transformNodeValue`, `transformStyle` first parameter is no longer `editor` as it's provided by `then` if needed.\n    - the previously `getOverrideProps` is now the core behavior if `inject.props` is defined.\n  - `serialize` to `serializeHtml`\n  - `deserialize` to `deserializeHtml`\n    - can be an array\n    - the old deserializer options are merged to `deserializeHtml`\n\n  ```tsx\n  type DeserializeHtml = {\n    /** List of HTML attribute names to store their values in `node.attributes`. */\n    attributeNames?: string[];\n\n    /**\n     * Deserialize an element. Use this instead of plugin.isElement if you don't\n     * want the plugin to renderElement.\n     *\n     * @default plugin.isElement\n     */\n    isElement?: boolean;\n\n    /**\n     * Deserialize a leaf. Use this instead of plugin.isLeaf if you don't want the\n     * plugin to renderLeaf.\n     *\n     * @default plugin.isLeaf\n     */\n    isLeaf?: boolean;\n\n    /** Deserialize html element to slate node. */\n    getNode?: (element: HTMLElement) => AnyObject | undefined;\n\n    query?: (element: HTMLElement) => boolean;\n\n    /**\n     * Deserialize an element:\n     *\n     * - If this option (string) is in the element attribute names.\n     * - If this option (object) values match the element attributes.\n     */\n    validAttribute?: string | { [key: string]: string | string[] };\n\n    /** Valid element `className`. */\n    validClassName?: string;\n\n    /** Valid element `nodeName`. Set '*' to allow any node name. */\n    validNodeName?: string | string[];\n\n    /**\n     * Valid element style values. Can be a list of string (only one match is\n     * needed).\n     */\n    validStyle?: Partial<\n      Record<keyof CSSStyleDeclaration, string | string[] | undefined>\n    >;\n\n    /** Whether or not to include deserialized children on this node */\n    withoutChildren?: boolean;\n  };\n  ```\n\n  - handlers starting by `on...` are moved to `handlers` property.\n\n  ```ts\n  // Before\n  onDrop: handler;\n\n  // After\n  handlers: {\n    onDrop: handler;\n  }\n  ```\n\n  Removed:\n\n  - `renderElement` is favor of:\n    - `isElement` is a boolean that enables element rendering.\n    - the previously `getRenderElement` is now the core behavior.\n  - `renderLeaf` is favor of:\n    - `isLeaf` is a boolean that enables leaf rendering.\n    - the previously `getRenderLeaf` is now the core behavior.\n  - `inlineTypes` and `voidTypes` for:\n    - `isInline` is a boolean that enables inline rendering.\n    - `isVoid` is a boolean that enables void rendering.\n\n  ### General\n\n  - the following plugins are now part of the core plugins, so you need to remove these from your `plugins` prop:\n\n  ```ts\n  const corePlugins = [\n    createReactPlugin(),\n    createHistoryPlugin(),\n    createEventEditorPlugin(),\n    createInlineVoidPlugin(),\n    createInsertDataPlugin(),\n    createDeserializeAstPlugin(),\n    createDeserializeHtmlPlugin(),\n  ];\n  ```\n\n  - `plugins` is not a parameter anymore as it can be retrieved in `editor.plugins`\n  - `withInlineVoid` is now using plugins `isInline` and `isVoid` plugin properties.\n\n  Renamed:\n\n  - `getPlatePluginType` to `getPluginType`\n  - `getEditorOptions` to `getPlugins`\n  - `getPlatePluginOptions` to `getPlugin`\n  - `pipeOverrideProps` to `pipeInjectProps`\n  - `getOverrideProps` to `pluginInjectProps`\n  - `serializeHTMLFromNodes` to `serializeHtml`\n    - `getLeaf` to `leafToHtml`\n    - `getNode` to `elementToHtml`\n  - `xDeserializerId` to `KEY_DESERIALIZE_X`\n  - `deserializeHTMLToText` to `htmlTextNodeToString`\n  - `deserializeHTMLToMarks` to `htmlElementToLeaf` and `pipeDeserializeHtmlLeaf`\n  - `deserializeHTMLToElement` to `htmlElementToElement` and `pipeDeserializeHtmlElement`\n  - `deserializeHTMLToFragment` to `htmlBodyToFragment`\n  - `deserializeHTMLToDocumentFragment` to `deserializeHtml`\n  - `deserializeHTMLToBreak` to `htmlBrToNewLine`\n  - `deserializeHTMLNode` to `deserializeHtmlNode`\n  - `deserializeHTMLElement` to `deserializeHtmlElement`\n\n  Removed:\n\n  - `usePlateKeys`, `getPlateKeys`\n  - `usePlateOptions` for `getPlugin`\n  - `getPlateSelection` for `getPlateEditorRef().selection`\n  - `flatMapByKey`\n  - `getEditableRenderElement` and `getRenderElement` for `pipeRenderElement` and `pluginRenderElement`\n  - `getEditableRenderLeaf` and `getRenderLeaf` for `pipeRenderLeaf` and `pluginRenderLeaf`\n  - `getInlineTypes`\n  - `getVoidTypes`\n  - `getPlatePluginTypes`\n  - `getPlatePluginWithOverrides`\n  - `mapPlatePluginKeysToOptions`\n  - `withDeserializeX` for `PlatePlugin.editor.insertData`\n\n  Changed types:\n\n  - `PlateEditor`:\n    - removed `options` for `pluginsByKey`\n  - `WithOverride` is not returning an extended editor anymore (input and output editors are assumed to be the same types for simplicity).\n  - `PlateState`\n    - renamed `keyChange` to `keyEditor`\n    - removed `plugins` for `editor.plugins`\n    - removed `pluginKeys`\n    - removed `selection` for `editor.selection`\n    - actions:\n      - removed `setSelection`, `setPlugins`, `setPluginKeys`\n      - removed `incrementKeyChange` for\n\n  Renamed types:\n\n  - `XHTMLY` to `XHtmlY`\n  - `Deserialize` to `DeseralizeHtml`\n\n  Removed types:\n\n  - `PlatePluginOptions`:\n    - `type` to `PlatePlugin.type`\n    - `component` to `PlatePlugin.component`\n    - `deserialize` to `PlatePlugin.deserializeHtml`\n    - `getNodeProps` to `PlatePlugin.props.nodeProps`\n    - `hotkey` to `HotkeyPlugin`\n    - `clear` to `ToggleMarkPlugin`\n    - `defaultType` is hardcoded to `p.type`\n  - `OverrideProps` for `PlatePlugin.inject.props`\n  - `Serialize` for `PlatePlugin.serializeHtml`\n  - `NodeProps` for `AnyObject`\n  - `OnKeyDownElementOptions` for `HotkeyPlugin`\n  - `OnKeyDownMarkOptions` for `ToggleMarkPlugin`\n  - `WithInlineVoidOptions`\n  - `GetNodeProps` for `PlatePluginProps`\n  - `DeserializeOptions`, `GetLeafDeserializerOptions`, `GetElementDeserializerOptions`, `GetNodeDeserializerOptions`, `GetNodeDeserializerRule`, `DeserializeNode` for `PlatePlugin.deserializeHtml`\n  - `PlateOptions`\n  - `RenderNodeOptions`\n  - `DeserializedHTMLElement`\n\n## @udecode/plate-find-replace@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `useFindReplacePlugin` for `createFindReplacePlugin`\n\n## @udecode/plate-alignment@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) –\n  - `setAlign`\n    - moved param 3 to param 2 as `setNodesOptions`\n\n## @udecode/plate-basic-elements@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) –\n  - renamed `createBasicElementPlugins` to `createBasicElementsPlugin`\n\n## @udecode/plate-code-block@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `getCodeBlockPluginOptions` for `getPlugin`\n  - `getCodeLinePluginOptions` for `getPlugin`\n\n## @udecode/plate-heading@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Renamed:\n  - `HeadingPluginOptions` to `HeadingsPlugin`\n\n## @udecode/plate-mention@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Removed:\n  - `getMentionInputPluginOptions` for `getPlugin`\n  - `getMentionInputType` for `getPluginType`\n  - `COMBOBOX_TRIGGER_MENTION`\n\n## @udecode/plate-basic-marks@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) –\n  - renamed `createBasicMarkPlugins` to `createBasicMarksPlugin`\n\n## @udecode/plate@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Breaking changes:\n\n  - all plugins options are now defined in the plugin itself\n  - plugins which now have nested plugins instead of array:\n    - `createBasicElementsPlugin`\n    - `createCodeBlockPlugin`\n    - `createHeadingPlugin`\n    - `createListPlugin`\n    - `createTablePlugin`\n    - `createBasicMarksPlugin`\n\n  Removed:\n\n  - `createEditorPlugins` for `createPlateEditor` (without components) and `createPlateEditorUI` (with Plate components)\n  - `createPlateOptions` for `createPlugins`\n  - all `DEFAULTS_X`: these are defined in the plugins\n  - all `getXDeserialize`: these are defined in the plugins\n  - all `WithXOptions` for extended plugins\n  - all `getXRenderElement`\n  - some plugin option types are removed for `PlatePlugin`\n\n  Renamed:\n\n  - `createPlateComponents` to `createPlateUI`\n  - all `getXY` handlers to `yX` (e.g. `getXOnKeyDown` to `onKeyDownX`)\n  - all `XPluginOptions` to `XPlugin`\n  - all `pluginKey` parameter to `key` except in components\n\n  Renamed types:\n\n  - `DecorateSearchHighlightOptions` to `FindReplacePlugin`\n\n  Updated deps:\n\n  - `\"slate\": \"0.70.0\"`\n  - `\"slate-react\": \"0.70.1\"`\n\n  Removed deps (merged to core):\n\n  - `plate-common`\n  - `plate-ast-serializer`\n  - `plate-html-serializer`\n  - `plate-serializer`\n\n## @udecode/plate-serializer-csv@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) – Renamed:\n  - `createDeserializeCSVPlugin` to `createDeserializeCsvPlugin`\n  - `deserializeCSV` to `deserializeCsv`\n\n## @udecode/plate-serializer-md@8.0.0\n\n- [#1234](https://github.com/udecode/plate/pull/1234) by [@zbeyens](https://github.com/zbeyens) –\n\n  - `createDeserializeMdPlugin`:\n    - is now disabled if there is html data in the data transfer.\n\n  Renamed:\n\n  - `createDeserializeMDPlugin` to `createDeserializeMdPlugin`\n  - `deserializeMD` to `deserializeMd`\n\n# 7.0.0\n\n## `@udecode/plate-core`\n\n- renamed:\n  - `SPEditor` to `PEditor` (note that `PlateEditor` is the new default)\n  - `SPRenderNodeProps` to `PlateRenderNodeProps`\n  - `SPRenderElementProps` to `PlateRenderElementProps`\n  - `SPRenderLeafProps` to `PlateRenderLeafProps`\n  - `useEventEditorId` to `usePlateEventId`\n  - `useStoreEditorOptions` to `usePlateOptions`\n  - `useStoreEditorRef` to `usePlateEditorRef`\n  - `useStoreEditorSelection` to `usePlateSelection`\n  - `useStoreEditorState` to `usePlateEditorState`\n  - `useStoreEditorValue` to `usePlateValue`\n  - `useStoreEnabled` to `usePlateEnabled`\n  - `useStorePlate` to `usePlatePlugins`\n  - `useStorePlatePluginKeys` to `usePlateKeys`\n  - `useStoreState` to `usePlateState`\n- `getPlateId`: Get the last focused editor id, else get the last blurred editor id, else get the first editor id, else `null`\n- `getPlateState`:\n  - removed first parameter `state`\n  - previously when giving no parameter, it was returning the first editor. Now it's returning the editor with id = `getPlateId()`. It means `useEventEditorId('focus')` is no longer needed for\n    - `usePlateEditorRef`\n    - `usePlateEditorState`\n    - `usePlateX`...\n\n## `@udecode/plate-alignment`\n\n- `setAlign`: option `align` renamed to `value`\n- removed `getAlignOverrideProps()` in favor of `getOverrideProps(KEY_ALIGN)`\n\n## `@udecode/plate-indent`\n\n- removed `getIndentOverrideProps()` in favor of `getOverrideProps(KEY_INDENT)`\n- rename `onKeyDownHandler` to `getIndentOnKeyDown()`\n- `IndentPluginOptions`\n  - rename `types` to `validTypes`\n  - rename `cssPropName` to `styleKey`\n  - rename `transformCssValue` to `transformNodeValue`\n\n## `@udecode/plate-line-height`\n\n- `setLineHeight`: option `lineHeight` renamed to `value`\n- removed `getLineHeightOverrideProps` in favor of `getOverrideProps(KEY_LINE_HEIGHT)`\n\n## `@udecode/plate-mention`\n\n- `getMentionOnSelectItem`:\n  - removed `createMentionNode` in favor of plugin options\n  - removed `insertSpaceAfterMention` in favor of plugin options\n\n## `@udecode/plate-mention-ui`\n\n- `MentionCombobox` props:\n  - removed `trigger` in favor of plugin options\n  - removed `insertSpaceAfterMention` in favor of plugin options\n  - removed `createMentionNode` in favor of plugin options\n\n## `@udecode/plate-x-ui`\n\n- renamed `ToolbarAlign` to `AlignToolbarButton`\n- renamed `ToolbarCodeBlock` to `CodeBlockToolbarButton`\n- renamed `ToolbarElement` to `BlockToolbarButton`\n- renamed `ToolbarImage` to `ImageToolbarButton`\n- renamed `ToolbarLink` to `LinkToolbarButton`\n- renamed `ToolbarList` to `ListToolbarButton`\n- renamed `ToolbarLineHeight` to `LineHeightToolbarDropdown`\n- renamed `ToolbarMark` to `MarkToolbarButton`\n- renamed `ToolbarMediaEmbed` to `MediaEmbedToolbarButton`\n- renamed `ToolbarSearchHighlight` to `SearchHighlightToolbar`\n- renamed `ToolbarTable` to `TableToolbarButton`\n\n# 6.0.0\n\n## `@udecode/plate-alignment`\n\nThe align plugin is no longer wrapping a block, but instead setting an `align` property to an existing block.\n\n- `createAlignPlugin`:\n  - removed `pluginKeys`, `renderElement` and `deserialize`\n- removed:\n  - `ELEMENT_ALIGN_LEFT`\n  - `ELEMENT_ALIGN_CENTER`\n  - `ELEMENT_ALIGN_RIGHT`\n  - `ELEMENT_ALIGN_JUSTIFY`\n  - `KEYS_ALIGN` in favor of `KEY_ALIGN`\n  - `getAlignDeserialize`\n  - `upsertAlign` in favor of `setAlign`\n\nMigration (normalizer):\n\n- for each node:\n  - run `parent = getParent(editor, path)`, if `parent[0].type` is one of the alignment values:\n    - run `setAlign(editor, { align }, { at: path })`\n    - run `unwrapNodes(editor, { at: path })`\n\n## `@udecode/plate-alignment-ui`\n\n- `ToolbarAlignProps`:\n  - removed `type` in favor of `align`\n  - removed `unwrapTypes`\n  - added `align`\n\n# 5.0.0\n\n## `@udecode/plate-mention`\n\nThe mention plugin is now using the combobox.\n\n- removed `useMentionPlugin` in favor of `createMentionPlugin`\n  - migration: replace `useMentionPlugin().plugin` by `createMentionPlugin()`\n- removed options:\n  - `mentionableSearchPattern`\n  - `insertSpaceAfterMention`\n  - `maxSuggestions`: moved to `comboboxStore`\n  - `trigger`: moved to `comboboxStore`\n  - `mentionables`: moved to `items` in `comboboxStore`\n  - `mentionableFilter`: moved to `filter` in `comboboxStore`\n- removed `matchesTriggerAndPattern` in favor of `getTextFromTrigger`\n- removed `MentionNodeData` in favor of `ComboboxItemData`\n\n```ts\nexport interface ComboboxItemData {\n  /** Unique key. */\n  key: string;\n  /** Item text. */\n  text: any;\n  /**\n   * Whether the item is disabled.\n   *\n   * @default false\n   */\n  disabled?: boolean;\n  /** Data available to `onRenderItem`. */\n  data?: unknown;\n}\n```\n\n## `@udecode/plate-mention-ui`\n\n- removed `MentionSelect` in favor of `MentionCombobox`\n\n## `@udecode/plate-toolbar`\n\n- removed `setPositionAtSelection` in favor of `useBalloonToolbarPopper`\n- removed `useBalloonMove` in favor of `useBalloonToolbarPopper`\n- removed `usePopupPosition` in favor of `useBalloonToolbarPopper`\n- removed `useBalloonShow` in favor of `useBalloonToolbarPopper`\n  `BalloonToolbar` props:\n- removed `direction` in favor of `popperOptions.placement`\n- renamed `scrollContainer` to `popperContainer`\n\n# 4.0.0\n\n## `@udecode/plate-toolbar`\n\n- `BalloonToolbar`: removed `hiddenDelay` prop.\n\n# 3.0.0\n\n## All UI packages\n\nThere was multiple instances of `styled-components` across all the packages.\nSo we moved `styled-components` from dependencies to peer dependencies.\n\n### Before\n\n`styled-components` was not listed in your dependencies\n\n### After\n\nAdd `styled-components` to your dependencies\n\n# 2.0.0\n\n## `@udecode/plate-autoformat`\n\n- `autoformatBlock`:\n  - signatude changed\n\n```ts\n// Before\n(\n  editor: TEditor,\n  type: string,\n  at: Location,\n  options: Pick<AutoformatRule, 'preFormat' | 'format'>\n)\n```\n\n```ts\n// After\n(editor: TEditor, options: AutoformatBlockOptions)\n```\n\n- moved the checks from `withAutoformat`\n- `autoformatInline`:\n  - renamed to `autoformatMark`\n  - signatured changed\n\n```ts\n// Before\n(\n  editor: TEditor,\n  options: Pick<AutoformatRule, 'type' | 'between' | 'markup' | 'ignoreTrim'>\n)\n```\n\n```ts\n// After\n(\n  editor: TEditor,\n  options: AutoformatMarkOptions\n)\n```\n\n- `AutoformatRule` is now `AutoformatBlockRule\n| AutoformatMarkRule\n| AutoformatTextRule;`\n  - `mode: 'inline'` renamed to `mode: 'mark'`\n  - `markup` and `between` have been replaced by `match: string | string[] | MatchRange | MatchRange[]`: The rule applies when the trigger and the text just before the cursor matches. For `mode: 'block'`: lookup for the end match(es) before the cursor. For `mode: 'text'`: lookup for the end match(es) before the cursor. If `format` is an array, also lookup for the start match(es). For `mode: 'mark'`: lookup for the start and end matches. Note: `'_*'`, `['_*']` and `{ start: '_*', end: '*_' }` are equivalent.\n  - `trigger` now defaults to the last character of `match` or `match.end` (previously `' '`)\n- the plugin now checks that there is no character before the start match to apply autoformatting. For example, nothing will happen by typing `a*text*`.\n",
          "type": "registry:file",
          "target": "content/docs/plate/migration/v48.mdx"
        }
      ],
      "type": "registry:file"
    },
    {
      "name": "releases-index-docs",
      "title": "Releases",
      "description": "Documentation for Releases",
      "files": [
        {
          "path": "../../content/docs/releases/index.mdx",
          "content": "---\ntitle: Releases\n---\n\n<ReleaseIndex />\n",
          "type": "registry:file",
          "target": "content/docs/plate/releases/index.mdx"
        }
      ],
      "type": "registry:file"
    }
  ]
}