@opentui/react 0.1.12 → 0.1.14
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 +18 -38
- package/index.js +28 -14
- package/jsx-namespace.d.ts +0 -2
- package/package.json +2 -2
- package/src/components/index.d.ts +1 -2
- package/src/hooks/use-event.d.ts +9 -0
- package/src/reconciler/renderer.js +7 -9
- package/src/types/components.d.ts +1 -2
package/README.md
CHANGED
|
@@ -15,12 +15,12 @@ import { render } from "@opentui/react"
|
|
|
15
15
|
|
|
16
16
|
function App() {
|
|
17
17
|
return (
|
|
18
|
-
<
|
|
18
|
+
<box>
|
|
19
19
|
<text fg="#00FF00">Hello, Terminal!</text>
|
|
20
20
|
<box title="Welcome" padding={2}>
|
|
21
|
-
<text>Welcome to OpenTUI with React
|
|
21
|
+
<text>Welcome to OpenTUI with React!</text>
|
|
22
22
|
</box>
|
|
23
|
-
</
|
|
23
|
+
</box>
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -35,7 +35,6 @@ OpenTUI React provides several built-in components that map to OpenTUI core rend
|
|
|
35
35
|
|
|
36
36
|
- **`<text>`** - Display text with styling
|
|
37
37
|
- **`<box>`** - Container with borders and layout
|
|
38
|
-
- **`<group>`** - Layout container for organizing components
|
|
39
38
|
- **`<input>`** - Text input field
|
|
40
39
|
- **`<select>`** - Selection dropdown
|
|
41
40
|
- **`<tab-select>`** - Tab-based selection
|
|
@@ -147,14 +146,14 @@ function MyComponent() {
|
|
|
147
146
|
const { width, height } = useTerminalDimensions()
|
|
148
147
|
|
|
149
148
|
return (
|
|
150
|
-
<
|
|
149
|
+
<box>
|
|
151
150
|
<text>
|
|
152
151
|
Terminal dimensions: {width}x{height}
|
|
153
152
|
</text>
|
|
154
153
|
<box style={{ width: Math.floor(width / 2), height: Math.floor(height / 3) }}>
|
|
155
154
|
<text>Half-width, third-height box</text>
|
|
156
155
|
</box>
|
|
157
|
-
</
|
|
156
|
+
</box>
|
|
158
157
|
)
|
|
159
158
|
}
|
|
160
159
|
```
|
|
@@ -172,7 +171,7 @@ import { bold, fg, t } from "@opentui/core"
|
|
|
172
171
|
|
|
173
172
|
function TextExample() {
|
|
174
173
|
return (
|
|
175
|
-
<
|
|
174
|
+
<box>
|
|
176
175
|
{/* Simple text */}
|
|
177
176
|
<text>Hello World</text>
|
|
178
177
|
|
|
@@ -181,7 +180,7 @@ function TextExample() {
|
|
|
181
180
|
|
|
182
181
|
{/* Template literals */}
|
|
183
182
|
<text>{t`${bold("Bold")} and ${fg("blue")("Blue")}`}</text>
|
|
184
|
-
</
|
|
183
|
+
</box>
|
|
185
184
|
)
|
|
186
185
|
}
|
|
187
186
|
```
|
|
@@ -193,7 +192,7 @@ Container with borders and layout capabilities.
|
|
|
193
192
|
```tsx
|
|
194
193
|
function BoxExample() {
|
|
195
194
|
return (
|
|
196
|
-
<
|
|
195
|
+
<box flexDirection="column">
|
|
197
196
|
{/* Basic box */}
|
|
198
197
|
<box>
|
|
199
198
|
<text>Simple box</text>
|
|
@@ -216,26 +215,7 @@ function BoxExample() {
|
|
|
216
215
|
>
|
|
217
216
|
<text>Centered content</text>
|
|
218
217
|
</box>
|
|
219
|
-
</
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Group Component
|
|
225
|
-
|
|
226
|
-
Layout container for organizing multiple components.
|
|
227
|
-
|
|
228
|
-
```tsx
|
|
229
|
-
function GroupExample() {
|
|
230
|
-
return (
|
|
231
|
-
<group flexDirection="row">
|
|
232
|
-
<box>
|
|
233
|
-
<text>Left</text>
|
|
234
|
-
</box>
|
|
235
|
-
<box>
|
|
236
|
-
<text>Right</text>
|
|
237
|
-
</box>
|
|
238
|
-
</group>
|
|
218
|
+
</box>
|
|
239
219
|
)
|
|
240
220
|
}
|
|
241
221
|
```
|
|
@@ -318,7 +298,7 @@ function ASCIIFontExample() {
|
|
|
318
298
|
})
|
|
319
299
|
|
|
320
300
|
return (
|
|
321
|
-
<
|
|
301
|
+
<box style={{ paddingLeft: 1, paddingRight: 1 }}>
|
|
322
302
|
<box
|
|
323
303
|
style={{
|
|
324
304
|
height: 8,
|
|
@@ -356,7 +336,7 @@ function ASCIIFontExample() {
|
|
|
356
336
|
</box>
|
|
357
337
|
|
|
358
338
|
<ascii-font style={{ width, height }} text={text} font={font} />
|
|
359
|
-
</
|
|
339
|
+
</box>
|
|
360
340
|
)
|
|
361
341
|
}
|
|
362
342
|
```
|
|
@@ -390,7 +370,7 @@ function LoginForm() {
|
|
|
390
370
|
}, [username, password])
|
|
391
371
|
|
|
392
372
|
return (
|
|
393
|
-
<
|
|
373
|
+
<box style={{ padding: 2, flexDirection: "column" }}>
|
|
394
374
|
<text fg="#FFFF00">Login Form</text>
|
|
395
375
|
|
|
396
376
|
<box title="Username" style={{ width: 40, height: 3, marginTop: 1 }}>
|
|
@@ -418,7 +398,7 @@ function LoginForm() {
|
|
|
418
398
|
>
|
|
419
399
|
{status.toUpperCase()}
|
|
420
400
|
</text>
|
|
421
|
-
</
|
|
401
|
+
</box>
|
|
422
402
|
)
|
|
423
403
|
}
|
|
424
404
|
|
|
@@ -460,7 +440,7 @@ import { render } from "@opentui/react"
|
|
|
460
440
|
|
|
461
441
|
function StyledTextShowcase() {
|
|
462
442
|
return (
|
|
463
|
-
<
|
|
443
|
+
<box style={{ flexDirection: "column" }}>
|
|
464
444
|
<text>Simple text</text>
|
|
465
445
|
<text>{bold("Bold text")}</text>
|
|
466
446
|
<text>{underline("Underlined text")}</text>
|
|
@@ -468,7 +448,7 @@ function StyledTextShowcase() {
|
|
|
468
448
|
<text>{blue("Blue text")}</text>
|
|
469
449
|
<text>{bold(red("Bold red text"))}</text>
|
|
470
450
|
<text>{t`${bold("Bold")} and ${blue("blue")} combined`}</text>
|
|
471
|
-
</
|
|
451
|
+
</box>
|
|
472
452
|
)
|
|
473
453
|
}
|
|
474
454
|
|
|
@@ -504,7 +484,7 @@ class ButtonRenderable extends BoxRenderable {
|
|
|
504
484
|
|
|
505
485
|
set label(value: string) {
|
|
506
486
|
this._label = value
|
|
507
|
-
this.
|
|
487
|
+
this.requestRender()
|
|
508
488
|
}
|
|
509
489
|
}
|
|
510
490
|
|
|
@@ -521,10 +501,10 @@ extend({ button: ButtonRenderable })
|
|
|
521
501
|
// Use in JSX
|
|
522
502
|
function App() {
|
|
523
503
|
return (
|
|
524
|
-
<
|
|
504
|
+
<box>
|
|
525
505
|
<button label="Click me!" style={{ backgroundColor: "blue" }} />
|
|
526
506
|
<button label="Another button" style={{ backgroundColor: "green" }} />
|
|
527
|
-
</
|
|
507
|
+
</box>
|
|
528
508
|
)
|
|
529
509
|
}
|
|
530
510
|
|
package/index.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import {
|
|
4
4
|
ASCIIFontRenderable,
|
|
5
5
|
BoxRenderable,
|
|
6
|
-
GroupRenderable,
|
|
7
6
|
InputRenderable,
|
|
8
7
|
SelectRenderable,
|
|
9
8
|
TabSelectRenderable,
|
|
@@ -12,7 +11,6 @@ import {
|
|
|
12
11
|
var baseComponents = {
|
|
13
12
|
box: BoxRenderable,
|
|
14
13
|
text: TextRenderable,
|
|
15
|
-
group: GroupRenderable,
|
|
16
14
|
input: InputRenderable,
|
|
17
15
|
select: SelectRenderable,
|
|
18
16
|
"ascii-font": ASCIIFontRenderable,
|
|
@@ -36,14 +34,30 @@ var useAppContext = () => {
|
|
|
36
34
|
};
|
|
37
35
|
// src/hooks/use-keyboard.tsx
|
|
38
36
|
import { useEffect } from "react";
|
|
37
|
+
|
|
38
|
+
// src/hooks/use-event.tsx
|
|
39
|
+
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
40
|
+
function useEvent(handler) {
|
|
41
|
+
const handlerRef = useRef(handler);
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
handlerRef.current = handler;
|
|
44
|
+
});
|
|
45
|
+
return useCallback((...args) => {
|
|
46
|
+
const fn = handlerRef.current;
|
|
47
|
+
return fn(...args);
|
|
48
|
+
}, []);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/hooks/use-keyboard.tsx
|
|
39
52
|
var useKeyboard = (handler) => {
|
|
40
53
|
const { keyHandler } = useAppContext();
|
|
54
|
+
const stableHandler = useEvent(handler);
|
|
41
55
|
useEffect(() => {
|
|
42
|
-
keyHandler?.on("keypress",
|
|
56
|
+
keyHandler?.on("keypress", stableHandler);
|
|
43
57
|
return () => {
|
|
44
|
-
keyHandler?.off("keypress",
|
|
58
|
+
keyHandler?.off("keypress", stableHandler);
|
|
45
59
|
};
|
|
46
|
-
}, [keyHandler,
|
|
60
|
+
}, [keyHandler, stableHandler]);
|
|
47
61
|
};
|
|
48
62
|
// src/hooks/use-renderer.tsx
|
|
49
63
|
var useRenderer = () => {
|
|
@@ -70,8 +84,8 @@ import { useState } from "react";
|
|
|
70
84
|
var useTerminalDimensions = () => {
|
|
71
85
|
const renderer = useRenderer();
|
|
72
86
|
const [dimensions, setDimensions] = useState({
|
|
73
|
-
width: renderer.
|
|
74
|
-
height: renderer.
|
|
87
|
+
width: renderer.width,
|
|
88
|
+
height: renderer.height
|
|
75
89
|
});
|
|
76
90
|
const cb = (width, height) => {
|
|
77
91
|
setDimensions({ width, height });
|
|
@@ -289,7 +303,7 @@ var hostConfig = {
|
|
|
289
303
|
return null;
|
|
290
304
|
},
|
|
291
305
|
resetAfterCommit(containerInfo) {
|
|
292
|
-
containerInfo.
|
|
306
|
+
containerInfo.requestRender();
|
|
293
307
|
},
|
|
294
308
|
getRootHostContext(rootContainerInstance) {
|
|
295
309
|
return {};
|
|
@@ -323,11 +337,11 @@ var hostConfig = {
|
|
|
323
337
|
commitMount(instance, type, props, internalInstanceHandle) {},
|
|
324
338
|
commitUpdate(instance, type, oldProps, newProps, internalInstanceHandle) {
|
|
325
339
|
updateProperties(instance, type, oldProps, newProps);
|
|
326
|
-
instance.
|
|
340
|
+
instance.requestRender();
|
|
327
341
|
},
|
|
328
342
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
329
343
|
textInstance.content = newText;
|
|
330
|
-
textInstance.
|
|
344
|
+
textInstance.requestRender();
|
|
331
345
|
},
|
|
332
346
|
appendChildToContainer(container, child) {
|
|
333
347
|
container.add(child);
|
|
@@ -337,19 +351,19 @@ var hostConfig = {
|
|
|
337
351
|
},
|
|
338
352
|
hideInstance(instance) {
|
|
339
353
|
instance.visible = false;
|
|
340
|
-
instance.
|
|
354
|
+
instance.requestRender();
|
|
341
355
|
},
|
|
342
356
|
unhideInstance(instance, props) {
|
|
343
357
|
instance.visible = true;
|
|
344
|
-
instance.
|
|
358
|
+
instance.requestRender();
|
|
345
359
|
},
|
|
346
360
|
hideTextInstance(textInstance) {
|
|
347
361
|
textInstance.visible = false;
|
|
348
|
-
textInstance.
|
|
362
|
+
textInstance.requestRender();
|
|
349
363
|
},
|
|
350
364
|
unhideTextInstance(textInstance, text) {
|
|
351
365
|
textInstance.visible = true;
|
|
352
|
-
textInstance.
|
|
366
|
+
textInstance.requestRender();
|
|
353
367
|
},
|
|
354
368
|
clearContainer(container) {
|
|
355
369
|
const children = container.getChildren();
|
package/jsx-namespace.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type * as React from "react"
|
|
|
2
2
|
import type {
|
|
3
3
|
AsciiFontProps,
|
|
4
4
|
BoxProps,
|
|
5
|
-
GroupProps,
|
|
6
5
|
InputProps,
|
|
7
6
|
SelectProps,
|
|
8
7
|
TabSelectProps,
|
|
@@ -28,7 +27,6 @@ export namespace JSX {
|
|
|
28
27
|
interface IntrinsicElements extends React.JSX.IntrinsicElements, ExtendedIntrinsicElements<OpenTUIComponents> {
|
|
29
28
|
box: BoxProps
|
|
30
29
|
text: TextProps
|
|
31
|
-
group: GroupProps
|
|
32
30
|
input: InputProps
|
|
33
31
|
select: SelectProps
|
|
34
32
|
"ascii-font": AsciiFontProps
|
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.14",
|
|
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.1.
|
|
38
|
+
"@opentui/core": "0.1.14",
|
|
39
39
|
"react-reconciler": "^0.32.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { ASCIIFontRenderable, BoxRenderable,
|
|
1
|
+
import { ASCIIFontRenderable, BoxRenderable, InputRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
|
|
2
2
|
import type { RenderableConstructor } from "../types/components";
|
|
3
3
|
export declare const baseComponents: {
|
|
4
4
|
box: typeof BoxRenderable;
|
|
5
5
|
text: typeof TextRenderable;
|
|
6
|
-
group: typeof GroupRenderable;
|
|
7
6
|
input: typeof InputRenderable;
|
|
8
7
|
select: typeof SelectRenderable;
|
|
9
8
|
"ascii-font": typeof ASCIIFontRenderable;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a stable callback that always calls the latest version of the provided handler.
|
|
3
|
+
* This prevents unnecessary re-renders and effect re-runs while ensuring the callback
|
|
4
|
+
* always has access to the latest props and state.
|
|
5
|
+
*
|
|
6
|
+
* Useful for event handlers that need to be passed to effects with empty dependency arrays
|
|
7
|
+
* or memoized child components.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useEvent<T extends (...args: any[]) => any>(handler: T): T;
|
|
@@ -22,7 +22,6 @@ import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constant
|
|
|
22
22
|
import {
|
|
23
23
|
ASCIIFontRenderable,
|
|
24
24
|
BoxRenderable,
|
|
25
|
-
GroupRenderable,
|
|
26
25
|
InputRenderable,
|
|
27
26
|
SelectRenderable,
|
|
28
27
|
TabSelectRenderable,
|
|
@@ -31,7 +30,6 @@ import {
|
|
|
31
30
|
var baseComponents = {
|
|
32
31
|
box: BoxRenderable,
|
|
33
32
|
text: TextRenderable,
|
|
34
|
-
group: GroupRenderable,
|
|
35
33
|
input: InputRenderable,
|
|
36
34
|
select: SelectRenderable,
|
|
37
35
|
"ascii-font": ASCIIFontRenderable,
|
|
@@ -240,7 +238,7 @@ var hostConfig = {
|
|
|
240
238
|
return null;
|
|
241
239
|
},
|
|
242
240
|
resetAfterCommit(containerInfo) {
|
|
243
|
-
containerInfo.
|
|
241
|
+
containerInfo.requestRender();
|
|
244
242
|
},
|
|
245
243
|
getRootHostContext(rootContainerInstance) {
|
|
246
244
|
return {};
|
|
@@ -274,11 +272,11 @@ var hostConfig = {
|
|
|
274
272
|
commitMount(instance, type, props, internalInstanceHandle) {},
|
|
275
273
|
commitUpdate(instance, type, oldProps, newProps, internalInstanceHandle) {
|
|
276
274
|
updateProperties(instance, type, oldProps, newProps);
|
|
277
|
-
instance.
|
|
275
|
+
instance.requestRender();
|
|
278
276
|
},
|
|
279
277
|
commitTextUpdate(textInstance, oldText, newText) {
|
|
280
278
|
textInstance.content = newText;
|
|
281
|
-
textInstance.
|
|
279
|
+
textInstance.requestRender();
|
|
282
280
|
},
|
|
283
281
|
appendChildToContainer(container, child) {
|
|
284
282
|
container.add(child);
|
|
@@ -288,19 +286,19 @@ var hostConfig = {
|
|
|
288
286
|
},
|
|
289
287
|
hideInstance(instance) {
|
|
290
288
|
instance.visible = false;
|
|
291
|
-
instance.
|
|
289
|
+
instance.requestRender();
|
|
292
290
|
},
|
|
293
291
|
unhideInstance(instance, props) {
|
|
294
292
|
instance.visible = true;
|
|
295
|
-
instance.
|
|
293
|
+
instance.requestRender();
|
|
296
294
|
},
|
|
297
295
|
hideTextInstance(textInstance) {
|
|
298
296
|
textInstance.visible = false;
|
|
299
|
-
textInstance.
|
|
297
|
+
textInstance.requestRender();
|
|
300
298
|
},
|
|
301
299
|
unhideTextInstance(textInstance, text) {
|
|
302
300
|
textInstance.visible = true;
|
|
303
|
-
textInstance.
|
|
301
|
+
textInstance.requestRender();
|
|
304
302
|
},
|
|
305
303
|
clearContainer(container) {
|
|
306
304
|
const children = container.getChildren();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable,
|
|
1
|
+
import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, Renderable, RenderableOptions, RenderContext, SelectOption, SelectRenderable, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextChunk, 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}`;
|
|
@@ -29,7 +29,6 @@ export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
|
|
|
29
29
|
children?: TextChildren | StyledText | TextChunk | Array<TextChildren | StyledText | TextChunk>;
|
|
30
30
|
};
|
|
31
31
|
export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
|
|
32
|
-
export type GroupProps = ComponentProps<ContainerProps<RenderableOptions>, GroupRenderable>;
|
|
33
32
|
export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
|
|
34
33
|
focused?: boolean;
|
|
35
34
|
onInput?: (value: string) => void;
|