diff --git a/packages/react/README.md b/packages/react/README.md
index 53254c9..2b64849 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -316,6 +316,13 @@ function RegionTools() {
Lasso area
{capture.active && }
+ {capture.selectionState && (
+
+ )}
>
);
}
@@ -326,8 +333,10 @@ The lasso overlay ships with the core `ASKABLE_REGION_CAPTURE_THEME`. Pass
region/circle fill, or lasso gradient for your app.
Use `selectionAffordance` to keep the selected area visible and optionally show
an anchored prompt that focuses by default. Pass `dismissible: true` to include
-a built-in clear button. Call `capture.getSelection()` when the chat composer
-needs the current pinned packet, selection geometry, and affordance element.
+a built-in clear button. Read `capture.selectionState` when React should render
+a confirmation chip, inline question input, or custom composer for the pinned
+selection. Call `capture.getSelection()` when non-render code needs the current
+pinned packet, selection geometry, and affordance element.
Use `onSelectionChange(state)` to mirror that pinned context into external
state; it receives `null` when the selection is cleared.
@@ -368,14 +377,23 @@ function SelectionTools() {
{selection.active && }
+ {selection.selectionState && (
+
+ )}
>
);
}
```
-`selection.getSelection()` returns the current pinned text packet, selected
-range metadata, and affordance element. It returns `null` after the selection is
-cleared, dismissed, cancelled, or destroyed.
+`selection.selectionState` updates when text is pinned, cleared, dismissed,
+cancelled, or destroyed. Use it for app-rendered selected-text confirmation and
+inline chat inputs. `selection.getSelection()` returns the same current pinned
+text packet, selected range metadata, and affordance element for imperative
+code.
Use `onSelectionChange(state)` to keep chat input state aligned with the pinned
text selection.
diff --git a/packages/react/src/__tests__/useAskableRegionCapture.test.tsx b/packages/react/src/__tests__/useAskableRegionCapture.test.tsx
index 0ab3e37..ae2ae93 100644
--- a/packages/react/src/__tests__/useAskableRegionCapture.test.tsx
+++ b/packages/react/src/__tests__/useAskableRegionCapture.test.tsx
@@ -33,6 +33,7 @@ describe('useAskableRegionCapture', () => {
{String(capture.active)}{capture.lastPacket ? JSON.stringify(capture.lastPacket) : 'null'}{capture.getSelection() ? JSON.stringify(capture.getSelection()?.selection) : 'null'}
+ {capture.selectionState ? JSON.stringify(capture.selectionState.selection) : 'null'}
);
}
@@ -56,6 +57,7 @@ describe('useAskableRegionCapture', () => {
expect(screen.getByTestId('active').textContent).toBe('false');
expect(screen.getByTestId('packet').textContent).not.toBe('null');
expect(screen.getByTestId('selected').textContent).not.toBe('null');
+ expect(screen.getByTestId('selection-state').textContent).not.toBe('null');
});
const packet = JSON.parse(screen.getByTestId('packet').textContent!);
@@ -77,6 +79,53 @@ describe('useAskableRegionCapture', () => {
shape: 'region',
bounds: { x: 20, y: 30, width: 60, height: 60 },
});
+ expect(JSON.parse(screen.getByTestId('selection-state').textContent!)).toMatchObject({
+ shape: 'region',
+ bounds: { x: 20, y: 30, width: 60, height: 60 },
+ });
+ });
+
+ it('exposes pinned selection state and clears it from React state', async () => {
+ function Consumer() {
+ const capture = useAskableRegionCapture({ selectionAffordance: true });
+
+ return (
+