块选择

PreviousNext

Select and manipulate entire text blocks.

Loading…

块选择功能允许用户选择和操作整个文本块,而不是单个单词或字符。

功能特性

  • 通过单一操作选择整个块。
  • 使用鼠标拖动或键盘快捷键进行多块选择。
  • 对选中的块执行复制、剪切和删除操作。
  • 快速选择的键盘快捷键:
    • Cmd+A:选择所有块。
    • 方向键:选择上方或下方的块。
  • 可自定义选中块的样式。

Kit 使用方式

安装

添加块选择最快的方式是使用 BlockSelectionKit,它包含预配置的 BlockSelectionPluginBlockSelection UI 组件。

'use client';
 
import { AIChatPlugin } from '@platejs/ai/react';
import { BlockSelectionPlugin } from '@platejs/selection/react';
import { getPluginTypes, isHotkey, KEYS } from 'platejs';
 
import { BlockSelection } from '@/components/ui/block-selection';
 
export const hasSelectableClass = ({
  attributes,
  className,
}: {
  attributes: { className?: string };
  className?: string;
}) =>
  [className, attributes.className]
    .filter(Boolean)
    .join(' ')
    .includes('slate-selectable');
 
export const BlockSelectionKit = [
  BlockSelectionPlugin.configure(({ editor }) => ({
    options: {
      enableContextMenu: true,
      isSelectable: (element) =>
        !getPluginTypes(editor, [KEYS.column, KEYS.codeLine, KEYS.td]).includes(
          element.type
        ),
      onKeyDownSelecting: (editor, e) => {
        if (isHotkey('mod+j')(e)) {
          editor.getApi(AIChatPlugin).aiChat.show();
        }
      },
    },
    render: {
      belowRootNodes: (props) => {
        if (!hasSelectableClass(props)) return null;
 
        return <BlockSelection {...(props as any)} />;
      },
    },
  })),
];
'use client';
 
import { AIChatPlugin } from '@platejs/ai/react';
import { BlockSelectionPlugin } from '@platejs/selection/react';
import { getPluginTypes, isHotkey, KEYS } from 'platejs';
 
import { BlockSelection } from '@/components/ui/block-selection';
 
export const hasSelectableClass = ({
  attributes,
  className,
}: {
  attributes: { className?: string };
  className?: string;
}) =>
  [className, attributes.className]
    .filter(Boolean)
    .join(' ')
    .includes('slate-selectable');
 
export const BlockSelectionKit = [
  BlockSelectionPlugin.configure(({ editor }) => ({
    options: {
      enableContextMenu: true,
      isSelectable: (element) =>
        !getPluginTypes(editor, [KEYS.column, KEYS.codeLine, KEYS.td]).includes(
          element.type
        ),
      onKeyDownSelecting: (editor, e) => {
        if (isHotkey('mod+j')(e)) {
          editor.getApi(AIChatPlugin).aiChat.show();
        }
      },
    },
    render: {
      belowRootNodes: (props) => {
        if (!hasSelectableClass(props)) return null;
 
        return <BlockSelection {...(props as any)} />;
      },
    },
  })),
];

添加 Kit

BlockSelectionKit 默认启用上下文菜单,并提供默认的 isSelectable 逻辑来排除常见的不可选择块,如代码行和表格单元格。

import { createPlateEditor } from 'platejs/react';
import { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...BlockSelectionKit,
  ],
});
import { createPlateEditor } from 'platejs/react';
import { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...BlockSelectionKit,
  ],
});

手动使用方式

安装

pnpm add @platejs/selection
pnpm add @platejs/selection

添加插件

import { BlockSelectionPlugin } from '@platejs/selection/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    BlockSelectionPlugin,
  ],
});
import { BlockSelectionPlugin } from '@platejs/selection/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    BlockSelectionPlugin,
  ],
});

将此插件放在任何覆盖 selectAllCmd+A 的其他插件(代码块、表格、列等)之前,以避免冲突。

从选择中排除块

您可以使用 options.isSelectable 控制哪些块可被选择。此函数接收元素及其路径,如果块可选择则返回 true

例如,排除代码行、列和表格单元格:

import { BlockSelectionPlugin } from '@platejs/selection/react';
 
BlockSelectionPlugin.configure({
  options: {
    isSelectable: (element, path) => {
      if (['code_line', 'column', 'td'].includes(element.type)) {
        return false;
      }
      // 排除表格行内的块
      if (editor.api.block({ above: true, at: path, match: { type: 'tr' } })) {
        return false;
      }
      return true;
    },
  },
});
import { BlockSelectionPlugin } from '@platejs/selection/react';
 
BlockSelectionPlugin.configure({
  options: {
    isSelectable: (element, path) => {
      if (['code_line', 'column', 'td'].includes(element.type)) {
        return false;
      }
      // 排除表格行内的块
      if (editor.api.block({ above: true, at: path, match: { type: 'tr' } })) {
        return false;
      }
      return true;
    },
  },
});

自定义滚动行为

如果您的编辑器位于可滚动容器内,您可能需要配置选择区域的边界和滚动速度。

  1. 为滚动容器添加 id,例如 id={editor.meta.uid}
  2. 在容器上设置 position: relative
  3. 使用 areaOptions 配置边界和滚动行为。
BlockSelectionPlugin.configure({
  options: {
    areaOptions: {
      boundaries: `#${editor.meta.uid}`,
      container: `#${editor.meta.uid}`,
      behaviour: {
        scrolling: {
          // 推荐速度,接近原生体验
          speedDivider: 0.8,
        },
        // 开始选择区域的阈值
        startThreshold: 4,
      },
    },
  },
});
BlockSelectionPlugin.configure({
  options: {
    areaOptions: {
      boundaries: `#${editor.meta.uid}`,
      container: `#${editor.meta.uid}`,
      behaviour: {
        scrolling: {
          // 推荐速度,接近原生体验
          speedDivider: 0.8,
        },
        // 开始选择区域的阈值
        startThreshold: 4,
      },
    },
  },
});

全页面选择

您可以通过添加 data-plate-selectable 属性,为 <Editor /> 组件外部的元素启用块选择。

<Cover data-plate-selectable />
<Sidebar data-plate-selectable />
<Cover data-plate-selectable />
<Sidebar data-plate-selectable />

要防止点击某些元素(例如工具栏按钮)时取消选择块,请添加 data-plate-prevent-unselect 属性。

<YourToolbarButton data-plate-prevent-unselect />
<YourToolbarButton data-plate-prevent-unselect />

要在点击可选择区域外部时重置选择,您可以使用点击处理程序或直接调用 API:

// 1. 直接调用 API
editor.api.blockSelection.deselect();
 
// 2. 外部点击处理程序
const handleClickOutside = (event: MouseEvent) => {
  if (!(event.target as HTMLElement).closest('[data-plate-selectable]')) {
    editor.api.blockSelection.deselect();
  }
};
// 1. 直接调用 API
editor.api.blockSelection.deselect();
 
// 2. 外部点击处理程序
const handleClickOutside = (event: MouseEvent) => {
  if (!(event.target as HTMLElement).closest('[data-plate-selectable]')) {
    editor.api.blockSelection.deselect();
  }
};

样式

选择区域

通过定位 .slate-selection-area 类来设置选择区域的样式,该类会添加到编辑器容器上。

/* 使用 Tailwind CSS 工具类的示例 */
'[&_.slate-selection-area]:border [&_.slate-selection-area]:border-primary [&_.slate-selection-area]:bg-primary/10'
/* 使用 Tailwind CSS 工具类的示例 */
'[&_.slate-selection-area]:border [&_.slate-selection-area]:border-primary [&_.slate-selection-area]:bg-primary/10'

选中元素

使用 useBlockSelected hook 来确定块是否被选中。您可以渲染一个视觉指示器,例如专为此目的设计的 BlockSelection 组件。

Plate UI 使用 render.belowRootNodes 为所有可选择的块渲染此组件:

render: {
  belowRootNodes: (props) => {
    if (!props.className?.includes('slate-selectable')) return null;
 
    return <BlockSelection />;
  },
},
render: {
  belowRootNodes: (props) => {
    if (!props.className?.includes('slate-selectable')) return null;
 
    return <BlockSelection />;
  },
},

插件

BlockSelectionPlugin

块选择功能的插件。

Options

    选择区域的选项。查看 SelectionJS 文档 了解所有可用选项。

    {
      boundaries: [`#${editor.meta.uid}`],
      container: [`#${editor.meta.uid}`],
      selectables: [`#${editor.meta.uid} .slate-selectable`],
      selectionAreaClass: 'slate-selection-area',
    }
    {
      boundaries: [`#${editor.meta.uid}`],
      container: [`#${editor.meta.uid}`],
      selectables: [`#${editor.meta.uid} .slate-selectable`],
      selectionAreaClass: 'slate-selection-area',
    }

    启用或禁用块选择的上下文菜单。

    • 默认值: false

    指示块选择当前是否处于活动状态。

    • 默认值: false

    处理选择时键盘按下事件的函数。

    块选择期间查询节点的选项。

    • 默认值: { maxLevel: 1 }

    当前选中块的 ID 集合。

    • 默认值: new Set()

    (内部)当前选择中锚点块的 ID。用于基于 Shift 的选择。

    • 默认值: null

    确定块元素是否可选择的函数。

    • 默认值: () => true

API

api.blockSelection.add

将一个或多个块添加到选择中。

Parameters

    要选择的块的 ID。

api.blockSelection.clear

将选中的 ID 集合重置为空集合。

api.blockSelection.delete

从选择中移除一个或多个块。

Parameters

    要从选择中移除的块的 ID。

api.blockSelection.deselect

取消选择所有块并将 isSelecting 标志设置为 false。

api.blockSelection.focus

聚焦块选择的影子输入框。此输入框处理选中块的复制、删除和粘贴事件。

api.blockSelection.getNodes

获取编辑器中选中的块。

Parameters

    获取节点的选项。

Optionsobject

    如果为 true,且块选择未选中任何块,该方法将使用编辑器的原始选择来获取块。- 默认值: false

ReturnsNodeEntry[]

    选中块条目的数组。

api.blockSelection.has

检查一个或多个块是否被选中。

Parameters

    要检查的块的 ID。

Returns

    块是否被选中。

api.blockSelection.isSelectable

根据 isSelectable 插件选项检查给定路径的块是否可选择。

Parameters

    要检查的块元素。

    块元素的路径。

Returnsboolean

    块是否可选择。

api.blockSelection.moveSelection

将选择向上或向下移动到下一个可选择的块。

向上移动时:

  • 从最顶部选中的块获取前一个可选择的块
  • 将其设置为新的锚点
  • 清除之前的选择并仅选择此块 向下移动时:
  • 从最底部选中的块获取下一个可选择的块
  • 将其设置为新的锚点
  • 清除之前的选择并仅选择此块

Parameters

    移动选择的方向。

api.blockSelection.selectAll

选择编辑器中所有可选择的块。

api.blockSelection.set

将选择设置为一个或多个块,清除任何现有选择。

Parameters

    要选择的块的 ID。

api.blockSelection.shiftSelection

根据锚点块扩展或收缩选择。

对于 Shift+ArrowDown

  • 如果锚点在最顶部:通过在最底部下方添加块向下扩展
  • 否则:从最顶部收缩(除非最顶部是锚点) 对于 Shift+ArrowUp
  • 如果锚点在最底部:通过在最顶部上方添加块向上扩展
  • 否则:从最底部收缩(除非最底部是锚点) 锚点块始终保持选中状态。如果未设置锚点,则默认为:
  • Shift+ArrowUp 时为最底部的块
  • Shift+ArrowDown 时为最顶部的块

Parameters

    扩展/收缩选择的方向。

Transforms

tf.blockSelection.duplicate

复制选中的块。

tf.blockSelection.removeNodes

从编辑器中移除选中的节点。

tf.blockSelection.select

在编辑器中选择由 getNodes() 返回的节点并重置选中的 ID。

tf.blockSelection.setNodes

设置选中节点的属性。

Parameters

    要设置到选中节点的属性。

    设置节点的选项。

tf.blockSelection.setTexts

设置选中节点的文本属性。

Parameters

    要设置到选中节点的文本属性。

    设置文本节点的选项,不包括 'at' 属性。

Hooks

useBlockSelectable

提供使块元素可选择的 props 的 hook,包括上下文菜单行为。

Returnsobject

    要展开到块元素上的 props。

useBlockSelected

Returnsboolean

    上下文块是否被选中。

useBlockSelectionNodes

ReturnsNodeEntry[]

    选中块条目的数组。

useBlockSelectionFragment

ReturnsNode[]

    选中块节点的数组。

useBlockSelectionFragmentProp

ReturnsNode[]

    选中块的 fragment prop。

useSelectionArea

初始化和管理选择区域功能。