@opentui/react 0.0.0-20250908-4906ddad → 0.0.0-20250912-12c969f4
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 +106 -52
- package/index.js +74 -65
- package/jsx-namespace.d.ts +8 -0
- package/package.json +2 -2
- package/src/components/index.d.ts +7 -0
- package/src/components/text.d.ts +20 -0
- package/src/reconciler/reconciler.d.ts +1 -1
- package/src/reconciler/renderer.js +74 -65
- package/src/types/components.d.ts +8 -5
- package/src/types/host.d.ts +6 -4
package/README.md
CHANGED
|
@@ -4,6 +4,14 @@ A React renderer for building terminal user interfaces using [OpenTUI core](http
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
Quick start with [bun](https://bun.sh) and [create-tui](https://github.com/msmps/create-tui):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun create tui --template react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Manual installation:
|
|
14
|
+
|
|
7
15
|
```bash
|
|
8
16
|
bun install @opentui/react @opentui/core react
|
|
9
17
|
```
|
|
@@ -14,14 +22,7 @@ bun install @opentui/react @opentui/core react
|
|
|
14
22
|
import { render } from "@opentui/react"
|
|
15
23
|
|
|
16
24
|
function App() {
|
|
17
|
-
return
|
|
18
|
-
<box>
|
|
19
|
-
<text fg="#00FF00">Hello, Terminal!</text>
|
|
20
|
-
<box title="Welcome" padding={2}>
|
|
21
|
-
<text>Welcome to OpenTUI with React!</text>
|
|
22
|
-
</box>
|
|
23
|
-
</box>
|
|
24
|
-
)
|
|
25
|
+
return <text>Hello, world!</text>
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
render(<App />)
|
|
@@ -56,6 +57,7 @@ OpenTUI React provides several built-in components that map to OpenTUI core rend
|
|
|
56
57
|
- **`<box>`** - Container with borders and layout
|
|
57
58
|
- **`<input>`** - Text input field
|
|
58
59
|
- **`<select>`** - Selection dropdown
|
|
60
|
+
- **`<scrollbox>`** - A scrollable box
|
|
59
61
|
- **`<tab-select>`** - Tab-based selection
|
|
60
62
|
- **`<ascii-font>`** - Display ASCII art text with different font styles
|
|
61
63
|
|
|
@@ -65,11 +67,13 @@ Components can be styled using props or the `style` prop:
|
|
|
65
67
|
|
|
66
68
|
```tsx
|
|
67
69
|
// Direct props
|
|
68
|
-
<
|
|
70
|
+
<box backgroundColor="blue" padding={2}>
|
|
71
|
+
<text>Hello, world!</text>
|
|
72
|
+
</box>
|
|
69
73
|
|
|
70
74
|
// Style prop
|
|
71
75
|
<box style={{ backgroundColor: "blue", padding: 2 }}>
|
|
72
|
-
<text>
|
|
76
|
+
<text>Hello, world!</text>
|
|
73
77
|
</box>
|
|
74
78
|
```
|
|
75
79
|
|
|
@@ -102,14 +106,15 @@ Access the OpenTUI renderer instance.
|
|
|
102
106
|
```tsx
|
|
103
107
|
import { useRenderer } from "@opentui/react"
|
|
104
108
|
|
|
105
|
-
function
|
|
109
|
+
function App() {
|
|
106
110
|
const renderer = useRenderer()
|
|
107
111
|
|
|
108
112
|
useEffect(() => {
|
|
109
|
-
renderer.
|
|
113
|
+
renderer.console.show()
|
|
114
|
+
console.log("Hello, from the console!")
|
|
110
115
|
}, [])
|
|
111
116
|
|
|
112
|
-
return <
|
|
117
|
+
return <box />
|
|
113
118
|
}
|
|
114
119
|
```
|
|
115
120
|
|
|
@@ -120,7 +125,7 @@ Handle keyboard events.
|
|
|
120
125
|
```tsx
|
|
121
126
|
import { useKeyboard } from "@opentui/react"
|
|
122
127
|
|
|
123
|
-
function
|
|
128
|
+
function App() {
|
|
124
129
|
useKeyboard((key) => {
|
|
125
130
|
if (key.name === "escape") {
|
|
126
131
|
process.exit(0)
|
|
@@ -139,7 +144,7 @@ Handle terminal resize events.
|
|
|
139
144
|
import { useOnResize, useRenderer } from "@opentui/react"
|
|
140
145
|
import { useEffect } from "react"
|
|
141
146
|
|
|
142
|
-
function
|
|
147
|
+
function App() {
|
|
143
148
|
const renderer = useRenderer()
|
|
144
149
|
|
|
145
150
|
useEffect(() => {
|
|
@@ -161,7 +166,7 @@ Get current terminal dimensions and automatically update when the terminal is re
|
|
|
161
166
|
```tsx
|
|
162
167
|
import { useTerminalDimensions } from "@opentui/react"
|
|
163
168
|
|
|
164
|
-
function
|
|
169
|
+
function App() {
|
|
165
170
|
const { width, height } = useTerminalDimensions()
|
|
166
171
|
|
|
167
172
|
return (
|
|
@@ -188,7 +193,7 @@ Display text with rich formatting.
|
|
|
188
193
|
```tsx
|
|
189
194
|
import { bold, fg, t } from "@opentui/core"
|
|
190
195
|
|
|
191
|
-
function
|
|
196
|
+
function App() {
|
|
192
197
|
return (
|
|
193
198
|
<box>
|
|
194
199
|
{/* Simple text */}
|
|
@@ -209,22 +214,23 @@ function TextExample() {
|
|
|
209
214
|
Container with borders and layout capabilities.
|
|
210
215
|
|
|
211
216
|
```tsx
|
|
212
|
-
function
|
|
217
|
+
function App() {
|
|
213
218
|
return (
|
|
214
219
|
<box flexDirection="column">
|
|
215
220
|
{/* Basic box */}
|
|
216
|
-
<box>
|
|
221
|
+
<box border>
|
|
217
222
|
<text>Simple box</text>
|
|
218
223
|
</box>
|
|
219
224
|
|
|
220
225
|
{/* Box with title and styling */}
|
|
221
|
-
<box title="Settings" borderStyle="double" padding={2} backgroundColor="blue">
|
|
226
|
+
<box title="Settings" border borderStyle="double" padding={2} backgroundColor="blue">
|
|
222
227
|
<text>Box content</text>
|
|
223
228
|
</box>
|
|
224
229
|
|
|
225
230
|
{/* Styled box */}
|
|
226
231
|
<box
|
|
227
232
|
style={{
|
|
233
|
+
border: true,
|
|
228
234
|
width: 40,
|
|
229
235
|
height: 10,
|
|
230
236
|
margin: 1,
|
|
@@ -246,20 +252,16 @@ Text input field with event handling.
|
|
|
246
252
|
```tsx
|
|
247
253
|
import { useState } from "react"
|
|
248
254
|
|
|
249
|
-
function
|
|
255
|
+
function App() {
|
|
250
256
|
const [value, setValue] = useState("")
|
|
251
|
-
const [focused, setFocused] = useState(true)
|
|
252
257
|
|
|
253
258
|
return (
|
|
254
|
-
<box title="Enter your name" style={{ height: 3 }}>
|
|
259
|
+
<box title="Enter your name" style={{ border: true, height: 3 }}>
|
|
255
260
|
<input
|
|
256
261
|
placeholder="Type here..."
|
|
257
|
-
focused
|
|
262
|
+
focused
|
|
258
263
|
onInput={setValue}
|
|
259
264
|
onSubmit={(value) => console.log("Submitted:", value)}
|
|
260
|
-
style={{
|
|
261
|
-
focusedBackgroundColor: "#333333",
|
|
262
|
-
}}
|
|
263
265
|
/>
|
|
264
266
|
</box>
|
|
265
267
|
)
|
|
@@ -274,7 +276,7 @@ Dropdown selection component.
|
|
|
274
276
|
import type { SelectOption } from "@opentui/core"
|
|
275
277
|
import { useState } from "react"
|
|
276
278
|
|
|
277
|
-
function
|
|
279
|
+
function App() {
|
|
278
280
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
279
281
|
|
|
280
282
|
const options: SelectOption[] = [
|
|
@@ -284,7 +286,7 @@ function SelectExample() {
|
|
|
284
286
|
]
|
|
285
287
|
|
|
286
288
|
return (
|
|
287
|
-
<box style={{ height: 24 }}>
|
|
289
|
+
<box style={{ border: true, height: 24 }}>
|
|
288
290
|
<select
|
|
289
291
|
style={{ height: 22 }}
|
|
290
292
|
options={options}
|
|
@@ -299,6 +301,50 @@ function SelectExample() {
|
|
|
299
301
|
}
|
|
300
302
|
```
|
|
301
303
|
|
|
304
|
+
### Scrollbox Component
|
|
305
|
+
|
|
306
|
+
A scrollable box.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
function App() {
|
|
310
|
+
return (
|
|
311
|
+
<scrollbox
|
|
312
|
+
style={{
|
|
313
|
+
rootOptions: {
|
|
314
|
+
backgroundColor: "#24283b",
|
|
315
|
+
},
|
|
316
|
+
wrapperOptions: {
|
|
317
|
+
backgroundColor: "#1f2335",
|
|
318
|
+
},
|
|
319
|
+
viewportOptions: {
|
|
320
|
+
backgroundColor: "#1a1b26",
|
|
321
|
+
},
|
|
322
|
+
contentOptions: {
|
|
323
|
+
backgroundColor: "#16161e",
|
|
324
|
+
},
|
|
325
|
+
scrollbarOptions: {
|
|
326
|
+
showArrows: true,
|
|
327
|
+
trackOptions: {
|
|
328
|
+
foregroundColor: "#7aa2f7",
|
|
329
|
+
backgroundColor: "#414868",
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
}}
|
|
333
|
+
focused
|
|
334
|
+
>
|
|
335
|
+
{Array.from({ length: 1000 }).map((_, i) => (
|
|
336
|
+
<box
|
|
337
|
+
key={i}
|
|
338
|
+
style={{ width: "100%", padding: 1, marginBottom: 1, backgroundColor: i % 2 === 0 ? "#292e42" : "#2f3449" }}
|
|
339
|
+
>
|
|
340
|
+
<text content={`Box ${i}`} />
|
|
341
|
+
</box>
|
|
342
|
+
))}
|
|
343
|
+
</scrollbox>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
302
348
|
### ASCII Font Component
|
|
303
349
|
|
|
304
350
|
Display ASCII art text with different font styles.
|
|
@@ -307,7 +353,7 @@ Display ASCII art text with different font styles.
|
|
|
307
353
|
import { measureText } from "@opentui/core"
|
|
308
354
|
import { useState } from "react"
|
|
309
355
|
|
|
310
|
-
function
|
|
356
|
+
function App() {
|
|
311
357
|
const text = "ASCII"
|
|
312
358
|
const [font, setFont] = useState<"block" | "shade" | "slick" | "tiny">("tiny")
|
|
313
359
|
|
|
@@ -317,10 +363,11 @@ function ASCIIFontExample() {
|
|
|
317
363
|
})
|
|
318
364
|
|
|
319
365
|
return (
|
|
320
|
-
<box style={{ paddingLeft: 1, paddingRight: 1 }}>
|
|
366
|
+
<box style={{ border: true, paddingLeft: 1, paddingRight: 1 }}>
|
|
321
367
|
<box
|
|
322
368
|
style={{
|
|
323
369
|
height: 8,
|
|
370
|
+
border: true,
|
|
324
371
|
marginBottom: 1,
|
|
325
372
|
}}
|
|
326
373
|
>
|
|
@@ -365,10 +412,10 @@ function ASCIIFontExample() {
|
|
|
365
412
|
### Login Form
|
|
366
413
|
|
|
367
414
|
```tsx
|
|
368
|
-
import { useState, useCallback } from "react"
|
|
369
415
|
import { render, useKeyboard } from "@opentui/react"
|
|
416
|
+
import { useCallback, useState } from "react"
|
|
370
417
|
|
|
371
|
-
function
|
|
418
|
+
function App() {
|
|
372
419
|
const [username, setUsername] = useState("")
|
|
373
420
|
const [password, setPassword] = useState("")
|
|
374
421
|
const [focused, setFocused] = useState<"username" | "password">("username")
|
|
@@ -389,10 +436,10 @@ function LoginForm() {
|
|
|
389
436
|
}, [username, password])
|
|
390
437
|
|
|
391
438
|
return (
|
|
392
|
-
<box style={{ padding: 2, flexDirection: "column" }}>
|
|
439
|
+
<box style={{ border: true, padding: 2, flexDirection: "column", gap: 1 }}>
|
|
393
440
|
<text fg="#FFFF00">Login Form</text>
|
|
394
441
|
|
|
395
|
-
<box title="Username" style={{ width: 40, height: 3
|
|
442
|
+
<box title="Username" style={{ border: true, width: 40, height: 3 }}>
|
|
396
443
|
<input
|
|
397
444
|
placeholder="Enter username..."
|
|
398
445
|
onInput={setUsername}
|
|
@@ -401,7 +448,7 @@ function LoginForm() {
|
|
|
401
448
|
/>
|
|
402
449
|
</box>
|
|
403
450
|
|
|
404
|
-
<box title="Password" style={{ width: 40, height: 3
|
|
451
|
+
<box title="Password" style={{ border: true, width: 40, height: 3 }}>
|
|
405
452
|
<input
|
|
406
453
|
placeholder="Enter password..."
|
|
407
454
|
onInput={setPassword}
|
|
@@ -421,16 +468,16 @@ function LoginForm() {
|
|
|
421
468
|
)
|
|
422
469
|
}
|
|
423
470
|
|
|
424
|
-
render(<
|
|
471
|
+
render(<App />)
|
|
425
472
|
```
|
|
426
473
|
|
|
427
474
|
### Counter with Timer
|
|
428
475
|
|
|
429
476
|
```tsx
|
|
430
|
-
import { useState, useEffect } from "react"
|
|
431
477
|
import { render } from "@opentui/react"
|
|
478
|
+
import { useEffect, useState } from "react"
|
|
432
479
|
|
|
433
|
-
function
|
|
480
|
+
function App() {
|
|
434
481
|
const [count, setCount] = useState(0)
|
|
435
482
|
|
|
436
483
|
useEffect(() => {
|
|
@@ -448,7 +495,7 @@ function Counter() {
|
|
|
448
495
|
)
|
|
449
496
|
}
|
|
450
497
|
|
|
451
|
-
render(<
|
|
498
|
+
render(<App />)
|
|
452
499
|
```
|
|
453
500
|
|
|
454
501
|
### Styled Text Showcase
|
|
@@ -457,7 +504,7 @@ render(<Counter />)
|
|
|
457
504
|
import { blue, bold, red, t, underline } from "@opentui/core"
|
|
458
505
|
import { render } from "@opentui/react"
|
|
459
506
|
|
|
460
|
-
function
|
|
507
|
+
function App() {
|
|
461
508
|
return (
|
|
462
509
|
<box style={{ flexDirection: "column" }}>
|
|
463
510
|
<text>Simple text</text>
|
|
@@ -471,25 +518,32 @@ function StyledTextShowcase() {
|
|
|
471
518
|
)
|
|
472
519
|
}
|
|
473
520
|
|
|
474
|
-
render(<
|
|
521
|
+
render(<App />)
|
|
475
522
|
```
|
|
476
523
|
|
|
477
524
|
## Component Extension
|
|
478
525
|
|
|
479
|
-
You can create custom components by extending
|
|
526
|
+
You can create custom components by extending OpenTUIs base renderables:
|
|
480
527
|
|
|
481
528
|
```tsx
|
|
482
|
-
import { BoxRenderable, OptimizedBuffer, RGBA } from "@opentui/core"
|
|
529
|
+
import { BoxRenderable, OptimizedBuffer, RGBA, type BoxOptions, type RenderContext } from "@opentui/core"
|
|
483
530
|
import { extend, render } from "@opentui/react"
|
|
484
531
|
|
|
485
532
|
// Create custom component class
|
|
486
533
|
class ButtonRenderable extends BoxRenderable {
|
|
487
534
|
private _label: string = "Button"
|
|
488
535
|
|
|
489
|
-
constructor(
|
|
490
|
-
super(
|
|
491
|
-
|
|
492
|
-
|
|
536
|
+
constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) {
|
|
537
|
+
super(ctx, {
|
|
538
|
+
border: true,
|
|
539
|
+
borderStyle: "single",
|
|
540
|
+
minHeight: 3,
|
|
541
|
+
...options,
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
if (options.label) {
|
|
545
|
+
this._label = options.label
|
|
546
|
+
}
|
|
493
547
|
}
|
|
494
548
|
|
|
495
549
|
protected renderSelf(buffer: OptimizedBuffer): void {
|
|
@@ -510,19 +564,19 @@ class ButtonRenderable extends BoxRenderable {
|
|
|
510
564
|
// Add TypeScript support
|
|
511
565
|
declare module "@opentui/react" {
|
|
512
566
|
interface OpenTUIComponents {
|
|
513
|
-
|
|
567
|
+
consoleButton: typeof ButtonRenderable
|
|
514
568
|
}
|
|
515
569
|
}
|
|
516
570
|
|
|
517
571
|
// Register the component
|
|
518
|
-
extend({
|
|
572
|
+
extend({ consoleButton: ButtonRenderable })
|
|
519
573
|
|
|
520
574
|
// Use in JSX
|
|
521
575
|
function App() {
|
|
522
576
|
return (
|
|
523
577
|
<box>
|
|
524
|
-
<
|
|
525
|
-
<
|
|
578
|
+
<consoleButton label="Click me!" style={{ backgroundColor: "blue" }} />
|
|
579
|
+
<consoleButton label="Another button" style={{ backgroundColor: "green" }} />
|
|
526
580
|
</box>
|
|
527
581
|
)
|
|
528
582
|
}
|
package/index.js
CHANGED
|
@@ -9,6 +9,51 @@ import {
|
|
|
9
9
|
TabSelectRenderable,
|
|
10
10
|
TextRenderable
|
|
11
11
|
} from "@opentui/core";
|
|
12
|
+
|
|
13
|
+
// src/components/text.ts
|
|
14
|
+
import { TextAttributes, TextNodeRenderable } from "@opentui/core";
|
|
15
|
+
var textNodeKeys = ["span", "b", "strong", "i", "em", "u"];
|
|
16
|
+
|
|
17
|
+
class SpanRenderable extends TextNodeRenderable {
|
|
18
|
+
ctx;
|
|
19
|
+
constructor(ctx, options) {
|
|
20
|
+
super(options);
|
|
21
|
+
this.ctx = ctx;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TextModifierRenderable extends SpanRenderable {
|
|
26
|
+
constructor(options, modifier) {
|
|
27
|
+
super(null, options);
|
|
28
|
+
if (modifier === "b" || modifier === "strong") {
|
|
29
|
+
this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
|
|
30
|
+
} else if (modifier === "i" || modifier === "em") {
|
|
31
|
+
this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
|
|
32
|
+
} else if (modifier === "u") {
|
|
33
|
+
this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class BoldSpanRenderable extends TextModifierRenderable {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
super(options, "b");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
super(options, "i");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
51
|
+
constructor(options) {
|
|
52
|
+
super(options, "u");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/components/index.ts
|
|
12
57
|
var baseComponents = {
|
|
13
58
|
box: BoxRenderable,
|
|
14
59
|
text: TextRenderable,
|
|
@@ -16,7 +61,13 @@ var baseComponents = {
|
|
|
16
61
|
select: SelectRenderable,
|
|
17
62
|
scrollbox: ScrollBoxRenderable,
|
|
18
63
|
"ascii-font": ASCIIFontRenderable,
|
|
19
|
-
"tab-select": TabSelectRenderable
|
|
64
|
+
"tab-select": TabSelectRenderable,
|
|
65
|
+
span: SpanRenderable,
|
|
66
|
+
b: BoldSpanRenderable,
|
|
67
|
+
strong: BoldSpanRenderable,
|
|
68
|
+
i: ItalicSpanRenderable,
|
|
69
|
+
em: ItalicSpanRenderable,
|
|
70
|
+
u: UnderlineSpanRenderable
|
|
20
71
|
};
|
|
21
72
|
var componentCatalogue = { ...baseComponents };
|
|
22
73
|
function extend(objects) {
|
|
@@ -104,6 +155,7 @@ import ReactReconciler from "react-reconciler";
|
|
|
104
155
|
import { ConcurrentRoot } from "react-reconciler/constants";
|
|
105
156
|
|
|
106
157
|
// src/reconciler/host-config.ts
|
|
158
|
+
import { TextNodeRenderable as TextNodeRenderable2 } from "@opentui/core";
|
|
107
159
|
import { createContext as createContext2 } from "react";
|
|
108
160
|
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
|
|
109
161
|
|
|
@@ -122,13 +174,11 @@ function getNextId(type) {
|
|
|
122
174
|
import {
|
|
123
175
|
InputRenderable as InputRenderable2,
|
|
124
176
|
InputRenderableEvents,
|
|
177
|
+
isRenderable,
|
|
125
178
|
SelectRenderable as SelectRenderable2,
|
|
126
179
|
SelectRenderableEvents,
|
|
127
|
-
StyledText,
|
|
128
180
|
TabSelectRenderable as TabSelectRenderable2,
|
|
129
|
-
TabSelectRenderableEvents
|
|
130
|
-
TextRenderable as TextRenderable2,
|
|
131
|
-
stringToStyledText
|
|
181
|
+
TabSelectRenderableEvents
|
|
132
182
|
} from "@opentui/core";
|
|
133
183
|
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
134
184
|
if (previousListener) {
|
|
@@ -138,46 +188,6 @@ function initEventListeners(instance, eventName, listener, previousListener) {
|
|
|
138
188
|
instance.on(eventName, listener);
|
|
139
189
|
}
|
|
140
190
|
}
|
|
141
|
-
function handleTextChildren(textInstance, children) {
|
|
142
|
-
if (children == null) {
|
|
143
|
-
textInstance.content = stringToStyledText("");
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (Array.isArray(children)) {
|
|
147
|
-
const chunks = [];
|
|
148
|
-
for (const child of children) {
|
|
149
|
-
if (typeof child === "string") {
|
|
150
|
-
chunks.push({
|
|
151
|
-
__isChunk: true,
|
|
152
|
-
text: new TextEncoder().encode(child),
|
|
153
|
-
plainText: child
|
|
154
|
-
});
|
|
155
|
-
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
156
|
-
chunks.push(child);
|
|
157
|
-
} else if (child instanceof StyledText) {
|
|
158
|
-
chunks.push(...child.chunks);
|
|
159
|
-
} else if (child != null) {
|
|
160
|
-
const stringValue = String(child);
|
|
161
|
-
chunks.push({
|
|
162
|
-
__isChunk: true,
|
|
163
|
-
text: new TextEncoder().encode(stringValue),
|
|
164
|
-
plainText: stringValue
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
textInstance.content = new StyledText(chunks);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (typeof children === "string") {
|
|
172
|
-
textInstance.content = stringToStyledText(children);
|
|
173
|
-
} else if (children && typeof children === "object" && "__isChunk" in children) {
|
|
174
|
-
textInstance.content = new StyledText([children]);
|
|
175
|
-
} else if (children instanceof StyledText) {
|
|
176
|
-
textInstance.content = children;
|
|
177
|
-
} else {
|
|
178
|
-
textInstance.content = stringToStyledText(String(children));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
191
|
function setStyle(instance, styles, oldStyles) {
|
|
182
192
|
if (styles && typeof styles === "object") {
|
|
183
193
|
if (oldStyles != null) {
|
|
@@ -226,19 +236,18 @@ function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
|
226
236
|
}
|
|
227
237
|
break;
|
|
228
238
|
case "focused":
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
239
|
+
if (isRenderable(instance)) {
|
|
240
|
+
if (!!propValue) {
|
|
241
|
+
instance.focus();
|
|
242
|
+
} else {
|
|
243
|
+
instance.blur();
|
|
244
|
+
}
|
|
233
245
|
}
|
|
234
246
|
break;
|
|
235
247
|
case "style":
|
|
236
248
|
setStyle(instance, propValue, oldPropValue);
|
|
237
249
|
break;
|
|
238
250
|
case "children":
|
|
239
|
-
if (type === "text" && instance instanceof TextRenderable2) {
|
|
240
|
-
handleTextChildren(instance, propValue);
|
|
241
|
-
}
|
|
242
251
|
break;
|
|
243
252
|
default:
|
|
244
253
|
instance[propKey] = propValue;
|
|
@@ -279,10 +288,13 @@ var hostConfig = {
|
|
|
279
288
|
supportsPersistence: false,
|
|
280
289
|
supportsHydration: false,
|
|
281
290
|
createInstance(type, props, rootContainerInstance, hostContext) {
|
|
291
|
+
if (textNodeKeys.includes(type) && !hostContext.isInsideText) {
|
|
292
|
+
throw new Error(`Component of type "${type}" must be created inside of a text node`);
|
|
293
|
+
}
|
|
282
294
|
const id = getNextId(type);
|
|
283
295
|
const components = getComponentCatalogue();
|
|
284
296
|
if (!components[type]) {
|
|
285
|
-
throw new Error(`
|
|
297
|
+
throw new Error(`Unknown component type: ${type}`);
|
|
286
298
|
}
|
|
287
299
|
return new components[type](rootContainerInstance.ctx, {
|
|
288
300
|
id,
|
|
@@ -311,23 +323,20 @@ var hostConfig = {
|
|
|
311
323
|
containerInfo.requestRender();
|
|
312
324
|
},
|
|
313
325
|
getRootHostContext(rootContainerInstance) {
|
|
314
|
-
return {};
|
|
326
|
+
return { isInsideText: false };
|
|
315
327
|
},
|
|
316
328
|
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
317
|
-
|
|
329
|
+
const isInsideText = ["text", "span", "b", "strong", "i", "em", "u"].includes(type);
|
|
330
|
+
return { ...parentHostContext, isInsideText };
|
|
318
331
|
},
|
|
319
332
|
shouldSetTextContent(type, props) {
|
|
320
|
-
if (type === "text") {
|
|
321
|
-
return true;
|
|
322
|
-
}
|
|
323
333
|
return false;
|
|
324
334
|
},
|
|
325
335
|
createTextInstance(text, rootContainerInstance, hostContext) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
336
|
+
if (!hostContext.isInsideText) {
|
|
337
|
+
throw new Error("Text must be created inside of a text node");
|
|
338
|
+
}
|
|
339
|
+
return TextNodeRenderable2.fromString(text);
|
|
331
340
|
},
|
|
332
341
|
scheduleTimeout: setTimeout,
|
|
333
342
|
cancelTimeout: clearTimeout,
|
|
@@ -345,7 +354,7 @@ var hostConfig = {
|
|
|
345
354
|
instance.requestRender();
|
|
346
355
|
},
|
|
347
356
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
348
|
-
textInstance.
|
|
357
|
+
textInstance.children = [newText];
|
|
349
358
|
textInstance.requestRender();
|
|
350
359
|
},
|
|
351
360
|
appendChildToContainer(container, child) {
|
|
@@ -408,7 +417,7 @@ var hostConfig = {
|
|
|
408
417
|
},
|
|
409
418
|
detachDeletedInstance(instance) {
|
|
410
419
|
if (!instance.parent) {
|
|
411
|
-
instance.
|
|
420
|
+
instance.destroyRecursively();
|
|
412
421
|
}
|
|
413
422
|
},
|
|
414
423
|
getPublicInstance(instance) {
|
package/jsx-namespace.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OpenTUIComponents,
|
|
8
8
|
ScrollBoxProps,
|
|
9
9
|
SelectProps,
|
|
10
|
+
SpanProps,
|
|
10
11
|
TabSelectProps,
|
|
11
12
|
TextProps,
|
|
12
13
|
} from "./src/types/components"
|
|
@@ -29,10 +30,17 @@ export namespace JSX {
|
|
|
29
30
|
interface IntrinsicElements extends React.JSX.IntrinsicElements, ExtendedIntrinsicElements<OpenTUIComponents> {
|
|
30
31
|
box: BoxProps
|
|
31
32
|
text: TextProps
|
|
33
|
+
span: SpanProps
|
|
32
34
|
input: InputProps
|
|
33
35
|
select: SelectProps
|
|
34
36
|
scrollbox: ScrollBoxProps
|
|
35
37
|
"ascii-font": AsciiFontProps
|
|
36
38
|
"tab-select": TabSelectProps
|
|
39
|
+
// Text modifiers
|
|
40
|
+
b: SpanProps
|
|
41
|
+
i: SpanProps
|
|
42
|
+
u: SpanProps
|
|
43
|
+
strong: SpanProps
|
|
44
|
+
em: SpanProps
|
|
37
45
|
}
|
|
38
46
|
}
|
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.0.0-
|
|
7
|
+
"version": "0.0.0-20250912-12c969f4",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@opentui/core": "0.0.0-
|
|
38
|
+
"@opentui/core": "0.0.0-20250912-12c969f4",
|
|
39
39
|
"react-reconciler": "^0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ASCIIFontRenderable, BoxRenderable, InputRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
|
|
2
2
|
import type { RenderableConstructor } from "../types/components";
|
|
3
|
+
import { BoldSpanRenderable, ItalicSpanRenderable, SpanRenderable, UnderlineSpanRenderable } from "./text";
|
|
3
4
|
export declare const baseComponents: {
|
|
4
5
|
box: typeof BoxRenderable;
|
|
5
6
|
text: typeof TextRenderable;
|
|
@@ -8,6 +9,12 @@ export declare const baseComponents: {
|
|
|
8
9
|
scrollbox: typeof ScrollBoxRenderable;
|
|
9
10
|
"ascii-font": typeof ASCIIFontRenderable;
|
|
10
11
|
"tab-select": typeof TabSelectRenderable;
|
|
12
|
+
span: typeof SpanRenderable;
|
|
13
|
+
b: typeof BoldSpanRenderable;
|
|
14
|
+
strong: typeof BoldSpanRenderable;
|
|
15
|
+
i: typeof ItalicSpanRenderable;
|
|
16
|
+
em: typeof ItalicSpanRenderable;
|
|
17
|
+
u: typeof UnderlineSpanRenderable;
|
|
11
18
|
};
|
|
12
19
|
type ComponentCatalogue = Record<string, RenderableConstructor>;
|
|
13
20
|
export declare const componentCatalogue: ComponentCatalogue;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TextNodeRenderable, type RenderContext, type TextNodeOptions } from "@opentui/core";
|
|
2
|
+
export declare const textNodeKeys: readonly ["span", "b", "strong", "i", "em", "u"];
|
|
3
|
+
export type TextNodeKey = (typeof textNodeKeys)[number];
|
|
4
|
+
export declare class SpanRenderable extends TextNodeRenderable {
|
|
5
|
+
private readonly ctx;
|
|
6
|
+
constructor(ctx: RenderContext | null, options: TextNodeOptions);
|
|
7
|
+
}
|
|
8
|
+
declare class TextModifierRenderable extends SpanRenderable {
|
|
9
|
+
constructor(options: any, modifier?: TextNodeKey);
|
|
10
|
+
}
|
|
11
|
+
export declare class BoldSpanRenderable extends TextModifierRenderable {
|
|
12
|
+
constructor(options: any);
|
|
13
|
+
}
|
|
14
|
+
export declare class ItalicSpanRenderable extends TextModifierRenderable {
|
|
15
|
+
constructor(options: any);
|
|
16
|
+
}
|
|
17
|
+
export declare class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
18
|
+
constructor(options: any);
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RootRenderable } from "@opentui/core";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import ReactReconciler from "react-reconciler";
|
|
4
|
-
export declare const reconciler: ReactReconciler.Reconciler<RootRenderable, import("@opentui/core").
|
|
4
|
+
export declare const reconciler: ReactReconciler.Reconciler<RootRenderable, import("@opentui/core").BaseRenderable, import("@opentui/core").TextNodeRenderable, unknown, unknown, import("@opentui/core").BaseRenderable>;
|
|
5
5
|
export declare function _render(element: React.ReactNode, root: RootRenderable): void;
|
|
@@ -15,6 +15,7 @@ import ReactReconciler from "react-reconciler";
|
|
|
15
15
|
import { ConcurrentRoot } from "react-reconciler/constants";
|
|
16
16
|
|
|
17
17
|
// src/reconciler/host-config.ts
|
|
18
|
+
import { TextNodeRenderable as TextNodeRenderable2 } from "@opentui/core";
|
|
18
19
|
import { createContext as createContext2 } from "react";
|
|
19
20
|
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
|
|
20
21
|
|
|
@@ -28,6 +29,51 @@ import {
|
|
|
28
29
|
TabSelectRenderable,
|
|
29
30
|
TextRenderable
|
|
30
31
|
} from "@opentui/core";
|
|
32
|
+
|
|
33
|
+
// src/components/text.ts
|
|
34
|
+
import { TextAttributes, TextNodeRenderable } from "@opentui/core";
|
|
35
|
+
var textNodeKeys = ["span", "b", "strong", "i", "em", "u"];
|
|
36
|
+
|
|
37
|
+
class SpanRenderable extends TextNodeRenderable {
|
|
38
|
+
ctx;
|
|
39
|
+
constructor(ctx, options) {
|
|
40
|
+
super(options);
|
|
41
|
+
this.ctx = ctx;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class TextModifierRenderable extends SpanRenderable {
|
|
46
|
+
constructor(options, modifier) {
|
|
47
|
+
super(null, options);
|
|
48
|
+
if (modifier === "b" || modifier === "strong") {
|
|
49
|
+
this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
|
|
50
|
+
} else if (modifier === "i" || modifier === "em") {
|
|
51
|
+
this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
|
|
52
|
+
} else if (modifier === "u") {
|
|
53
|
+
this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class BoldSpanRenderable extends TextModifierRenderable {
|
|
59
|
+
constructor(options) {
|
|
60
|
+
super(options, "b");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
65
|
+
constructor(options) {
|
|
66
|
+
super(options, "i");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
71
|
+
constructor(options) {
|
|
72
|
+
super(options, "u");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/components/index.ts
|
|
31
77
|
var baseComponents = {
|
|
32
78
|
box: BoxRenderable,
|
|
33
79
|
text: TextRenderable,
|
|
@@ -35,7 +81,13 @@ var baseComponents = {
|
|
|
35
81
|
select: SelectRenderable,
|
|
36
82
|
scrollbox: ScrollBoxRenderable,
|
|
37
83
|
"ascii-font": ASCIIFontRenderable,
|
|
38
|
-
"tab-select": TabSelectRenderable
|
|
84
|
+
"tab-select": TabSelectRenderable,
|
|
85
|
+
span: SpanRenderable,
|
|
86
|
+
b: BoldSpanRenderable,
|
|
87
|
+
strong: BoldSpanRenderable,
|
|
88
|
+
i: ItalicSpanRenderable,
|
|
89
|
+
em: ItalicSpanRenderable,
|
|
90
|
+
u: UnderlineSpanRenderable
|
|
39
91
|
};
|
|
40
92
|
var componentCatalogue = { ...baseComponents };
|
|
41
93
|
function getComponentCatalogue() {
|
|
@@ -57,13 +109,11 @@ function getNextId(type) {
|
|
|
57
109
|
import {
|
|
58
110
|
InputRenderable as InputRenderable2,
|
|
59
111
|
InputRenderableEvents,
|
|
112
|
+
isRenderable,
|
|
60
113
|
SelectRenderable as SelectRenderable2,
|
|
61
114
|
SelectRenderableEvents,
|
|
62
|
-
StyledText,
|
|
63
115
|
TabSelectRenderable as TabSelectRenderable2,
|
|
64
|
-
TabSelectRenderableEvents
|
|
65
|
-
TextRenderable as TextRenderable2,
|
|
66
|
-
stringToStyledText
|
|
116
|
+
TabSelectRenderableEvents
|
|
67
117
|
} from "@opentui/core";
|
|
68
118
|
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
69
119
|
if (previousListener) {
|
|
@@ -73,46 +123,6 @@ function initEventListeners(instance, eventName, listener, previousListener) {
|
|
|
73
123
|
instance.on(eventName, listener);
|
|
74
124
|
}
|
|
75
125
|
}
|
|
76
|
-
function handleTextChildren(textInstance, children) {
|
|
77
|
-
if (children == null) {
|
|
78
|
-
textInstance.content = stringToStyledText("");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (Array.isArray(children)) {
|
|
82
|
-
const chunks = [];
|
|
83
|
-
for (const child of children) {
|
|
84
|
-
if (typeof child === "string") {
|
|
85
|
-
chunks.push({
|
|
86
|
-
__isChunk: true,
|
|
87
|
-
text: new TextEncoder().encode(child),
|
|
88
|
-
plainText: child
|
|
89
|
-
});
|
|
90
|
-
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
91
|
-
chunks.push(child);
|
|
92
|
-
} else if (child instanceof StyledText) {
|
|
93
|
-
chunks.push(...child.chunks);
|
|
94
|
-
} else if (child != null) {
|
|
95
|
-
const stringValue = String(child);
|
|
96
|
-
chunks.push({
|
|
97
|
-
__isChunk: true,
|
|
98
|
-
text: new TextEncoder().encode(stringValue),
|
|
99
|
-
plainText: stringValue
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
textInstance.content = new StyledText(chunks);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (typeof children === "string") {
|
|
107
|
-
textInstance.content = stringToStyledText(children);
|
|
108
|
-
} else if (children && typeof children === "object" && "__isChunk" in children) {
|
|
109
|
-
textInstance.content = new StyledText([children]);
|
|
110
|
-
} else if (children instanceof StyledText) {
|
|
111
|
-
textInstance.content = children;
|
|
112
|
-
} else {
|
|
113
|
-
textInstance.content = stringToStyledText(String(children));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
126
|
function setStyle(instance, styles, oldStyles) {
|
|
117
127
|
if (styles && typeof styles === "object") {
|
|
118
128
|
if (oldStyles != null) {
|
|
@@ -161,19 +171,18 @@ function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
|
161
171
|
}
|
|
162
172
|
break;
|
|
163
173
|
case "focused":
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
if (isRenderable(instance)) {
|
|
175
|
+
if (!!propValue) {
|
|
176
|
+
instance.focus();
|
|
177
|
+
} else {
|
|
178
|
+
instance.blur();
|
|
179
|
+
}
|
|
168
180
|
}
|
|
169
181
|
break;
|
|
170
182
|
case "style":
|
|
171
183
|
setStyle(instance, propValue, oldPropValue);
|
|
172
184
|
break;
|
|
173
185
|
case "children":
|
|
174
|
-
if (type === "text" && instance instanceof TextRenderable2) {
|
|
175
|
-
handleTextChildren(instance, propValue);
|
|
176
|
-
}
|
|
177
186
|
break;
|
|
178
187
|
default:
|
|
179
188
|
instance[propKey] = propValue;
|
|
@@ -214,10 +223,13 @@ var hostConfig = {
|
|
|
214
223
|
supportsPersistence: false,
|
|
215
224
|
supportsHydration: false,
|
|
216
225
|
createInstance(type, props, rootContainerInstance, hostContext) {
|
|
226
|
+
if (textNodeKeys.includes(type) && !hostContext.isInsideText) {
|
|
227
|
+
throw new Error(`Component of type "${type}" must be created inside of a text node`);
|
|
228
|
+
}
|
|
217
229
|
const id = getNextId(type);
|
|
218
230
|
const components = getComponentCatalogue();
|
|
219
231
|
if (!components[type]) {
|
|
220
|
-
throw new Error(`
|
|
232
|
+
throw new Error(`Unknown component type: ${type}`);
|
|
221
233
|
}
|
|
222
234
|
return new components[type](rootContainerInstance.ctx, {
|
|
223
235
|
id,
|
|
@@ -246,23 +258,20 @@ var hostConfig = {
|
|
|
246
258
|
containerInfo.requestRender();
|
|
247
259
|
},
|
|
248
260
|
getRootHostContext(rootContainerInstance) {
|
|
249
|
-
return {};
|
|
261
|
+
return { isInsideText: false };
|
|
250
262
|
},
|
|
251
263
|
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
252
|
-
|
|
264
|
+
const isInsideText = ["text", "span", "b", "strong", "i", "em", "u"].includes(type);
|
|
265
|
+
return { ...parentHostContext, isInsideText };
|
|
253
266
|
},
|
|
254
267
|
shouldSetTextContent(type, props) {
|
|
255
|
-
if (type === "text") {
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
268
|
return false;
|
|
259
269
|
},
|
|
260
270
|
createTextInstance(text, rootContainerInstance, hostContext) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
271
|
+
if (!hostContext.isInsideText) {
|
|
272
|
+
throw new Error("Text must be created inside of a text node");
|
|
273
|
+
}
|
|
274
|
+
return TextNodeRenderable2.fromString(text);
|
|
266
275
|
},
|
|
267
276
|
scheduleTimeout: setTimeout,
|
|
268
277
|
cancelTimeout: clearTimeout,
|
|
@@ -280,7 +289,7 @@ var hostConfig = {
|
|
|
280
289
|
instance.requestRender();
|
|
281
290
|
},
|
|
282
291
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
283
|
-
textInstance.
|
|
292
|
+
textInstance.children = [newText];
|
|
284
293
|
textInstance.requestRender();
|
|
285
294
|
},
|
|
286
295
|
appendChildToContainer(container, child) {
|
|
@@ -343,7 +352,7 @@ var hostConfig = {
|
|
|
343
352
|
},
|
|
344
353
|
detachDeletedInstance(instance) {
|
|
345
354
|
if (!instance.parent) {
|
|
346
|
-
instance.
|
|
355
|
+
instance.destroyRecursively();
|
|
347
356
|
}
|
|
348
357
|
},
|
|
349
358
|
getPublicInstance(instance) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions,
|
|
1
|
+
import type { ASCIIFontOptions, ASCIIFontRenderable, BaseRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextNodeOptions, TextNodeRenderable, TextOptions, TextRenderable } from "@opentui/core";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
/** Properties that should not be included in the style prop */
|
|
4
4
|
export type NonStyledProps = "id" | "buffered" | "live" | "enableLayout" | "selectable" | "renderAfter" | "renderBefore" | `on${string}`;
|
|
@@ -8,7 +8,7 @@ export type ReactProps<TRenderable = unknown> = {
|
|
|
8
8
|
ref?: React.Ref<TRenderable>;
|
|
9
9
|
};
|
|
10
10
|
/** Base type for any renderable constructor */
|
|
11
|
-
export type RenderableConstructor<TRenderable extends
|
|
11
|
+
export type RenderableConstructor<TRenderable extends BaseRenderable = BaseRenderable> = new (ctx: RenderContext, options: any) => TRenderable;
|
|
12
12
|
/** Extract the options type from a renderable constructor */
|
|
13
13
|
type ExtractRenderableOptions<TConstructor> = TConstructor extends new (ctx: RenderContext, options: infer TOptions) => any ? TOptions : never;
|
|
14
14
|
/** Extract the renderable type from a constructor */
|
|
@@ -20,13 +20,16 @@ type ContainerProps<TOptions> = TOptions & {
|
|
|
20
20
|
children?: React.ReactNode;
|
|
21
21
|
};
|
|
22
22
|
/** Smart component props that automatically determine excluded properties */
|
|
23
|
-
type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends
|
|
23
|
+
type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends BaseRenderable> = TOptions & {
|
|
24
24
|
style?: Partial<Omit<TOptions, GetNonStyledProperties<RenderableConstructor<TRenderable>>>>;
|
|
25
25
|
} & ReactProps<TRenderable>;
|
|
26
26
|
/** Valid text content types for Text component children */
|
|
27
|
-
type TextChildren = string | number | boolean | null | undefined;
|
|
27
|
+
type TextChildren = string | number | boolean | null | undefined | React.ReactNode;
|
|
28
28
|
export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
|
|
29
|
-
children?: TextChildren
|
|
29
|
+
children?: TextChildren;
|
|
30
|
+
};
|
|
31
|
+
export type SpanProps = ComponentProps<TextNodeOptions, TextNodeRenderable> & {
|
|
32
|
+
children?: TextChildren;
|
|
30
33
|
};
|
|
31
34
|
export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
|
|
32
35
|
export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
|
package/src/types/host.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BaseRenderable, RootRenderable, TextNodeRenderable } from "@opentui/core";
|
|
2
2
|
import { baseComponents } from "../components";
|
|
3
3
|
export type Type = keyof typeof baseComponents;
|
|
4
4
|
export type Props = Record<string, any>;
|
|
5
5
|
export type Container = RootRenderable;
|
|
6
|
-
export type Instance =
|
|
7
|
-
export type TextInstance =
|
|
6
|
+
export type Instance = BaseRenderable;
|
|
7
|
+
export type TextInstance = TextNodeRenderable;
|
|
8
8
|
export type PublicInstance = Instance;
|
|
9
|
-
export type HostContext = Record<string, any
|
|
9
|
+
export type HostContext = Record<string, any> & {
|
|
10
|
+
isInsideText?: boolean;
|
|
11
|
+
};
|