SelectionActions
A floating bar that hands a text selection to an agent.
Usage
Rewriting a Selection
The host moves the bar through idle → thinking → streaming → result; the thinking label shimmers.
Loading demo...
Tracking the Selection
The component only presents; useSelectionAnchor() tracks selections in ordinary prose, and anywhere else you build a SelectionPayload yourself.
import { resolveSelectionPayload, useSelectionAnchor } from '@talex-touch/tuffex/selection-actions'
const { selection, clear } = useSelectionAnchor({
root: articleRef, // track selections inside this subtree only
debounce: 120, // selectionchange fires every frame while dragging
minLength: 1, // a selection too short to be worth a bar
ignore: () => [barEl], // focus landing on the bar is not a deselection
})
Best Practices
- While streaming, call
updatePosition()in the same frame as the text update, not throughsetTimeout. - Pass the instance's
eltoignore:() => [barRef.value?.el ?? null].document.querySelectorreturns the wrong bar once a page has two. - Call
clear()afterkeepordiscard, or the snapshot lingers and the bar never retracts. - Scope
rootto the article container, or any selection on the page pops the bar. - Use
TxMessageActionsfor copy, regenerate, and speak at the foot of a message; this bar is only for rewriting a selection.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
selection | SelectionPayload | null | null | { text, rects, range? } snapshot; null retracts the bar. |
state | 'idle' | 'thinking' | 'streaming' | 'result' | 'idle' | The host owns the state machine; the component never calls a model. |
actions | SelectionActionItem[] | Explain / Improve plus folded Shorten / Tone / Grammar | Actions as { id, label, more?, busyLabel? }; give custom actions a busyLabel. |
activeActionId | string | — | Id of the running action; picks the busy wording. |
expanded | boolean | — | v-model:expanded, the folded action group. |
prompt | string | — | v-model:prompt, the free-text instruction. |
hidePrompt | boolean | false | Hides the text field and send control, for read-only surfaces. |
placeholder | string | 'Describe edits' | Field placeholder, also its accessible name. |
ariaLabel | string | 'Selection actions' | Accessible name of the bar. |
keepLabel / discardLabel | string | 'Keep' / 'Discard' | The two result-state buttons. |
retryLabel | string | 'Try again' | Accessible name of the retry control. |
sendLabel | string | 'Send edit instruction' | Accessible name of the send control. |
expandLabel / collapseLabel | string | 'Show more actions' / 'Show fewer actions' | Accessible names of the expand control. |
busyLabel | string | 'Editing' | Fallback busy wording for an action without its own. |
offset | number | 8 | Distance from the selection's last line, in px. |
Events
| Event | Arguments | Description |
|---|---|---|
action | ({ id, action, selection }) | A preset action was pressed; carries the selection snapshot. |
submit | ({ prompt, selection }) | Enter or send was pressed; prompt is trimmed, and a blank one never fires. |
keep / discard | () | Keep or discard in the result state. |
retry | () | Retry in the result state. |
update:expanded | (expanded: boolean) | The folded group opened or closed. |
update:prompt | (prompt: string) | The field's content changed. |
Slots
| Slot | Scope | Description |
|---|---|---|
action-icon | { action } | Replaces an action's glyph; required for a custom id. |
busy | { label } | Replaces the busy readout. |
result | — | Replaces the keep / discard / retry cluster. |
Exposed Methods
| Method | Description |
|---|---|
updatePosition() | Repositions against the current selection.rects; a streaming host must call it. |
focusInput() | Focuses the free-text field. |
el | The bar's root element, for useSelectionAnchor's ignore. |
useSelectionAnchor Options
| Option | Type | Default | Description |
|---|---|---|---|
root | MaybeRefOrGetter<Element | null | undefined> | — | Confines tracking to one subtree; omit to watch the whole document. |
debounce | number | 120 | Settle time for selectionchange, in ms. |
minLength | number | 1 | Minimum trimmed length. |
disabled | MaybeRefOrGetter<boolean> | false | Stops reporting without unmounting. |
ignore | MaybeRefOrGetter<Array<Element | null | undefined>> | [] | Focus inside these elements doesn't count as a deselection. |
Returns { selection, clear }: selection is a Ref<SelectionPayload | null>, and clear() is called once the rewrite is applied or dropped. resolveSelectionPayload() is its pure internal rule, exported for testing or custom tracking. Both are runtime functions; import them explicitly from @talex-touch/tuffex/selection-actions.
Overview
- The anchor is the bottom of the selection's last line, centered on the whole selection. The bar never flips (
disableFlip); it is only shifted back into the viewport horizontally. - The anchor is a virtual reference, so
floating-uiwon't follow text reflow; the host callsupdatePosition()while streaming, and the component handles resize and scroll. selectionis a snapshot: focusing the field clears the browser selection, but actions still target the payload passed in.- The root swallows
pointerdown, so pressing a button neither moves focus nor destroys the selection, except inside aninput,textarea, orcontenteditable. Safari and Firefox differ in caret behavior afterward; verify on real browsers. - The root is
role="group", leaving arrow keys to the field's caret; folded actions, and the send control whilepromptis empty, stay out of the tab order. - The entrance is a spring that overshoots its end value; under reduced motion it is dropped, the width tween is skipped, and the bar rests on its visible end state.
Technologies
- The width morph runs through the Web Animations API, so the component reads
prefers-reduced-motionin script; tracking builds onuseTextSelectionfrom@vueuse/coreand clones the selection's range. - Adapted from Beautiful UI (https://www.beautifului.dev), © 2026 Shane Levine, MIT; upstream has no real selection, so the tracking layer is new to this port.
- Source:
packages/tuffex/packages/components/src/selection-actions/.
查看源码
packages/tuffex/packages/components/src/selection-actions/index.ts