aidenybai / aidenybai/react-grab
Feature Request: Add onSelectionCommit — one interceptable hook per committed selection gesture
- 主要语言
- TypeScript
- 星标
- 7.6k
- 派生
- 340
- 平均合并
- 5 小时 12 分钟
- 30 天内合并 PR
- 6
描述
## Problem
As of `0.2.0`, plugins still don't have a single interceptable boundary for a committed selection gesture.
The existing hooks expose different pieces of the lifecycle:
* `onElementSelect(element)` fires once **per element**. Drag-selecting 3 elements produces 3 calls, so integrations have to reconstruct the gesture boundary themselves.
* No selection hook exposes the **pointer position** that committed the selection. Plugins that need to anchor UI at the commit point have to keep their own pointer listener running.
* `onDragEnd(elements, bounds)` exposes the committed group, but it is notification-only: it cannot intercept the default clipboard / `Copied` feedback path.
My current workaround reconstructs the commit boundary by batching `onElementSelect` calls:
```ts
let pendingBatch: {
elements: Element[];
resolvers: Array<(handled: boolean) => void>;
} | null = null;
const onElementSelect = (element: Element): Promise => {
if (!pendingBatch) {
pendingBatch = { elements: [], resolvers: [] };
queueMicrotask(flushBatch);
}
const batch = pendingBatch;
if (!batch.elements.includes(element)) {
batch.elements.push(element);
}
return new Promise((resolve) => {
batch.resolvers.push(resolve);
});
};
```
The integration also keeps separate pointer tracking so it can recover the position associated with that commit.
## Proposal
Add one async, interceptable hook that fires once for each committed selection gesture:
```ts
interface SelectionCommit {
readonly elements: readonly Element[];
readonly point: Position; // pointer/client position at commit time
readonly kind: "click" | "drag";
readonly additive: boolean;
readonly bounds: DragRect | null; // non-null for drag commits
}
interface PluginHooks {
onSelectionCommit?: (
selection: SelectionCommit,
) => boolean | void | Promise;
}
```
Semantics:
* Invoke it **once per committed gesture**, regardless of the number of selected elements.
* Resolving `true` means the plugin handled the selection — skip React Grab's built-in clipboard write and `Copied` feedback.
* `false` / `undefined` preserves the existing copy behavior.
* `additive` indicates that the gesture extended the existing selection, e.g. Shift + click or Shift + drag.
* For keyboard-committed selections, `point` falls back to the center of the target element.
* `onElementSelect` and `onDragEnd` stay unchanged for backward compatibility.
## Why a commit-level hook?
The plugin API already exposes most of the required information, but at different lifecycle boundaries:
```ts
onElementSelect(element)
onDragEnd(elements, bounds)
```
The missing piece is a single interception point representing:
> "This selection gesture has now committed these elements."
For pointer-driven selections, React Grab already has the selected elements, gesture type, pointer position, and drag bounds while processing the commit.
This would keep gesture reconstruction inside the component that already owns selection state rather than requiring every integration to rebuild it independently.
## Plugin use cases
A commit-level boundary would also make higher-level workflows implementable outside core as plugins, for example:
* accumulated selections / "Copy list" workflows (#317)
* the former comments dropdown / Copy All workflow (#421)
* comment + element accumulation workflows such as #634
Those plugins could consume the committed selection and own their storage/UI/export behavior without requiring those workflows to live in React Grab core.
## Current integration cost
My integration currently has roughly ~200 lines across batching, resolver fan-out, pointer tracking, and tests to reconstruct this boundary.
With this hook, the integration becomes roughly:
```ts
hooks: {
onSelectionCommit: async (selection) => {
await annotations.add(selection);
return true;
},
}
```
Related: #340 (multi-select, shipped) · #421 · #317 · #634
Happy to submit a PR if this API direction sounds useful.
贡献指南
调研方向
Start by finding the plugin hook definitions for onElementSelect and onDragEnd, then trace where selection gestures commit for click, drag, and keyboard paths. The proposal defines the desired SelectionCommit shape and behavior; done means onSelectionCommit fires once per committed gesture, can skip built-in copy feedback when it returns true, and existing hooks remain compatible.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- react, typescript
- 领域
- api, frontend
- Issue 类型
- 功能
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 冷清
- 描述清晰度
- 基本清楚
- 新手友好度
- 35/100