@opentui/react 0.1.22 → 0.1.24
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 +31 -14
- package/index.js +86 -63
- package/jsx-namespace.d.ts +10 -0
- package/package.json +5 -5
- package/src/components/index.d.ts +8 -0
- package/src/components/text.d.ts +24 -0
- package/src/reconciler/reconciler.d.ts +1 -1
- package/src/reconciler/renderer.js +86 -63
- package/src/types/components.d.ts +9 -5
- package/src/types/host.d.ts +6 -4
package/README.md
CHANGED
|
@@ -61,6 +61,10 @@ OpenTUI React provides several built-in components that map to OpenTUI core rend
|
|
|
61
61
|
- **`<tab-select>`** - Tab-based selection
|
|
62
62
|
- **`<ascii-font>`** - Display ASCII art text with different font styles
|
|
63
63
|
|
|
64
|
+
Helpers:
|
|
65
|
+
|
|
66
|
+
- **`<span>`, `<strong>`, `<em>`, `<u>`, `<b>`, `<i>`, `<br>`** - Text modifiers (_must be used inside of the text component_)
|
|
67
|
+
|
|
64
68
|
### Styling
|
|
65
69
|
|
|
66
70
|
Components can be styled using props or the `style` prop:
|
|
@@ -191,8 +195,6 @@ function App() {
|
|
|
191
195
|
Display text with rich formatting.
|
|
192
196
|
|
|
193
197
|
```tsx
|
|
194
|
-
import { bold, fg, t } from "@opentui/core"
|
|
195
|
-
|
|
196
198
|
function App() {
|
|
197
199
|
return (
|
|
198
200
|
<box>
|
|
@@ -200,10 +202,14 @@ function App() {
|
|
|
200
202
|
<text>Hello World</text>
|
|
201
203
|
|
|
202
204
|
{/* Rich text with children */}
|
|
203
|
-
<text>
|
|
205
|
+
<text>
|
|
206
|
+
<span fg="red">Red Text</span>
|
|
207
|
+
</text>
|
|
204
208
|
|
|
205
|
-
{/*
|
|
206
|
-
<text>
|
|
209
|
+
{/* Text modifiers */}
|
|
210
|
+
<text>
|
|
211
|
+
<strong>Bold</strong>, <em>Italic</em>, and <u>Underlined</u>
|
|
212
|
+
</text>
|
|
207
213
|
</box>
|
|
208
214
|
)
|
|
209
215
|
}
|
|
@@ -501,20 +507,31 @@ render(<App />)
|
|
|
501
507
|
### Styled Text Showcase
|
|
502
508
|
|
|
503
509
|
```tsx
|
|
504
|
-
import { blue, bold, red, t, underline } from "@opentui/core"
|
|
505
510
|
import { render } from "@opentui/react"
|
|
506
511
|
|
|
507
512
|
function App() {
|
|
508
513
|
return (
|
|
509
|
-
|
|
514
|
+
<>
|
|
510
515
|
<text>Simple text</text>
|
|
511
|
-
<text>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
<text>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
<text>
|
|
517
|
+
<strong>Bold text</strong>
|
|
518
|
+
</text>
|
|
519
|
+
<text>
|
|
520
|
+
<u>Underlined text</u>
|
|
521
|
+
</text>
|
|
522
|
+
<text>
|
|
523
|
+
<span fg="red">Red text</span>
|
|
524
|
+
</text>
|
|
525
|
+
<text>
|
|
526
|
+
<span fg="blue">Blue text</span>
|
|
527
|
+
</text>
|
|
528
|
+
<text>
|
|
529
|
+
<strong fg="red">Bold red text</strong>
|
|
530
|
+
</text>
|
|
531
|
+
<text>
|
|
532
|
+
<strong>Bold</strong> and <span fg="blue">blue</span> combined
|
|
533
|
+
</text>
|
|
534
|
+
</>
|
|
518
535
|
)
|
|
519
536
|
}
|
|
520
537
|
|
package/index.js
CHANGED
|
@@ -9,6 +9,62 @@ 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", "br"];
|
|
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(_ctx, options) {
|
|
40
|
+
super(options, "b");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
45
|
+
constructor(_ctx, options) {
|
|
46
|
+
super(options, "i");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
51
|
+
constructor(_ctx, options) {
|
|
52
|
+
super(options, "u");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class LineBreakRenderable extends SpanRenderable {
|
|
57
|
+
constructor(_ctx, options) {
|
|
58
|
+
super(null, options);
|
|
59
|
+
this.add();
|
|
60
|
+
}
|
|
61
|
+
add() {
|
|
62
|
+
return super.add(`
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/components/index.ts
|
|
12
68
|
var baseComponents = {
|
|
13
69
|
box: BoxRenderable,
|
|
14
70
|
text: TextRenderable,
|
|
@@ -16,7 +72,14 @@ var baseComponents = {
|
|
|
16
72
|
select: SelectRenderable,
|
|
17
73
|
scrollbox: ScrollBoxRenderable,
|
|
18
74
|
"ascii-font": ASCIIFontRenderable,
|
|
19
|
-
"tab-select": TabSelectRenderable
|
|
75
|
+
"tab-select": TabSelectRenderable,
|
|
76
|
+
span: SpanRenderable,
|
|
77
|
+
br: LineBreakRenderable,
|
|
78
|
+
b: BoldSpanRenderable,
|
|
79
|
+
strong: BoldSpanRenderable,
|
|
80
|
+
i: ItalicSpanRenderable,
|
|
81
|
+
em: ItalicSpanRenderable,
|
|
82
|
+
u: UnderlineSpanRenderable
|
|
20
83
|
};
|
|
21
84
|
var componentCatalogue = { ...baseComponents };
|
|
22
85
|
function extend(objects) {
|
|
@@ -104,6 +167,7 @@ import ReactReconciler from "react-reconciler";
|
|
|
104
167
|
import { ConcurrentRoot } from "react-reconciler/constants";
|
|
105
168
|
|
|
106
169
|
// src/reconciler/host-config.ts
|
|
170
|
+
import { TextNodeRenderable as TextNodeRenderable2 } from "@opentui/core";
|
|
107
171
|
import { createContext as createContext2 } from "react";
|
|
108
172
|
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
|
|
109
173
|
|
|
@@ -122,13 +186,11 @@ function getNextId(type) {
|
|
|
122
186
|
import {
|
|
123
187
|
InputRenderable as InputRenderable2,
|
|
124
188
|
InputRenderableEvents,
|
|
189
|
+
isRenderable,
|
|
125
190
|
SelectRenderable as SelectRenderable2,
|
|
126
191
|
SelectRenderableEvents,
|
|
127
|
-
StyledText,
|
|
128
192
|
TabSelectRenderable as TabSelectRenderable2,
|
|
129
|
-
TabSelectRenderableEvents
|
|
130
|
-
TextRenderable as TextRenderable2,
|
|
131
|
-
stringToStyledText
|
|
193
|
+
TabSelectRenderableEvents
|
|
132
194
|
} from "@opentui/core";
|
|
133
195
|
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
134
196
|
if (previousListener) {
|
|
@@ -138,44 +200,6 @@ function initEventListeners(instance, eventName, listener, previousListener) {
|
|
|
138
200
|
instance.on(eventName, listener);
|
|
139
201
|
}
|
|
140
202
|
}
|
|
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: child
|
|
153
|
-
});
|
|
154
|
-
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
155
|
-
chunks.push(child);
|
|
156
|
-
} else if (child instanceof StyledText) {
|
|
157
|
-
chunks.push(...child.chunks);
|
|
158
|
-
} else if (child != null) {
|
|
159
|
-
const stringValue = String(child);
|
|
160
|
-
chunks.push({
|
|
161
|
-
__isChunk: true,
|
|
162
|
-
text: stringValue
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
textInstance.content = new StyledText(chunks);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (typeof children === "string") {
|
|
170
|
-
textInstance.content = stringToStyledText(children);
|
|
171
|
-
} else if (children && typeof children === "object" && "__isChunk" in children) {
|
|
172
|
-
textInstance.content = new StyledText([children]);
|
|
173
|
-
} else if (children instanceof StyledText) {
|
|
174
|
-
textInstance.content = children;
|
|
175
|
-
} else {
|
|
176
|
-
textInstance.content = stringToStyledText(String(children));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
203
|
function setStyle(instance, styles, oldStyles) {
|
|
180
204
|
if (styles && typeof styles === "object") {
|
|
181
205
|
if (oldStyles != null) {
|
|
@@ -224,19 +248,18 @@ function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
|
224
248
|
}
|
|
225
249
|
break;
|
|
226
250
|
case "focused":
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
if (isRenderable(instance)) {
|
|
252
|
+
if (!!propValue) {
|
|
253
|
+
instance.focus();
|
|
254
|
+
} else {
|
|
255
|
+
instance.blur();
|
|
256
|
+
}
|
|
231
257
|
}
|
|
232
258
|
break;
|
|
233
259
|
case "style":
|
|
234
260
|
setStyle(instance, propValue, oldPropValue);
|
|
235
261
|
break;
|
|
236
262
|
case "children":
|
|
237
|
-
if (type === "text" && instance instanceof TextRenderable2) {
|
|
238
|
-
handleTextChildren(instance, propValue);
|
|
239
|
-
}
|
|
240
263
|
break;
|
|
241
264
|
default:
|
|
242
265
|
instance[propKey] = propValue;
|
|
@@ -277,10 +300,13 @@ var hostConfig = {
|
|
|
277
300
|
supportsPersistence: false,
|
|
278
301
|
supportsHydration: false,
|
|
279
302
|
createInstance(type, props, rootContainerInstance, hostContext) {
|
|
303
|
+
if (textNodeKeys.includes(type) && !hostContext.isInsideText) {
|
|
304
|
+
throw new Error(`Component of type "${type}" must be created inside of a text node`);
|
|
305
|
+
}
|
|
280
306
|
const id = getNextId(type);
|
|
281
307
|
const components = getComponentCatalogue();
|
|
282
308
|
if (!components[type]) {
|
|
283
|
-
throw new Error(`
|
|
309
|
+
throw new Error(`Unknown component type: ${type}`);
|
|
284
310
|
}
|
|
285
311
|
return new components[type](rootContainerInstance.ctx, {
|
|
286
312
|
id,
|
|
@@ -309,23 +335,20 @@ var hostConfig = {
|
|
|
309
335
|
containerInfo.requestRender();
|
|
310
336
|
},
|
|
311
337
|
getRootHostContext(rootContainerInstance) {
|
|
312
|
-
return {};
|
|
338
|
+
return { isInsideText: false };
|
|
313
339
|
},
|
|
314
340
|
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
315
|
-
|
|
341
|
+
const isInsideText = ["text", ...textNodeKeys].includes(type);
|
|
342
|
+
return { ...parentHostContext, isInsideText };
|
|
316
343
|
},
|
|
317
344
|
shouldSetTextContent(type, props) {
|
|
318
|
-
if (type === "text") {
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
345
|
return false;
|
|
322
346
|
},
|
|
323
347
|
createTextInstance(text, rootContainerInstance, hostContext) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
348
|
+
if (!hostContext.isInsideText) {
|
|
349
|
+
throw new Error("Text must be created inside of a text node");
|
|
350
|
+
}
|
|
351
|
+
return TextNodeRenderable2.fromString(text);
|
|
329
352
|
},
|
|
330
353
|
scheduleTimeout: setTimeout,
|
|
331
354
|
cancelTimeout: clearTimeout,
|
|
@@ -343,7 +366,7 @@ var hostConfig = {
|
|
|
343
366
|
instance.requestRender();
|
|
344
367
|
},
|
|
345
368
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
346
|
-
textInstance.
|
|
369
|
+
textInstance.children = [newText];
|
|
347
370
|
textInstance.requestRender();
|
|
348
371
|
},
|
|
349
372
|
appendChildToContainer(container, child) {
|
|
@@ -406,7 +429,7 @@ var hostConfig = {
|
|
|
406
429
|
},
|
|
407
430
|
detachDeletedInstance(instance) {
|
|
408
431
|
if (!instance.parent) {
|
|
409
|
-
instance.
|
|
432
|
+
instance.destroyRecursively();
|
|
410
433
|
}
|
|
411
434
|
},
|
|
412
435
|
getPublicInstance(instance) {
|
package/jsx-namespace.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ import type {
|
|
|
4
4
|
BoxProps,
|
|
5
5
|
ExtendedIntrinsicElements,
|
|
6
6
|
InputProps,
|
|
7
|
+
LineBreakProps,
|
|
7
8
|
OpenTUIComponents,
|
|
8
9
|
ScrollBoxProps,
|
|
9
10
|
SelectProps,
|
|
11
|
+
SpanProps,
|
|
10
12
|
TabSelectProps,
|
|
11
13
|
TextProps,
|
|
12
14
|
} from "./src/types/components"
|
|
@@ -29,10 +31,18 @@ export namespace JSX {
|
|
|
29
31
|
interface IntrinsicElements extends React.JSX.IntrinsicElements, ExtendedIntrinsicElements<OpenTUIComponents> {
|
|
30
32
|
box: BoxProps
|
|
31
33
|
text: TextProps
|
|
34
|
+
span: SpanProps
|
|
32
35
|
input: InputProps
|
|
33
36
|
select: SelectProps
|
|
34
37
|
scrollbox: ScrollBoxProps
|
|
35
38
|
"ascii-font": AsciiFontProps
|
|
36
39
|
"tab-select": TabSelectProps
|
|
40
|
+
// Text modifiers
|
|
41
|
+
b: SpanProps
|
|
42
|
+
i: SpanProps
|
|
43
|
+
u: SpanProps
|
|
44
|
+
strong: SpanProps
|
|
45
|
+
em: SpanProps
|
|
46
|
+
br: LineBreakProps
|
|
37
47
|
}
|
|
38
48
|
}
|
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.24",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -35,15 +35,15 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@opentui/core": "0.1.
|
|
38
|
+
"@opentui/core": "0.1.24",
|
|
39
39
|
"react-reconciler": "^0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bun": "latest",
|
|
43
|
-
"@types/react-reconciler": "^0.32.0"
|
|
43
|
+
"@types/react-reconciler": "^0.32.0",
|
|
44
|
+
"typescript": "^5"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"react": ">=19.0.0"
|
|
47
|
-
"typescript": "^5"
|
|
47
|
+
"react": ">=19.0.0"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -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, LineBreakRenderable, SpanRenderable, UnderlineSpanRenderable } from "./text";
|
|
3
4
|
export declare const baseComponents: {
|
|
4
5
|
box: typeof BoxRenderable;
|
|
5
6
|
text: typeof TextRenderable;
|
|
@@ -8,6 +9,13 @@ 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
|
+
br: typeof LineBreakRenderable;
|
|
14
|
+
b: typeof BoldSpanRenderable;
|
|
15
|
+
strong: typeof BoldSpanRenderable;
|
|
16
|
+
i: typeof ItalicSpanRenderable;
|
|
17
|
+
em: typeof ItalicSpanRenderable;
|
|
18
|
+
u: typeof UnderlineSpanRenderable;
|
|
11
19
|
};
|
|
12
20
|
type ComponentCatalogue = Record<string, RenderableConstructor>;
|
|
13
21
|
export declare const componentCatalogue: ComponentCatalogue;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TextNodeRenderable, type RenderContext, type TextNodeOptions } from "@opentui/core";
|
|
2
|
+
export declare const textNodeKeys: readonly ["span", "b", "strong", "i", "em", "u", "br"];
|
|
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: TextNodeOptions, modifier?: TextNodeKey);
|
|
10
|
+
}
|
|
11
|
+
export declare class BoldSpanRenderable extends TextModifierRenderable {
|
|
12
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
13
|
+
}
|
|
14
|
+
export declare class ItalicSpanRenderable extends TextModifierRenderable {
|
|
15
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
16
|
+
}
|
|
17
|
+
export declare class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
18
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
19
|
+
}
|
|
20
|
+
export declare class LineBreakRenderable extends SpanRenderable {
|
|
21
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
22
|
+
add(): number;
|
|
23
|
+
}
|
|
24
|
+
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,62 @@ 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", "br"];
|
|
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(_ctx, options) {
|
|
60
|
+
super(options, "b");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
65
|
+
constructor(_ctx, options) {
|
|
66
|
+
super(options, "i");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
71
|
+
constructor(_ctx, options) {
|
|
72
|
+
super(options, "u");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class LineBreakRenderable extends SpanRenderable {
|
|
77
|
+
constructor(_ctx, options) {
|
|
78
|
+
super(null, options);
|
|
79
|
+
this.add();
|
|
80
|
+
}
|
|
81
|
+
add() {
|
|
82
|
+
return super.add(`
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/components/index.ts
|
|
31
88
|
var baseComponents = {
|
|
32
89
|
box: BoxRenderable,
|
|
33
90
|
text: TextRenderable,
|
|
@@ -35,7 +92,14 @@ var baseComponents = {
|
|
|
35
92
|
select: SelectRenderable,
|
|
36
93
|
scrollbox: ScrollBoxRenderable,
|
|
37
94
|
"ascii-font": ASCIIFontRenderable,
|
|
38
|
-
"tab-select": TabSelectRenderable
|
|
95
|
+
"tab-select": TabSelectRenderable,
|
|
96
|
+
span: SpanRenderable,
|
|
97
|
+
br: LineBreakRenderable,
|
|
98
|
+
b: BoldSpanRenderable,
|
|
99
|
+
strong: BoldSpanRenderable,
|
|
100
|
+
i: ItalicSpanRenderable,
|
|
101
|
+
em: ItalicSpanRenderable,
|
|
102
|
+
u: UnderlineSpanRenderable
|
|
39
103
|
};
|
|
40
104
|
var componentCatalogue = { ...baseComponents };
|
|
41
105
|
function getComponentCatalogue() {
|
|
@@ -57,13 +121,11 @@ function getNextId(type) {
|
|
|
57
121
|
import {
|
|
58
122
|
InputRenderable as InputRenderable2,
|
|
59
123
|
InputRenderableEvents,
|
|
124
|
+
isRenderable,
|
|
60
125
|
SelectRenderable as SelectRenderable2,
|
|
61
126
|
SelectRenderableEvents,
|
|
62
|
-
StyledText,
|
|
63
127
|
TabSelectRenderable as TabSelectRenderable2,
|
|
64
|
-
TabSelectRenderableEvents
|
|
65
|
-
TextRenderable as TextRenderable2,
|
|
66
|
-
stringToStyledText
|
|
128
|
+
TabSelectRenderableEvents
|
|
67
129
|
} from "@opentui/core";
|
|
68
130
|
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
69
131
|
if (previousListener) {
|
|
@@ -73,44 +135,6 @@ function initEventListeners(instance, eventName, listener, previousListener) {
|
|
|
73
135
|
instance.on(eventName, listener);
|
|
74
136
|
}
|
|
75
137
|
}
|
|
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: child
|
|
88
|
-
});
|
|
89
|
-
} else if (child && typeof child === "object" && "__isChunk" in child) {
|
|
90
|
-
chunks.push(child);
|
|
91
|
-
} else if (child instanceof StyledText) {
|
|
92
|
-
chunks.push(...child.chunks);
|
|
93
|
-
} else if (child != null) {
|
|
94
|
-
const stringValue = String(child);
|
|
95
|
-
chunks.push({
|
|
96
|
-
__isChunk: true,
|
|
97
|
-
text: stringValue
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
textInstance.content = new StyledText(chunks);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (typeof children === "string") {
|
|
105
|
-
textInstance.content = stringToStyledText(children);
|
|
106
|
-
} else if (children && typeof children === "object" && "__isChunk" in children) {
|
|
107
|
-
textInstance.content = new StyledText([children]);
|
|
108
|
-
} else if (children instanceof StyledText) {
|
|
109
|
-
textInstance.content = children;
|
|
110
|
-
} else {
|
|
111
|
-
textInstance.content = stringToStyledText(String(children));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
138
|
function setStyle(instance, styles, oldStyles) {
|
|
115
139
|
if (styles && typeof styles === "object") {
|
|
116
140
|
if (oldStyles != null) {
|
|
@@ -159,19 +183,18 @@ function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
|
159
183
|
}
|
|
160
184
|
break;
|
|
161
185
|
case "focused":
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
186
|
+
if (isRenderable(instance)) {
|
|
187
|
+
if (!!propValue) {
|
|
188
|
+
instance.focus();
|
|
189
|
+
} else {
|
|
190
|
+
instance.blur();
|
|
191
|
+
}
|
|
166
192
|
}
|
|
167
193
|
break;
|
|
168
194
|
case "style":
|
|
169
195
|
setStyle(instance, propValue, oldPropValue);
|
|
170
196
|
break;
|
|
171
197
|
case "children":
|
|
172
|
-
if (type === "text" && instance instanceof TextRenderable2) {
|
|
173
|
-
handleTextChildren(instance, propValue);
|
|
174
|
-
}
|
|
175
198
|
break;
|
|
176
199
|
default:
|
|
177
200
|
instance[propKey] = propValue;
|
|
@@ -212,10 +235,13 @@ var hostConfig = {
|
|
|
212
235
|
supportsPersistence: false,
|
|
213
236
|
supportsHydration: false,
|
|
214
237
|
createInstance(type, props, rootContainerInstance, hostContext) {
|
|
238
|
+
if (textNodeKeys.includes(type) && !hostContext.isInsideText) {
|
|
239
|
+
throw new Error(`Component of type "${type}" must be created inside of a text node`);
|
|
240
|
+
}
|
|
215
241
|
const id = getNextId(type);
|
|
216
242
|
const components = getComponentCatalogue();
|
|
217
243
|
if (!components[type]) {
|
|
218
|
-
throw new Error(`
|
|
244
|
+
throw new Error(`Unknown component type: ${type}`);
|
|
219
245
|
}
|
|
220
246
|
return new components[type](rootContainerInstance.ctx, {
|
|
221
247
|
id,
|
|
@@ -244,23 +270,20 @@ var hostConfig = {
|
|
|
244
270
|
containerInfo.requestRender();
|
|
245
271
|
},
|
|
246
272
|
getRootHostContext(rootContainerInstance) {
|
|
247
|
-
return {};
|
|
273
|
+
return { isInsideText: false };
|
|
248
274
|
},
|
|
249
275
|
getChildHostContext(parentHostContext, type, rootContainerInstance) {
|
|
250
|
-
|
|
276
|
+
const isInsideText = ["text", ...textNodeKeys].includes(type);
|
|
277
|
+
return { ...parentHostContext, isInsideText };
|
|
251
278
|
},
|
|
252
279
|
shouldSetTextContent(type, props) {
|
|
253
|
-
if (type === "text") {
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
280
|
return false;
|
|
257
281
|
},
|
|
258
282
|
createTextInstance(text, rootContainerInstance, hostContext) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
});
|
|
283
|
+
if (!hostContext.isInsideText) {
|
|
284
|
+
throw new Error("Text must be created inside of a text node");
|
|
285
|
+
}
|
|
286
|
+
return TextNodeRenderable2.fromString(text);
|
|
264
287
|
},
|
|
265
288
|
scheduleTimeout: setTimeout,
|
|
266
289
|
cancelTimeout: clearTimeout,
|
|
@@ -278,7 +301,7 @@ var hostConfig = {
|
|
|
278
301
|
instance.requestRender();
|
|
279
302
|
},
|
|
280
303
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
281
|
-
textInstance.
|
|
304
|
+
textInstance.children = [newText];
|
|
282
305
|
textInstance.requestRender();
|
|
283
306
|
},
|
|
284
307
|
appendChildToContainer(container, child) {
|
|
@@ -341,7 +364,7 @@ var hostConfig = {
|
|
|
341
364
|
},
|
|
342
365
|
detachDeletedInstance(instance) {
|
|
343
366
|
if (!instance.parent) {
|
|
344
|
-
instance.
|
|
367
|
+
instance.destroyRecursively();
|
|
345
368
|
}
|
|
346
369
|
},
|
|
347
370
|
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,14 +20,18 @@ 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
30
|
};
|
|
31
|
+
export type SpanProps = ComponentProps<TextNodeOptions, TextNodeRenderable> & {
|
|
32
|
+
children?: TextChildren;
|
|
33
|
+
};
|
|
34
|
+
export type LineBreakProps = Pick<SpanProps, "id">;
|
|
31
35
|
export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
|
|
32
36
|
export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
|
|
33
37
|
focused?: boolean;
|
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
|
+
};
|