@nuvin/ink 7.0.2-alpha → 7.1.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/App.d.ts +3 -1
- package/build/components/App.js +4 -2
- package/build/components/App.js.map +1 -1
- package/build/components/Text.d.ts +9 -1
- package/build/components/Text.js +2 -2
- package/build/components/Text.js.map +1 -1
- package/build/components/TextSelectionContext.d.ts +6 -0
- package/build/components/TextSelectionContext.js +7 -0
- package/build/components/TextSelectionContext.js.map +1 -0
- package/build/dom.d.ts +1 -0
- package/build/dom.js.map +1 -1
- package/build/hooks/use-text-selection-actions.d.ts +10 -0
- package/build/hooks/use-text-selection-actions.js +25 -0
- package/build/hooks/use-text-selection-actions.js.map +1 -0
- package/build/hooks/use-text-selection.d.ts +9 -0
- package/build/hooks/use-text-selection.js +25 -0
- package/build/hooks/use-text-selection.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/ink.d.ts +1 -0
- package/build/ink.js +12 -2
- package/build/ink.js.map +1 -1
- package/build/output.d.ts +9 -1
- package/build/output.js +31 -4
- package/build/output.js.map +1 -1
- package/build/paint-selection.d.ts +12 -2
- package/build/paint-selection.js +45 -98
- package/build/paint-selection.js.map +1 -1
- package/build/reconciler.js +8 -0
- package/build/reconciler.js.map +1 -1
- package/build/render-node-to-output.js +8 -2
- package/build/render-node-to-output.js.map +1 -1
- package/build/renderer.d.ts +2 -1
- package/build/renderer.js +13 -2
- package/build/renderer.js.map +1 -1
- package/build/text-selection-controller.d.ts +106 -0
- package/build/text-selection-controller.js +311 -0
- package/build/text-selection-controller.js.map +1 -0
- package/build/text-selection.d.ts +41 -0
- package/build/text-selection.js +101 -0
- package/build/text-selection.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +46 -0
- package/build/current-frame.d.ts +0 -27
- package/build/current-frame.js +0 -73
- package/build/current-frame.js.map +0 -1
- package/build/hooks/use-selection-overlay.d.ts +0 -3
- package/build/hooks/use-selection-overlay.js +0 -14
- package/build/hooks/use-selection-overlay.js.map +0 -1
- package/build/selection.d.ts +0 -28
- package/build/selection.js +0 -9
- package/build/selection.js.map +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type TextSelectionPoint, type TextSelectionSpan } from './text-selection.js';
|
|
2
|
+
/**
|
|
3
|
+
Live viewport of the selection container, in Ink output grid cells.
|
|
4
|
+
*/
|
|
5
|
+
export type SelectionViewport = {
|
|
6
|
+
top: number;
|
|
7
|
+
left: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
/** Vertical scroll offset in rendered rows: content row shown at `top`. */
|
|
11
|
+
scrollY: number;
|
|
12
|
+
};
|
|
13
|
+
export type TextSelectionSnapshot = {
|
|
14
|
+
/** Content coordinates (see TextSelectionPoint). */
|
|
15
|
+
anchor: TextSelectionPoint | null;
|
|
16
|
+
focus: TextSelectionPoint | null;
|
|
17
|
+
isDragging: boolean;
|
|
18
|
+
text: string;
|
|
19
|
+
isEmpty: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type TextSelectionActions = {
|
|
22
|
+
/**
|
|
23
|
+
Points passed to start/update are 0-based SCREEN cells on the Ink output
|
|
24
|
+
grid; the controller converts to content space via the viewport provider.
|
|
25
|
+
*/
|
|
26
|
+
start: (screenPoint: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
update: (screenPoint: {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}) => void;
|
|
34
|
+
finish: () => void;
|
|
35
|
+
clear: () => void;
|
|
36
|
+
/**
|
|
37
|
+
Registered by the mouse bridge. Returning null means "no scroll container":
|
|
38
|
+
the whole output grid is the viewport with scrollY = 0.
|
|
39
|
+
*/
|
|
40
|
+
setViewportProvider: (provider: (() => SelectionViewport | null) | null) => void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
Selection actions plus an imperative snapshot read. Unlike the snapshot from
|
|
44
|
+
`useTextSelection()`, holding this handle does NOT subscribe the component to
|
|
45
|
+
selection changes — use it where selection state is only needed at event time
|
|
46
|
+
(mouse bridges, key handlers) so drags never re-render the host component.
|
|
47
|
+
*/
|
|
48
|
+
export type TextSelectionHandle = TextSelectionActions & {
|
|
49
|
+
getSnapshot: () => TextSelectionSnapshot;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
Framework-agnostic selection state owned by an Ink instance. Selection points
|
|
53
|
+
live in content space (container-relative rows that survive scrolling); the
|
|
54
|
+
viewport provider converts screen ↔ content at event time and paint time.
|
|
55
|
+
*/
|
|
56
|
+
export declare class TextSelectionController {
|
|
57
|
+
/** Wired by Ink to schedule a repaint when the highlight must change. */
|
|
58
|
+
onInvalidate?: () => void;
|
|
59
|
+
private anchor;
|
|
60
|
+
private focus;
|
|
61
|
+
private isDragging;
|
|
62
|
+
private text;
|
|
63
|
+
private readonly rowCache;
|
|
64
|
+
private viewportProvider;
|
|
65
|
+
private lastViewport;
|
|
66
|
+
private readonly listeners;
|
|
67
|
+
private snapshot;
|
|
68
|
+
private notifyScheduled;
|
|
69
|
+
subscribe: (listener: () => void) => (() => void);
|
|
70
|
+
getSnapshot: () => TextSelectionSnapshot;
|
|
71
|
+
setViewportProvider: (provider: (() => SelectionViewport | null) | null) => void;
|
|
72
|
+
start: (screenPoint: {
|
|
73
|
+
x: number;
|
|
74
|
+
y: number;
|
|
75
|
+
}) => void;
|
|
76
|
+
update: (screenPoint: {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
}) => void;
|
|
80
|
+
finish: () => void;
|
|
81
|
+
clear: () => void;
|
|
82
|
+
/**
|
|
83
|
+
Called from the renderer's paint hook on every render of the main output.
|
|
84
|
+
Caches visible rows under content-row keys. Never invalidates synchronously
|
|
85
|
+
— it runs during a render pass and must not schedule another render.
|
|
86
|
+
|
|
87
|
+
While dragging, cache entries update freely (auto-scroll depends on it).
|
|
88
|
+
After `finish()`, the cache is frozen: existing entries are never
|
|
89
|
+
overwritten, so extracted text is immutable. If a visible row inside the
|
|
90
|
+
selected range no longer matches its frozen entry (content reflowed
|
|
91
|
+
underneath the selection), the selection auto-clears.
|
|
92
|
+
*/
|
|
93
|
+
captureRows: (plainRows: readonly string[], maskRows?: ReadonlyArray<readonly boolean[]>) => void;
|
|
94
|
+
/**
|
|
95
|
+
Convert the selection's content spans to screen spans using the current
|
|
96
|
+
viewport. Rows outside the viewport or the grid are dropped (they are
|
|
97
|
+
still part of the extracted text).
|
|
98
|
+
*/
|
|
99
|
+
getPaintSpans: (outputWidth: number, outputHeight: number) => TextSelectionSpan[];
|
|
100
|
+
private resolveViewport;
|
|
101
|
+
private toContentPoint;
|
|
102
|
+
/** Returns true when the extracted text changed. */
|
|
103
|
+
private recomputeText;
|
|
104
|
+
private emit;
|
|
105
|
+
private scheduleNotify;
|
|
106
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-restricted-types --
|
|
2
|
+
The public selection API uses null (matching React ref idioms and the
|
|
3
|
+
mouse bridge in ink-input); keep it consistent throughout this file. */
|
|
4
|
+
import stringWidth from 'string-width';
|
|
5
|
+
import { extractTextFromRows, getSelectionSpans, } from './text-selection.js';
|
|
6
|
+
const fullGridViewport = {
|
|
7
|
+
top: 0,
|
|
8
|
+
left: 0,
|
|
9
|
+
width: Number.POSITIVE_INFINITY,
|
|
10
|
+
height: Number.POSITIVE_INFINITY,
|
|
11
|
+
scrollY: 0,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
Framework-agnostic selection state owned by an Ink instance. Selection points
|
|
15
|
+
live in content space (container-relative rows that survive scrolling); the
|
|
16
|
+
viewport provider converts screen ↔ content at event time and paint time.
|
|
17
|
+
*/
|
|
18
|
+
export class TextSelectionController {
|
|
19
|
+
/** Wired by Ink to schedule a repaint when the highlight must change. */
|
|
20
|
+
onInvalidate;
|
|
21
|
+
anchor = null;
|
|
22
|
+
focus = null;
|
|
23
|
+
isDragging = false;
|
|
24
|
+
text = '';
|
|
25
|
+
rowCache = new Map();
|
|
26
|
+
viewportProvider = null;
|
|
27
|
+
// Last viewport a registered provider returned. Reused for one-frame gaps
|
|
28
|
+
// where the provider returns null (e.g. bounds momentarily 0 mid-re-render),
|
|
29
|
+
// so the highlight doesn't flash to full-grid coordinates and the frozen
|
|
30
|
+
// row cache isn't compared against a differently-sliced viewport.
|
|
31
|
+
lastViewport = null;
|
|
32
|
+
listeners = new Set();
|
|
33
|
+
snapshot;
|
|
34
|
+
notifyScheduled = false;
|
|
35
|
+
subscribe = (listener) => {
|
|
36
|
+
this.listeners.add(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
this.listeners.delete(listener);
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
getSnapshot = () => {
|
|
42
|
+
this.snapshot ??= {
|
|
43
|
+
anchor: this.anchor,
|
|
44
|
+
focus: this.focus,
|
|
45
|
+
isDragging: this.isDragging,
|
|
46
|
+
text: this.text,
|
|
47
|
+
isEmpty: this.text.length === 0,
|
|
48
|
+
};
|
|
49
|
+
return this.snapshot;
|
|
50
|
+
};
|
|
51
|
+
setViewportProvider = (provider) => {
|
|
52
|
+
this.viewportProvider = provider;
|
|
53
|
+
};
|
|
54
|
+
start = (screenPoint) => {
|
|
55
|
+
const point = this.toContentPoint(screenPoint);
|
|
56
|
+
this.anchor = point;
|
|
57
|
+
this.focus = point;
|
|
58
|
+
this.isDragging = true;
|
|
59
|
+
this.recomputeText();
|
|
60
|
+
this.emit();
|
|
61
|
+
this.onInvalidate?.();
|
|
62
|
+
};
|
|
63
|
+
update = (screenPoint) => {
|
|
64
|
+
if (!this.anchor) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const point = this.toContentPoint(screenPoint);
|
|
68
|
+
const focusChanged = this.focus?.x !== point.x || this.focus.y !== point.y;
|
|
69
|
+
this.focus = point;
|
|
70
|
+
const textChanged = this.recomputeText();
|
|
71
|
+
if (focusChanged || textChanged) {
|
|
72
|
+
this.emit();
|
|
73
|
+
this.onInvalidate?.();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
finish = () => {
|
|
77
|
+
if (!this.isDragging) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.isDragging = false;
|
|
81
|
+
this.emit();
|
|
82
|
+
};
|
|
83
|
+
clear = () => {
|
|
84
|
+
const hadSelection = this.anchor !== null || this.text.length > 0 || this.isDragging;
|
|
85
|
+
this.anchor = null;
|
|
86
|
+
this.focus = null;
|
|
87
|
+
this.isDragging = false;
|
|
88
|
+
this.text = '';
|
|
89
|
+
this.rowCache.clear();
|
|
90
|
+
if (hadSelection) {
|
|
91
|
+
this.emit();
|
|
92
|
+
this.onInvalidate?.();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
Called from the renderer's paint hook on every render of the main output.
|
|
97
|
+
Caches visible rows under content-row keys. Never invalidates synchronously
|
|
98
|
+
— it runs during a render pass and must not schedule another render.
|
|
99
|
+
|
|
100
|
+
While dragging, cache entries update freely (auto-scroll depends on it).
|
|
101
|
+
After `finish()`, the cache is frozen: existing entries are never
|
|
102
|
+
overwritten, so extracted text is immutable. If a visible row inside the
|
|
103
|
+
selected range no longer matches its frozen entry (content reflowed
|
|
104
|
+
underneath the selection), the selection auto-clears.
|
|
105
|
+
*/
|
|
106
|
+
captureRows = (plainRows, maskRows) => {
|
|
107
|
+
const resolved = this.resolveViewport();
|
|
108
|
+
// A registered provider that returns null means the container's bounds are
|
|
109
|
+
// not measurable this frame (transient during a re-render). Capturing or
|
|
110
|
+
// comparing against the full-grid fallback would corrupt the cache and
|
|
111
|
+
// spuriously auto-clear a finished selection. Skip the frame instead.
|
|
112
|
+
if (!resolved.available && this.anchor !== null) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { viewport } = resolved;
|
|
116
|
+
const top = Math.max(0, viewport.top);
|
|
117
|
+
const left = Math.max(0, viewport.left);
|
|
118
|
+
const bottom = Math.min(plainRows.length, viewport.top + viewport.height);
|
|
119
|
+
const hasSelection = this.anchor !== null && this.focus !== null;
|
|
120
|
+
const frozen = hasSelection && !this.isDragging;
|
|
121
|
+
// Without a selection there is nothing to preserve: reset the cache to
|
|
122
|
+
// the currently visible rows so a subsequent start() has fresh data and
|
|
123
|
+
// stale rows from earlier scroll positions don't accumulate.
|
|
124
|
+
if (!hasSelection) {
|
|
125
|
+
this.rowCache.clear();
|
|
126
|
+
}
|
|
127
|
+
let mismatch = false;
|
|
128
|
+
const range = hasSelection
|
|
129
|
+
? (() => {
|
|
130
|
+
const [a, b] = [this.anchor, this.focus];
|
|
131
|
+
return a.y <= b.y ? { min: a.y, max: b.y } : { min: b.y, max: a.y };
|
|
132
|
+
})()
|
|
133
|
+
: undefined;
|
|
134
|
+
for (let y = top; y < bottom; y++) {
|
|
135
|
+
const contentY = viewport.scrollY + (y - viewport.top);
|
|
136
|
+
const text = sliceRow(plainRows[y] ?? '', left, viewport.width);
|
|
137
|
+
const mask = maskRows
|
|
138
|
+
? sliceMask(maskRows[y], left, viewport.width)
|
|
139
|
+
: undefined;
|
|
140
|
+
const row = { text, mask };
|
|
141
|
+
const existing = this.rowCache.get(contentY);
|
|
142
|
+
if (frozen && existing) {
|
|
143
|
+
if (range &&
|
|
144
|
+
contentY >= range.min &&
|
|
145
|
+
contentY <= range.max &&
|
|
146
|
+
existing.text !== text) {
|
|
147
|
+
mismatch = true;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
this.rowCache.set(contentY, row);
|
|
153
|
+
}
|
|
154
|
+
if (mismatch) {
|
|
155
|
+
// Content reflowed underneath a finished selection: clearing is the
|
|
156
|
+
// only honest option. Defer the notification outside the render pass.
|
|
157
|
+
this.anchor = null;
|
|
158
|
+
this.focus = null;
|
|
159
|
+
this.isDragging = false;
|
|
160
|
+
this.text = '';
|
|
161
|
+
this.rowCache.clear();
|
|
162
|
+
this.snapshot = undefined;
|
|
163
|
+
this.scheduleNotify(true);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (hasSelection && !frozen && this.recomputeText()) {
|
|
167
|
+
this.scheduleNotify(false);
|
|
168
|
+
}
|
|
169
|
+
else if (hasSelection &&
|
|
170
|
+
frozen &&
|
|
171
|
+
this.text.length === 0 && // Rows may have arrived only after the gesture ended.
|
|
172
|
+
this.recomputeText()) {
|
|
173
|
+
this.scheduleNotify(false);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
Convert the selection's content spans to screen spans using the current
|
|
178
|
+
viewport. Rows outside the viewport or the grid are dropped (they are
|
|
179
|
+
still part of the extracted text).
|
|
180
|
+
*/
|
|
181
|
+
getPaintSpans = (outputWidth, outputHeight) => {
|
|
182
|
+
if (!this.anchor || !this.focus) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const { viewport } = this.resolveViewport();
|
|
186
|
+
const spans = getSelectionSpans(this.anchor, this.focus, y => this.rowCache.get(y));
|
|
187
|
+
const left = Math.max(0, viewport.left);
|
|
188
|
+
const right = Math.min(outputWidth, viewport.left + viewport.width);
|
|
189
|
+
const screenTop = Math.max(0, viewport.top);
|
|
190
|
+
const screenBottom = Math.min(outputHeight, viewport.top + viewport.height);
|
|
191
|
+
const result = [];
|
|
192
|
+
for (const span of spans) {
|
|
193
|
+
const screenY = viewport.top + (span.y - viewport.scrollY);
|
|
194
|
+
if (screenY < screenTop || screenY >= screenBottom) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const x1 = Math.max(left, viewport.left + span.x1);
|
|
198
|
+
const x2 = Math.min(right, viewport.left + span.x2);
|
|
199
|
+
if (x2 <= x1) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
result.push({ y: screenY, x1, x2 });
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
};
|
|
206
|
+
resolveViewport() {
|
|
207
|
+
if (!this.viewportProvider) {
|
|
208
|
+
// No scroll container: the whole grid is the viewport.
|
|
209
|
+
return { viewport: fullGridViewport, available: true };
|
|
210
|
+
}
|
|
211
|
+
const current = this.viewportProvider();
|
|
212
|
+
if (current) {
|
|
213
|
+
this.lastViewport = current;
|
|
214
|
+
return { viewport: current, available: true };
|
|
215
|
+
}
|
|
216
|
+
// Provider registered but bounds unavailable this frame. Reuse the last
|
|
217
|
+
// known viewport so the highlight stays put; report unavailable so
|
|
218
|
+
// captureRows skips the frame.
|
|
219
|
+
return {
|
|
220
|
+
viewport: this.lastViewport ?? fullGridViewport,
|
|
221
|
+
available: false,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
toContentPoint(screenPoint) {
|
|
225
|
+
const { viewport } = this.resolveViewport();
|
|
226
|
+
const maxX = viewport.width === Number.POSITIVE_INFINITY
|
|
227
|
+
? Number.POSITIVE_INFINITY
|
|
228
|
+
: viewport.left + viewport.width - 1;
|
|
229
|
+
const maxY = viewport.height === Number.POSITIVE_INFINITY
|
|
230
|
+
? Number.POSITIVE_INFINITY
|
|
231
|
+
: viewport.top + viewport.height - 1;
|
|
232
|
+
const x = Math.min(Math.max(screenPoint.x, viewport.left), maxX);
|
|
233
|
+
const y = Math.min(Math.max(screenPoint.y, viewport.top), maxY);
|
|
234
|
+
return {
|
|
235
|
+
x: x - viewport.left,
|
|
236
|
+
y: y - viewport.top + viewport.scrollY,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** Returns true when the extracted text changed. */
|
|
240
|
+
recomputeText() {
|
|
241
|
+
const next = this.anchor && this.focus
|
|
242
|
+
? extractTextFromRows(this.anchor, this.focus, y => this.rowCache.get(y))
|
|
243
|
+
: '';
|
|
244
|
+
if (next === this.text) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
this.text = next;
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
emit() {
|
|
251
|
+
this.snapshot = undefined;
|
|
252
|
+
for (const listener of this.listeners) {
|
|
253
|
+
listener();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
scheduleNotify(invalidate) {
|
|
257
|
+
this.snapshot = undefined;
|
|
258
|
+
if (this.notifyScheduled) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.notifyScheduled = true;
|
|
262
|
+
queueMicrotask(() => {
|
|
263
|
+
this.notifyScheduled = false;
|
|
264
|
+
for (const listener of this.listeners) {
|
|
265
|
+
listener();
|
|
266
|
+
}
|
|
267
|
+
if (invalidate) {
|
|
268
|
+
this.onInvalidate?.();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Slice a row by display cells, not string indices, so wide characters keep
|
|
274
|
+
// the text aligned with the cell-indexed mask. A wide glyph straddling the
|
|
275
|
+
// viewport edge is replaced by spaces for the cells that remain visible.
|
|
276
|
+
const sliceRow = (row, left, width) => {
|
|
277
|
+
if (left === 0 && width === Number.POSITIVE_INFINITY) {
|
|
278
|
+
return row.trimEnd();
|
|
279
|
+
}
|
|
280
|
+
const right = width === Number.POSITIVE_INFINITY
|
|
281
|
+
? Number.POSITIVE_INFINITY
|
|
282
|
+
: left + width;
|
|
283
|
+
let cell = 0;
|
|
284
|
+
let result = '';
|
|
285
|
+
for (const char of row) {
|
|
286
|
+
const charWidth = Math.max(1, stringWidth(char));
|
|
287
|
+
const start = cell;
|
|
288
|
+
const end = cell + charWidth;
|
|
289
|
+
cell = end;
|
|
290
|
+
if (end <= left) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (start >= right) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
result +=
|
|
297
|
+
start >= left && end <= right
|
|
298
|
+
? char
|
|
299
|
+
: ' '.repeat(Math.min(end, right) - Math.max(start, left));
|
|
300
|
+
}
|
|
301
|
+
return result.trimEnd();
|
|
302
|
+
};
|
|
303
|
+
const sliceMask = (mask, left, width) => {
|
|
304
|
+
if (!mask) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
return width === Number.POSITIVE_INFINITY
|
|
308
|
+
? mask.slice(left)
|
|
309
|
+
: mask.slice(left, left + width);
|
|
310
|
+
};
|
|
311
|
+
//# sourceMappingURL=text-selection-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-selection-controller.js","sourceRoot":"","sources":["../src/text-selection-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
A point in container content space: `x` is cells from the container's content
|
|
3
|
+
left edge, `y` is the content row index (survives scrolling).
|
|
4
|
+
*/
|
|
5
|
+
export type TextSelectionPoint = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
};
|
|
9
|
+
export type TextSelectionSpan = {
|
|
10
|
+
y: number;
|
|
11
|
+
x1: number;
|
|
12
|
+
x2: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
A single cached row of rendered content. `mask` flags which display cells are
|
|
16
|
+
selectable (written by `<Text>` and not opted out); a missing mask means every
|
|
17
|
+
cell is selectable.
|
|
18
|
+
*/
|
|
19
|
+
export type SelectionRow = {
|
|
20
|
+
text: string;
|
|
21
|
+
mask?: readonly boolean[];
|
|
22
|
+
};
|
|
23
|
+
export type RowLookup = (y: number) => SelectionRow | undefined;
|
|
24
|
+
export declare function normalizeSelection(anchor: TextSelectionPoint, focus: TextSelectionPoint): {
|
|
25
|
+
start: TextSelectionPoint;
|
|
26
|
+
end: TextSelectionPoint;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
One past the last printable selectable cell of the row. Trailing whitespace
|
|
30
|
+
and trailing chrome cells are excluded.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getRowEnd(row: SelectionRow): number;
|
|
33
|
+
export declare function getSelectionSpans(anchor: TextSelectionPoint, focus: TextSelectionPoint, getRow: RowLookup): TextSelectionSpan[];
|
|
34
|
+
/**
|
|
35
|
+
Extract the selectable text of `row` between display cells `[from, to)`.
|
|
36
|
+
Non-selectable runs at the edges are dropped; interior non-selectable runs
|
|
37
|
+
collapse to a single space. Wide glyphs are included when the range overlaps
|
|
38
|
+
either of their cells.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sliceSelectableCells(row: SelectionRow, from: number, to: number): string;
|
|
41
|
+
export declare function extractTextFromRows(anchor: TextSelectionPoint, focus: TextSelectionPoint, getRow: RowLookup): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import stringWidth from 'string-width';
|
|
2
|
+
export function normalizeSelection(anchor, focus) {
|
|
3
|
+
if (anchor.y < focus.y || (anchor.y === focus.y && anchor.x <= focus.x)) {
|
|
4
|
+
return { start: anchor, end: focus };
|
|
5
|
+
}
|
|
6
|
+
return { start: focus, end: anchor };
|
|
7
|
+
}
|
|
8
|
+
const isCharSelectable = (mask, charStart, charEnd) => {
|
|
9
|
+
if (!mask) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
for (let cell = charStart; cell < charEnd; cell++) {
|
|
13
|
+
if (mask[cell] === false) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
One past the last printable selectable cell of the row. Trailing whitespace
|
|
21
|
+
and trailing chrome cells are excluded.
|
|
22
|
+
*/
|
|
23
|
+
export function getRowEnd(row) {
|
|
24
|
+
const { text, mask } = row;
|
|
25
|
+
let cell = 0;
|
|
26
|
+
let end = 0;
|
|
27
|
+
for (const char of text) {
|
|
28
|
+
const width = Math.max(1, stringWidth(char));
|
|
29
|
+
const charStart = cell;
|
|
30
|
+
const charEnd = cell + width;
|
|
31
|
+
cell = charEnd;
|
|
32
|
+
if (/^\s*$/.test(char)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (isCharSelectable(mask, charStart, charEnd)) {
|
|
36
|
+
end = charEnd;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return end;
|
|
40
|
+
}
|
|
41
|
+
export function getSelectionSpans(anchor, focus, getRow) {
|
|
42
|
+
const { start, end } = normalizeSelection(anchor, focus);
|
|
43
|
+
if (start.x === end.x && start.y === end.y) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const spans = [];
|
|
47
|
+
for (let { y } = start; y <= end.y; y++) {
|
|
48
|
+
const rowEnd = getRowEnd(getRow(y) ?? { text: '' });
|
|
49
|
+
let x1 = y === start.y ? start.x : 0;
|
|
50
|
+
let x2 = y === end.y ? end.x : rowEnd;
|
|
51
|
+
x1 = Math.max(0, Math.min(x1, rowEnd));
|
|
52
|
+
x2 = Math.max(0, Math.min(x2, rowEnd));
|
|
53
|
+
// Emit empty spans (x1 === x2) so blank lines survive extraction.
|
|
54
|
+
spans.push({ y, x1, x2 });
|
|
55
|
+
}
|
|
56
|
+
return spans;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
Extract the selectable text of `row` between display cells `[from, to)`.
|
|
60
|
+
Non-selectable runs at the edges are dropped; interior non-selectable runs
|
|
61
|
+
collapse to a single space. Wide glyphs are included when the range overlaps
|
|
62
|
+
either of their cells.
|
|
63
|
+
*/
|
|
64
|
+
export function sliceSelectableCells(row, from, to) {
|
|
65
|
+
if (to <= from) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
const { text, mask } = row;
|
|
69
|
+
let cell = 0;
|
|
70
|
+
let result = '';
|
|
71
|
+
let pendingGap = false;
|
|
72
|
+
for (const char of text) {
|
|
73
|
+
const width = Math.max(1, stringWidth(char));
|
|
74
|
+
const charStart = cell;
|
|
75
|
+
const charEnd = cell + width;
|
|
76
|
+
cell = charEnd;
|
|
77
|
+
if (charEnd <= from) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (charStart >= to) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
if (isCharSelectable(mask, charStart, charEnd)) {
|
|
84
|
+
if (pendingGap && result.length > 0) {
|
|
85
|
+
result += ' ';
|
|
86
|
+
}
|
|
87
|
+
pendingGap = false;
|
|
88
|
+
result += char;
|
|
89
|
+
}
|
|
90
|
+
else if (result.length > 0) {
|
|
91
|
+
pendingGap = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
export function extractTextFromRows(anchor, focus, getRow) {
|
|
97
|
+
return getSelectionSpans(anchor, focus, getRow)
|
|
98
|
+
.map(span => sliceSelectableCells(getRow(span.y) ?? { text: '' }, span.x1, span.x2))
|
|
99
|
+
.join('\n');
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=text-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-selection.js","sourceRoot":"","sources":["../src/text-selection.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AAmBvC,MAAM,UAAU,kBAAkB,CACjC,MAA0B,EAC1B,KAAyB;IAEzB,IAAI,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,OAAO,EAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAC,CAAC;IACpC,CAAC;IAED,OAAO,EAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAC,CAAC;AACpC,CAAC;AAED,MAAM,gBAAgB,GAAG,CACxB,IAAoC,EACpC,SAAiB,EACjB,OAAe,EACL,EAAE;IACZ,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,IAAI,IAAI,GAAG,SAAS,EAAE,IAAI,GAAG,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QACnD,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF;;;EAGE;AACF,MAAM,UAAU,SAAS,CAAC,GAAiB;IAC1C,MAAM,EAAC,IAAI,EAAE,IAAI,EAAC,GAAG,GAAG,CAAC;IACzB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,GAAG,GAAG,CAAC,CAAC;IAEZ,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,IAAI,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC;QAC7B,IAAI,GAAG,OAAO,CAAC;QAEf,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS;QACV,CAAC;QAED,IAAI,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAChD,GAAG,GAAG,OAAO,CAAC;QACf,CAAC;IACF,CAAC;IAED,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAChC,MAA0B,EAC1B,KAAyB,EACzB,MAAiB;IAEjB,MAAM,EAAC,KAAK,EAAE,GAAG,EAAC,GAAG,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5C,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,KAAK,IAAI,EAAC,CAAC,EAAC,GAAG,KAAK,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAC,IAAI,EAAE,EAAE,EAAC,CAAC,CAAC;QAClD,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAEtC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QACvC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAEvC,kEAAkE;QAClE,KAAK,CAAC,IAAI,CAAC,EAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAC,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;EAKE;AACF,MAAM,UAAU,oBAAoB,CACnC,GAAiB,EACjB,IAAY,EACZ,EAAU;IAEV,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,EAAC,IAAI,EAAE,IAAI,EAAC,GAAG,GAAG,CAAC;IACzB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,IAAI,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC;QAC7B,IAAI,GAAG,OAAO,CAAC;QAEf,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACrB,SAAS;QACV,CAAC;QAED,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;YACrB,MAAM;QACP,CAAC;QAED,IAAI,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,CAAC;YACf,CAAC;YAED,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,IAAI,IAAI,CAAC;QAChB,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,UAAU,GAAG,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB,CAClC,MAA0B,EAC1B,KAAyB,EACzB,MAAiB;IAEjB,OAAO,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;SAC7C,GAAG,CAAC,IAAI,CAAC,EAAE,CACX,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAC,IAAI,EAAE,EAAE,EAAC,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CACpE;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -2527,6 +2527,52 @@ Type: `() => void`
|
|
|
2527
2527
|
|
|
2528
2528
|
Resets `frame`, `time`, and `delta` to `0` and restarts timing from the current moment. Useful for one-shot animations triggered by events.
|
|
2529
2529
|
|
|
2530
|
+
### useTextSelection()
|
|
2531
|
+
|
|
2532
|
+
A React hook exposing the instance-owned text selection: the currently selected terminal text plus actions to drive a selection programmatically. Ink paints the highlight as a final overlay on the rendered output and recomputes `text` as rows render.
|
|
2533
|
+
|
|
2534
|
+
Selection is **Text-only**: only cells written by `<Text>` components are highlighted and extracted. Box borders, backgrounds, and padding/margin gaps are never part of a selection. Mark chrome text (icons, line-number gutters, diff markers) with `<Text selectable={false}>` to exclude it too — extraction drops chrome at the edges of a line and collapses interior chrome gaps to a single space.
|
|
2535
|
+
|
|
2536
|
+
```jsx
|
|
2537
|
+
import {Text, useTextSelection} from 'ink';
|
|
2538
|
+
|
|
2539
|
+
const SelectionFooter = () => {
|
|
2540
|
+
const selection = useTextSelection();
|
|
2541
|
+
|
|
2542
|
+
return <Text dimColor>{selection.isEmpty ? 'no selection' : selection.text}</Text>;
|
|
2543
|
+
};
|
|
2544
|
+
```
|
|
2545
|
+
|
|
2546
|
+
This hook is mouse-agnostic; mouse-driven selection (click-and-drag, edge auto-scroll) is provided by `useMouseTextSelection()` in `@nuvin/ink-input`, which drives these same actions.
|
|
2547
|
+
|
|
2548
|
+
When a component only needs to *drive* the selection (mouse bridges, key handlers that clear it), use `useTextSelectionActions()` instead: it returns the same actions plus an imperative `getSnapshot()` but never subscribes, so the host component does not re-render on selection changes. Reserve `useTextSelection()` for components that display selection state.
|
|
2549
|
+
|
|
2550
|
+
#### Return value
|
|
2551
|
+
|
|
2552
|
+
- `anchor` / `focus` — `{x, y} | null` in **content space**: `x` is cells from the selection container's left edge, `y` is the content row index (survives scrolling).
|
|
2553
|
+
- `isDragging` — `boolean`, true between `start()` and `finish()`.
|
|
2554
|
+
- `text` — `string`, the selected text. Blank lines inside the selection are preserved; trailing whitespace is not selectable.
|
|
2555
|
+
- `isEmpty` — `boolean`, true when no text is selected.
|
|
2556
|
+
- `start(screenPoint)` / `update(screenPoint)` — begin/extend a selection. Points are 0-based **screen cells** on the Ink output grid; the registered viewport provider converts them to content space.
|
|
2557
|
+
- `finish()` — end the drag; the selected text stays available (and is frozen — if content reflows underneath a finished selection, it auto-clears rather than silently changing).
|
|
2558
|
+
- `clear()` — clear the selection and its row cache.
|
|
2559
|
+
- `setViewportProvider(fn)` — register a function returning `{top, left, width, height, scrollY}` for the selection container (in output grid cells, `scrollY` in rendered rows). Return `null`, or pass `null`, to treat the whole output grid as the viewport.
|
|
2560
|
+
|
|
2561
|
+
#### `<Text selectable>`
|
|
2562
|
+
|
|
2563
|
+
Type: `boolean`\
|
|
2564
|
+
Default: `true`
|
|
2565
|
+
|
|
2566
|
+
When `false`, the text is never highlighted and never appears in extracted selection text. Note that nested `<Text>` squashes into its outermost `<Text>` ancestor's write, so `selectable` must be set on the outermost text element (use sibling `<Text>` elements in a `<Box>` to mix chrome and content on one line).
|
|
2567
|
+
|
|
2568
|
+
#### Known limits
|
|
2569
|
+
|
|
2570
|
+
- Mouse-to-grid mapping (in `useMouseTextSelection`) assumes the Ink output starts at terminal cell (1,1) — i.e. fullscreen/alternate-screen apps.
|
|
2571
|
+
- The selection container must not be nested inside another scroll container (`getBounds()` ignores ancestor scroll offsets).
|
|
2572
|
+
- A Text node's own padding cells count as selectable.
|
|
2573
|
+
- Horizontal scrolling containers are not supported.
|
|
2574
|
+
- Clipboard integration is app-owned: read `useTextSelection().text` and apply your own copy policy.
|
|
2575
|
+
|
|
2530
2576
|
## API
|
|
2531
2577
|
|
|
2532
2578
|
#### render(tree, options?)
|