Files
simple-react-app-kit/packages/lexical
Maofeng 0d817537be feat(lexical): add declarative rich text editor
Introduce @workspace/lexical with composable actions, toolbars, embeds, rich-text nodes, media uploads, selection utilities, HTML serialization, and theme styling.\n\nLocalize built-in controls through MessageDescriptor catalogs for English and Simplified Chinese, while providing an English I18nProvider fallback for standalone editor use. Include focused unit coverage for actions, controls, serialization, plugins, public API, and catalogs.
2026-07-31 15:07:09 +08:00
..

@workspace/lexical

基于 Lexical 的声明式富文本编辑器。编辑器能力由 <LexicalActions /> 中出现的 action 组件决定;action 同时声明自己依赖的 node、plugin 和 embed<LexicalRoot /> 会在创建 Composer 前自动收集并去重。

使用预设

useDefaults 提供 minimalfull 两套预设。使用预设时不再接收 children,避免默认 action 与手动 action 的优先级不明确。

import {
  LexicalActions,
  LexicalBubbleToolbar,
  LexicalContent,
  LexicalFixedToolbar,
  LexicalFooter,
  LexicalRoot,
} from "@workspace/lexical"

export function Editor() {
  return (
    <LexicalRoot value="" onChange={(html) => console.log(html)}>
      <LexicalActions useDefaults="full" />
      <LexicalFixedToolbar />
      <LexicalContent placeholder="开始输入…" />
      <LexicalBubbleToolbar />
      <LexicalFooter />
    </LexicalRoot>
  )
}

LexicalFixedToolbarLexicalBubbleToolbarLexicalFooter 都是可选的。 当对应区域没有 action,也没有自定义 children 时,组件不会产生 DOM。 action、node 与 plugin 会在编辑器首次挂载时确定;需要切换整套能力时,应为 LexicalRoot 提供新的 key 以重新创建编辑器。

国际化

内置控件会使用 @workspace/i18n 的当前语言;包提供英文与简体中文 catalog。 应用应将对应 locale source 加入自己的 i18n.config.json

{
  "catalogSources": ["@workspace/lexical/locales/{locale}"]
}

未置于 I18nProvider 内时,编辑器会回退到英文。外部 action 的 label 仍可直接传字符串,也可以传 { id, message } 消息描述符。

自定义 action 布局

action 默认显示在固定工具栏。通过 in 可以指定单个区域或多个区域; ActionGroup 可以只做普通组合,也可以渲染成下拉菜单。

import {
  ActionGroup,
  Bold,
  BulletList,
  CheckList,
  ClearFormatting,
  Date,
  Heading,
  LexicalActions,
  NormalText,
  OrderedList,
  Quote,
  Redo,
  Undo,
} from "@workspace/lexical"

;<LexicalActions>
  <Undo />
  <Redo />

  <ActionGroup type="menu" label="段落样式" showActiveAction>
    <NormalText />
    <Heading level={1} />
    <Heading level={2} />
    <Heading level={3} />
    <OrderedList />
    <BulletList />
    <CheckList />
    <Quote />
  </ActionGroup>

  <Bold in={["toolbar", "bubble"]} />
  <Date in="bubble" />
  <ClearFormatting in="footer" />
</LexicalActions>

定义扩展 action

外部 action 把行为和 Composer 依赖放在同一份定义中,不需要额外注册 feature。只要 action 出现在 LexicalActions 中,它声明的 node 和 plugin 就会自动启用。

import {
  defineLexicalAction,
  type LexicalActionDefinition,
} from "@workspace/lexical"

const mentionAction: LexicalActionDefinition = {
  name: "mention",
  label: "插入提及",
  nodes: [MentionNode],
  plugins: [MentionPopoverPlugin],
  execute: ({ editor }) => openMentionPicker(editor),
}

export const Mention = defineLexicalAction(mentionAction)

action 组件可以用 render function 替换默认控件,但依赖收集仍由同一个 action 完成:

<Mention>
  {({ disabled, execute }) => (
    <CustomMentionButton
      disabled={disabled}
      onSelect={(mention) => execute(mention)}
    />
  )}
</Mention>

render context 中的 execute(value?) 会调用该 action,并保留 value 类型。 onClickexecute() 的无参快捷方式,适合普通按钮。

图片和视频 action 在不传 value 时继续使用内置输入弹窗;自定义上传器可以 在上传结束后直接把结果交给 execute,不需要操作 Lexical editor

<Image>
  {({ execute }) => (
    <ImageUploader
      onUploaded={({ src, alt, caption }) =>
        execute({ src, alt, caption })
      }
    />
  )}
</Image>

<Video>
  {({ execute }) => (
    <VideoUploader
      onUploaded={({ src, poster, caption }) =>
        execute({ src, poster, caption })
      }
    />
  )}
</Video>

粘贴图片

full 预设已包含 <ClipboardImages />:直接粘贴截图或拖入图片文件时, 会立即插入本地预览;默认完成后把图片编码成可序列化的 data: URL。 生产环境通常更适合传入 resolveImage,先把图片上传到对象存储,再返回 持久 URL。上传期间可通过 reportProgress 汇报 01 的进度;不汇报 时编辑器会显示不确定进度:

import { ClipboardImages, LexicalActions } from "@workspace/lexical"

;<LexicalActions>
  {/* 其他 action */}
  <ClipboardImages
    resolveImage={async (file, { reportProgress, signal }) => {
      const uploaded = await uploadImage(file, {
        signal,
        onProgress: reportProgress,
      })
      return {
        alt: file.name,
        src: uploaded.url,
      }
    }}
    onError={(error, file) => reportUploadError(error, file)}
  />
</LexicalActions>

非图片文件不会被编辑器接管,粘贴仍由浏览器处理。图片被选中后可以通过 控制点缩放、添加或修改说明、切换左/中/右对齐,也可以使用可见删除按钮、 Delete 或 Backspace 删除。该能力同时复用 Lexical 的文件拖放命令,因此 相同 resolver 也适用于拖入编辑器的图片。

没有可见控件、只提供编辑行为的能力也可以声明为 hidden action。包内的 <DraggableBlocks /> 就使用这种方式,因此它会启用拖拽 plugin,但不会占据 任何工具栏位置。