组件/SelectionActions 划词工具条

SelectionActions 划词工具条

浮在选区下方的工具条,把选中的文字交给智能体改写。

Verified自 0.3.9

SelectionActions 划词工具条

基础用法

划词改写

选中段落里的句子,工具条浮现在选区最后一行的下方。四个状态:idlethinkingstreamingresult

示例加载中...

两层:追踪与呈现

组件本身只负责呈现:给它一个 selection 快照和一个 state,它负责定位、形变与动作行。选区从哪来是宿主的事。

useSelectionAnchor() 是配套的追踪层,基于 @vueuse/coreuseTextSelection,适用于普通文档正文。contenteditable、虚拟列表、iframe 这类场景自己构造 SelectionPayload 喂进来即可。

import { resolveSelectionPayload, useSelectionAnchor } from '@talex-touch/tuffex/selection-actions'

const { selection, clear } = useSelectionAnchor({
  root: articleRef,        // 只追踪这棵子树里的选区
  debounce: 120,           // selectionchange 每帧都触发,必须收敛
  minLength: 1,            // 太短的选区不值得弹条
  ignore: () => [barEl],   // 焦点落在工具条上不算「取消选中」
})

resolveSelectionPayload() 是它内部那条纯函数规则,单独导出以便测试或自定义追踪。

useSelectionAnchorresolveSelectionPayload运行时函数,不是组件TxSelectionActions 作为全局标签可以直接写在模板里,但这两个必须像上面那样从 @talex-touch/tuffex/selection-actions 显式 import——它们不在全局组件注册表里。

useSelectionAnchor 选项

选项类型默认值说明
rootElement | null限定追踪范围,不传则监听整篇文档。
debouncenumber120selectionchange 的收敛时间(ms)。拖选时它每帧都触发。
minLengthnumber1去空白后的最小长度。
disabledbooleanfalse暂停上报而不卸载。
ignoreElement[][]焦点落进这些元素时不视为取消选中。

返回 { selection, clear }selectionRef<SelectionPayload | null>clear() 在改写落地或放弃后调用。

API

Props

属性名类型默认值说明
selectionSelectionPayload | nullnull{ text, rects, range? }。为空即收起工具条。
state'idle' | 'thinking' | 'streaming' | 'result''idle'状态机由宿主持有,组件不发请求。
actionsSelectionActionItem[]Explain / Improve + 折叠的 Shorten / Tone / Grammar动作列表:{ id, label, more?, busyLabel? }
activeActionIdstring正在运行的动作 id,决定忙碌文案。
expandedbooleanv-model:expanded,折叠动作组。
promptstringv-model:prompt,自由指令输入。
hidePromptbooleanfalse隐藏输入框与发送键。
placeholderstring'Describe edits'输入框占位,同时用作它的无障碍名。
ariaLabelstring'Selection actions'工具条的无障碍名。
keepLabel / discardLabelstring'Keep' / 'Discard'结果态两颗按钮。
retryLabelstring'Try again'重试按钮的无障碍名。
sendLabelstring'Send edit instruction'发送键的无障碍名。
expandLabel / collapseLabelstring'Show more actions' / 'Show fewer actions'chevron 的无障碍名。
busyLabelstring'Editing'动作没写 busyLabel 时的兜底忙碌文案。
offsetnumber8距选区最后一行的距离(px)。

Events

事件名参数说明
action({ id, action, selection })按下某个预设动作,携带选区快照。
submit({ prompt, selection })输入框回车或按发送键,prompt 已 trim。
keep / discard()结果态的保留 / 放弃。
retry()结果态的重试。
update:expanded(expanded: boolean)折叠组开合。
update:prompt(prompt: string)输入内容变化。

Slots

插槽名作用域参数说明
action-icon{ action }覆盖动作图标。自定义 id 必须用它。
busy{ label }覆盖忙碌区。
result覆盖保留 / 放弃 / 重试整组。

Exposed

方法说明
updatePosition()按当前 selection.rects 重新定位。流式宿主必须调用,见下。
focusInput()聚焦自由指令输入框。
el工具条根元素。传给 useSelectionAnchorignore

交互契约

  • updatePosition() 是宿主的责任,不是可选优化。 工具条锚在一个虚拟参考点上,floating-ui 没有可观察的元素,因此不会跟随文本重排。改写流式写入时每个 delta 都会让段落重排,宿主必须在更新完文本后调一次 updatePosition(),否则工具条会停在旧位置。窗口 resize 与滚动由组件自己处理。
  • 锚点是选区最后一行的底边,水平居中于整段选区。 跨三行的选区,工具条落在读者停下的那一行下面,而不是整块的中点。
  • 工具条不翻面。 它给 TxBaseAnchor 传了 disableFlip:靠近视口底部时不跳到选区上方,因为改写途中换边会被读成另一个控件。shift 仍在,横向会被推回视口内。
  • selection 是快照,不是实时选区。 聚焦工具条的输入框会清掉浏览器选区;组件持有的是传进来那一份,所以动作仍有目标。useSelectionAnchor 内部用 range.cloneRange() 也是这个原因。
  • 工具条根节点吞掉 pointerdownpreventDefault),按按钮不会转移焦点、不会毁掉选区。处理函数会放行 inputtextareacontenteditable 内部的按下,并且用 closest() 判定而不是比对事件目标本身:输入框外面套着 <form>,一旦指针落在输入框边缘一像素之外,按目标比对就会漏判、输入框随之无法聚焦——点它焦点会跑到 <body>,打字也没有任何反应。
  • 必须接上 ignore,否则工具条会自己消失。 聚焦输入框会让文档选区塌缩,而 useSelectionAnchor 在工具条未持有焦点时会把塌缩当成读者清除了选区。请传入实例的 elignore: () => [barRef.value?.el ?? null]。不要用 document.querySelector('.tx-bui-selection-actions')——它返回文档中第一个工具条,页面上只要有两个就会取错。
  • 折叠的动作在收起时 tabindex="-1",不出现在 Tab 序列里;发送键在 prompt 为空时同样如此。
  • 整条是 role="group" 而不是 role="toolbar":里面有文本输入框,方向键要留给光标。
  • thinkingstreaming 的区别只有一处——前者的文案带微光,后者是普通文本。
  • 宽度形变走 WAAPI,CSS 媒体查询管不到它,因此组件在脚本里读 prefers-reduced-motion 并跳过补间;状态机照常推进,只是宽度直接落位。
  • 空白 prompt 不会派发 submit

最佳实践

  • 流式改写时把 updatePosition() 接在文本更新之后、同一帧里调用,别放 setTimeout
  • useSelectionAnchorignore 一定要带上工具条本身,否则用户一点输入框,选区收拢,工具条立刻消失。
  • keep / discard 之后调 clear(),否则快照会一直留着,工具条不退。
  • 自定义动作给上 busyLabel,「润色中…」比兜底的「编辑中…」准确得多。
  • root 限定到正文容器,别监听整篇文档——页面上任何一次选中都会弹条。
  • 只读展示场景可以 hidePrompt,留下预设动作即可。

Source

  • Component source: packages/tuffex/packages/components/src/selection-actions/src/TxSelectionActions.vue
  • Composable: packages/tuffex/packages/components/src/selection-actions/src/use-selection-anchor.ts
  • Types: packages/tuffex/packages/components/src/selection-actions/src/types.ts
  • 实测覆盖: selection-actions.test.ts(23 项)验证按选区显隐、role="group"、锚点取最后一行底边与整段水平范围、disableFlip、折叠组的 tabindexaria-expanded、Explain 也派发事件、prompt 门控与 trim、忙碌文案与微光分支、结果态三个动作、pointerdown 在条上被吞而在输入框放行、根元素被暴露以便宿主把工具条排除在选区塌缩之外;selection-actions-position.test.ts(2 项)从组件一路断言到 floating-uiupdate 确实被调用;use-selection-anchor.test.ts(9 项)覆盖空选区、最小长度、折叠 range、root 限定与零尺寸 rect 过滤。
  • 移植自 Beautiful UI(https://www.beautifului.dev),© 2026 Shane Levine,MIT。
查看源码
packages/tuffex/packages/components/src/selection-actions/index.ts