@opentui/react 0.1.42 → 0.1.43
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/index.js +12 -8
- package/package.json +7 -2
- package/src/reconciler/reconciler.d.ts +1 -1
- package/src/reconciler/renderer.d.ts +6 -8
- package/src/reconciler/renderer.js +12 -8
- package/src/test-utils/test-utils.js +480 -0
- package/src/test-utils.d.ts +10 -0
package/index.js
CHANGED
|
@@ -181,7 +181,7 @@ var useTimeline = (options = {}) => {
|
|
|
181
181
|
return timeline;
|
|
182
182
|
};
|
|
183
183
|
// src/reconciler/renderer.ts
|
|
184
|
-
import {
|
|
184
|
+
import { engine as engine2 } from "@opentui/core";
|
|
185
185
|
import React2 from "react";
|
|
186
186
|
|
|
187
187
|
// src/components/error-boundary.tsx
|
|
@@ -504,19 +504,24 @@ var reconciler = ReactReconciler(hostConfig);
|
|
|
504
504
|
function _render(element, root) {
|
|
505
505
|
const container = reconciler.createContainer(root, ConcurrentRoot, null, false, null, "", console.error, console.error, console.error, console.error, null);
|
|
506
506
|
reconciler.updateContainer(element, container, null, () => {});
|
|
507
|
+
return container;
|
|
507
508
|
}
|
|
508
509
|
|
|
509
510
|
// src/reconciler/renderer.ts
|
|
510
|
-
async function render(node, rendererConfig = {}) {
|
|
511
|
-
const renderer = await createCliRenderer(rendererConfig);
|
|
512
|
-
engine2.attach(renderer);
|
|
513
|
-
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
514
|
-
}
|
|
515
511
|
function createRoot(renderer) {
|
|
512
|
+
let container = null;
|
|
516
513
|
return {
|
|
517
514
|
render: (node) => {
|
|
518
515
|
engine2.attach(renderer);
|
|
519
|
-
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
516
|
+
container = _render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
517
|
+
},
|
|
518
|
+
unmount: () => {
|
|
519
|
+
if (!container) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
reconciler.updateContainer(null, container, null, () => {});
|
|
523
|
+
reconciler.flushSyncWork();
|
|
524
|
+
container = null;
|
|
520
525
|
}
|
|
521
526
|
};
|
|
522
527
|
}
|
|
@@ -527,7 +532,6 @@ export {
|
|
|
527
532
|
useOnResize,
|
|
528
533
|
useKeyboard,
|
|
529
534
|
useAppContext,
|
|
530
|
-
render,
|
|
531
535
|
getComponentCatalogue,
|
|
532
536
|
extend,
|
|
533
537
|
createRoot,
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.43",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
"import": "./src/reconciler/renderer.js",
|
|
24
24
|
"require": "./src/reconciler/renderer.js"
|
|
25
25
|
},
|
|
26
|
+
"./test-utils": {
|
|
27
|
+
"types": "./src/test-utils.d.ts",
|
|
28
|
+
"import": "./src/test-utils/test-utils.js",
|
|
29
|
+
"require": "./src/test-utils/test-utils.js"
|
|
30
|
+
},
|
|
26
31
|
"./jsx-runtime": {
|
|
27
32
|
"types": "./jsx-runtime.d.ts",
|
|
28
33
|
"import": "./jsx-runtime.js",
|
|
@@ -35,7 +40,7 @@
|
|
|
35
40
|
}
|
|
36
41
|
},
|
|
37
42
|
"dependencies": {
|
|
38
|
-
"@opentui/core": "0.1.
|
|
43
|
+
"@opentui/core": "0.1.43",
|
|
39
44
|
"react-reconciler": "^0.32.0"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
@@ -2,4 +2,4 @@ import type { RootRenderable } from "@opentui/core";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import ReactReconciler from "react-reconciler";
|
|
4
4
|
export declare const reconciler: ReactReconciler.Reconciler<RootRenderable, import("@opentui/core").BaseRenderable, import("@opentui/core").TextNodeRenderable, unknown, unknown, import("@opentui/core").BaseRenderable>;
|
|
5
|
-
export declare function _render(element: React.ReactNode, root: RootRenderable):
|
|
5
|
+
export declare function _render(element: React.ReactNode, root: RootRenderable): any;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { CliRenderer
|
|
1
|
+
import { CliRenderer } from "@opentui/core";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export type Root = {
|
|
4
|
+
render: (node: ReactNode) => void;
|
|
5
|
+
unmount: () => void;
|
|
6
|
+
};
|
|
7
7
|
/**
|
|
8
8
|
* Creates a root for rendering a React tree with the given CLI renderer.
|
|
9
9
|
* @param renderer The CLI renderer to use
|
|
@@ -14,6 +14,4 @@ export declare function render(node: ReactNode, rendererConfig?: CliRendererConf
|
|
|
14
14
|
* createRoot(renderer).render(<App />)
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export declare function createRoot(renderer: CliRenderer):
|
|
18
|
-
render: (node: ReactNode) => void;
|
|
19
|
-
};
|
|
17
|
+
export declare function createRoot(renderer: CliRenderer): Root;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/reconciler/renderer.ts
|
|
3
|
-
import {
|
|
3
|
+
import { engine } from "@opentui/core";
|
|
4
4
|
import React2 from "react";
|
|
5
5
|
|
|
6
6
|
// src/components/app.tsx
|
|
@@ -421,23 +421,27 @@ var reconciler = ReactReconciler(hostConfig);
|
|
|
421
421
|
function _render(element, root) {
|
|
422
422
|
const container = reconciler.createContainer(root, ConcurrentRoot, null, false, null, "", console.error, console.error, console.error, console.error, null);
|
|
423
423
|
reconciler.updateContainer(element, container, null, () => {});
|
|
424
|
+
return container;
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
// src/reconciler/renderer.ts
|
|
427
|
-
async function render(node, rendererConfig = {}) {
|
|
428
|
-
const renderer = await createCliRenderer(rendererConfig);
|
|
429
|
-
engine.attach(renderer);
|
|
430
|
-
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
431
|
-
}
|
|
432
428
|
function createRoot(renderer) {
|
|
429
|
+
let container = null;
|
|
433
430
|
return {
|
|
434
431
|
render: (node) => {
|
|
435
432
|
engine.attach(renderer);
|
|
436
|
-
_render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
433
|
+
container = _render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
434
|
+
},
|
|
435
|
+
unmount: () => {
|
|
436
|
+
if (!container) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
reconciler.updateContainer(null, container, null, () => {});
|
|
440
|
+
reconciler.flushSyncWork();
|
|
441
|
+
container = null;
|
|
437
442
|
}
|
|
438
443
|
};
|
|
439
444
|
}
|
|
440
445
|
export {
|
|
441
|
-
render,
|
|
442
446
|
createRoot
|
|
443
447
|
};
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/test-utils.ts
|
|
3
|
+
import { createTestRenderer } from "@opentui/core/testing";
|
|
4
|
+
import { act } from "react";
|
|
5
|
+
|
|
6
|
+
// src/reconciler/renderer.ts
|
|
7
|
+
import { engine } from "@opentui/core";
|
|
8
|
+
import React2 from "react";
|
|
9
|
+
|
|
10
|
+
// src/components/app.tsx
|
|
11
|
+
import { createContext, useContext } from "react";
|
|
12
|
+
var AppContext = createContext({
|
|
13
|
+
keyHandler: null,
|
|
14
|
+
renderer: null
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// src/components/error-boundary.tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
|
|
20
|
+
// jsx-dev-runtime.js
|
|
21
|
+
import { Fragment, jsxDEV } from "react/jsx-dev-runtime";
|
|
22
|
+
|
|
23
|
+
// src/components/error-boundary.tsx
|
|
24
|
+
class ErrorBoundary extends React.Component {
|
|
25
|
+
constructor(props) {
|
|
26
|
+
super(props);
|
|
27
|
+
this.state = { hasError: false, error: null };
|
|
28
|
+
}
|
|
29
|
+
static getDerivedStateFromError(error) {
|
|
30
|
+
return { hasError: true, error };
|
|
31
|
+
}
|
|
32
|
+
render() {
|
|
33
|
+
if (this.state.hasError && this.state.error) {
|
|
34
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
35
|
+
style: { flexDirection: "column", padding: 2 },
|
|
36
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
37
|
+
fg: "red",
|
|
38
|
+
children: this.state.error.stack || this.state.error.message
|
|
39
|
+
}, undefined, false, undefined, this)
|
|
40
|
+
}, undefined, false, undefined, this);
|
|
41
|
+
}
|
|
42
|
+
return this.props.children;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/reconciler/reconciler.ts
|
|
47
|
+
import ReactReconciler from "react-reconciler";
|
|
48
|
+
import { ConcurrentRoot } from "react-reconciler/constants";
|
|
49
|
+
|
|
50
|
+
// src/reconciler/host-config.ts
|
|
51
|
+
import { TextNodeRenderable as TextNodeRenderable2 } from "@opentui/core";
|
|
52
|
+
import { createContext as createContext2 } from "react";
|
|
53
|
+
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
|
|
54
|
+
|
|
55
|
+
// src/components/index.ts
|
|
56
|
+
import {
|
|
57
|
+
ASCIIFontRenderable,
|
|
58
|
+
BoxRenderable,
|
|
59
|
+
CodeRenderable,
|
|
60
|
+
InputRenderable,
|
|
61
|
+
ScrollBoxRenderable,
|
|
62
|
+
SelectRenderable,
|
|
63
|
+
TabSelectRenderable,
|
|
64
|
+
TextareaRenderable,
|
|
65
|
+
TextRenderable
|
|
66
|
+
} from "@opentui/core";
|
|
67
|
+
|
|
68
|
+
// src/components/text.ts
|
|
69
|
+
import { TextAttributes, TextNodeRenderable } from "@opentui/core";
|
|
70
|
+
var textNodeKeys = ["span", "b", "strong", "i", "em", "u", "br"];
|
|
71
|
+
|
|
72
|
+
class SpanRenderable extends TextNodeRenderable {
|
|
73
|
+
ctx;
|
|
74
|
+
constructor(ctx, options) {
|
|
75
|
+
super(options);
|
|
76
|
+
this.ctx = ctx;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class TextModifierRenderable extends SpanRenderable {
|
|
81
|
+
constructor(options, modifier) {
|
|
82
|
+
super(null, options);
|
|
83
|
+
if (modifier === "b" || modifier === "strong") {
|
|
84
|
+
this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
|
|
85
|
+
} else if (modifier === "i" || modifier === "em") {
|
|
86
|
+
this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
|
|
87
|
+
} else if (modifier === "u") {
|
|
88
|
+
this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class BoldSpanRenderable extends TextModifierRenderable {
|
|
94
|
+
constructor(_ctx, options) {
|
|
95
|
+
super(options, "b");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
100
|
+
constructor(_ctx, options) {
|
|
101
|
+
super(options, "i");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
106
|
+
constructor(_ctx, options) {
|
|
107
|
+
super(options, "u");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class LineBreakRenderable extends SpanRenderable {
|
|
112
|
+
constructor(_ctx, options) {
|
|
113
|
+
super(null, options);
|
|
114
|
+
this.add();
|
|
115
|
+
}
|
|
116
|
+
add() {
|
|
117
|
+
return super.add(`
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/components/index.ts
|
|
123
|
+
var baseComponents = {
|
|
124
|
+
box: BoxRenderable,
|
|
125
|
+
text: TextRenderable,
|
|
126
|
+
code: CodeRenderable,
|
|
127
|
+
input: InputRenderable,
|
|
128
|
+
select: SelectRenderable,
|
|
129
|
+
textarea: TextareaRenderable,
|
|
130
|
+
scrollbox: ScrollBoxRenderable,
|
|
131
|
+
"ascii-font": ASCIIFontRenderable,
|
|
132
|
+
"tab-select": TabSelectRenderable,
|
|
133
|
+
span: SpanRenderable,
|
|
134
|
+
br: LineBreakRenderable,
|
|
135
|
+
b: BoldSpanRenderable,
|
|
136
|
+
strong: BoldSpanRenderable,
|
|
137
|
+
i: ItalicSpanRenderable,
|
|
138
|
+
em: ItalicSpanRenderable,
|
|
139
|
+
u: UnderlineSpanRenderable
|
|
140
|
+
};
|
|
141
|
+
var componentCatalogue = { ...baseComponents };
|
|
142
|
+
function getComponentCatalogue() {
|
|
143
|
+
return componentCatalogue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/utils/id.ts
|
|
147
|
+
var idCounter = new Map;
|
|
148
|
+
function getNextId(type) {
|
|
149
|
+
if (!idCounter.has(type)) {
|
|
150
|
+
idCounter.set(type, 0);
|
|
151
|
+
}
|
|
152
|
+
const value = idCounter.get(type) + 1;
|
|
153
|
+
idCounter.set(type, value);
|
|
154
|
+
return `${type}-${value}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/utils/index.ts
|
|
158
|
+
import {
|
|
159
|
+
InputRenderable as InputRenderable2,
|
|
160
|
+
InputRenderableEvents,
|
|
161
|
+
isRenderable,
|
|
162
|
+
SelectRenderable as SelectRenderable2,
|
|
163
|
+
SelectRenderableEvents,
|
|
164
|
+
TabSelectRenderable as TabSelectRenderable2,
|
|
165
|
+
TabSelectRenderableEvents
|
|
166
|
+
} from "@opentui/core";
|
|
167
|
+
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
168
|
+
if (previousListener) {
|
|
169
|
+
instance.off(eventName, previousListener);
|
|
170
|
+
}
|
|
171
|
+
if (listener) {
|
|
172
|
+
instance.on(eventName, listener);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function setStyle(instance, styles, oldStyles) {
|
|
176
|
+
if (styles && typeof styles === "object") {
|
|
177
|
+
if (oldStyles != null) {
|
|
178
|
+
for (const styleName in styles) {
|
|
179
|
+
const value = styles[styleName];
|
|
180
|
+
if (styles.hasOwnProperty(styleName) && oldStyles[styleName] !== value) {
|
|
181
|
+
instance[styleName] = value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
for (const styleName in styles) {
|
|
186
|
+
if (styles.hasOwnProperty(styleName)) {
|
|
187
|
+
const value = styles[styleName];
|
|
188
|
+
instance[styleName] = value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
195
|
+
switch (propKey) {
|
|
196
|
+
case "onChange":
|
|
197
|
+
if (instance instanceof InputRenderable2) {
|
|
198
|
+
initEventListeners(instance, InputRenderableEvents.CHANGE, propValue, oldPropValue);
|
|
199
|
+
} else if (instance instanceof SelectRenderable2) {
|
|
200
|
+
initEventListeners(instance, SelectRenderableEvents.SELECTION_CHANGED, propValue, oldPropValue);
|
|
201
|
+
} else if (instance instanceof TabSelectRenderable2) {
|
|
202
|
+
initEventListeners(instance, TabSelectRenderableEvents.SELECTION_CHANGED, propValue, oldPropValue);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case "onInput":
|
|
206
|
+
if (instance instanceof InputRenderable2) {
|
|
207
|
+
initEventListeners(instance, InputRenderableEvents.INPUT, propValue, oldPropValue);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case "onSubmit":
|
|
211
|
+
if (instance instanceof InputRenderable2) {
|
|
212
|
+
initEventListeners(instance, InputRenderableEvents.ENTER, propValue, oldPropValue);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case "onSelect":
|
|
216
|
+
if (instance instanceof SelectRenderable2) {
|
|
217
|
+
initEventListeners(instance, SelectRenderableEvents.ITEM_SELECTED, propValue, oldPropValue);
|
|
218
|
+
} else if (instance instanceof TabSelectRenderable2) {
|
|
219
|
+
initEventListeners(instance, TabSelectRenderableEvents.ITEM_SELECTED, propValue, oldPropValue);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
case "focused":
|
|
223
|
+
if (isRenderable(instance)) {
|
|
224
|
+
if (!!propValue) {
|
|
225
|
+
instance.focus();
|
|
226
|
+
} else {
|
|
227
|
+
instance.blur();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case "style":
|
|
232
|
+
setStyle(instance, propValue, oldPropValue);
|
|
233
|
+
break;
|
|
234
|
+
case "children":
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
instance[propKey] = propValue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function setInitialProperties(instance, type, props) {
|
|
241
|
+
for (const propKey in props) {
|
|
242
|
+
if (!props.hasOwnProperty(propKey)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const propValue = props[propKey];
|
|
246
|
+
if (propValue == null) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
setProperty(instance, type, propKey, propValue);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function updateProperties(instance, type, oldProps, newProps) {
|
|
253
|
+
for (const propKey in oldProps) {
|
|
254
|
+
const oldProp = oldProps[propKey];
|
|
255
|
+
if (oldProps.hasOwnProperty(propKey) && oldProp != null && !newProps.hasOwnProperty(propKey)) {
|
|
256
|
+
setProperty(instance, type, propKey, null, oldProp);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const propKey in newProps) {
|
|
260
|
+
const newProp = newProps[propKey];
|
|
261
|
+
const oldProp = oldProps[propKey];
|
|
262
|
+
if (newProps.hasOwnProperty(propKey) && newProp !== oldProp && (newProp != null || oldProp != null)) {
|
|
263
|
+
setProperty(instance, type, propKey, newProp, oldProp);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/reconciler/host-config.ts
|
|
269
|
+
var currentUpdatePriority = NoEventPriority;
|
|
270
|
+
var hostConfig = {
|
|
271
|
+
supportsMutation: true,
|
|
272
|
+
supportsPersistence: false,
|
|
273
|
+
supportsHydration: false,
|
|
274
|
+
createInstance(type, props, rootContainerInstance, hostContext) {
|
|
275
|
+
if (textNodeKeys.includes(type) && !hostContext.isInsideText) {
|
|
276
|
+
throw new Error(`Component of type "${type}" must be created inside of a text node`);
|
|
277
|
+
}
|
|
278
|
+
const id = getNextId(type);
|
|
279
|
+
const components = getComponentCatalogue();
|
|
280
|
+
if (!components[type]) {
|
|
281
|
+
throw new Error(`Unknown component type: ${type}`);
|
|
282
|
+
}
|
|
283
|
+
return new components[type](rootContainerInstance.ctx, {
|
|
284
|
+
id,
|
|
285
|
+
...props
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
appendChild(parent, child) {
|
|
289
|
+
parent.add(child);
|
|
290
|
+
},
|
|
291
|
+
removeChild(parent, child) {
|
|
292
|
+
parent.remove(child.id);
|
|
293
|
+
},
|
|
294
|
+
insertBefore(parent, child, beforeChild) {
|
|
295
|
+
parent.insertBefore(child, beforeChild);
|
|
296
|
+
},
|
|
297
|
+
insertInContainerBefore(parent, child, beforeChild) {
|
|
298
|
+
parent.insertBefore(child, beforeChild);
|
|
299
|
+
},
|
|
300
|
+
removeChildFromContainer(parent, child) {
|
|
301
|
+
parent.remove(child.id);
|
|
302
|
+
},
|
|
303
|
+
prepareForCommit(containerInfo) {
|
|
304
|
+
return null;
|
|
305
|
+
},
|
|
306
|
+
resetAfterCommit(containerInfo) {
|
|
307
|
+
containerInfo.requestRender();
|
|
308
|
+
},
|
|
309
|
+
getRootHostContext(rootContainerInstance) {
|
|
310
|
+
return { isInsideText: false };
|
|
311
|
+
},
|
|
312
|
+
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
313
|
+
const isInsideText = ["text", ...textNodeKeys].includes(type);
|
|
314
|
+
return { ...parentHostContext, isInsideText };
|
|
315
|
+
},
|
|
316
|
+
shouldSetTextContent(type, props) {
|
|
317
|
+
return false;
|
|
318
|
+
},
|
|
319
|
+
createTextInstance(text, rootContainerInstance, hostContext) {
|
|
320
|
+
if (!hostContext.isInsideText) {
|
|
321
|
+
throw new Error("Text must be created inside of a text node");
|
|
322
|
+
}
|
|
323
|
+
return TextNodeRenderable2.fromString(text);
|
|
324
|
+
},
|
|
325
|
+
scheduleTimeout: setTimeout,
|
|
326
|
+
cancelTimeout: clearTimeout,
|
|
327
|
+
noTimeout: -1,
|
|
328
|
+
shouldAttemptEagerTransition() {
|
|
329
|
+
return false;
|
|
330
|
+
},
|
|
331
|
+
finalizeInitialChildren(instance, type, props, rootContainerInstance, hostContext) {
|
|
332
|
+
setInitialProperties(instance, type, props);
|
|
333
|
+
return false;
|
|
334
|
+
},
|
|
335
|
+
commitMount(instance, type, props, internalInstanceHandle) {},
|
|
336
|
+
commitUpdate(instance, type, oldProps, newProps, internalInstanceHandle) {
|
|
337
|
+
updateProperties(instance, type, oldProps, newProps);
|
|
338
|
+
instance.requestRender();
|
|
339
|
+
},
|
|
340
|
+
commitTextUpdate(textInstance, oldText, newText) {
|
|
341
|
+
textInstance.children = [newText];
|
|
342
|
+
textInstance.requestRender();
|
|
343
|
+
},
|
|
344
|
+
appendChildToContainer(container, child) {
|
|
345
|
+
container.add(child);
|
|
346
|
+
},
|
|
347
|
+
appendInitialChild(parent, child) {
|
|
348
|
+
parent.add(child);
|
|
349
|
+
},
|
|
350
|
+
hideInstance(instance) {
|
|
351
|
+
instance.visible = false;
|
|
352
|
+
instance.requestRender();
|
|
353
|
+
},
|
|
354
|
+
unhideInstance(instance, props) {
|
|
355
|
+
instance.visible = true;
|
|
356
|
+
instance.requestRender();
|
|
357
|
+
},
|
|
358
|
+
hideTextInstance(textInstance) {
|
|
359
|
+
textInstance.visible = false;
|
|
360
|
+
textInstance.requestRender();
|
|
361
|
+
},
|
|
362
|
+
unhideTextInstance(textInstance, text) {
|
|
363
|
+
textInstance.visible = true;
|
|
364
|
+
textInstance.requestRender();
|
|
365
|
+
},
|
|
366
|
+
clearContainer(container) {
|
|
367
|
+
const children = container.getChildren();
|
|
368
|
+
children.forEach((child) => container.remove(child.id));
|
|
369
|
+
},
|
|
370
|
+
setCurrentUpdatePriority(newPriority) {
|
|
371
|
+
currentUpdatePriority = newPriority;
|
|
372
|
+
},
|
|
373
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
374
|
+
resolveUpdatePriority() {
|
|
375
|
+
if (currentUpdatePriority !== NoEventPriority) {
|
|
376
|
+
return currentUpdatePriority;
|
|
377
|
+
}
|
|
378
|
+
return DefaultEventPriority;
|
|
379
|
+
},
|
|
380
|
+
maySuspendCommit() {
|
|
381
|
+
return false;
|
|
382
|
+
},
|
|
383
|
+
NotPendingTransition: null,
|
|
384
|
+
HostTransitionContext: createContext2(null),
|
|
385
|
+
resetFormInstance() {},
|
|
386
|
+
requestPostPaintCallback() {},
|
|
387
|
+
trackSchedulerEvent() {},
|
|
388
|
+
resolveEventType() {
|
|
389
|
+
return null;
|
|
390
|
+
},
|
|
391
|
+
resolveEventTimeStamp() {
|
|
392
|
+
return -1.1;
|
|
393
|
+
},
|
|
394
|
+
preloadInstance() {
|
|
395
|
+
return true;
|
|
396
|
+
},
|
|
397
|
+
startSuspendingCommit() {},
|
|
398
|
+
suspendInstance() {},
|
|
399
|
+
waitForCommitToBeReady() {
|
|
400
|
+
return null;
|
|
401
|
+
},
|
|
402
|
+
detachDeletedInstance(instance) {
|
|
403
|
+
if (!instance.parent) {
|
|
404
|
+
instance.destroyRecursively();
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
getPublicInstance(instance) {
|
|
408
|
+
return instance;
|
|
409
|
+
},
|
|
410
|
+
preparePortalMount(containerInfo) {},
|
|
411
|
+
isPrimaryRenderer: true,
|
|
412
|
+
getInstanceFromNode() {
|
|
413
|
+
return null;
|
|
414
|
+
},
|
|
415
|
+
beforeActiveInstanceBlur() {},
|
|
416
|
+
afterActiveInstanceBlur() {},
|
|
417
|
+
prepareScopeUpdate() {},
|
|
418
|
+
getInstanceFromScope() {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/reconciler/reconciler.ts
|
|
424
|
+
var reconciler = ReactReconciler(hostConfig);
|
|
425
|
+
function _render(element, root) {
|
|
426
|
+
const container = reconciler.createContainer(root, ConcurrentRoot, null, false, null, "", console.error, console.error, console.error, console.error, null);
|
|
427
|
+
reconciler.updateContainer(element, container, null, () => {});
|
|
428
|
+
return container;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/reconciler/renderer.ts
|
|
432
|
+
function createRoot(renderer) {
|
|
433
|
+
let container = null;
|
|
434
|
+
return {
|
|
435
|
+
render: (node) => {
|
|
436
|
+
engine.attach(renderer);
|
|
437
|
+
container = _render(React2.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
438
|
+
},
|
|
439
|
+
unmount: () => {
|
|
440
|
+
if (!container) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
reconciler.updateContainer(null, container, null, () => {});
|
|
444
|
+
reconciler.flushSyncWork();
|
|
445
|
+
container = null;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/test-utils.ts
|
|
451
|
+
function setIsReactActEnvironment(isReactActEnvironment) {
|
|
452
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = isReactActEnvironment;
|
|
453
|
+
}
|
|
454
|
+
async function testRender(node, testRendererOptions) {
|
|
455
|
+
let root = null;
|
|
456
|
+
setIsReactActEnvironment(true);
|
|
457
|
+
const testSetup = await createTestRenderer({
|
|
458
|
+
...testRendererOptions,
|
|
459
|
+
onDestroy() {
|
|
460
|
+
act(() => {
|
|
461
|
+
if (root) {
|
|
462
|
+
root.unmount();
|
|
463
|
+
root = null;
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
testRendererOptions.onDestroy?.();
|
|
467
|
+
setIsReactActEnvironment(false);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
root = createRoot(testSetup.renderer);
|
|
471
|
+
act(() => {
|
|
472
|
+
if (root) {
|
|
473
|
+
root.render(node);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
return testSetup;
|
|
477
|
+
}
|
|
478
|
+
export {
|
|
479
|
+
testRender
|
|
480
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type TestRendererOptions } from "@opentui/core/testing";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export declare function testRender(node: ReactNode, testRendererOptions: TestRendererOptions): Promise<{
|
|
4
|
+
renderer: import("@opentui/core/testing").TestRenderer;
|
|
5
|
+
mockInput: import("@opentui/core/testing").MockInput;
|
|
6
|
+
mockMouse: import("@opentui/core/testing").MockMouse;
|
|
7
|
+
renderOnce: () => Promise<void>;
|
|
8
|
+
captureCharFrame: () => string;
|
|
9
|
+
resize: (width: number, height: number) => void;
|
|
10
|
+
}>;
|