@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @opentui/react
2
2
 
3
- A React renderer for building terminal user interfaces using [OpenTUI core](https://github.com/anomalyco/opentui). Create rich, interactive console applications with familiar React patterns and components.
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
- For optimal TypeScript support, configure your `tsconfig.json`:
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-deprecated)
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 provides several built-in components that map to OpenTUI core renderables:
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 code support is available from `@opentui/qrcode/react` and must be registered explicitly with `registerQRCode()`.
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>`** - Selection dropdown
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 (_must be used inside of the text component_)
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
- Components can be styled using props or the `style` prop:
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:** An object with a `render` method that accepts a React element.
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?)` (Deprecated)
164
+ ### `render(element, config?)` (Removed)
164
165
 
165
- > **Deprecated:** Use `createRoot(renderer).render(node)` instead.
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, only receives press events (including key repeats with `repeated: true`). Set `options.release` to `true` to also receive release events.
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. Fires when the terminal window gains focus.
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. Fires when the terminal window loses focus.
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 (e.g., when the user selects text via mouse drag).
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 and automatically update when the terminal is resized.
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 and manage animations using OpenTUI's timeline system. This hook automatically registers and unregisters the timeline with the animation engine.
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 is paused
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
- Dropdown selection component.
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
- const syntaxStyle = SyntaxStyle.fromStyles({
691
- keyword: { fg: RGBA.fromHex("#ff6b6b"), bold: true }, // red, bold
692
- string: { fg: RGBA.fromHex("#51cf66") }, // green
693
- comment: { fg: RGBA.fromHex("#868e96"), italic: true }, // gray, italic
694
- number: { fg: RGBA.fromHex("#ffd43b") }, // yellow
695
- default: { fg: RGBA.fromHex("#ffffff") }, // white
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
- function App() {
726
- const lineNumberRef = useRef<LineNumberRenderable>(null)
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-view diffs with syntax highlighting, customizable themes, and line number support. Supports multiple view modes (unified/split), word wrapping, and theme customization.
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
- You can create custom components by extending OpenTUIs base renderables:
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, you should see the component tree in React DevTools. You can inspect and modify props in real-time, and changes will be reflected immediately in your terminal UI.
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 may prevent your process from exiting naturally.
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.5.17",
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.once(CliRenderEvents.DESTROY, cleanup);
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-r98m55jk.js";
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 { engine, Timeline } from "@opentui/core";
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
- timeline.pause();
145
- engine.unregister(timeline);
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.5.17",
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.5.17",
55
+ "@opentui/core": "0.6.0",
56
56
  "react-reconciler": "^0.33.0"
57
57
  },
58
58
  "devDependencies": {
package/test-utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  createRoot2
4
- } from "./chunk-r98m55jk.js";
4
+ } from "./chunk-p521ag9g.js";
5
5
 
6
6
  // src/test-utils.ts
7
7
  import { createTestRenderer } from "@opentui/core/testing";