@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
package/src/quick-lru.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Derived from `quick-lru` (MIT, Sindre Sorhus), reduced to the surface Ink
|
|
2
|
+
// uses. Implements the same two-generation LRU algorithm: when the hot map
|
|
3
|
+
// fills up, it becomes the cold generation and reads promote entries back.
|
|
4
|
+
export class QuickLru<KeyType, ValueType> {
|
|
5
|
+
#size = 0;
|
|
6
|
+
#cache = new Map<KeyType, ValueType>();
|
|
7
|
+
#oldCache = new Map<KeyType, ValueType>();
|
|
8
|
+
readonly #maxSize: number;
|
|
9
|
+
|
|
10
|
+
constructor({ maxSize }: { maxSize: number }) {
|
|
11
|
+
if (!(maxSize && maxSize > 0)) {
|
|
12
|
+
throw new TypeError("`maxSize` must be a number greater than 0");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.#maxSize = maxSize;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get size(): number {
|
|
19
|
+
let oldCacheSize = 0;
|
|
20
|
+
|
|
21
|
+
for (const key of this.#oldCache.keys()) {
|
|
22
|
+
if (!this.#cache.has(key)) {
|
|
23
|
+
oldCacheSize++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Math.min(this.#size + oldCacheSize, this.#maxSize);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get(key: KeyType): ValueType | undefined {
|
|
31
|
+
if (this.#cache.has(key)) {
|
|
32
|
+
return this.#cache.get(key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (this.#oldCache.has(key)) {
|
|
36
|
+
const value = this.#oldCache.get(key)!;
|
|
37
|
+
this.#oldCache.delete(key);
|
|
38
|
+
this.#set(key, value);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set(key: KeyType, value: ValueType): this {
|
|
46
|
+
if (this.#cache.has(key)) {
|
|
47
|
+
this.#cache.set(key, value);
|
|
48
|
+
} else {
|
|
49
|
+
this.#set(key, value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
has(key: KeyType): boolean {
|
|
56
|
+
return this.#cache.has(key) || this.#oldCache.has(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
delete(key: KeyType): boolean {
|
|
60
|
+
const deleted = this.#cache.delete(key);
|
|
61
|
+
|
|
62
|
+
if (deleted) {
|
|
63
|
+
this.#size--;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return this.#oldCache.delete(key) || deleted;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
clear(): void {
|
|
70
|
+
this.#cache.clear();
|
|
71
|
+
this.#oldCache.clear();
|
|
72
|
+
this.#size = 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#set(key: KeyType, value: ValueType): void {
|
|
76
|
+
this.#cache.set(key, value);
|
|
77
|
+
this.#size++;
|
|
78
|
+
|
|
79
|
+
if (this.#size >= this.#maxSize) {
|
|
80
|
+
this.#size = 0;
|
|
81
|
+
this.#oldCache = this.#cache;
|
|
82
|
+
this.#cache = new Map();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { createContext, version as reactVersion } from "react";
|
|
4
|
+
import createReconciler, { type ReactContext } from "react-reconciler";
|
|
5
|
+
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants.js";
|
|
6
|
+
import * as Scheduler from "scheduler";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createTextNode,
|
|
10
|
+
appendChildNode,
|
|
11
|
+
insertBeforeNode,
|
|
12
|
+
removeChildNode,
|
|
13
|
+
detachYogaSubtree,
|
|
14
|
+
emitLayoutListeners,
|
|
15
|
+
setStyle,
|
|
16
|
+
setTextNodeValue,
|
|
17
|
+
createNode,
|
|
18
|
+
setAttribute,
|
|
19
|
+
type DOMNodeAttribute,
|
|
20
|
+
type TextNode,
|
|
21
|
+
type ElementNames,
|
|
22
|
+
type DOMElement,
|
|
23
|
+
} from "./dom.ts";
|
|
24
|
+
import { type OutputTransformer } from "./render-node-to-output.ts";
|
|
25
|
+
import { styles as applyStyles, type Styles } from "./styles.ts";
|
|
26
|
+
import { Yoga } from "./yoga/index.ts";
|
|
27
|
+
|
|
28
|
+
// We need to conditionally perform devtools connection to avoid
|
|
29
|
+
// accidentally breaking other third-party code.
|
|
30
|
+
// See https://github.com/vadimdemedes/ink/issues/384
|
|
31
|
+
// See https://github.com/vadimdemedes/ink/issues/648
|
|
32
|
+
if (process.env["SIGIL_DEV"] === "true") {
|
|
33
|
+
// Intentionally no warning when the package is missing.
|
|
34
|
+
// SIGIL_DEV may be set for other reasons; devtools is opt-in via installing the package.
|
|
35
|
+
let isDevtoolsInstalled = false;
|
|
36
|
+
try {
|
|
37
|
+
import.meta.resolve("react-devtools-core");
|
|
38
|
+
isDevtoolsInstalled = true;
|
|
39
|
+
} catch {}
|
|
40
|
+
|
|
41
|
+
if (isDevtoolsInstalled) {
|
|
42
|
+
await import("./devtools.ts");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type AnyObject = Record<string, unknown>;
|
|
47
|
+
|
|
48
|
+
const diff = (before: AnyObject, after: AnyObject): AnyObject | undefined => {
|
|
49
|
+
if (before === after) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!before) {
|
|
54
|
+
return after;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const changed: AnyObject = {};
|
|
58
|
+
let isChanged = false;
|
|
59
|
+
|
|
60
|
+
for (const key of Object.keys(before)) {
|
|
61
|
+
const isDeleted = after ? !Object.hasOwn(after, key) : true;
|
|
62
|
+
|
|
63
|
+
if (isDeleted) {
|
|
64
|
+
changed[key] = undefined;
|
|
65
|
+
isChanged = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (after) {
|
|
70
|
+
for (const key of Object.keys(after)) {
|
|
71
|
+
if (after[key] !== before[key]) {
|
|
72
|
+
changed[key] = after[key];
|
|
73
|
+
isChanged = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return isChanged ? changed : undefined;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const findRootNode = (node: DOMElement): DOMElement | undefined => {
|
|
82
|
+
let current: DOMElement | undefined = node;
|
|
83
|
+
|
|
84
|
+
while (current) {
|
|
85
|
+
if (current.nodeName === "ink-root") {
|
|
86
|
+
return current;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
current = current.parentNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clear the root's cached `staticNode` when the node it points at is being
|
|
97
|
+
* removed as part of a larger subtree.
|
|
98
|
+
*
|
|
99
|
+
* The previous identity check (`staticNode === removeNode`) only caught direct
|
|
100
|
+
* removal of the `<Static>` element. When an *ancestor* of `<Static>` is
|
|
101
|
+
* removed, the stale `staticNode` reference survives and the next render would
|
|
102
|
+
* replay stale static output (and, before `detachYogaSubtree`, trap on detached
|
|
103
|
+
* WASM memory — see QwenLM/qwen-code#6820).
|
|
104
|
+
*
|
|
105
|
+
* The owning root is derived from the host parent passed to the removal hook,
|
|
106
|
+
* not a module-level global, so instances with separate stdout streams don't
|
|
107
|
+
* clobber each other's pointers.
|
|
108
|
+
*/
|
|
109
|
+
const clearStaticNodeIfContained = (
|
|
110
|
+
rootNode: DOMElement | undefined,
|
|
111
|
+
removeNode: DOMElement | TextNode,
|
|
112
|
+
): void => {
|
|
113
|
+
if (!rootNode?.staticNode) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Walk up from staticNode to see if removeNode is an ancestor.
|
|
118
|
+
let current: DOMElement | undefined = rootNode.staticNode;
|
|
119
|
+
|
|
120
|
+
while (current) {
|
|
121
|
+
if (current === removeNode) {
|
|
122
|
+
// Only clear staticNode, not previousStaticNode. The inequality
|
|
123
|
+
// (undefined !== previousStaticNode) triggers onStaticChange in
|
|
124
|
+
// resetAfterCommit, which resets fullStaticOutput.
|
|
125
|
+
rootNode.staticNode = undefined;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
current = current.parentNode;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type Props = Record<string, unknown>;
|
|
134
|
+
|
|
135
|
+
type HostContext = {
|
|
136
|
+
isInsideText: boolean;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
let currentUpdatePriority = NoEventPriority;
|
|
140
|
+
|
|
141
|
+
async function loadPackageJson() {
|
|
142
|
+
const fs = await import("node:fs");
|
|
143
|
+
const content = fs.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
144
|
+
|
|
145
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
146
|
+
const parsedContent = JSON.parse(content) as
|
|
147
|
+
| {
|
|
148
|
+
name?: string;
|
|
149
|
+
version?: string;
|
|
150
|
+
}
|
|
151
|
+
| undefined;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
name: parsedContent?.name,
|
|
155
|
+
version: parsedContent?.version,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let packageInfo = {
|
|
160
|
+
name: "ink",
|
|
161
|
+
version: reactVersion,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (process.env["DEV"] === "true") {
|
|
165
|
+
try {
|
|
166
|
+
const loaded = await loadPackageJson();
|
|
167
|
+
packageInfo = {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
169
|
+
name: loaded.name || packageInfo.name,
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
171
|
+
version: loaded.version || packageInfo.version,
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.warn(
|
|
175
|
+
"Failed to load package.json in development mode. Falling back to default renderer metadata.",
|
|
176
|
+
error,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const reconciler = createReconciler<
|
|
182
|
+
ElementNames,
|
|
183
|
+
Props,
|
|
184
|
+
DOMElement,
|
|
185
|
+
DOMElement,
|
|
186
|
+
TextNode,
|
|
187
|
+
DOMElement,
|
|
188
|
+
unknown,
|
|
189
|
+
unknown,
|
|
190
|
+
unknown,
|
|
191
|
+
HostContext,
|
|
192
|
+
unknown,
|
|
193
|
+
unknown,
|
|
194
|
+
unknown,
|
|
195
|
+
unknown
|
|
196
|
+
>({
|
|
197
|
+
getRootHostContext: () => ({
|
|
198
|
+
isInsideText: false,
|
|
199
|
+
}),
|
|
200
|
+
prepareForCommit: () => null,
|
|
201
|
+
preparePortalMount: () => null,
|
|
202
|
+
clearContainer: () => false,
|
|
203
|
+
resetAfterCommit(rootNode) {
|
|
204
|
+
if (typeof rootNode.onComputeLayout === "function") {
|
|
205
|
+
rootNode.onComputeLayout();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
emitLayoutListeners(rootNode);
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
Fire `onStaticChange` BEFORE `onImmediateRender` so ink resets accumulated static output before the new instance emits. Without this, items from a replaced/removed <Static> stay in `fullStaticOutput` and get replayed on rewrites.
|
|
212
|
+
*/
|
|
213
|
+
if (rootNode.staticNode !== rootNode.previousStaticNode) {
|
|
214
|
+
rootNode.previousStaticNode = rootNode.staticNode;
|
|
215
|
+
if (typeof rootNode.onStaticChange === "function") {
|
|
216
|
+
rootNode.onStaticChange();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Since renders are throttled at the instance level and <Static> component children
|
|
221
|
+
// are rendered only once and then get deleted, we need an escape hatch to
|
|
222
|
+
// trigger an immediate render to ensure <Static> children are written to output before they get erased
|
|
223
|
+
if (rootNode.isStaticDirty) {
|
|
224
|
+
rootNode.isStaticDirty = false;
|
|
225
|
+
if (typeof rootNode.onImmediateRender === "function") {
|
|
226
|
+
rootNode.onImmediateRender();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typeof rootNode.onRender === "function") {
|
|
233
|
+
rootNode.onRender();
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
getChildHostContext(parentHostContext, type) {
|
|
237
|
+
const previousIsInsideText = parentHostContext.isInsideText;
|
|
238
|
+
const isInsideText = type === "ink-text" || type === "ink-virtual-text";
|
|
239
|
+
|
|
240
|
+
if (previousIsInsideText === isInsideText) {
|
|
241
|
+
return parentHostContext;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { isInsideText };
|
|
245
|
+
},
|
|
246
|
+
shouldSetTextContent: () => false,
|
|
247
|
+
createInstance(originalType, newProps, rootNode, hostContext) {
|
|
248
|
+
if (hostContext.isInsideText && originalType === "ink-box") {
|
|
249
|
+
throw new Error(`<Box> can’t be nested inside <Text> component`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const type =
|
|
253
|
+
originalType === "ink-text" && hostContext.isInsideText ? "ink-virtual-text" : originalType;
|
|
254
|
+
|
|
255
|
+
const node = createNode(type);
|
|
256
|
+
|
|
257
|
+
for (const [key, value] of Object.entries(newProps)) {
|
|
258
|
+
if (key === "children") {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (key === "style") {
|
|
263
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
264
|
+
setStyle(node, value as Styles);
|
|
265
|
+
|
|
266
|
+
if (node.yogaNode) {
|
|
267
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
268
|
+
applyStyles(node.yogaNode, value as Styles);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (key === "internal_transform") {
|
|
275
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
276
|
+
node.internal_transform = value as OutputTransformer;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (key === "internal_static") {
|
|
281
|
+
node.internal_static = true;
|
|
282
|
+
rootNode.isStaticDirty = true;
|
|
283
|
+
|
|
284
|
+
// Save reference to <Static> node to skip traversal of entire
|
|
285
|
+
// node tree to find it
|
|
286
|
+
rootNode.staticNode = node;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
setAttribute(node, key, value as DOMNodeAttribute);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return node;
|
|
294
|
+
},
|
|
295
|
+
createTextInstance(text, _root, hostContext) {
|
|
296
|
+
if (!hostContext.isInsideText) {
|
|
297
|
+
throw new Error(`Text string "${text}" must be rendered inside <Text> component`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return createTextNode(text);
|
|
301
|
+
},
|
|
302
|
+
resetTextContent() {},
|
|
303
|
+
hideTextInstance(node) {
|
|
304
|
+
setTextNodeValue(node, "");
|
|
305
|
+
},
|
|
306
|
+
unhideTextInstance(node, text) {
|
|
307
|
+
setTextNodeValue(node, text);
|
|
308
|
+
},
|
|
309
|
+
getPublicInstance: (instance) => instance,
|
|
310
|
+
hideInstance(node) {
|
|
311
|
+
node.yogaNode?.setDisplay(Yoga.DISPLAY_NONE);
|
|
312
|
+
},
|
|
313
|
+
unhideInstance(node) {
|
|
314
|
+
node.yogaNode?.setDisplay(Yoga.DISPLAY_FLEX);
|
|
315
|
+
},
|
|
316
|
+
appendInitialChild: appendChildNode,
|
|
317
|
+
appendChild: appendChildNode,
|
|
318
|
+
insertBefore: insertBeforeNode,
|
|
319
|
+
finalizeInitialChildren() {
|
|
320
|
+
return false;
|
|
321
|
+
},
|
|
322
|
+
isPrimaryRenderer: true,
|
|
323
|
+
supportsMutation: true,
|
|
324
|
+
supportsPersistence: false,
|
|
325
|
+
supportsHydration: false,
|
|
326
|
+
// Scheduler integration for concurrent mode
|
|
327
|
+
supportsMicrotasks: true,
|
|
328
|
+
scheduleMicrotask: queueMicrotask,
|
|
329
|
+
// @ts-expect-error @types/react-reconciler is outdated and doesn't include scheduleCallback
|
|
330
|
+
scheduleCallback: Scheduler.unstable_scheduleCallback,
|
|
331
|
+
cancelCallback: Scheduler.unstable_cancelCallback,
|
|
332
|
+
shouldYield: Scheduler.unstable_shouldYield,
|
|
333
|
+
now: Scheduler.unstable_now,
|
|
334
|
+
scheduleTimeout: setTimeout,
|
|
335
|
+
cancelTimeout: clearTimeout,
|
|
336
|
+
noTimeout: -1,
|
|
337
|
+
beforeActiveInstanceBlur() {},
|
|
338
|
+
afterActiveInstanceBlur() {},
|
|
339
|
+
detachDeletedInstance() {},
|
|
340
|
+
getInstanceFromNode: () => null,
|
|
341
|
+
prepareScopeUpdate() {},
|
|
342
|
+
getInstanceFromScope: () => null,
|
|
343
|
+
appendChildToContainer: appendChildNode,
|
|
344
|
+
insertInContainerBefore: insertBeforeNode,
|
|
345
|
+
removeChildFromContainer(node, removeNode) {
|
|
346
|
+
// `node` is the container, i.e. the root itself. Clear before
|
|
347
|
+
// removeChildNode breaks the parent chain.
|
|
348
|
+
clearStaticNodeIfContained(findRootNode(node), removeNode);
|
|
349
|
+
|
|
350
|
+
removeChildNode(node, removeNode);
|
|
351
|
+
detachYogaSubtree(removeNode);
|
|
352
|
+
},
|
|
353
|
+
commitUpdate(node, _type, oldProps, newProps) {
|
|
354
|
+
if (node.internal_static) {
|
|
355
|
+
const rootNode = findRootNode(node);
|
|
356
|
+
|
|
357
|
+
if (rootNode) {
|
|
358
|
+
rootNode.isStaticDirty = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const props = diff(oldProps, newProps);
|
|
363
|
+
|
|
364
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
365
|
+
const style = diff(oldProps["style"] as Styles, newProps["style"] as Styles);
|
|
366
|
+
|
|
367
|
+
if (!props && !style) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (props) {
|
|
372
|
+
for (const [key, value] of Object.entries(props)) {
|
|
373
|
+
if (key === "style") {
|
|
374
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
375
|
+
setStyle(node, value as Styles);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (key === "internal_transform") {
|
|
380
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
381
|
+
node.internal_transform = value as OutputTransformer;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (key === "internal_static") {
|
|
386
|
+
node.internal_static = true;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
391
|
+
setAttribute(node, key, value as DOMNodeAttribute);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (style && node.yogaNode) {
|
|
396
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
397
|
+
applyStyles(node.yogaNode, style, (newProps["style"] as Styles | undefined) ?? {});
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
commitTextUpdate(node, _oldText, newText) {
|
|
401
|
+
setTextNodeValue(node, newText);
|
|
402
|
+
},
|
|
403
|
+
removeChild(node, removeNode) {
|
|
404
|
+
// `node` is the host parent; its chain up to the root is still intact
|
|
405
|
+
// here, so derive the owning root from it rather than a global.
|
|
406
|
+
clearStaticNodeIfContained(findRootNode(node), removeNode);
|
|
407
|
+
|
|
408
|
+
removeChildNode(node, removeNode);
|
|
409
|
+
detachYogaSubtree(removeNode);
|
|
410
|
+
},
|
|
411
|
+
setCurrentUpdatePriority(newPriority: number) {
|
|
412
|
+
currentUpdatePriority = newPriority;
|
|
413
|
+
},
|
|
414
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
415
|
+
resolveUpdatePriority() {
|
|
416
|
+
if (currentUpdatePriority !== NoEventPriority) {
|
|
417
|
+
return currentUpdatePriority;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return DefaultEventPriority;
|
|
421
|
+
},
|
|
422
|
+
maySuspendCommit() {
|
|
423
|
+
// Return true to enable Suspense resource preloading
|
|
424
|
+
return true;
|
|
425
|
+
},
|
|
426
|
+
NotPendingTransition: undefined,
|
|
427
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
428
|
+
HostTransitionContext: createContext(null) as unknown as ReactContext<unknown>,
|
|
429
|
+
resetFormInstance() {},
|
|
430
|
+
requestPostPaintCallback() {},
|
|
431
|
+
shouldAttemptEagerTransition() {
|
|
432
|
+
return false;
|
|
433
|
+
},
|
|
434
|
+
trackSchedulerEvent() {},
|
|
435
|
+
resolveEventType() {
|
|
436
|
+
return null;
|
|
437
|
+
},
|
|
438
|
+
resolveEventTimeStamp() {
|
|
439
|
+
return -1.1;
|
|
440
|
+
},
|
|
441
|
+
preloadInstance() {
|
|
442
|
+
return true;
|
|
443
|
+
},
|
|
444
|
+
startSuspendingCommit() {},
|
|
445
|
+
suspendInstance() {},
|
|
446
|
+
waitForCommitToBeReady() {
|
|
447
|
+
return null;
|
|
448
|
+
},
|
|
449
|
+
rendererPackageName: packageInfo.name,
|
|
450
|
+
rendererVersion: packageInfo.version,
|
|
451
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { colorize } from "./colorize.ts";
|
|
2
|
+
import { type DOMNode } from "./dom.ts";
|
|
3
|
+
import type { Output } from "./output.ts";
|
|
4
|
+
|
|
5
|
+
export const renderBackground = (x: number, y: number, node: DOMNode, output: Output): void => {
|
|
6
|
+
if (!node.style.backgroundColor) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const width = node.yogaNode!.getComputedWidth();
|
|
11
|
+
const height = node.yogaNode!.getComputedHeight();
|
|
12
|
+
|
|
13
|
+
// Calculate the actual content area considering borders
|
|
14
|
+
const leftBorderWidth = node.style.borderStyle && node.style.borderLeft !== false ? 1 : 0;
|
|
15
|
+
const rightBorderWidth = node.style.borderStyle && node.style.borderRight !== false ? 1 : 0;
|
|
16
|
+
const topBorderHeight = node.style.borderStyle && node.style.borderTop !== false ? 1 : 0;
|
|
17
|
+
const bottomBorderHeight = node.style.borderStyle && node.style.borderBottom !== false ? 1 : 0;
|
|
18
|
+
|
|
19
|
+
const contentWidth = width - leftBorderWidth - rightBorderWidth;
|
|
20
|
+
const contentHeight = height - topBorderHeight - bottomBorderHeight;
|
|
21
|
+
|
|
22
|
+
if (!(contentWidth > 0 && contentHeight > 0)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create background fill for each row
|
|
27
|
+
const backgroundLine = colorize(
|
|
28
|
+
" ".repeat(contentWidth),
|
|
29
|
+
node.style.backgroundColor,
|
|
30
|
+
"background",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
for (let row = 0; row < contentHeight; row++) {
|
|
34
|
+
output.write(x + leftBorderWidth, y + topBorderHeight + row, backgroundLine, {
|
|
35
|
+
transformers: [],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { chalk } from "./ansi/chalk.ts";
|
|
2
|
+
import { boxes as cliBoxes } from "./boxes.ts";
|
|
3
|
+
import { colorize } from "./colorize.ts";
|
|
4
|
+
import { type DOMNode } from "./dom.ts";
|
|
5
|
+
import type { Output } from "./output.ts";
|
|
6
|
+
|
|
7
|
+
const stylePiece = (segment: string, fg?: string, bg?: string, dim?: boolean): string => {
|
|
8
|
+
let styled = colorize(segment, fg, "foreground");
|
|
9
|
+
styled = colorize(styled, bg, "background");
|
|
10
|
+
if (dim) {
|
|
11
|
+
styled = chalk.dim(styled);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return styled;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const renderBorder = (x: number, y: number, node: DOMNode, output: Output): void => {
|
|
18
|
+
if (node.style.borderStyle) {
|
|
19
|
+
const width = node.yogaNode!.getComputedWidth();
|
|
20
|
+
const height = node.yogaNode!.getComputedHeight();
|
|
21
|
+
const box =
|
|
22
|
+
typeof node.style.borderStyle === "string"
|
|
23
|
+
? cliBoxes[node.style.borderStyle]
|
|
24
|
+
: node.style.borderStyle;
|
|
25
|
+
|
|
26
|
+
const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
|
|
27
|
+
const bottomBorderColor = node.style.borderBottomColor ?? node.style.borderColor;
|
|
28
|
+
const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor;
|
|
29
|
+
const rightBorderColor = node.style.borderRightColor ?? node.style.borderColor;
|
|
30
|
+
|
|
31
|
+
const topBorderBackgroundColor =
|
|
32
|
+
node.style.borderTopBackgroundColor ?? node.style.borderBackgroundColor;
|
|
33
|
+
const bottomBorderBackgroundColor =
|
|
34
|
+
node.style.borderBottomBackgroundColor ?? node.style.borderBackgroundColor;
|
|
35
|
+
const leftBorderBackgroundColor =
|
|
36
|
+
node.style.borderLeftBackgroundColor ?? node.style.borderBackgroundColor;
|
|
37
|
+
const rightBorderBackgroundColor =
|
|
38
|
+
node.style.borderRightBackgroundColor ?? node.style.borderBackgroundColor;
|
|
39
|
+
|
|
40
|
+
const dimTopBorderColor = node.style.borderTopDimColor ?? node.style.borderDimColor;
|
|
41
|
+
|
|
42
|
+
const dimBottomBorderColor = node.style.borderBottomDimColor ?? node.style.borderDimColor;
|
|
43
|
+
|
|
44
|
+
const dimLeftBorderColor = node.style.borderLeftDimColor ?? node.style.borderDimColor;
|
|
45
|
+
|
|
46
|
+
const dimRightBorderColor = node.style.borderRightDimColor ?? node.style.borderDimColor;
|
|
47
|
+
|
|
48
|
+
const showTopBorder = node.style.borderTop !== false;
|
|
49
|
+
const showBottomBorder = node.style.borderBottom !== false;
|
|
50
|
+
const showLeftBorder = node.style.borderLeft !== false;
|
|
51
|
+
const showRightBorder = node.style.borderRight !== false;
|
|
52
|
+
|
|
53
|
+
const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
|
|
54
|
+
|
|
55
|
+
let topBorder = showTopBorder
|
|
56
|
+
? (showLeftBorder ? box.topLeft : "") +
|
|
57
|
+
box.top.repeat(contentWidth) +
|
|
58
|
+
(showRightBorder ? box.topRight : "")
|
|
59
|
+
: undefined;
|
|
60
|
+
|
|
61
|
+
topBorder &&= stylePiece(
|
|
62
|
+
topBorder,
|
|
63
|
+
topBorderColor,
|
|
64
|
+
topBorderBackgroundColor,
|
|
65
|
+
dimTopBorderColor,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
let verticalBorderHeight = height;
|
|
69
|
+
|
|
70
|
+
if (showTopBorder) {
|
|
71
|
+
verticalBorderHeight -= 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (showBottomBorder) {
|
|
75
|
+
verticalBorderHeight -= 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let leftBorder = "";
|
|
79
|
+
|
|
80
|
+
if (showLeftBorder) {
|
|
81
|
+
const one = stylePiece(
|
|
82
|
+
box.left,
|
|
83
|
+
leftBorderColor,
|
|
84
|
+
leftBorderBackgroundColor,
|
|
85
|
+
dimLeftBorderColor,
|
|
86
|
+
);
|
|
87
|
+
leftBorder = (one + "\n").repeat(verticalBorderHeight);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let rightBorder = "";
|
|
91
|
+
|
|
92
|
+
if (showRightBorder) {
|
|
93
|
+
const one = stylePiece(
|
|
94
|
+
box.right,
|
|
95
|
+
rightBorderColor,
|
|
96
|
+
rightBorderBackgroundColor,
|
|
97
|
+
dimRightBorderColor,
|
|
98
|
+
);
|
|
99
|
+
rightBorder = (one + "\n").repeat(verticalBorderHeight);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let bottomBorder = showBottomBorder
|
|
103
|
+
? (showLeftBorder ? box.bottomLeft : "") +
|
|
104
|
+
box.bottom.repeat(contentWidth) +
|
|
105
|
+
(showRightBorder ? box.bottomRight : "")
|
|
106
|
+
: undefined;
|
|
107
|
+
bottomBorder &&= stylePiece(
|
|
108
|
+
bottomBorder,
|
|
109
|
+
bottomBorderColor,
|
|
110
|
+
bottomBorderBackgroundColor,
|
|
111
|
+
dimBottomBorderColor,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const offsetY = showTopBorder ? 1 : 0;
|
|
115
|
+
|
|
116
|
+
if (topBorder) {
|
|
117
|
+
output.write(x, y, topBorder, { transformers: [] });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (leftBorder) {
|
|
121
|
+
output.write(x, y + offsetY, leftBorder, { transformers: [] });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (rightBorder) {
|
|
125
|
+
output.write(x + width - 1, y + offsetY, rightBorder, {
|
|
126
|
+
transformers: [],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (bottomBorder) {
|
|
131
|
+
output.write(x, y + height - 1, bottomBorder, { transformers: [] });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|