@opentui/react 0.1.23 → 0.1.25

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 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>{bold(fg("red")("Bold Red Text"))}</text>
205
+ <text>
206
+ <span fg="red">Red Text</span>
207
+ </text>
204
208
 
205
- {/* Template literals */}
206
- <text>{t`${bold("Bold")} and ${fg("blue")("Blue")}`}</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
- <box style={{ flexDirection: "column" }}>
514
+ <>
510
515
  <text>Simple text</text>
511
- <text>{bold("Bold text")}</text>
512
- <text>{underline("Underlined text")}</text>
513
- <text>{red("Red text")}</text>
514
- <text>{blue("Blue text")}</text>
515
- <text>{bold(red("Bold red text"))}</text>
516
- <text>{t`${bold("Bold")} and ${blue("blue")} combined`}</text>
517
- </box>
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) {
@@ -96,7 +159,7 @@ var useTerminalDimensions = () => {
96
159
  return dimensions;
97
160
  };
98
161
  // src/reconciler/renderer.ts
99
- import { createCliRenderer, getKeyHandler } from "@opentui/core";
162
+ import { createCliRenderer } from "@opentui/core";
100
163
  import React from "react";
101
164
 
102
165
  // src/reconciler/reconciler.ts
@@ -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 (!!propValue) {
228
- instance.focus();
229
- } else {
230
- instance.blur();
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(`[Reconciler] Unknown component type: ${type}`);
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
- return parentHostContext;
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
- const components = getComponentCatalogue();
325
- return new components["text"](rootContainerInstance.ctx, {
326
- id: getNextId("text"),
327
- content: text
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.content = newText;
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.destroy();
432
+ instance.destroyRecursively();
410
433
  }
411
434
  },
412
435
  getPublicInstance(instance) {
@@ -433,10 +456,9 @@ function _render(element, root) {
433
456
  }
434
457
 
435
458
  // src/reconciler/renderer.ts
436
- var keyHandler = getKeyHandler();
437
459
  async function render(node, rendererConfig = {}) {
438
460
  const renderer = await createCliRenderer(rendererConfig);
439
- _render(React.createElement(AppContext.Provider, { value: { keyHandler, renderer } }, node), renderer.root);
461
+ _render(React.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, node), renderer.root);
440
462
  }
441
463
  export {
442
464
  useTerminalDimensions,
@@ -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.23",
7
+ "version": "0.1.25",
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.23",
38
+ "@opentui/core": "0.1.25",
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").Renderable, import("@opentui/core").TextRenderable, unknown, unknown, import("@opentui/core").Renderable>;
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;
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // src/reconciler/renderer.ts
3
- import { createCliRenderer, getKeyHandler } from "@opentui/core";
3
+ import { createCliRenderer } from "@opentui/core";
4
4
  import React from "react";
5
5
 
6
6
  // src/components/app.tsx
@@ -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 (!!propValue) {
163
- instance.focus();
164
- } else {
165
- instance.blur();
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(`[Reconciler] Unknown component type: ${type}`);
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
- return parentHostContext;
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
- const components = getComponentCatalogue();
260
- return new components["text"](rootContainerInstance.ctx, {
261
- id: getNextId("text"),
262
- content: text
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.content = newText;
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.destroy();
367
+ instance.destroyRecursively();
345
368
  }
346
369
  },
347
370
  getPublicInstance(instance) {
@@ -368,10 +391,9 @@ function _render(element, root) {
368
391
  }
369
392
 
370
393
  // src/reconciler/renderer.ts
371
- var keyHandler = getKeyHandler();
372
394
  async function render(node, rendererConfig = {}) {
373
395
  const renderer = await createCliRenderer(rendererConfig);
374
- _render(React.createElement(AppContext.Provider, { value: { keyHandler, renderer } }, node), renderer.root);
396
+ _render(React.createElement(AppContext.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, node), renderer.root);
375
397
  }
376
398
  export {
377
399
  render
@@ -1,4 +1,4 @@
1
- import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, Renderable, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextChunk, TextOptions, TextRenderable } from "@opentui/core";
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 Renderable = Renderable> = new (ctx: RenderContext, options: any) => TRenderable;
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 Renderable> = TOptions & {
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 | StyledText | TextChunk | Array<TextChildren | StyledText | TextChunk>;
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;
@@ -1,9 +1,11 @@
1
- import type { Renderable, RootRenderable, TextRenderable } from "@opentui/core";
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 = Renderable;
7
- export type TextInstance = TextRenderable;
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
+ };