组合框(Combobox)

功能特性

  • 创建基于触发器的组合框功能的实用工具
  • 可配置的触发字符和模式
  • 键盘导航和选择处理

创建组合框插件

安装

pnpm add @platejs/combobox

创建输入插件

首先创建一个输入插件,当触发器激活时将被插入:

import { createSlatePlugin } from 'platejs';
 
const TagInputPlugin = createSlatePlugin({
  key: 'tag_input',
  editOnly: true,
  node: {
    isElement: true,
    isInline: true,
    isVoid: true,
  },
});

创建主插件

使用withTriggerCombobox创建主插件:

import { createTSlatePlugin, type PluginConfig } from 'platejs';
import { 
  type TriggerComboboxPluginOptions, 
  withTriggerCombobox 
} from '@platejs/combobox';
 
type TagConfig = PluginConfig<'tag', TriggerComboboxPluginOptions>;
 
export const TagPlugin = createTSlatePlugin<TagConfig>({
  key: 'tag',
  node: { isElement: true, isInline: true, isVoid: true },
  options: {
    trigger: '#',
    triggerPreviousCharPattern: /^\s?$/,
    createComboboxInput: () => ({
      children: [{ text: '' }],
      type: 'tag_input',
    }),
  },
  plugins: [TagInputPlugin],
}).overrideEditor(withTriggerCombobox);
  • node.isElement: 定义此为元素节点(非文本)
  • node.isInline: 使标签元素内联(非块级)
  • node.isVoid: 防止在标签元素内编辑
  • options.trigger: 触发组合框的字符(本例为#)
  • options.triggerPreviousCharPattern: 必须匹配触发器前字符的正则表达式。/^\s?$/允许在行首或空白后触发
  • options.createComboboxInput: 触发器激活时创建输入元素节点的函数

创建组件

使用InlineCombobox创建输入元素组件:

import { PlateElement, useFocused, useReadOnly, useSelected } from 'platejs/react';
import {
  InlineCombobox,
  InlineComboboxContent,
  InlineComboboxEmpty,
  InlineComboboxInput,
  InlineComboboxItem,
} from '@/components/ui/inline-combobox';
import { cn } from '@/lib/utils';
 
const tags = [
  { id: 'frontend', name: '前端', color: 'blue' },
  { id: 'backend', name: '后端', color: 'green' },
  { id: 'design', name: '设计', color: 'purple' },
  { id: 'urgent', name: '紧急', color: 'red' },
];
 
export function TagInputElement({ element, ...props }) {
  return (
    <PlateElement as="span" {...props}>
      <InlineCombobox element={element} trigger="#">
        <InlineComboboxInput />
        
        <InlineComboboxContent>
          <InlineComboboxEmpty>未找到标签</InlineComboboxEmpty>
          
          {tags.map((tag) => (
            <InlineComboboxItem
              key={tag.id}
              value={tag.name}
              onClick={() => {
                // 插入实际标签元素
                editor.tf.insertNodes({
                  type: 'tag',
                  tagId: tag.id,
                  children: [{ text: tag.name }],
                });
              }}
            >
              <span 
                className={`w-3 h-3 rounded-full bg-${tag.color}-500 mr-2`}
              />
              #{tag.name}
            </InlineComboboxItem>
          ))}
        </InlineComboboxContent>
      </InlineCombobox>
      
      {props.children}
    </PlateElement>
  );
}
 
export function TagElement({ element, ...props }) {
  const selected = useSelected();
  const focused = useFocused();
  const readOnly = useReadOnly();
 
  return (
    <PlateElement
      {...props}
      className={cn(
        'inline-block rounded-md bg-primary/10 px-1.5 py-0.5 align-baseline text-sm font-medium text-primary',
        !readOnly && 'cursor-pointer',
        selected && focused && 'ring-2 ring-ring'
      )}
      attributes={{
        ...props.attributes,
        contentEditable: false,
        'data-slate-value': element.value,
      }}
    >
      #{element.value}
      {props.children}
    </PlateElement>
  );
}

添加到编辑器

import { createPlateEditor } from 'platejs/react';
import { TagPlugin, TagInputPlugin } from './tag-plugin';
import { TagElement, TagInputElement } from './tag-components';
 
const editor = createPlateEditor({
  plugins: [
    // ...其他插件
    TagPlugin.configure({
      options: {
        triggerQuery: (editor) => {
          // 在代码块中禁用
          return !editor.api.some({ match: { type: 'code_block' } });
        },
      },
    }).withComponent(TagElement),
    TagInputPlugin.withComponent(TagInputElement),
  ],
});
  • options.triggerQuery: 根据编辑器状态有条件启用/禁用触发器的可选函数

示例

Loading...
Loading...
Loading...

配置选项

TriggerComboboxPluginOptions

基于触发器的组合框插件的配置选项。

Options

  • createComboboxInput (trigger: string) => TElement

    触发器激活时创建输入节点的函数。

  • trigger RegExp | string[] | string

    触发组合框的字符。可以是:

    • 单个字符(如'@')
    • 字符数组
    • 正则表达式
  • triggerPreviousCharPattern optional RegExp

    匹配触发器前字符的模式。

    • 示例: /^\s?$/ 匹配行首或空格
  • triggerQuery optional (editor: SlateEditor) => boolean

    控制触发器何时激活的自定义查询函数。

钩子函数

useComboboxInput

管理组合框输入行为和键盘交互的钩子。

Options

Collapse all
  • ref RefObject<HTMLElement>

    输入元素的引用。

  • autoFocus optional boolean

    挂载时自动聚焦输入。

    • 默认: true
  • cancelInputOnArrowLeftRight optional boolean

    方向键取消输入。

    • 默认: true
  • cancelInputOnBackspace optional boolean

    起始位置退格键取消输入。

    • 默认: true
  • cancelInputOnBlur optional boolean

    失去焦点时取消输入。

    • 默认: true
  • cancelInputOnDeselect optional boolean

    取消选择时取消输入。

    • 默认: true
  • cancelInputOnEscape optional boolean

    Escape键取消输入。

    • 默认: true
  • cursorState optional ComboboxInputCursorState

    当前光标位置状态。

  • forwardUndoRedoToEditor optional boolean

    将撤销/重做转发给编辑器。

    • 默认: true
  • onCancelInput optional (cause: CancelComboboxInputCause) => void

    输入取消时的回调函数。

Returns

Collapse all
  • cancelInput (cause?: CancelComboboxInputCause, focusEditor?: boolean) => void

    取消输入的函数。

  • props object

    输入元素的属性。

  • removeInput (focusEditor?: boolean) => void

    移除输入节点的函数。

useHTMLInputCursorState

跟踪HTML输入元素中光标位置的钩子。

Parameters

Collapse all
  • ref RefObject<HTMLInputElement>

    要跟踪的输入元素的引用。

Returns

Collapse all
  • atStart boolean

    光标是否在输入起始位置。

  • atEnd boolean

    光标是否在输入结束位置。