Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/lexical-extension/src/HorizontalRuleExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ import {EditorStateExtension} from './EditorStateExtension';
import {NodeSelectionExtension} from './NodeSelectionExtension';
import {batch, effect, ReadonlySignal, Signal, signal} from './signals';

/**
* The serialized form of a {@link HorizontalRuleNode}. It has no extra fields
* beyond the base serialized node.
*/
export type SerializedHorizontalRuleNode = SerializedLexicalNode;

/**
* Command that inserts a {@link HorizontalRuleNode} at the current selection.
* Dispatch it with
* `editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)`.
*/
export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> =
/* @__PURE__ */ createCommand('INSERT_HORIZONTAL_RULE_COMMAND');

Expand Down Expand Up @@ -103,6 +112,9 @@ export function $createHorizontalRuleNode(): HorizontalRuleNode {
return $create(HorizontalRuleNode);
}

/**
* @returns `true` if `node` is a {@link HorizontalRuleNode}, narrowing its type.
*/
export function $isHorizontalRuleNode(
node: LexicalNode | null | undefined,
): node is HorizontalRuleNode {
Expand Down
7 changes: 7 additions & 0 deletions packages/lexical-extension/src/TabIndentationExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ function $defaultCanIndent(node: ElementNode) {
return node.canIndent();
}

/**
* Registers a `KEY_TAB_COMMAND` handler that makes Tab and Shift+Tab indent and
* outdent block elements (and otherwise insert a tab). Pass `maxIndent` to cap
* the indent depth and `$canIndent` to control which elements may be indented.
*
* @returns A cleanup function that unregisters the handler.
*/
export function registerTabIndentation(
editor: LexicalEditor,
maxIndent?: number | ReadonlySignal<null | number>,
Expand Down
7 changes: 7 additions & 0 deletions packages/lexical-history/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export type HistoryStateEntry = {
editor: LexicalEditor;
editorState: EditorState;
};
/**
* The undo/redo history maintained by the history plugin: the `current` entry
* plus the `undoStack` and `redoStack` of previous and future
* {@link HistoryStateEntry}s. Create an empty one with
* {@link createEmptyHistoryState} and pass it to the history plugin to share
* history across editors.
*/
export type HistoryState = {
current: null | HistoryStateEntry;
redoStack: HistoryStateEntry[];
Expand Down
17 changes: 17 additions & 0 deletions packages/lexical-link/src/LexicalAutoLinkExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import {
TOGGLE_LINK_COMMAND,
} from './LexicalLinkNode';

/**
* A callback invoked when the auto-link plugin creates, updates, or removes an
* automatic link. It receives the new `url` and the `prevUrl`; either may be
* `null` when a link is added or removed.
*/
export type ChangeHandler = (
url: string | null,
prevUrl: string | null,
Expand All @@ -46,8 +51,20 @@ export interface LinkMatcherResult {
url: string;
}

/**
* A function that inspects a piece of `text` and returns a
* {@link LinkMatcherResult} for the first URL it recognizes, or `null` if none
* is found. Used by the auto-link plugin to detect links as the user types.
*/
export type LinkMatcher = (text: string) => LinkMatcherResult | null;

/**
* Builds a {@link LinkMatcher} from a regular expression. The matched text is
* used as the link URL, optionally rewritten by `urlTransformer` (for example
* to prepend a protocol). Pass the result to the auto-link plugin's `matchers`.
*
* @returns A matcher that reports the first match of `regExp` in the text.
*/
export function createLinkMatcherWithRegExp(
regExp: RegExp,
urlTransformer: (text: string) => string = text => text,
Expand Down
25 changes: 25 additions & 0 deletions packages/lexical-react/src/LexicalAutoEmbedPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,23 @@ import {
} from 'lexical';
import {useCallback, useEffect, useMemo, useState} from 'react';

/**
* The result of matching a URL for an embed: the matched `url`, an `id`
* identifying the embedded resource, and optional provider-specific `data`.
*/
export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
url: string;
id: string;
data?: TEmbedMatchResult;
};

/**
* Describes a kind of embed (for example YouTube, a tweet, or Google Maps) that
* {@link LexicalAutoEmbedPlugin} can detect and insert. Each config has a `type`
* identifier, a `parseUrl` function that decides whether a URL matches and
* extracts its data, and an `insertNode` function that inserts the corresponding
* Lexical node.
*/
export interface EmbedConfig<
TEmbedMatchResultData = unknown,
TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
Expand All @@ -54,12 +65,26 @@ export interface EmbedConfig<
insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
}

/**
* A general-purpose regular expression for detecting URLs, provided as a
* convenience for implementing an {@link EmbedConfig}'s `parseUrl`.
*/
export const URL_MATCHER =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;

/**
* Command dispatched to start inserting an embed. Its payload is the `type` of
* the {@link EmbedConfig} to use; {@link LexicalAutoEmbedPlugin} listens for it
* and runs that config's URL detection flow.
*/
export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
/* @__PURE__ */ createCommand('INSERT_EMBED_COMMAND');

/**
* A {@link MenuOption} for the auto-embed menu, pairing a display `title` with
* an `onSelect` callback invoked when the user chooses to embed the detected
* URL.
*/
export class AutoEmbedOption extends MenuOption {
title: string;
onSelect: (targetNode: LexicalNode | null) => void;
Expand Down
10 changes: 10 additions & 0 deletions packages/lexical-react/src/LexicalAutoFocusPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ type Props = {
defaultSelection?: 'rootStart' | 'rootEnd';
};

/**
* Focuses the editor when the component is mounted. Pass `defaultSelection`
* to control whether the selection is placed at the start (`'rootStart'`) or
* end (`'rootEnd'`) of the root when there is no existing selection to restore.
*
* This is a legacy plugin. When building an editor with the extension API,
* configure {@link AutoFocusExtension} instead.
*
* @returns `null`, this plugin renders no DOM of its own.
*/
export function AutoFocusPlugin({defaultSelection}: Props): null {
const [editor] = useLexicalComposerContext();

Expand Down
13 changes: 13 additions & 0 deletions packages/lexical-react/src/LexicalAutoLinkPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ function useAutoLink(
}, [editor, matchers, onChange, excludeParents]);
}

/**
* Automatically converts text that matches one of the provided `matchers` into
* {@link AutoLinkNode}s as the user types, and reverts them back to plain text
* when they no longer match. Provide `onChange` to react to links being
* created, updated, or removed, and `excludeParents` to skip matching inside
* particular ancestor nodes. The editor must have the {@link AutoLinkNode}
* registered.
*
* This is a legacy plugin. When building an editor with the extension API,
* configure {@link AutoLinkExtension} instead.
*
* @returns `null`, this plugin renders no DOM of its own.
*/
export function AutoLinkPlugin({
matchers,
onChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ type Props = Readonly<{
}>;
}>;

/**
* A wrapper component for the contents of a {@link DecoratorBlockNode} that
* keeps the block in sync with node selection and element alignment. It renders
* its `children` inside a container that reflects the node's `format`
* alignment, responds to `FORMAT_ELEMENT_COMMAND` to update that alignment, and
* toggles the node's selection when the container is clicked.
*
* @returns The element to render for the decorator block.
*/
export function BlockWithAlignableContents({
children,
format,
Expand Down
10 changes: 10 additions & 0 deletions packages/lexical-react/src/LexicalCharacterLimitPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ function DefaultRenderer({remainingCharacters}: {remainingCharacters: number}) {
);
}

/**
* Tracks the length of the editor's text content against `maxLength` and
* renders the number of remaining characters, marking any overflowing text so
* it can be styled. Length is measured in either `'UTF-8'` or `'UTF-16'`
* (default) code units via the `charset` prop, and the display can be
* customized with the `renderer` prop.
*
* @returns The element produced by `renderer` (by default a `<span>` showing
* the number of remaining characters).
*/
export function CharacterLimitPlugin({
charset = 'UTF-16',
maxLength = CHARACTER_LIMIT,
Expand Down
11 changes: 11 additions & 0 deletions packages/lexical-react/src/LexicalCheckListPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import {registerCheckList} from '@lexical/list';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {useEffect} from 'react';

/**
* Enables check list support, wiring up the keyboard and pointer interactions
* that toggle the checked state of check list items. Pass
* `disableTakeFocusOnClick` to stop the editor from taking focus when a
* checkbox is clicked.
*
* This is a legacy plugin. When building an editor with the extension API,
* configure {@link CheckListExtension} instead.
*
* @returns `null`, this plugin renders no DOM of its own.
*/
export function CheckListPlugin({
disableTakeFocusOnClick = false,
}: {
Expand Down
10 changes: 10 additions & 0 deletions packages/lexical-react/src/LexicalClearEditorPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ type Props = Readonly<{
onClear?: () => void;
}>;

/**
* Registers a handler for `CLEAR_EDITOR_COMMAND` that empties the editor and
* resets the selection. Provide `onClear` to run your own logic in place of the
* default clearing behavior.
*
* This is a legacy plugin. When building an editor with the extension API,
* configure {@link ClearEditorExtension} instead.
*
* @returns `null`, this plugin renders no DOM of its own.
*/
export function ClearEditorPlugin({onClear}: Props): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useLayoutEffect(
Expand Down
10 changes: 10 additions & 0 deletions packages/lexical-react/src/LexicalClickableLinkPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {registerClickableLink} from '@lexical/link';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {useEffect} from 'react';

/**
* Makes {@link LinkNode}s clickable, navigating to the link's URL when it is
* clicked (opening it in a new tab when `newTab` is `true`, the default). Set
* `disabled` to temporarily turn the behavior off, for example while editing.
*
* This is a legacy plugin. When building an editor with the extension API,
* configure {@link ClickableLinkExtension} instead.
*
* @returns `null`, this plugin renders no DOM of its own.
*/
export function ClickableLinkPlugin({
newTab = true,
disabled = false,
Expand Down
25 changes: 25 additions & 0 deletions packages/lexical-react/src/LexicalCollaborationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import type {Doc} from 'yjs';
import devInvariant from '@lexical/internal/devInvariant';
import {createContext, useContext, useMemo} from 'react';

/**
* The value stored in the {@link CollaborationContext}: the local user's
* display `name` and cursor `color`, whether collaboration is currently active,
* and the map of Yjs documents shared by the editors under this provider.
*/
export type CollaborationContextType = {
color: string;
isCollabActive: boolean;
Expand Down Expand Up @@ -39,6 +44,11 @@ const entries = [

const randomEntry = entries[Math.floor(Math.random() * entries.length)];

/**
* The React context that holds the shared {@link CollaborationContextType} for
* collaborative editors. Provide it with {@link LexicalCollaboration} and read
* it with {@link useCollaborationContext}.
*/
export const CollaborationContext =
createContext<CollaborationContextType | null>(null);

Expand All @@ -55,6 +65,14 @@ function newContext() {
// a shared context across editors is likely to lead to bugs.
const UNSAFE_GLOBAL_CONTEXT = newContext();

/**
* A provider component that creates a fresh {@link CollaborationContextType}
* and makes it available to descendant editors via {@link CollaborationContext}.
* Wrap a group of editors that should share collaboration state in this
* component.
*
* @returns A context provider wrapping `children`.
*/
export function LexicalCollaboration({children}: {children: React.ReactNode}) {
const collabContext = useMemo(() => newContext(), []);

Expand All @@ -65,6 +83,13 @@ export function LexicalCollaboration({children}: {children: React.ReactNode}) {
);
}

/**
* Reads the current {@link CollaborationContextType} from the nearest
* {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
* to set the local user's display name and cursor color.
*
* @returns The active collaboration context.
*/
export function useCollaborationContext(
username?: string,
color?: string,
Expand Down
18 changes: 18 additions & 0 deletions packages/lexical-react/src/LexicalCollaborationPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ type CollaborationPluginProps = {
selectionHighlight?: boolean;
};

/**
* Connects the editor to a Yjs document for real-time collaboration, syncing
* editor state and rendering remote users' cursors and selections. Provide a
* `providerFactory` that creates the Yjs {@link Provider} for the given
* document `id`. Must be used within a {@link LexicalCollaboration} provider.
*
* @returns The element that renders collaborators' cursors (or an empty
* fragment until the provider and binding are initialized).
*/
export function CollaborationPlugin({
id,
providerFactory,
Expand Down Expand Up @@ -226,6 +235,15 @@ type CollaborationPluginV2Props = {
selectionHighlight?: boolean;
};

/**
* A variant of {@link CollaborationPlugin} that takes an already-created Yjs
* `doc` and {@link Provider} directly instead of a provider factory, giving the
* application full control over their lifecycle. Must be used within a
* {@link LexicalCollaboration} provider.
*
* @experimental The API may change in a future release.
* @returns The element that renders collaborators' cursors.
*/
export function CollaborationPluginV2__EXPERIMENTAL({
id,
doc,
Expand Down
22 changes: 22 additions & 0 deletions packages/lexical-react/src/LexicalComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ export type InitialEditorStateType =
| EditorState
| ((editor: LexicalEditor) => void);

/**
* The configuration passed to {@link LexicalComposer} via its `initialConfig`
* prop. It is read once when the editor is created and describes the editor's
* `namespace`, registered `nodes`, `theme`, error handling, initial editable
* state, optional initial {@link InitialEditorStateType}, and HTML
* import/export configuration.
*/
export type InitialConfigType = Readonly<{
namespace: string;
nodes?: readonly (Klass<LexicalNode> | LexicalNodeReplacement)[];
Expand Down Expand Up @@ -97,6 +104,21 @@ type Props = React.PropsWithChildren<{
initialConfig: InitialConfigType;
}>;

/**
* The root component for a Lexical editor in React. It creates a
* {@link LexicalEditor} from `initialConfig`, provides it (and its
* {@link LexicalComposerContextType}) to descendants through React context, and
* renders its `children`. Place plugins and UI such as {@link RichTextPlugin}
* and {@link ContentEditable} inside it, and read the editor from descendants
* with {@link useLexicalComposerContext}.
*
* `LexicalComposer` uses the legacy plugin pattern and does not support the
* extension API. To build an editor from extensions, use
* {@link LexicalExtensionComposer} instead; see the
* [React extensions guide](https://lexical.dev/docs/extensions/react).
*
* @returns A context provider wrapping `children`.
*/
export function LexicalComposer({initialConfig, children}: Props): JSX.Element {
const composerContext: [LexicalEditor, LexicalComposerContextType] = useMemo(
() => {
Expand Down
Loading
Loading