@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.2
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/README.md +299 -299
- package/dist/ansi.d.ts +223 -0
- package/dist/ansi.js +2 -0
- package/dist/{devtools-QpCMm9JH.mjs → devtools-BhYGjb7h.js} +1 -1
- package/dist/index-DDVME65c.d.ts +919 -0
- package/dist/index.d.ts +1657 -0
- package/dist/index.js +4643 -0
- package/dist/sgr-CMfEpjSk.d.ts +91 -0
- package/dist/truncate-CBiyyZzw.js +2156 -0
- package/dist/yoga-5jKhYCJC.js +3465 -0
- package/dist/yoga.d.ts +2 -0
- package/dist/yoga.js +2 -0
- package/package.json +37 -17
- package/src/ansi/chalk.ts +179 -0
- package/src/ansi/cursor.ts +48 -0
- package/src/ansi/east-asian-width.ts +215 -0
- package/src/ansi/escapes.ts +128 -0
- package/src/ansi/index.ts +27 -0
- package/src/ansi/sgr.ts +237 -0
- package/src/ansi/slice.ts +43 -0
- package/src/ansi/string-width.ts +236 -0
- package/src/ansi/strip.ts +33 -0
- package/src/ansi/supports-color.ts +213 -0
- package/src/ansi/tokenize.ts +453 -0
- package/src/ansi/truncate.ts +194 -0
- package/src/ansi/widest-line.ts +12 -0
- package/src/ansi/wrap.ts +766 -0
- package/src/ansi-tokenizer.ts +510 -0
- package/src/auto-bind.ts +41 -0
- package/src/boxes.ts +100 -0
- package/src/code-excerpt.ts +39 -0
- package/src/colorize.ts +60 -0
- package/src/components/AccessibilityContext.ts +5 -0
- package/src/components/AnimationContext.ts +24 -0
- package/src/components/App.tsx +781 -0
- package/src/components/AppContext.ts +111 -0
- package/src/components/BackgroundContext.ts +8 -0
- package/src/components/Box.tsx +116 -0
- package/src/components/CursorContext.ts +19 -0
- package/src/components/ErrorBoundary.tsx +38 -0
- package/src/components/ErrorOverview.tsx +133 -0
- package/src/components/FocusContext.ts +30 -0
- package/src/components/Newline.tsx +15 -0
- package/src/components/Spacer.tsx +10 -0
- package/src/components/Static.tsx +59 -0
- package/src/components/StderrContext.ts +26 -0
- package/src/components/StdinContext.ts +49 -0
- package/src/components/StdoutContext.ts +28 -0
- package/src/components/Text.tsx +144 -0
- package/src/components/Transform.tsx +37 -0
- package/src/cursor-position.ts +103 -0
- package/src/devtools-window-polyfill.ts +73 -0
- package/src/devtools.ts +43 -0
- package/src/dom.ts +292 -0
- package/src/get-max-width.ts +11 -0
- package/src/global.d.ts +36 -0
- package/src/hooks/use-animation.ts +142 -0
- package/src/hooks/use-app.ts +8 -0
- package/src/hooks/use-box-metrics.ts +134 -0
- package/src/hooks/use-cursor.ts +33 -0
- package/src/hooks/use-focus-manager.ts +62 -0
- package/src/hooks/use-focus.ts +83 -0
- package/src/hooks/use-input.ts +267 -0
- package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
- package/src/hooks/use-paste.ts +78 -0
- package/src/hooks/use-stderr.ts +8 -0
- package/src/hooks/use-stdin.ts +10 -0
- package/src/hooks/use-stdout.ts +8 -0
- package/src/hooks/use-window-size.ts +41 -0
- package/src/indent-string.ts +16 -0
- package/src/index.ts +44 -0
- package/src/ink.tsx +1506 -0
- package/src/input-parser.ts +283 -0
- package/src/instances.ts +9 -0
- package/src/is-in-ci.ts +7 -0
- package/src/kitty-keyboard.ts +57 -0
- package/src/log-update.ts +370 -0
- package/src/measure-element.ts +62 -0
- package/src/measure-text.ts +31 -0
- package/src/output.ts +308 -0
- package/src/parse-keypress.ts +516 -0
- package/src/parse-stack-line.ts +139 -0
- package/src/patch-console.ts +62 -0
- package/src/quick-lru.ts +85 -0
- package/src/reconciler.ts +451 -0
- package/src/render-background.ts +38 -0
- package/src/render-border.ts +134 -0
- package/src/render-node-to-output.ts +191 -0
- package/src/render-to-string.ts +131 -0
- package/src/render.ts +276 -0
- package/src/renderer.ts +73 -0
- package/src/sanitize-ansi.ts +33 -0
- package/src/signal-exit.ts +107 -0
- package/src/squash-text-nodes.ts +40 -0
- package/src/stream.ts +30 -0
- package/src/styles.ts +748 -0
- package/src/terminal-size.ts +57 -0
- package/src/throttle.ts +73 -0
- package/src/types.ts +15 -0
- package/src/utils.ts +40 -0
- package/src/wrap-text.ts +50 -0
- package/src/write-synchronized.ts +9 -0
- package/src/yoga/config.ts +57 -0
- package/src/yoga/core/absoluteLayout.ts +626 -0
- package/src/yoga/core/baseline.ts +66 -0
- package/src/yoga/core/cache.ts +136 -0
- package/src/yoga/core/calculateLayout.ts +2920 -0
- package/src/yoga/core/config.ts +104 -0
- package/src/yoga/core/flexLine.ts +177 -0
- package/src/yoga/core/helpers.ts +293 -0
- package/src/yoga/core/layoutResults.ts +167 -0
- package/src/yoga/core/node.ts +611 -0
- package/src/yoga/core/numeric.ts +44 -0
- package/src/yoga/core/pixelGrid.ts +151 -0
- package/src/yoga/core/style.ts +887 -0
- package/src/yoga/core/types.ts +224 -0
- package/src/yoga/generated/YGEnums.ts +263 -0
- package/src/yoga/index.ts +19 -0
- package/src/yoga/node.ts +1140 -0
- package/dist/index.d.mts +0 -2379
- package/dist/index.mjs +0 -10072
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { cliCursor } from "./ansi/cursor.ts";
|
|
2
|
+
import { ansiEscapes } from "./ansi/escapes.ts";
|
|
3
|
+
import {
|
|
4
|
+
type CursorPosition,
|
|
5
|
+
cursorPositionChanged,
|
|
6
|
+
buildCursorSuffix,
|
|
7
|
+
buildCursorOnlySequence,
|
|
8
|
+
buildReturnToBottomPrefix,
|
|
9
|
+
hideCursorEscape,
|
|
10
|
+
} from "./cursor-position.ts";
|
|
11
|
+
|
|
12
|
+
export type { CursorPosition } from "./cursor-position.ts";
|
|
13
|
+
|
|
14
|
+
export type LogUpdate = {
|
|
15
|
+
clear: () => void;
|
|
16
|
+
done: () => void;
|
|
17
|
+
reset: () => void;
|
|
18
|
+
sync: (str: string) => void;
|
|
19
|
+
setCursorPosition: (position: CursorPosition | undefined) => void;
|
|
20
|
+
isCursorDirty: () => boolean;
|
|
21
|
+
willRender: (str: string) => boolean;
|
|
22
|
+
(str: string): boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Count visible lines in a string, ignoring the trailing empty element
|
|
26
|
+
// that `split('\n')` produces when the string ends with '\n'.
|
|
27
|
+
const visibleLineCount = (lines: string[], str: string): number =>
|
|
28
|
+
str.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
29
|
+
|
|
30
|
+
const createStandard = (stream: NodeJS.WritableStream, { showCursor = false } = {}): LogUpdate => {
|
|
31
|
+
let previousLineCount = 0;
|
|
32
|
+
let previousOutput = "";
|
|
33
|
+
let hasHiddenCursor = false;
|
|
34
|
+
let cursorPosition: CursorPosition | undefined;
|
|
35
|
+
let cursorDirty = false;
|
|
36
|
+
let previousCursorPosition: CursorPosition | undefined;
|
|
37
|
+
let cursorWasShown = false;
|
|
38
|
+
|
|
39
|
+
const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
|
|
40
|
+
const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
|
|
41
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
42
|
+
return str !== previousOutput || cursorChanged;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const render = (str: string) => {
|
|
46
|
+
if (!showCursor && !hasHiddenCursor) {
|
|
47
|
+
cliCursor.hide(stream);
|
|
48
|
+
hasHiddenCursor = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Only use cursor if setCursorPosition was called since last render.
|
|
52
|
+
// This ensures stale positions don't persist after component unmount.
|
|
53
|
+
const activeCursor = getActiveCursor();
|
|
54
|
+
cursorDirty = false;
|
|
55
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
56
|
+
|
|
57
|
+
if (!hasChanges(str, activeCursor)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const lines = str.split("\n");
|
|
62
|
+
const cursorSuffix = buildCursorSuffix(lines.length - 1, activeCursor);
|
|
63
|
+
|
|
64
|
+
if (str === previousOutput && cursorChanged) {
|
|
65
|
+
stream.write(
|
|
66
|
+
buildCursorOnlySequence({
|
|
67
|
+
cursorWasShown,
|
|
68
|
+
previousLineCount,
|
|
69
|
+
previousCursorPosition,
|
|
70
|
+
cursorPosition: activeCursor,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
previousOutput = str;
|
|
75
|
+
const returnPrefix = buildReturnToBottomPrefix(
|
|
76
|
+
cursorWasShown,
|
|
77
|
+
previousLineCount,
|
|
78
|
+
previousCursorPosition,
|
|
79
|
+
);
|
|
80
|
+
stream.write(returnPrefix + ansiEscapes.eraseLines(previousLineCount) + str + cursorSuffix);
|
|
81
|
+
previousLineCount = lines.length;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
85
|
+
cursorWasShown = activeCursor !== undefined;
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
render.clear = () => {
|
|
90
|
+
const prefix = buildReturnToBottomPrefix(
|
|
91
|
+
cursorWasShown,
|
|
92
|
+
previousLineCount,
|
|
93
|
+
previousCursorPosition,
|
|
94
|
+
);
|
|
95
|
+
stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
|
|
96
|
+
previousOutput = "";
|
|
97
|
+
previousLineCount = 0;
|
|
98
|
+
previousCursorPosition = undefined;
|
|
99
|
+
cursorWasShown = false;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
render.done = () => {
|
|
103
|
+
previousOutput = "";
|
|
104
|
+
previousLineCount = 0;
|
|
105
|
+
previousCursorPosition = undefined;
|
|
106
|
+
cursorWasShown = false;
|
|
107
|
+
|
|
108
|
+
if (!showCursor) {
|
|
109
|
+
cliCursor.show(stream);
|
|
110
|
+
hasHiddenCursor = false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
render.reset = () => {
|
|
115
|
+
previousOutput = "";
|
|
116
|
+
previousLineCount = 0;
|
|
117
|
+
previousCursorPosition = undefined;
|
|
118
|
+
cursorWasShown = false;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
render.sync = (str: string) => {
|
|
122
|
+
const activeCursor = cursorDirty ? cursorPosition : undefined;
|
|
123
|
+
cursorDirty = false;
|
|
124
|
+
|
|
125
|
+
const lines = str.split("\n");
|
|
126
|
+
previousOutput = str;
|
|
127
|
+
previousLineCount = lines.length;
|
|
128
|
+
|
|
129
|
+
if (!activeCursor && cursorWasShown) {
|
|
130
|
+
stream.write(hideCursorEscape);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (activeCursor) {
|
|
134
|
+
stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
138
|
+
cursorWasShown = activeCursor !== undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
render.setCursorPosition = (position: CursorPosition | undefined) => {
|
|
142
|
+
cursorPosition = position;
|
|
143
|
+
cursorDirty = true;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
render.isCursorDirty = () => cursorDirty;
|
|
147
|
+
render.willRender = (str: string) => hasChanges(str, getActiveCursor());
|
|
148
|
+
|
|
149
|
+
return render;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const createIncremental = (
|
|
153
|
+
stream: NodeJS.WritableStream,
|
|
154
|
+
{ showCursor = false } = {},
|
|
155
|
+
): LogUpdate => {
|
|
156
|
+
let previousLines: string[] = [];
|
|
157
|
+
let previousOutput = "";
|
|
158
|
+
let hasHiddenCursor = false;
|
|
159
|
+
let cursorPosition: CursorPosition | undefined;
|
|
160
|
+
let cursorDirty = false;
|
|
161
|
+
let previousCursorPosition: CursorPosition | undefined;
|
|
162
|
+
let cursorWasShown = false;
|
|
163
|
+
|
|
164
|
+
const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
|
|
165
|
+
const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
|
|
166
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
167
|
+
return str !== previousOutput || cursorChanged;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const render = (str: string) => {
|
|
171
|
+
if (!showCursor && !hasHiddenCursor) {
|
|
172
|
+
cliCursor.hide(stream);
|
|
173
|
+
hasHiddenCursor = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Only use cursor if setCursorPosition was called since last render.
|
|
177
|
+
// This ensures stale positions don't persist after component unmount.
|
|
178
|
+
const activeCursor = getActiveCursor();
|
|
179
|
+
cursorDirty = false;
|
|
180
|
+
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
181
|
+
|
|
182
|
+
if (!hasChanges(str, activeCursor)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const nextLines = str.split("\n");
|
|
187
|
+
const visibleCount = visibleLineCount(nextLines, str);
|
|
188
|
+
const previousVisible = visibleLineCount(previousLines, previousOutput);
|
|
189
|
+
|
|
190
|
+
if (str === previousOutput && cursorChanged) {
|
|
191
|
+
stream.write(
|
|
192
|
+
buildCursorOnlySequence({
|
|
193
|
+
cursorWasShown,
|
|
194
|
+
previousLineCount: previousLines.length,
|
|
195
|
+
previousCursorPosition,
|
|
196
|
+
cursorPosition: activeCursor,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
200
|
+
cursorWasShown = activeCursor !== undefined;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const returnPrefix = buildReturnToBottomPrefix(
|
|
205
|
+
cursorWasShown,
|
|
206
|
+
previousLines.length,
|
|
207
|
+
previousCursorPosition,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (str === "\n" || previousOutput.length === 0) {
|
|
211
|
+
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
212
|
+
stream.write(
|
|
213
|
+
returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
|
|
214
|
+
);
|
|
215
|
+
cursorWasShown = activeCursor !== undefined;
|
|
216
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
217
|
+
previousOutput = str;
|
|
218
|
+
previousLines = nextLines;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// A frame that grew may need rows past the bottom margin. The
|
|
223
|
+
// incremental walk moves over unchanged lines with cursorNextLine,
|
|
224
|
+
// which clamps at the bottom margin instead of scrolling (only a
|
|
225
|
+
// line feed scrolls), so a grown frame that reaches the bottom
|
|
226
|
+
// desynchronizes the tracked cursor position from the terminal.
|
|
227
|
+
// Rewrite the whole frame instead — every line is emitted with a
|
|
228
|
+
// newline, which scrolls correctly at the bottom.
|
|
229
|
+
if (visibleCount > previousVisible) {
|
|
230
|
+
const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
|
|
231
|
+
stream.write(
|
|
232
|
+
returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
|
|
233
|
+
);
|
|
234
|
+
cursorWasShown = activeCursor !== undefined;
|
|
235
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
236
|
+
previousOutput = str;
|
|
237
|
+
previousLines = nextLines;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const hasTrailingNewline = str.endsWith("\n");
|
|
242
|
+
|
|
243
|
+
// We aggregate all chunks for incremental rendering into a buffer, and then write them to stdout at the end.
|
|
244
|
+
const buffer: string[] = [];
|
|
245
|
+
|
|
246
|
+
buffer.push(returnPrefix);
|
|
247
|
+
|
|
248
|
+
// Clear extra lines if the current content's line count is lower than the previous.
|
|
249
|
+
if (visibleCount < previousVisible) {
|
|
250
|
+
const previousHadTrailingNewline = previousOutput.endsWith("\n");
|
|
251
|
+
const extraSlot = previousHadTrailingNewline ? 1 : 0;
|
|
252
|
+
buffer.push(
|
|
253
|
+
ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot),
|
|
254
|
+
ansiEscapes.cursorUp(visibleCount),
|
|
255
|
+
);
|
|
256
|
+
} else {
|
|
257
|
+
buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < visibleCount; i++) {
|
|
261
|
+
const isLastLine = i === visibleCount - 1;
|
|
262
|
+
|
|
263
|
+
// We do not write lines if the contents are the same. This prevents flickering during renders.
|
|
264
|
+
if (nextLines[i] === previousLines[i]) {
|
|
265
|
+
// Don't move past the last line when there's no trailing newline,
|
|
266
|
+
// otherwise the cursor overshoots the rendered block.
|
|
267
|
+
if (!isLastLine || hasTrailingNewline) {
|
|
268
|
+
buffer.push(ansiEscapes.cursorNextLine);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
buffer.push(
|
|
275
|
+
ansiEscapes.cursorTo(0) +
|
|
276
|
+
nextLines[i] +
|
|
277
|
+
ansiEscapes.eraseEndLine +
|
|
278
|
+
// Don't append newline after the last line when the input
|
|
279
|
+
// has no trailing newline (fullscreen mode).
|
|
280
|
+
(isLastLine && !hasTrailingNewline ? "" : "\n"),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
285
|
+
buffer.push(cursorSuffix);
|
|
286
|
+
|
|
287
|
+
stream.write(buffer.join(""));
|
|
288
|
+
|
|
289
|
+
cursorWasShown = activeCursor !== undefined;
|
|
290
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
291
|
+
previousOutput = str;
|
|
292
|
+
previousLines = nextLines;
|
|
293
|
+
return true;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
render.clear = () => {
|
|
297
|
+
const prefix = buildReturnToBottomPrefix(
|
|
298
|
+
cursorWasShown,
|
|
299
|
+
previousLines.length,
|
|
300
|
+
previousCursorPosition,
|
|
301
|
+
);
|
|
302
|
+
stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
|
|
303
|
+
previousOutput = "";
|
|
304
|
+
previousLines = [];
|
|
305
|
+
previousCursorPosition = undefined;
|
|
306
|
+
cursorWasShown = false;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
render.done = () => {
|
|
310
|
+
previousOutput = "";
|
|
311
|
+
previousLines = [];
|
|
312
|
+
previousCursorPosition = undefined;
|
|
313
|
+
cursorWasShown = false;
|
|
314
|
+
|
|
315
|
+
if (!showCursor) {
|
|
316
|
+
cliCursor.show(stream);
|
|
317
|
+
hasHiddenCursor = false;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
render.reset = () => {
|
|
322
|
+
previousOutput = "";
|
|
323
|
+
previousLines = [];
|
|
324
|
+
previousCursorPosition = undefined;
|
|
325
|
+
cursorWasShown = false;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
render.sync = (str: string) => {
|
|
329
|
+
const activeCursor = cursorDirty ? cursorPosition : undefined;
|
|
330
|
+
cursorDirty = false;
|
|
331
|
+
|
|
332
|
+
const lines = str.split("\n");
|
|
333
|
+
previousOutput = str;
|
|
334
|
+
previousLines = lines;
|
|
335
|
+
|
|
336
|
+
if (!activeCursor && cursorWasShown) {
|
|
337
|
+
stream.write(hideCursorEscape);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (activeCursor) {
|
|
341
|
+
stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
345
|
+
cursorWasShown = activeCursor !== undefined;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
render.setCursorPosition = (position: CursorPosition | undefined) => {
|
|
349
|
+
cursorPosition = position;
|
|
350
|
+
cursorDirty = true;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
render.isCursorDirty = () => cursorDirty;
|
|
354
|
+
render.willRender = (str: string) => hasChanges(str, getActiveCursor());
|
|
355
|
+
|
|
356
|
+
return render;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const create = (
|
|
360
|
+
stream: NodeJS.WritableStream,
|
|
361
|
+
{ showCursor = false, incremental = false } = {},
|
|
362
|
+
): LogUpdate => {
|
|
363
|
+
if (incremental) {
|
|
364
|
+
return createIncremental(stream, { showCursor });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return createStandard(stream, { showCursor });
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export const logUpdate = { create };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type DOMElement } from "./dom.ts";
|
|
2
|
+
|
|
3
|
+
type Output = {
|
|
4
|
+
/**
|
|
5
|
+
Horizontal position (0-based column) within the live layout region.
|
|
6
|
+
*/
|
|
7
|
+
x: number;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
Vertical position (0-based row) within the live layout region.
|
|
11
|
+
*/
|
|
12
|
+
y: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
Element width.
|
|
16
|
+
*/
|
|
17
|
+
width: number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
Element height.
|
|
21
|
+
*/
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Measure the layout metrics of a particular `<Box>` element.
|
|
27
|
+
Returns an object with `x`, `y`, `width`, and `height` properties.
|
|
28
|
+
|
|
29
|
+
`x` and `y` are the element's position within the live layout region, computed by walking up the layout tree and accumulating each ancestor's offset. These are layout-tree coordinates, not terminal viewport coordinates. To compare them with mouse events, convert the event coordinates using the live region's viewport position. This is necessary even in alternate-screen mode when output, such as `<Static>` content, appears above the live region.
|
|
30
|
+
|
|
31
|
+
Note: `measureElement()` returns `{x: 0, y: 0, width: 0, height: 0}` when called during render (before layout is calculated). Call it from post-render code, such as `useEffect`, `useLayoutEffect`, input handlers, or timer callbacks. When content changes, pass the relevant dependency to your effect so it re-measures after each update.
|
|
32
|
+
*/
|
|
33
|
+
export const measureElement = (node: DOMElement): Output => {
|
|
34
|
+
const { yogaNode } = node;
|
|
35
|
+
|
|
36
|
+
if (!yogaNode) {
|
|
37
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let x = yogaNode.getComputedLeft();
|
|
41
|
+
let y = yogaNode.getComputedTop();
|
|
42
|
+
|
|
43
|
+
let current = node.parentNode;
|
|
44
|
+
|
|
45
|
+
while (current) {
|
|
46
|
+
if (current.yogaNode) {
|
|
47
|
+
x += current.yogaNode.getComputedLeft();
|
|
48
|
+
y += current.yogaNode.getComputedTop();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
current = current.parentNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
x,
|
|
56
|
+
y,
|
|
57
|
+
width: yogaNode.getComputedWidth(),
|
|
58
|
+
height: yogaNode.getComputedHeight(),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type { Output as ElementMetrics };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { widestLine } from "./ansi/widest-line.ts";
|
|
2
|
+
import { QuickLru as QuickLRU } from "./quick-lru.ts";
|
|
3
|
+
|
|
4
|
+
const cache = new QuickLRU<string, Output>({ maxSize: 4096 });
|
|
5
|
+
|
|
6
|
+
type Output = {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const measureText = (text: string): Output => {
|
|
12
|
+
if (text.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
width: 0,
|
|
15
|
+
height: 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const cachedDimensions = cache.get(text);
|
|
20
|
+
|
|
21
|
+
if (cachedDimensions) {
|
|
22
|
+
return cachedDimensions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const width = widestLine(text);
|
|
26
|
+
const height = text.split("\n").length;
|
|
27
|
+
const dimensions = { width, height };
|
|
28
|
+
cache.set(text, dimensions);
|
|
29
|
+
|
|
30
|
+
return dimensions;
|
|
31
|
+
};
|