import * as react_jsx_runtime from 'react/jsx-runtime'; import * as React from 'react'; import * as lexical from 'lexical'; import { LexicalEditor, Spread, ElementFormatType, NodeKey, EditorConfig, LexicalNode, ElementNode, SerializedElementNode, DOMExportOutput, Klass, EditorThemeClasses, DecoratorNode, SerializedLexicalNode, LexicalCommand } from 'lexical'; import { HeadingTagType } from '@lexical/rich-text'; import { DecoratorBlockNode, SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'; /** * Resolves a CMS media ID into a displayable URL. May be synchronous * (an in-memory map) or asynchronous (a fetch against the host app's * media API). Returning `null` means "no such media" — the block then * renders a not-found placeholder instead of a broken image. */ type MediaResolver = (id: string) => string | null | Promise; interface MediaResolverProviderProps { children: React.ReactNode; resolveImageSrc?: MediaResolver; } /** * Makes the host app's media resolver available to decorator nodes. * Provided automatically by `RichTextEditor`. * * Decorator nodes are rendered through `createPortal` from inside * ``, and React portals inherit context from the * *component* tree rather than the DOM tree — so wrapping `children` * here is enough to reach every block's preview. */ declare function MediaResolverProvider({ children, resolveImageSrc, }: MediaResolverProviderProps): react_jsx_runtime.JSX.Element; /** * Read the configured resolver. Falls back to `null` when no provider is * mounted, so blocks render harmlessly outside an editor. */ declare function useMediaResolver(): MediaResolver | null; type ResolvedSrcStatus = /** No id was requested — nothing to resolve. */ "idle" /** An async resolver is in flight. */ | "loading" /** `src` holds a usable URL. */ | "resolved" /** The resolver returned null/threw, or none is configured. */ | "missing"; interface ResolvedSrc { src: string | null; status: ResolvedSrcStatus; } /** * Turn a media ID into a URL via the host-supplied resolver. * * Handles both sync and async resolvers, and ignores results from a * previous ID that land after the ID has already changed (or after * unmount) so a slow request can't overwrite a newer one. */ declare function useResolvedSrc(id: string | null | undefined): ResolvedSrc; /** * Lexical stores inline text formatting in two different places: * * - `format` — a **bitmask** of the toggles (bold, italic, underline …) * - `style` — a CSS string, written by `$patchStyleText` (colour, * background colour, font size …) * * That split is awkward to consume downstream, where you usually just * want "the CSS for this run of text". These helpers decode the bitmask * and merge both halves into a single declaration string. */ /** Bit for each inline format. Mirrors `IS_*` in Lexical's constants. */ declare const TEXT_FORMAT_BITS: { readonly bold: 1; readonly italic: 2; readonly strikethrough: 4; readonly underline: 8; readonly code: 16; readonly subscript: 32; readonly superscript: 64; readonly highlight: 128; readonly lowercase: 256; readonly uppercase: 512; readonly capitalize: 1024; }; type TextFormatName = keyof typeof TEXT_FORMAT_BITS; /** Does this bitmask include the given format? */ declare function hasTextFormat(format: number, name: TextFormatName): boolean; /** Every format switched on in the bitmask, e.g. `9` → `["bold", "underline"]`. */ declare function decodeTextFormat(format: number): TextFormatName[]; interface TextCssOptions { /** Background used for the `highlight` format. */ highlightColor?: string; /** Font stack used for the `code` format. */ codeFontFamily?: string; /** Background used for the `code` format. Pass `""` to skip it. */ codeBackground?: string; } /** * CSS declarations for a format bitmask. * * `underline` and `strikethrough` deliberately collapse into a single * `text-decoration`; emitting them as two declarations would make the * second overwrite the first and silently drop one of the two. */ declare function textFormatToCss(format: number, options?: TextCssOptions): Record; /** Parse a CSS declaration string into a map. */ declare function parseCssText(style: string): Record; /** Serialize a declaration map back into a CSS string. */ declare function stringifyCss(css: Record): string; /** * One CSS string covering everything applied to a run of text. * * The node's own `style` is applied last, so an explicitly picked colour * beats one implied by a format bit (e.g. a custom background colour * wins over `highlight`'s default yellow). */ declare function textNodeCss(format: number, style?: string, options?: TextCssOptions): string; interface InlineTextStyleOptions extends TextCssOptions { /** * Key the computed CSS is written under. Defaults to `"css"`. * * Avoid `"style"`: Lexical reads that key back on import into the * node's own `style`, so the derived declarations would become real * ones — and toggling bold off would no longer clear `font-weight`, * because that toggle only touches `format`. Keeping a separate key * leaves the JSON safe to feed back into `initialJson` / `setJson`. */ key?: string; } /** * Walk a serialized editor state and add a ready-to-use CSS string to * every text node, so consumers never have to decode the bitmask. * * ```ts * const json = JSON.parse(editorRef.current.getJson()); * withInlineTextStyles(json); * // { type: "text", text: "Merhaba", format: 9, style: "color: #ef4444;", * // css: "font-weight: 700; text-decoration: underline; color: #ef4444" } * ``` * * Returns the same object it was given (mutated in place) for easy * chaining; pass a copy if you need the original untouched. */ declare function withInlineTextStyles(serialized: T, options?: InlineTextStyleOptions): T; /** * Every user-facing string the editor chrome renders. Field labels that * belong to a consumer-supplied schema (`CmsBlockSpec.fields`) are not * here — those travel with the schema. */ interface RichTextMessages { cancel: string; save: string; insert: string; embed: string; delete: string; add: string; update: string; removeLink: string; searchBlocks: string; noBlocksFound: string; insertBlock: string; url: string; urlRequired: string; altText: string; altTextHint: string; caption: string; captionHint: string; maxWidth: string; auto: string; fixed: string; responsive: string; title: string; poster: string; aspectRatio: string; playerOptions: string; autoplay: string; mute: string; loop: string; showControls: string; preload: string; startAt: string; seconds: string; requiredByAutoplay: string; optional: string; imageId: string; videoId: string; resolvedUrl: string; resolving: string; notFoundForId: string; idOrUrlRequired: string; urlNotStoredHint: string; urlDisabledHint: string; /** Header of the image form, e.g. "Resim ekle" / "Resim". */ insertImage: string; imageBlock: string; chooseImage: string; noImagesAvailable: string; loadingImages: string; clearSelection: string; spellCheck: string; spellLoading: string; spellChecking: string; spellPassed: string; /** `{count}` is replaced with the number of issues. */ spellIssues: string; spellError: string; spellDisabled: string; spellMisspelling: string; spellCommonMistake: string; spellSeparateWords: string; spellSuggestionsLoading: string; spellNoSuggestions: string; spellIgnore: string; spellAddToDictionary: string; spellRecheck: string; spellShowIssues: string; spellHideIssues: string; loadingOptions: string; optionNotInList: string; textCase: string; upperCase: string; lowerCase: string; titleCase: string; } declare const trMessages: RichTextMessages; declare const enMessages: RichTextMessages; type RichTextLocale = "tr" | "en"; declare const LOCALE_MESSAGES: Record; /** Turkish is the default — this editor's primary audience. */ declare const DEFAULT_LOCALE: RichTextLocale; /** Resolve the `locale` / `messages` props into one catalog. */ declare function resolveMessages(locale?: RichTextLocale, overrides?: Partial): RichTextMessages; declare function RichTextI18nProvider({ children, messages, locale, }: { children: React.ReactNode; messages: RichTextMessages; /** Needed on its own by anything that has to case text correctly — * Turkish `i`/`İ` differs from the browser default. */ locale?: RichTextLocale; }): react_jsx_runtime.JSX.Element; /** * The active message catalog. Falls back to Turkish outside a provider so * components stay renderable in isolation (tests, Storybook fragments). */ declare function useMessages(): RichTextMessages; /** * One selectable item in the page's image library. * * `id` is the only required field — that's what gets stored on the node. * Everything else is a display convenience: leave `url` out and the * thumbnail is resolved through `resolveImageSrc` like any other ID, so * handing over a plain list of IDs is enough. */ interface MediaLibraryItem { id: string; /** Optional direct URL. Falls back to `resolveImageSrc(id)`. */ url?: string; /** Optional caption shown under the thumbnail. */ title?: string; } /** A static list, or a function returning one (optionally async). */ type MediaLibrary = MediaLibraryItem[] | (() => MediaLibraryItem[] | Promise); declare function MediaLibraryProvider({ children, library, }: { children: React.ReactNode; library?: MediaLibrary; }): react_jsx_runtime.JSX.Element; type MediaLibraryStatus = "idle" | "loading" | "ready"; interface MediaLibraryState { items: MediaLibraryItem[]; status: MediaLibraryStatus; /** `false` when the host configured no library at all — callers use * this to keep the manual ID/URL inputs editable. */ configured: boolean; } /** * Read the configured image library, loading it if it's a function. * * A loader is called once per mount; results arriving after unmount are * ignored so a slow fetch can't set state on a dead component. */ declare function useMediaLibrary(): MediaLibraryState; /** * Fields to hide, keyed by block type (`"image"`, `"galeri"`, * `"newsMoment"`, …). The `"*"` key applies to every block. * * ```ts * { image: ["caption", "maxWidth"], "*": ["position"] } * ``` */ type HiddenFieldsConfig = Record; declare function HiddenFieldsProvider({ children, hiddenFields, }: { children: React.ReactNode; hiddenFields?: HiddenFieldsConfig; }): react_jsx_runtime.JSX.Element; /** The raw config, for callers that serialize rather than render. */ declare function useHiddenFieldsConfig(): HiddenFieldsConfig | null; /** Field names hidden for one block type, including the `"*"` entries. */ declare function hiddenFieldsFor(config: HiddenFieldsConfig | null | undefined, blockType: string): Set; /** * `isHidden(fieldName)` for one block type — used by the forms to skip * rendering a field. */ declare function useHiddenFields(blockType: string): (field: string) => boolean; /** * Remove hidden fields from a serialized editor state, so they never * reach the host's payload. * * A name is dropped whether it sits at the node's top level (CMS blocks * store fields flat) or inside its `options` object (the built-in media * blocks) — callers name the field, not its nesting. Lexical's own * structural keys are never touched. * * **Pure**: the input is left alone and a stripped deep copy is * returned. That matters because `exportJSON()` can hand out live node * internals (a media node's `options` used to be the very object the * node stores), so deleting in place would erase the field from the * document itself — permanently, and outside an `editor.update()`. * * The clone only happens when there is something to strip, so editors * without `hiddenFields` pay nothing. */ declare function stripHiddenFields(serialized: T, config: HiddenFieldsConfig | null | undefined): T; interface CmsFieldOption { value: string; label: string; } type CmsFieldInputType = "text" | "url" | "number" | "select" | "textarea" /** Native date picker. Stored as `YYYY-MM-DD`. */ | "date" /** Native time picker. Stored as `HH:mm`. */ | "time" /** Comma-separated media IDs, previewed as thumbnails via the * resolver passed to `RichTextEditor`. */ | "image-ids"; interface CmsFieldSpec { /** Key this field is stored under — becomes a top-level key in the * node's serialized JSON. */ name: string; label: string; inputType: CmsFieldInputType; options?: CmsFieldOption[]; placeholder?: string; optional?: boolean; } /** Field values as stored on the node. Everything is a string; numeric * fields are parsed by the consumer. */ type CmsFieldValues = Record; interface CmsBlockSpec { /** * Lexical node type — also the `"type"` key in serialized JSON. * Must be unique across every registered node. */ type: string; title: string; description?: string; /** Menu icon (Insert dropdown / slash menu). */ icon: React.ReactElement; /** Extra search terms for the slash filter. `type` and `title` are * matched automatically. */ keywords?: string[]; fields: CmsFieldSpec[]; /** * Custom in-editor preview. Omit to get the generic card (title + * field summary). Rendered inside the block's decorator, so it can * use `useResolvedSrc()` to turn IDs into images. */ renderPreview?: (fields: CmsFieldValues) => React.ReactNode; } /** A fixed list, or a function returning one (optionally async). */ type CmsFieldOptionsSource = CmsFieldOption[] | (() => CmsFieldOption[] | Promise); /** * Replacement dropdown choices, keyed by block type and then field name. * The `"*"` block key applies to every block, so a field shared across * blocks (`position`) can be set once. * * ```ts * { * canliyayin: { channel: [{ value: "300", label: "BHT TV" }] }, * piyasa: { usd_eur: async () => fetchMarkets() }, * "*": { position: [{ value: "left", label: "Sola yaslı" }] }, * } * ``` */ type CmsFieldOptionsConfig = Record>; declare function CmsFieldOptionsProvider({ children, cmsFieldOptions, }: { children: React.ReactNode; cmsFieldOptions?: CmsFieldOptionsConfig; }): react_jsx_runtime.JSX.Element; type CmsFieldOptionsStatus = "schema" | "loading" | "ready" | "error"; interface CmsFieldOptionsState { options: CmsFieldOption[]; status: CmsFieldOptionsStatus; /** `true` when the host supplied a list for this field. */ overridden: boolean; } /** * The choices for one select field: whatever the host configured for * this block type (or for `"*"`), else the schema's own list. * * While an async loader runs, the schema list is held back and `status` * is `"loading"` — better a momentarily empty select than the author * picking a channel that is about to disappear. If the loader rejects, * the schema list comes back so the form stays usable. */ declare function useCmsFieldOptions(blockType: string, fieldName: string, schemaOptions?: CmsFieldOption[]): CmsFieldOptionsState; /** * The editor-level registration, without React — `RichTextBlockEdges` * is a thin wrapper around it, and tests drive it headlessly. * * Returns the unregister function. */ declare function registerBlockEdges(editor: LexicalEditor, { leading, trailing }?: RichTextBlockEdgesProps): () => void; interface RichTextBlockEdgesProps { /** Keep a paragraph above a leading block. Default `true`. */ leading?: boolean; /** Keep a paragraph below a trailing block. Default `true`. */ trailing?: boolean; } /** * Keeps an empty paragraph at either end of the document when the block * there can't be typed around — otherwise an image or CMS block that * opens or closes the document leaves nowhere to put the caret, and the * author can only get past it with the "+" menu. * * Mounted by `RichTextEditor`; switch it off with `blockEdges={false}`. */ declare function RichTextBlockEdges({ leading, trailing, }?: RichTextBlockEdgesProps): null; declare namespace RichTextBlockEdges { var displayName: string; } /** * How many characters a heading should stay under. One number covers * every heading level; an object sets them per level, and a level left * out of the object is not counted at all. */ type HeadingLimitConfig = number | Partial>; declare const DEFAULT_HEADING_LIMIT = 70; interface HeadingLimitOptions { /** Characters allowed before the counter turns red. Default `70`. */ limit?: HeadingLimitConfig; /** * Show the counter on every heading, not just the one being edited * and the ones already over. Default `false`. */ always?: boolean; } /** * The editor-level registration, without React. Returns the unregister * function. */ declare function registerHeadingLimit(editor: LexicalEditor, { limit, always }?: HeadingLimitOptions): () => void; interface RichTextHeadingLimitProps extends HeadingLimitOptions { } /** * Shows a character counter on headings — `62/70` next to the one * being edited, turning red once it is over. The limit is advisory: the * author can keep typing, and nothing is truncated. * * The counter is drawn with CSS from a data attribute, so it never * reaches the document: `onChange`, `getJson()`, the HTML export and * `getText()` are all unaffected. * * Mounted by `RichTextEditor`; configure with `headingLimit`. */ declare function RichTextHeadingLimit({ limit, always, }?: RichTextHeadingLimitProps): null; declare namespace RichTextHeadingLimit { var displayName: string; } type RichTextValue = { /** Lexical editor state JSON */ json: string; /** Serialized HTML */ html: string; /** Plain text content */ text: string; /** Markdown serialization (via @lexical/markdown TRANSFORMERS) */ markdown: string; }; interface RichTextEditorProps extends Omit, "onChange"> { /** Initial Lexical editor state JSON (string) */ initialJson?: string; /** Initial HTML — parsed and loaded into the editor on mount */ initialHtml?: string; /** Initial Markdown — parsed and loaded via TRANSFORMERS on mount */ initialMarkdown?: string; /** Called on every editor state change */ onChange?: (value: RichTextValue) => void; /** Read-only mode when false (default true) */ editable?: boolean; /** Auto-focus the editor on mount */ autoFocus?: boolean; /** Lexical namespace (default "eglador-rich-text") */ namespace?: string; /** Maximum character count. When set, content past the limit is wrapped * in an `OverflowNode` and visually marked. */ maxLength?: number; /** Charset for character counting (default `"UTF-16"`). UTF-8 counts * emoji and CJK characters as multiple bytes; UTF-16 matches `String.length`. */ charset?: "UTF-8" | "UTF-16"; /** * Turns a CMS media ID into a displayable URL. Called by any block * that stores an ID instead of a URL — the `image` / `video` blocks in * ID mode, and CMS blocks with `image-ids` fields (e.g. `newsMoment`). * * May be sync (an in-memory map) or async (a fetch). Return `null` * when the ID has no media, and the block renders a placeholder * instead of a broken image. */ resolveImageSrc?: MediaResolver; /** * Add a ready-to-use `css` string to every text node in the JSON — * merging the `format` bitmask with the node's `style`, so consumers * never decode `format: 9` by hand. **On by default**, affecting * `onChange`'s `json`, `useRichTextEditor().getJson()` and the * `RichTextOutput` JSON tab. * * The key is additive: the JSON still imports cleanly via * `initialJson` / `setJson`. Pass `false` for the raw Lexical shape, * or an options object to tune the `highlight` / `code` colours. */ inlineTextStyles?: boolean | InlineTextStyleOptions; /** UI language for the editor chrome. Default `"tr"`. */ locale?: RichTextLocale; /** Override individual strings without switching locale. */ messages?: Partial; /** * Images the author can pick from in the image block. Supply an array, * or a function (optionally async) returning one. Each item needs only * an `id`; a missing `url` is resolved through `resolveImageSrc`. * * When set, the image form shows a picker and its ID / URL fields * become read-only — the document stores the chosen ID. */ imageLibrary?: MediaLibrary; /** * Fields to drop, keyed by block type. The `*` key applies to every * block. They disappear from the block's form **and** from the * serialized payload (`onChange`, `getJson()`, the output panel). * * A name matches whether the block stores it flat (CMS blocks) or * inside `options` (the built-in media blocks). */ hiddenFields?: HiddenFieldsConfig; /** * Replace a CMS block's dropdown choices, keyed by block type and then * field name — the live-stream channel list, the market widgets, the * link colours. Each entry is a list or a function (optionally async) * returning one, so the values can come from your own API instead of * the built-in defaults. * * ```tsx * cmsFieldOptions={{ * canliyayin: { channel: async () => fetchChannels() }, * "*": { position: [{ value: "left", label: "Sola yaslı" }] }, * }} * ``` * * A stored value that is missing from the new list is kept and shown * as its own entry rather than being silently replaced. */ cmsFieldOptions?: CmsFieldOptionsConfig; /** * Keep an empty paragraph next to a block the caret can't be typed * around — a media embed or CMS block that opens or closes the * document, a table, a columns row. Without it the author has no way * to click above the first block or below the last one. * * **On by default.** Pass `false` to leave documents exactly as they * are (the JSON then never gains these paragraphs), or an object to * control each end: `blockEdges={{ leading: false }}`. */ blockEdges?: boolean | RichTextBlockEdgesProps; /** * Drop the blank spacer blocks word processors put between paragraphs, * so text pasted from Word, Google Docs or a web page doesn't arrive * double-spaced. **On by default**; `false` pastes verbatim. */ pasteCleanup?: boolean; /** * Advisory character limit for headings, shown as a `62/70` counter * next to the heading being edited and turning red once it is over. * Nothing is blocked or truncated — the author can keep typing. * * Defaults to 70 for every heading level. Pass a number to change * it, an object to vary it per level * (`{ limit: { h2: 70, h3: 60 } }` — a level left out is not * counted), `{ always: true }` to show every heading's counter, or * `false` to turn the counter off. * * The counter is drawn in CSS from a data attribute, so it never * reaches `onChange`, `getJson()`, the HTML export or `getText()`. */ headingLimit?: number | false | (HeadingLimitOptions & { limit?: HeadingLimitConfig; }); /** Receive the LexicalEditor instance once initialized (escape hatch) */ editorRef?: React.MutableRefObject | ((editor: LexicalEditor) => void); children: React.ReactNode; } declare function RichTextEditor({ initialJson, initialHtml, initialMarkdown, onChange, editable, autoFocus, namespace, maxLength, charset, resolveImageSrc, inlineTextStyles, locale, messages, imageLibrary, hiddenFields, cmsFieldOptions, blockEdges, pasteCleanup, headingLimit, editorRef, className, children, ...props }: RichTextEditorProps): react_jsx_runtime.JSX.Element; declare namespace RichTextEditor { var displayName: string; } interface LegacyFieldOption { value: string; label: string; } interface LegacyFieldSpec { /** Key under which this field's value is stored — becomes the field * name in the `#type#field#value#` output. */ name: string; label: string; inputType: "text" | "url" | "number" | "select" | "textarea"; options?: LegacyFieldOption[]; placeholder?: string; optional?: boolean; } interface LegacyComponentSpec { /** Shortcode type keyword, e.g. `"resim"`, `"video"` — entirely up to * the consumer, not constrained by this library. */ type: string; title: string; description?: string; /** Menu icon (Insert dropdown / slash menu). Falls back to a generic * icon when omitted — see `createLegacyComponentBlocks`. */ icon?: React.ReactElement; fields: LegacyFieldSpec[]; /** * Custom output mask for this type's shortcode string, e.g. * `"#{type}#{ID}#"`. `{type}` is replaced with `type`; `{fieldName}` * placeholders (matching a field's `name`) are replaced with that * field's submitted value. Any field not referenced by the template is * simply omitted from the output. * * Omit to use the default `#type#field#value#field#value#...#` layout * (every field rendered as its `name` followed by its value), e.g. * `{ type: "resim", fields: [{ name: "ID", ... }] }` → `#resim#ID#345456#`. * With `template: "#{type}#{ID}#"` the same submission instead renders * `#resim#345456#`. */ template?: string; } /** * Where a block can be invoked from. Each surface has its own UX * affordances: * - `"insert"` — top-toolbar Insert dropdown (supports multi-step forms) * - `"slash"` — `/` typeahead (quick action only, no forms) * - `"draggable"` — Notion-style block hover `+` (block conversion / quick insert) */ type BlockSurface = "insert" | "slash" | "draggable"; type BlockCategory = "text" | "list" | "block" | "separator" | "media" | "embed" | "layout"; interface BlockFormHandlers { /** Form should call when insertion is complete (host closes the popover). */ onComplete: () => void; /** Form should call when the user cancels (host returns to main view). */ onCancel: () => void; } /** * Single source of truth for an insertable / convertible block. All * surfaces (Insert menu, slash, draggable +) read from this registry — * adding a new block means adding one entry here. */ interface BlockSpec { /** Unique stable key. */ key: string; /** Display label. */ label: string; /** Short description for surfaces that show one (slash). */ description?: string; /** Icon node, sized for menu items (~16px). */ icon: React.ReactElement; /** Search keywords (slash filter). */ keywords?: string[]; /** Category — used for grouping / filtering. */ category: BlockCategory; /** Which UI surfaces this block appears in. */ surfaces: BlockSurface[]; /** * Quick action — runs directly on selection. Required for slash and * draggable surfaces. Optional when the block needs configuration * via `renderForm` only (e.g. embed forms). */ action?: (editor: LexicalEditor) => void; /** * Optional form. When provided, surfaces that support multi-step UI * (currently `"insert"`) show this form as a sub-view. The form is * responsible for calling `handlers.onComplete()` after successful * insertion or `handlers.onCancel()` when dismissed. */ renderForm?: (editor: LexicalEditor, handlers: BlockFormHandlers) => React.ReactNode; } /** * Turns a consumer-supplied `LegacyComponentSpec[]` into `BlockSpec[]` — * one entry per spec, each opening a form (built from `spec.fields`) on * the `"insert"` / `"slash"` surfaces. This library has no built-in * notion of what types exist; call this with your own schema and merge * the result into `defaultBlocks` (or pass it standalone) via the * `blocks` prop on `RichTextToolbar` / `RichTextSlashCommands` / * `RichTextDraggableBlock`. * * @example * const blocks = [...defaultBlocks, ...createLegacyComponentBlocks(mySchema)]; * * */ declare function createLegacyComponentBlocks(schema: LegacyComponentSpec[]): BlockSpec[]; /** * Default block registry. Pass a custom `blocks` prop to any surface to * override / extend / re-order. Use `getBlocksForSurface()` to filter * defaults by surface, then concatenate your own. */ declare const defaultBlocks: BlockSpec[]; /** Filter the registry to a specific surface. All three surfaces accept * both `action` and `renderForm`, so given the same `blocks` array they * return identical lists in identical order — the toolbar Insert menu, * the `/` menu and the drag-handle `+` menu never drift apart. */ declare function getBlocksForSurface(surface: BlockSurface, blocks?: BlockSpec[]): BlockSpec[]; interface RichTextContentProps { placeholder?: React.ReactNode; className?: string; contentClassName?: string; placeholderClassName?: string; /** Minimum editor height (Tailwind class). Default `min-h-32` */ minHeight?: string; /** Enable Notion-style drag handle + "+" insert menu (default `false`) */ draggable?: boolean; /** * Custom blocks shown inside the draggable "+" popover. Defaults to * `defaultBlocks` filtered to `surfaces.includes("draggable")`. */ draggableBlocks?: BlockSpec[]; /** Enable floating format toolbar that appears on text selection (default `false`) */ floatingToolbar?: boolean; /** Heading buttons in the floating toolbar (default `["h2", "h3"]`). */ floatingHeadings?: HeadingTagType[]; /** Editor mode (default `"rich"`). `"plain"` swaps RichTextPlugin for * PlainTextPlugin — text only, no inline/block formatting commands. */ mode?: "rich" | "plain"; } declare function RichTextContent({ placeholder, className, contentClassName, placeholderClassName, minHeight, draggable, draggableBlocks, floatingToolbar, floatingHeadings, mode, }: RichTextContentProps): react_jsx_runtime.JSX.Element; declare namespace RichTextContent { var displayName: string; } type HeadingMenuItem = "paragraph" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; type RichTextToolbarFeature = "bold" | "italic" | "underline" | "strikethrough" | "code" | "heading1" | "heading2" | "heading3" | "heading4" | "heading5" | "heading6" | "heading" | "paragraph" | "quote" | "bulletList" | "orderedList" | "checkList" | "link" | "textColor" | "backgroundColor" | "alignment" | "textTransform" | "insert" | "undo" | "redo" | "separator"; interface RichTextToolbarProps extends React.HTMLAttributes { features?: RichTextToolbarFeature[]; /** Items shown inside the heading dropdown (default: paragraph + h1–h6) */ headingItems?: HeadingMenuItem[]; /** * Blocks shown inside the Insert dropdown. Defaults to the built-in * `defaultBlocks` registry filtered to `surfaces.includes("insert")`. * Pass a custom array to extend / re-order / restrict. */ insertBlocks?: BlockSpec[]; } declare function RichTextToolbar({ features, headingItems, insertBlocks, className, ...props }: RichTextToolbarProps): react_jsx_runtime.JSX.Element; declare namespace RichTextToolbar { var displayName: string; } interface RichTextFloatingToolbarProps { /** * Kept for API compatibility. The toolbar now portals to the body and * positions itself from the selection's viewport rect, so it is not * used — passing it is harmless. */ anchorElem?: HTMLElement; /** * Heading buttons offered next to the inline formats. Each one turns * the selected block into that heading, or back into a paragraph when * it already is one. Pass `[]` to leave them out. */ headings?: HeadingTagType[]; } /** * Floating selection toolbar — appears above the selected text with format * buttons (bold, italic, underline, strikethrough, code, link). */ declare function RichTextFloatingToolbar({ headings, }?: RichTextFloatingToolbarProps): React.ReactPortal | null; declare namespace RichTextFloatingToolbar { var displayName: string; } interface RichTextDraggableBlockProps { anchorElem: HTMLElement; /** Hide the "+" insert button (default `false` — button is shown) */ hideInsertButton?: boolean; /** * Custom blocks registry. Defaults to `defaultBlocks` filtered to * `surfaces.includes("draggable")` and `action` defined. */ blocks?: BlockSpec[]; } /** * Notion-style drag handle with an optional "+" insert menu. * Hover over a block → grip handle appears on the left, drag to reorder. * Click "+" to insert a new block from the registry's draggable surface. */ declare function RichTextDraggableBlock({ anchorElem, hideInsertButton, blocks, }: RichTextDraggableBlockProps): react_jsx_runtime.JSX.Element; declare namespace RichTextDraggableBlock { var displayName: string; } interface RichTextTableActionsProps { /** Element used as positioning anchor (typically the editor's content wrapper) */ anchorElem: HTMLElement; } /** * Floating chevron button anchored to the active table cell. Click opens a * menu with structural operations (insert / delete row & column, merge, * unmerge, delete table). Operations use Lexical's official `@lexical/table` * helpers so they respect cell merge state and selection semantics. */ declare function RichTextTableActions({ anchorElem, }: RichTextTableActionsProps): React.ReactPortal | null; declare namespace RichTextTableActions { var displayName: string; } interface RichTextLinkEditorProps { /** Element used as positioning anchor (typically the editor's content wrapper) */ anchorElem: HTMLElement; } /** * Floating link editor that auto-opens when the cursor enters a link. * Anchored below the link element. Uses `LinkEditForm` for the actual * form, keyed by link node key so the form re-reads attributes when the * cursor moves to a different link. * * Closes when the cursor leaves the link, or when Update / Remove is * clicked (Update closes immediately; user can re-enter the link to * reopen). No Cancel — the editor is intrinsically tied to cursor * position. */ declare function RichTextLinkEditor({ anchorElem }: RichTextLinkEditorProps): React.ReactPortal | null; declare namespace RichTextLinkEditor { var displayName: string; } interface RichTextSlashCommandsProps { /** * Custom blocks registry. Defaults to the built-in `defaultBlocks` — * filtered to entries with `surfaces.includes("slash")`. Pass your own * list to extend / re-order / restrict. */ blocks?: BlockSpec[]; } /** * Slash command menu — type `/` in the editor to open a popover with quick * block insertions (Notion / Linear / Discord pattern). * * - `action`-based blocks run immediately on select (paragraph, heading, * list, divider, ...). * - `renderForm`-only blocks (YouTube, image, iframe, columns, ...) open * a small form popover anchored near the cursor on select; submit * inserts the block, cancel just dismisses. */ declare function RichTextSlashCommands({ blocks, }?: RichTextSlashCommandsProps): react_jsx_runtime.JSX.Element; declare namespace RichTextSlashCommands { var displayName: string; } /** * Auto-embed plugin — when a user pastes (or types) a recognized URL * (YouTube), a small inline menu offers to convert it into an embed * block. Built on Lexical's `LexicalAutoEmbedPlugin`. * * Opt-in component: * * ```tsx * * * * * ``` */ declare function RichTextAutoEmbed(): react_jsx_runtime.JSX.Element; declare namespace RichTextAutoEmbed { var displayName: string; } interface YouTubeOptions { /** Auto-start the video on load (browsers require `mute` for this to work). */ autoplay?: boolean; /** Start the video muted. Required by browsers when `autoplay` is on. */ mute?: boolean; /** Loop the video. YouTube requires the `playlist` param to be set to * the same video ID — handled automatically. */ loop?: boolean; /** Show YouTube's player controls (default `true`). */ controls?: boolean; /** Start position in seconds (`0` = beginning). */ start?: number; } type SerializedYouTubeNode = Spread<{ /** The URL exactly as the author entered it. */ url?: string; /** * Legacy: documents written before the node stored URLs kept only * the 11-character video ID. Still read on import, never written. */ videoID?: string; options?: YouTubeOptions; }, SerializedDecoratorBlockNode>; /** Build the canonical privacy-friendly embed URL for a bare video ID. */ declare function youTubeEmbedUrl(videoID: string): string; /** * The URL to actually put in the iframe. * * The stored URL is whatever the author typed; a `watch?v=` or * `youtu.be` link can't be framed (YouTube refuses with * `X-Frame-Options`), so those are normalized to their `/embed/` form * for rendering only — the document keeps the original string. */ declare function toYouTubeEmbedSrc(url: string): string; /** * YouTube embed block. Wraps an iframe pointing to youtube-nocookie.com * inside a `BlockWithAlignableContents` container so users can align, * select, and delete it like other block-level decorators. * * The block renders a hover-action button (gear icon) at the top-right * corner — clicking it opens a Notion-style options popover where the * player options (autoplay, mute, loop, controls, start) can be edited * live or the embed removed. */ declare class YouTubeNode extends DecoratorBlockNode { __url: string; __options: Required; static getType(): string; static clone(node: YouTubeNode): YouTubeNode; static importJSON(serialized: SerializedYouTubeNode): YouTubeNode; exportJSON(): SerializedYouTubeNode; constructor(url: string, options?: YouTubeOptions, format?: ElementFormatType, key?: NodeKey); getUrl(): string; setUrl(url: string): this; getOptions(): Required; setOptions(options: YouTubeOptions): this; getTextContent(): string; decorate(_editor: LexicalEditor, config: EditorConfig): React.ReactElement; } /** `url` is stored verbatim — whatever the author entered. */ declare function $createYouTubeNode(url: string, options?: YouTubeOptions): YouTubeNode; declare function $isYouTubeNode(node: LexicalNode | null | undefined): node is YouTubeNode; interface YouTubeUrlMatch { id: string; /** Start time in seconds, parsed from `?t=` or `?start=` if present. */ start?: number; } /** * Extract the 11-character video ID and optional start-time from any * YouTube URL form. Returns `null` if the URL doesn't match. */ declare function parseYouTubeUrl(url: string): YouTubeUrlMatch | null; interface YouTubeFormSubmit { /** The URL exactly as typed — stored verbatim on the node. */ url: string; options: YouTubeOptions; } declare const YOUTUBE_DEFAULT_OPTIONS: Required; interface YouTubeFormProps { /** `"insert"` for new embeds, `"edit"` for changing an existing one. */ mode: "insert" | "edit"; /** Pre-filled URL (typically `https://www.youtube.com/watch?v=ID`) when editing. */ initialUrl?: string; /** Current player options when editing. */ initialOptions?: Required; /** Submitted on Embed/Save click with parsed video ID + final options. */ onSubmit: (data: YouTubeFormSubmit) => void; /** Cancel — discards changes and closes the form. */ onCancel: () => void; /** Remove — only shown in `"edit"` mode. */ onRemove?: () => void; } /** * Unified YouTube create / edit form. Same fields render in both modes; * only the action buttons and header copy change. Inspired by Payload CMS, * which uses one form schema across the create and edit flows. */ declare function YouTubeForm({ mode, initialUrl, initialOptions, onSubmit, onCancel, onRemove, }: YouTubeFormProps): react_jsx_runtime.JSX.Element; declare namespace YouTubeForm { var displayName: string; } type AudioPreload = "none" | "metadata" | "auto"; interface AudioOptions { /** Optional title shown above the player */ title?: string; /** Auto-start playback on load (browsers require `muted` for this). */ autoplay?: boolean; /** Loop after the track ends. */ loop?: boolean; /** Start playback muted. Required by browsers when `autoplay` is on. */ muted?: boolean; /** Show native player controls (default `true`). */ controls?: boolean; /** How aggressively to preload the source (default `"metadata"`). */ preload?: AudioPreload; } type SerializedAudioNode = Spread<{ src: string; options?: AudioOptions; }, SerializedDecoratorBlockNode>; /** * Audio embed block backed by the native `