@opentui/react 0.5.17 → 0.6.0
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 +72 -41
- package/{chunk-r98m55jk.js → chunk-p521ag9g.js} +5 -2
- package/index.js +10 -5
- package/package.json +2 -2
- package/test-utils.js +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @opentui/react
|
|
2
2
|
|
|
3
|
-
A React renderer for
|
|
3
|
+
A React renderer for [OpenTUI Core](https://github.com/anomalyco/opentui). Build terminal user interfaces with React components, hooks, and state.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ createRoot(renderer).render(<App />)
|
|
|
32
32
|
|
|
33
33
|
## TypeScript Configuration
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Configure your `tsconfig.json`:
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
38
|
{
|
|
@@ -56,7 +56,7 @@ For optimal TypeScript support, configure your `tsconfig.json`:
|
|
|
56
56
|
- [Styling](#styling)
|
|
57
57
|
- [API Reference](#api-reference)
|
|
58
58
|
- [createRoot(renderer)](#createrootrenderer)
|
|
59
|
-
- [render(element, config?)](#renderelement-config-
|
|
59
|
+
- [render(element, config?)](#renderelement-config-removed)
|
|
60
60
|
- [Hooks](#hooks)
|
|
61
61
|
- [useRenderer()](#userenderer)
|
|
62
62
|
- [useKeyboard(handler, options?)](#usekeyboardhandler-options)
|
|
@@ -93,7 +93,7 @@ For optimal TypeScript support, configure your `tsconfig.json`:
|
|
|
93
93
|
|
|
94
94
|
### Components
|
|
95
95
|
|
|
96
|
-
OpenTUI React
|
|
96
|
+
OpenTUI React includes these built-in components. Each component maps to an OpenTUI Core renderable:
|
|
97
97
|
|
|
98
98
|
**Layout & Display:**
|
|
99
99
|
|
|
@@ -102,13 +102,13 @@ OpenTUI React provides several built-in components that map to OpenTUI core rend
|
|
|
102
102
|
- **`<scrollbox>`** - A scrollable box
|
|
103
103
|
- **`<ascii-font>`** - Display ASCII art text with different font styles
|
|
104
104
|
|
|
105
|
-
QR
|
|
105
|
+
For QR codes, call `registerQRCode()` from `@opentui/qrcode/react`. It registers the `<qr-code>` element.
|
|
106
106
|
|
|
107
107
|
**Input Components:**
|
|
108
108
|
|
|
109
109
|
- **`<input>`** - Text input field
|
|
110
110
|
- **`<textarea>`** - Multi-line text input field
|
|
111
|
-
- **`<select>`** -
|
|
111
|
+
- **`<select>`** - Vertical list of options
|
|
112
112
|
- **`<tab-select>`** - Tab-based selection
|
|
113
113
|
|
|
114
114
|
**Code & Diff Components:**
|
|
@@ -119,11 +119,11 @@ QR code support is available from `@opentui/qrcode/react` and must be registered
|
|
|
119
119
|
|
|
120
120
|
**Helpers:**
|
|
121
121
|
|
|
122
|
-
- **`<span>`, `<strong>`, `<em>`, `<u>`, `<b>`, `<i>`, `<br>`** - Text modifiers (
|
|
122
|
+
- **`<span>`, `<strong>`, `<em>`, `<u>`, `<b>`, `<i>`, `<br>`, `<a>`** - Text modifiers (_use them only inside a `<text>` component_)
|
|
123
123
|
|
|
124
124
|
### Styling
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
Style a component with props or with the `style` prop:
|
|
127
127
|
|
|
128
128
|
```tsx
|
|
129
129
|
// Direct props
|
|
@@ -158,13 +158,12 @@ createRoot(renderer).render(<App />)
|
|
|
158
158
|
|
|
159
159
|
- `renderer`: A `CliRenderer` instance (typically created with `createCliRenderer()`)
|
|
160
160
|
|
|
161
|
-
**Returns:**
|
|
161
|
+
**Returns:** A root with a `render(node)` method and an `unmount()` method. `render(node)` accepts a React element.
|
|
162
|
+
`unmount()` removes the React tree and keeps the renderer active. `renderer.destroy()` also unmounts the root.
|
|
162
163
|
|
|
163
|
-
### `render(element, config?)` (
|
|
164
|
+
### `render(element, config?)` (Removed)
|
|
164
165
|
|
|
165
|
-
> **
|
|
166
|
-
|
|
167
|
-
Renders a React element to the terminal. This function is deprecated in favor of `createRoot`.
|
|
166
|
+
> **Removed:** `@opentui/react` does not export `render()`. Use `createRoot(renderer).render(node)` instead.
|
|
168
167
|
|
|
169
168
|
### Hooks
|
|
170
169
|
|
|
@@ -174,6 +173,7 @@ Access the OpenTUI renderer instance.
|
|
|
174
173
|
|
|
175
174
|
```tsx
|
|
176
175
|
import { useRenderer } from "@opentui/react"
|
|
176
|
+
import { useEffect } from "react"
|
|
177
177
|
|
|
178
178
|
function App() {
|
|
179
179
|
const renderer = useRenderer()
|
|
@@ -211,7 +211,7 @@ function App() {
|
|
|
211
211
|
- `options?`: Optional configuration object:
|
|
212
212
|
- `release?`: Boolean to include key release events (default: `false`)
|
|
213
213
|
|
|
214
|
-
By default,
|
|
214
|
+
By default, the handler gets press events only. Press events include key repeats with `repeated: true`. Set `options.release` to `true` to also get release events.
|
|
215
215
|
|
|
216
216
|
**Example with release events:**
|
|
217
217
|
|
|
@@ -269,7 +269,7 @@ function App() {
|
|
|
269
269
|
|
|
270
270
|
#### `useFocus(handler)`
|
|
271
271
|
|
|
272
|
-
Subscribe to terminal window focus events.
|
|
272
|
+
Subscribe to terminal window focus events. The handler runs when the terminal window gains focus.
|
|
273
273
|
|
|
274
274
|
```tsx
|
|
275
275
|
import { useFocus } from "@opentui/react"
|
|
@@ -289,7 +289,7 @@ function App() {
|
|
|
289
289
|
|
|
290
290
|
#### `useBlur(handler)`
|
|
291
291
|
|
|
292
|
-
Subscribe to terminal window blur events.
|
|
292
|
+
Subscribe to terminal window blur events. The handler runs when the terminal window loses focus.
|
|
293
293
|
|
|
294
294
|
```tsx
|
|
295
295
|
import { useBlur } from "@opentui/react"
|
|
@@ -309,7 +309,7 @@ function App() {
|
|
|
309
309
|
|
|
310
310
|
#### `useSelectionHandler(handler)`
|
|
311
311
|
|
|
312
|
-
Handle text selection events
|
|
312
|
+
Handle text selection events, such as a mouse drag selection.
|
|
313
313
|
|
|
314
314
|
```tsx
|
|
315
315
|
import { useSelectionHandler } from "@opentui/react"
|
|
@@ -353,7 +353,7 @@ function App() {
|
|
|
353
353
|
|
|
354
354
|
#### `useTerminalDimensions()`
|
|
355
355
|
|
|
356
|
-
Get current terminal dimensions
|
|
356
|
+
Get the current terminal dimensions. The hook updates them when the terminal resizes.
|
|
357
357
|
|
|
358
358
|
```tsx
|
|
359
359
|
import { useTerminalDimensions } from "@opentui/react"
|
|
@@ -378,7 +378,7 @@ function App() {
|
|
|
378
378
|
|
|
379
379
|
#### `useTimeline(options?)`
|
|
380
380
|
|
|
381
|
-
Create
|
|
381
|
+
Create one `Timeline` for animations. The hook registers the timeline with the animation engine after mount. It pauses and unregisters the timeline during React effect cleanup.
|
|
382
382
|
|
|
383
383
|
```tsx
|
|
384
384
|
import { useTimeline } from "@opentui/react"
|
|
@@ -418,8 +418,8 @@ function App() {
|
|
|
418
418
|
- `duration?`: Animation duration in milliseconds (default: 1000)
|
|
419
419
|
- `loop?`: Whether the timeline should loop (default: false)
|
|
420
420
|
- `autoplay?`: Whether to automatically start the timeline (default: true)
|
|
421
|
-
- `onComplete?`: Callback when timeline completes
|
|
422
|
-
- `onPause?`: Callback when timeline
|
|
421
|
+
- `onComplete?`: Callback when the timeline completes
|
|
422
|
+
- `onPause?`: Callback when the timeline pauses
|
|
423
423
|
|
|
424
424
|
**Returns:** A `Timeline` instance with methods:
|
|
425
425
|
|
|
@@ -649,7 +649,7 @@ function App() {
|
|
|
649
649
|
|
|
650
650
|
#### Select Component
|
|
651
651
|
|
|
652
|
-
|
|
652
|
+
Choose one option from a vertical list.
|
|
653
653
|
|
|
654
654
|
```tsx
|
|
655
655
|
import type { SelectOption } from "@opentui/core"
|
|
@@ -684,16 +684,23 @@ function App() {
|
|
|
684
684
|
|
|
685
685
|
#### Code Component
|
|
686
686
|
|
|
687
|
+
Create a theme once for its renderer, not during a component render. Release it after you destroy its consumers.
|
|
688
|
+
|
|
687
689
|
```tsx
|
|
688
|
-
import { RGBA, SyntaxStyle } from "@opentui/core"
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
690
|
+
import { createCliRenderer, RGBA, SyntaxStyle } from "@opentui/core"
|
|
691
|
+
import { createRoot } from "@opentui/react"
|
|
692
|
+
|
|
693
|
+
const renderer = await createCliRenderer()
|
|
694
|
+
const syntaxStyle = SyntaxStyle.fromStyles(
|
|
695
|
+
{
|
|
696
|
+
keyword: { fg: RGBA.fromHex("#ff6b6b"), bold: true }, // red, bold
|
|
697
|
+
string: { fg: RGBA.fromHex("#51cf66") }, // green
|
|
698
|
+
comment: { fg: RGBA.fromHex("#868e96"), italic: true }, // gray, italic
|
|
699
|
+
number: { fg: RGBA.fromHex("#ffd43b") }, // yellow
|
|
700
|
+
default: { fg: RGBA.fromHex("#ffffff") }, // white
|
|
701
|
+
},
|
|
702
|
+
renderer.nativeScene!,
|
|
703
|
+
)
|
|
697
704
|
|
|
698
705
|
const codeExample = `function hello() {
|
|
699
706
|
// This is a comment
|
|
@@ -711,6 +718,14 @@ function App() {
|
|
|
711
718
|
</box>
|
|
712
719
|
)
|
|
713
720
|
}
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
createRoot(renderer).render(<App />)
|
|
724
|
+
await renderer.closed
|
|
725
|
+
} finally {
|
|
726
|
+
renderer.destroy()
|
|
727
|
+
syntaxStyle.destroy()
|
|
728
|
+
}
|
|
714
729
|
```
|
|
715
730
|
|
|
716
731
|
#### Line Number Component
|
|
@@ -719,18 +734,23 @@ Display code with line numbers, and optionally add diff highlights or diagnostic
|
|
|
719
734
|
|
|
720
735
|
```tsx
|
|
721
736
|
import type { LineNumberRenderable } from "@opentui/core"
|
|
722
|
-
import { RGBA, SyntaxStyle } from "@opentui/core"
|
|
737
|
+
import { createCliRenderer, RGBA, SyntaxStyle } from "@opentui/core"
|
|
738
|
+
import { createRoot } from "@opentui/react"
|
|
723
739
|
import { useEffect, useRef } from "react"
|
|
724
740
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const syntaxStyle = SyntaxStyle.fromStyles({
|
|
741
|
+
const renderer = await createCliRenderer()
|
|
742
|
+
const syntaxStyle = SyntaxStyle.fromStyles(
|
|
743
|
+
{
|
|
729
744
|
keyword: { fg: RGBA.fromHex("#C792EA") },
|
|
730
745
|
string: { fg: RGBA.fromHex("#C3E88D") },
|
|
731
746
|
number: { fg: RGBA.fromHex("#F78C6C") },
|
|
732
747
|
default: { fg: RGBA.fromHex("#A6ACCD") },
|
|
733
|
-
}
|
|
748
|
+
},
|
|
749
|
+
renderer.nativeScene!,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
function App() {
|
|
753
|
+
const lineNumberRef = useRef<LineNumberRenderable>(null)
|
|
734
754
|
|
|
735
755
|
const codeContent = `function fibonacci(n: number): number {
|
|
736
756
|
if (n <= 1) return n
|
|
@@ -765,13 +785,21 @@ console.log(fibonacci(10))`
|
|
|
765
785
|
</box>
|
|
766
786
|
)
|
|
767
787
|
}
|
|
788
|
+
|
|
789
|
+
try {
|
|
790
|
+
createRoot(renderer).render(<App />)
|
|
791
|
+
await renderer.closed
|
|
792
|
+
} finally {
|
|
793
|
+
renderer.destroy()
|
|
794
|
+
syntaxStyle.destroy()
|
|
795
|
+
}
|
|
768
796
|
```
|
|
769
797
|
|
|
770
798
|
For a more complete example with interactive diff highlights and diagnostics, see [`examples/line-number.tsx`](examples/line-number.tsx).
|
|
771
799
|
|
|
772
800
|
#### Diff Component
|
|
773
801
|
|
|
774
|
-
Display unified or split
|
|
802
|
+
Display a unified or split diff with syntax highlighting, line numbers, word wrapping, and custom colors.
|
|
775
803
|
|
|
776
804
|
For a complete interactive example with theme switching and keybindings, see [`examples/diff.tsx`](examples/diff.tsx).
|
|
777
805
|
|
|
@@ -992,7 +1020,10 @@ createRoot(renderer).render(<App />)
|
|
|
992
1020
|
|
|
993
1021
|
## Component Extension
|
|
994
1022
|
|
|
995
|
-
|
|
1023
|
+
To create a custom component, extend an OpenTUI Core renderable class and register it with `extend()`.
|
|
1024
|
+
React calls the constructor with `{ id, ...props }`. After construction, React assigns the props to the instance again.
|
|
1025
|
+
This example overrides the `renderSelf()` [paint hook](https://opentui.com/docs/extend/custom-renderables#use-paint-hooks). The hook uses screen cell coordinates, so it places the label
|
|
1026
|
+
relative to `this.x` and `this.y`:
|
|
996
1027
|
|
|
997
1028
|
```tsx
|
|
998
1029
|
import {
|
|
@@ -1083,8 +1114,8 @@ npx react-devtools@7
|
|
|
1083
1114
|
DEV=true bun run your-app.ts
|
|
1084
1115
|
```
|
|
1085
1116
|
|
|
1086
|
-
After the app starts,
|
|
1117
|
+
After the app starts, React DevTools shows the component tree. You can inspect and change props while the app runs. The terminal UI shows each change.
|
|
1087
1118
|
|
|
1088
1119
|
### Process Exit with DevTools
|
|
1089
1120
|
|
|
1090
|
-
When DevTools is connected, the WebSocket connection
|
|
1121
|
+
When DevTools is connected, the WebSocket connection can keep your process from exiting on its own.
|
|
@@ -164,7 +164,7 @@ import { ImageRenderable as ImageRenderable2, TextNodeRenderable as TextNodeRend
|
|
|
164
164
|
// package.json
|
|
165
165
|
var package_default = {
|
|
166
166
|
name: "@opentui/react",
|
|
167
|
-
version: "0.
|
|
167
|
+
version: "0.6.0",
|
|
168
168
|
description: "React renderer for building terminal user interfaces using OpenTUI core",
|
|
169
169
|
license: "MIT",
|
|
170
170
|
repository: {
|
|
@@ -584,9 +584,12 @@ function createRoot2(renderer) {
|
|
|
584
584
|
container = null;
|
|
585
585
|
}
|
|
586
586
|
};
|
|
587
|
-
renderer.
|
|
587
|
+
if (!renderer.isDestroyed)
|
|
588
|
+
renderer.once(CliRenderEvents.DESTROY, cleanup);
|
|
588
589
|
return {
|
|
589
590
|
render: (node) => {
|
|
591
|
+
if (renderer.isDestroyed)
|
|
592
|
+
return;
|
|
590
593
|
engine.attach(renderer);
|
|
591
594
|
container = _render(React2.createElement(AppContext2.Provider, { value: { keyHandler: renderer.keyInput, renderer } }, React2.createElement(ErrorBoundary, null, node)), renderer.root);
|
|
592
595
|
},
|
package/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
flushSync2,
|
|
12
12
|
createPortal2,
|
|
13
13
|
createRoot2
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-p521ag9g.js";
|
|
15
15
|
// src/hooks/use-blur.ts
|
|
16
16
|
import { useEffect } from "react";
|
|
17
17
|
|
|
@@ -130,21 +130,26 @@ var useTerminalDimensions = () => {
|
|
|
130
130
|
return dimensions;
|
|
131
131
|
};
|
|
132
132
|
// src/hooks/use-timeline.ts
|
|
133
|
-
import {
|
|
133
|
+
import { getTimelineEngine, Timeline } from "@opentui/core";
|
|
134
134
|
import { useEffect as useEffect7, useState as useState2 } from "react";
|
|
135
135
|
var useTimeline = (options = {}) => {
|
|
136
|
+
const renderer = useRenderer();
|
|
136
137
|
const [timeline] = useState2(() => new Timeline(options));
|
|
137
138
|
const [autoplay] = useState2(options.autoplay !== false);
|
|
138
139
|
useEffect7(() => {
|
|
140
|
+
const engine = getTimelineEngine(renderer);
|
|
139
141
|
if (autoplay) {
|
|
140
142
|
timeline.play();
|
|
141
143
|
}
|
|
142
144
|
engine.register(timeline);
|
|
143
145
|
return () => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
try {
|
|
147
|
+
timeline.pause();
|
|
148
|
+
} finally {
|
|
149
|
+
engine.unregister(timeline);
|
|
150
|
+
}
|
|
146
151
|
};
|
|
147
|
-
}, [autoplay, timeline]);
|
|
152
|
+
}, [autoplay, timeline, renderer]);
|
|
148
153
|
return timeline;
|
|
149
154
|
};
|
|
150
155
|
// src/plugins/slot.tsx
|
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.
|
|
7
|
+
"version": "0.6.0",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@opentui/core": "0.
|
|
55
|
+
"@opentui/core": "0.6.0",
|
|
56
56
|
"react-reconciler": "^0.33.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|