UNPKG

@lexical/clipboard

Version:

This package provides the copy/paste functionality for Lexical.

237 lines (236 loc) 9.5 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import type { BaseSelection } from 'lexical'; import { LexicalClipboardData } from './clipboard'; /** * A middleware function in a per-MIME-type clipboard-import stack. Mirrors * the shape of {@link ExportMimeTypeFunction} on the export side. * * - `data` is the non-empty string returned by `DataTransfer.getData(mime)` * for this MIME type. * - `selection` is the current editor selection at the insertion point. * - `$next` defers to the next-lower handler in the stack (i.e. the handler * that was registered earlier). Returns `true` if that handler claimed * the data; `false` if no handler accepted it. * - `dataTransfer` is the full {@link DataTransfer} the paste/drop came * from, so a handler can inspect companion MIME types or attached * files in addition to the slot it was invoked for (e.g. peek at * `'application/x-vscode-source'` while handling `'text/html'`). When * threading through the new pipeline, pass this into * `$generateNodesFromDOMViaExtension(dom, { * context: [contextValue(ImportSourceDataTransfer, dataTransfer)], * })` so rules and preprocessors can read it via * `ctx.get(ImportSourceDataTransfer)`. * * The function should return `true` if it consumed the data (the caller * stops trying further handlers for this MIME type and does not move on to * the next MIME type). Return `$next()` to delegate. Return `false` if the * function decided not to handle the data after inspecting it (e.g. the * JSON namespace didn't match) so a lower-priority handler — or the next * MIME type — gets a chance. * * @experimental */ export type ImportMimeTypeFunction = (data: string, selection: BaseSelection, $next: () => boolean, dataTransfer: DataTransfer) => boolean; /** * A mapping from MIME type to a stack of {@link ImportMimeTypeFunction}. * * Each entry is an ordered array; the function at the highest index runs * first and may call `next()` to fall through to the function below it. * The default config provides one handler each for * `'application/x-lexical-editor'`, `'text/html'`, and `'text/plain'` that * matches the legacy {@link $insertDataTransferForRichText} behavior. * * When {@link ClipboardImportExtension} merges a partial config, new * functions are appended to the existing array for each MIME type, so * later-registered handlers run before earlier ones (including the * defaults) and may delegate to them via `next()`. * * @experimental */ export type ImportMimeTypeConfig = { [key in keyof LexicalClipboardData | (string & {})]?: ImportMimeTypeFunction[] | undefined; }; /** * Per-MIME-type ordering weights. Lower numbers run first. * * Composable across extensions: each extension contributes weights for * its MIME types without needing to coordinate. A partial config that * sets `{'application/vnd.myapp+json': 5}` slots its type between the * built-in `application/x-lexical-editor` (0) and `text/html` (10) — no * need to enumerate the full ordering. mergeConfig spreads pairs (later * keys override earlier ones for the same MIME type, so an extension * can also re-rank a built-in by repeating its key with a new weight). * * Iteration: every MIME type that has a handler stack and is present in * the dataTransfer (regardless of whether it has an explicit weight) is * tried; MIME types with no explicit weight sort to the end, behind all * weighted ones, in lexical order. * * @experimental */ export type ImportMimeTypePriority = { readonly [key in keyof LexicalClipboardData | (string & {})]?: number | undefined; }; /** * Configuration for {@link ClipboardImportExtension}. * * @experimental */ export interface ClipboardImportConfig { /** * The per-MIME-type deserializer stacks used by * {@link $insertDataTransferForRichText} when handling a paste or drop * event. * * Merged with `[...prev, ...override]` per MIME type, matching the * behavior of {@link GetClipboardDataExtension.$exportMimeType}. * * Apps add a stack under a brand-new key to register a brand-new MIME * type. Set a {@link priority} weight to control where in the * iteration it sits relative to the built-ins. */ $importMimeType: ImportMimeTypeConfig; /** * See {@link ImportMimeTypePriority}. Spread-merged across configs — * extensions contribute weights without coordinating with each other. */ priority: ImportMimeTypePriority; } /** * Default per-MIME-type weights reproducing the legacy * `$insertDataTransferForRichText` ordering: * * `application/x-lexical-editor` (0) → `text/html` (10) → * `text/plain` (20) → `text/uri-list` (30). * * Gaps between weights let third-party MIME types slot in (e.g. weight * 5 to run between lexical and html). Apps can also override built-in * weights to demote them. * * @experimental */ export declare const DEFAULT_IMPORT_MIME_TYPE_PRIORITY: ImportMimeTypePriority; /** * The default per-MIME-type handler stacks reproducing the legacy * {@link $insertDataTransferForRichText} behavior exactly. Stacked * extensions append on top of these. * * @experimental */ export declare const DEFAULT_IMPORT_MIME_TYPE: ImportMimeTypeConfig; /** * Output of {@link ClipboardImportExtension}: the merged configuration * plus a self-contained {@link $insertDataTransfer} function that owns * the entire paste-side iteration over the priority list. Apps look this * up via peer-dependency and call it directly; {@link * $insertDataTransferForRichText} delegates to it. * * @experimental */ export interface ClipboardImportOutput extends ClipboardImportConfig { /** * Try every MIME type in `priority` order against the `DataTransfer`, * invoking the configured stack for the first one that has a non-empty * payload. Returns `true` if any stack claimed the data. */ $insertDataTransfer(dataTransfer: DataTransfer, selection: BaseSelection): boolean; } /** * @internal * * Look up the {@link ClipboardImportOutput} on the active editor. Returns * a static default-backed output when no {@link ClipboardImportExtension} * is configured, so callers can always invoke `output.$insertDataTransfer` * regardless of whether the editor opted in. */ export declare function $getImportOutput(): ClipboardImportOutput; /** * @experimental * * Mirror of {@link GetClipboardDataExtension} for the import direction. * Holds a per-MIME-type stack of {@link ImportMimeTypeFunction}s. * * @example * Route `text/html` pastes through {@link DOMImportExtension}, leaving the * defaults for other MIME types untouched: * ```ts * import {configExtension, defineExtension, $getEditor} from 'lexical'; * import { * ClipboardImportExtension, * $insertGeneratedNodes, * } from '@lexical/clipboard'; * import { * contextValue, * DOMImportExtension, * ImportSource, * ImportSourceDataTransfer, * $generateNodesFromDOMViaExtension, * } from '@lexical/html'; * * defineExtension({ * name: 'app', * dependencies: [ * DOMImportExtension, * configExtension(ClipboardImportExtension, { * $importMimeType: { * 'text/html': [ * (html, selection, _$next, dataTransfer) => { * const parser = new DOMParser(); * const dom = parser.parseFromString(html, 'text/html'); * const nodes = $generateNodesFromDOMViaExtension(dom, { * context: [ * contextValue(ImportSource, 'paste'), * contextValue(ImportSourceDataTransfer, dataTransfer), * ], * }); * $insertGeneratedNodes($getEditor(), nodes, selection); * return true; * }, * ], * }, * }), * ], * }); * ``` */ export declare const ClipboardImportExtension: import("lexical").LexicalExtension<ClipboardImportConfig, "@lexical/clipboard/Import", ClipboardImportOutput, unknown>; /** * @experimental * * Drop-in extension that routes `text/html` clipboard pastes and drops * through the {@link DOMImportExtension} pipeline (rules, schemas, * preprocessors, overlays) instead of the legacy * {@link $generateNodesFromDOM}. Add to your extension dependencies along * with the per-package import extensions you want active * ({@link CoreImportExtension}, {@link RichTextImportExtension}, etc.). * * The original {@link DataTransfer} and `'paste'` source kind are forwarded * into the import context so rules and preprocessors can read them via * `ctx.get(ImportSourceDataTransfer)` / `ctx.get(ImportSource)`. * * Equivalent to stacking this `text/html` handler manually via * `configExtension(ClipboardImportExtension, {...})`. * * @example * ```ts * import {defineExtension} from 'lexical'; * import {ClipboardDOMImportExtension} from '@lexical/clipboard'; * import {CoreImportExtension, RichTextImportExtension} from '@lexical/html'; * * defineExtension({ * name: 'app', * dependencies: [ * CoreImportExtension, * RichTextImportExtension, * ClipboardDOMImportExtension, * ], * }); * ``` */ export declare const ClipboardDOMImportExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@lexical/clipboard/DOMImport", unknown, unknown>;