@opentui/react 0.1.87 → 0.1.89

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.
@@ -166,7 +166,7 @@ import { TextNodeRenderable as TextNodeRenderable2 } from "@opentui/core";
166
166
  // package.json
167
167
  var package_default = {
168
168
  name: "@opentui/react",
169
- version: "0.1.87",
169
+ version: "0.1.89",
170
170
  description: "React renderer for building terminal user interfaces using OpenTUI core",
171
171
  license: "MIT",
172
172
  repository: {
@@ -187,6 +187,10 @@ var package_default = {
187
187
  import: "./src/test-utils.ts",
188
188
  types: "./src/test-utils.d.ts"
189
189
  },
190
+ "./runtime-plugin-support": {
191
+ import: "./scripts/runtime-plugin-support.ts",
192
+ types: "./scripts/runtime-plugin-support.ts"
193
+ },
190
194
  "./jsx-runtime": {
191
195
  import: "./jsx-runtime.js",
192
196
  types: "./jsx-runtime.d.ts"
@@ -198,6 +202,7 @@ var package_default = {
198
202
  },
199
203
  scripts: {
200
204
  build: "bun run scripts/build.ts",
205
+ "build:examples": "bun examples/build.ts",
201
206
  "build:dev": "bun run scripts/build.ts --dev",
202
207
  publish: "bun run scripts/publish.ts",
203
208
  test: "bun test"
@@ -569,4 +574,4 @@ function createRoot(renderer) {
569
574
  };
570
575
  }
571
576
 
572
- export { baseComponents, componentCatalogue, extend, getComponentCatalogue, AppContext, useAppContext, flushSync, createPortal, createRoot };
577
+ export { baseComponents, componentCatalogue, extend, getComponentCatalogue, AppContext, useAppContext, Fragment, jsxDEV, flushSync, createPortal, createRoot };
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AppContext,
4
+ Fragment,
4
5
  baseComponents,
5
6
  componentCatalogue,
6
7
  createPortal,
@@ -8,8 +9,9 @@ import {
8
9
  extend,
9
10
  flushSync,
10
11
  getComponentCatalogue,
12
+ jsxDEV,
11
13
  useAppContext
12
- } from "./chunk-6x3h6qt2.js";
14
+ } from "./chunk-m242ttx8.js";
13
15
  import"./chunk-2mx7fq49.js";
14
16
  // src/hooks/use-keyboard.ts
15
17
  import { useEffect } from "react";
@@ -96,6 +98,213 @@ var useTimeline = (options = {}) => {
96
98
  }, []);
97
99
  return timeline;
98
100
  };
101
+ // src/plugins/slot.tsx
102
+ import {
103
+ createSlotRegistry
104
+ } from "@opentui/core";
105
+ import React, { Fragment as Fragment2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState2 } from "react";
106
+ function createReactSlotRegistry(renderer, context, options = {}) {
107
+ return createSlotRegistry(renderer, "react:slot-registry", context, options);
108
+ }
109
+ function renderPluginFailurePlaceholder(registry, pluginFailurePlaceholder, failure, pluginId, slot) {
110
+ if (!pluginFailurePlaceholder) {
111
+ return null;
112
+ }
113
+ try {
114
+ return pluginFailurePlaceholder(failure);
115
+ } catch (error) {
116
+ registry.reportPluginError({
117
+ pluginId,
118
+ slot,
119
+ phase: "error_placeholder",
120
+ source: "react",
121
+ error
122
+ });
123
+ return null;
124
+ }
125
+ }
126
+
127
+ class PluginErrorBoundary extends React.Component {
128
+ constructor(props) {
129
+ super(props);
130
+ this.state = { failure: null };
131
+ }
132
+ componentDidCatch(error) {
133
+ const failure = this.props.registry.reportPluginError({
134
+ pluginId: this.props.pluginId,
135
+ slot: this.props.slotName,
136
+ phase: "render",
137
+ source: "react",
138
+ error
139
+ });
140
+ this.setState({ failure });
141
+ }
142
+ componentDidUpdate(previousProps) {
143
+ if (previousProps.resetToken !== this.props.resetToken && this.state.failure) {
144
+ this.setState({ failure: null });
145
+ }
146
+ }
147
+ render() {
148
+ if (this.state.failure) {
149
+ const placeholder = renderPluginFailurePlaceholder(this.props.registry, this.props.pluginFailurePlaceholder, this.state.failure, this.props.pluginId, this.props.slotName);
150
+ if (placeholder === null || placeholder === undefined || placeholder === false) {
151
+ return this.props.fallbackOnFailure ?? null;
152
+ }
153
+ return placeholder;
154
+ }
155
+ return this.props.children;
156
+ }
157
+ }
158
+ function getSlotProps(props) {
159
+ const {
160
+ children: _children,
161
+ mode: _mode,
162
+ name: _name,
163
+ registry: _registry,
164
+ pluginFailurePlaceholder: _pluginFailurePlaceholder,
165
+ ...slotProps
166
+ } = props;
167
+ return slotProps;
168
+ }
169
+ function createSlot(registry, options = {}) {
170
+ return function BoundSlot(props) {
171
+ return /* @__PURE__ */ jsxDEV(Slot, {
172
+ ...props,
173
+ registry,
174
+ pluginFailurePlaceholder: options.pluginFailurePlaceholder
175
+ }, undefined, false, undefined, this);
176
+ };
177
+ }
178
+ function Slot(props) {
179
+ const [version, setVersion] = useState2(0);
180
+ const registry = props.registry;
181
+ const slotName = String(props.name);
182
+ const renderFailuresByPluginRef = useRef2(new Map);
183
+ const pendingRenderReportsRef = useRef2(new Map);
184
+ useEffect4(() => {
185
+ return registry.subscribe(() => {
186
+ setVersion((current) => current + 1);
187
+ });
188
+ }, [registry]);
189
+ useEffect4(() => {
190
+ if (pendingRenderReportsRef.current.size === 0) {
191
+ return;
192
+ }
193
+ const pendingReports = [...pendingRenderReportsRef.current.values()];
194
+ pendingRenderReportsRef.current.clear();
195
+ for (const report of pendingReports) {
196
+ const failure = registry.reportPluginError({
197
+ pluginId: report.pluginId,
198
+ slot: report.slot,
199
+ phase: "render",
200
+ source: "react",
201
+ error: report.error
202
+ });
203
+ renderFailuresByPluginRef.current.set(`${report.slot}:${report.pluginId}:render`, failure);
204
+ }
205
+ });
206
+ const entries = useMemo(() => registry.resolveEntries(props.name), [registry, props.name, version]);
207
+ const slotProps = getSlotProps(props);
208
+ const renderEntry = (entry, fallbackOnFailure) => {
209
+ const key = `${slotName}:${entry.id}`;
210
+ const failureKey = `${slotName}:${entry.id}:render`;
211
+ try {
212
+ const rendered = entry.renderer(registry.context, slotProps);
213
+ renderFailuresByPluginRef.current.delete(failureKey);
214
+ pendingRenderReportsRef.current.delete(failureKey);
215
+ return /* @__PURE__ */ jsxDEV(PluginErrorBoundary, {
216
+ registry,
217
+ pluginFailurePlaceholder: props.pluginFailurePlaceholder,
218
+ pluginId: entry.id,
219
+ slotName,
220
+ resetToken: version,
221
+ fallbackOnFailure,
222
+ children: rendered
223
+ }, key, false, undefined, this);
224
+ } catch (error) {
225
+ const normalizedError = error instanceof Error ? error : typeof error === "string" ? new Error(error) : new Error(String(error));
226
+ const lastFailure = renderFailuresByPluginRef.current.get(failureKey);
227
+ const isSameFailure = lastFailure && lastFailure.error.message === normalizedError.message;
228
+ if (!isSameFailure) {
229
+ const queued = pendingRenderReportsRef.current.get(failureKey);
230
+ if (!queued || queued.error.message !== normalizedError.message) {
231
+ pendingRenderReportsRef.current.set(failureKey, {
232
+ pluginId: entry.id,
233
+ slot: slotName,
234
+ error: normalizedError
235
+ });
236
+ }
237
+ }
238
+ const failure = isSameFailure && lastFailure ? lastFailure : {
239
+ pluginId: entry.id,
240
+ slot: slotName,
241
+ phase: "render",
242
+ source: "react",
243
+ error: normalizedError,
244
+ timestamp: Date.now()
245
+ };
246
+ renderFailuresByPluginRef.current.set(failureKey, failure);
247
+ const placeholder = renderPluginFailurePlaceholder(registry, props.pluginFailurePlaceholder, failure, entry.id, slotName);
248
+ if (placeholder === null || placeholder === undefined || placeholder === false) {
249
+ return fallbackOnFailure ?? null;
250
+ }
251
+ return /* @__PURE__ */ jsxDEV(Fragment2, {
252
+ children: placeholder
253
+ }, key, false, undefined, this);
254
+ }
255
+ };
256
+ if (entries.length === 0) {
257
+ return /* @__PURE__ */ jsxDEV(Fragment, {
258
+ children: props.children
259
+ }, undefined, false, undefined, this);
260
+ }
261
+ if (props.mode === "single_winner") {
262
+ const winner = entries[0];
263
+ if (!winner) {
264
+ return /* @__PURE__ */ jsxDEV(Fragment, {
265
+ children: props.children
266
+ }, undefined, false, undefined, this);
267
+ }
268
+ const rendered = renderEntry(winner, props.children);
269
+ if (rendered === null || rendered === undefined || rendered === false) {
270
+ return /* @__PURE__ */ jsxDEV(Fragment, {
271
+ children: props.children
272
+ }, undefined, false, undefined, this);
273
+ }
274
+ return /* @__PURE__ */ jsxDEV(Fragment, {
275
+ children: rendered
276
+ }, undefined, false, undefined, this);
277
+ }
278
+ if (props.mode === "replace") {
279
+ if (entries.length === 1) {
280
+ const rendered = renderEntry(entries[0], props.children);
281
+ if (rendered === null || rendered === undefined || rendered === false) {
282
+ return /* @__PURE__ */ jsxDEV(Fragment, {
283
+ children: props.children
284
+ }, undefined, false, undefined, this);
285
+ }
286
+ return /* @__PURE__ */ jsxDEV(Fragment, {
287
+ children: rendered
288
+ }, undefined, false, undefined, this);
289
+ }
290
+ const renderedEntries = entries.map((entry) => renderEntry(entry));
291
+ const hasPluginOutput = renderedEntries.some((node) => node !== null && node !== undefined && node !== false);
292
+ if (!hasPluginOutput) {
293
+ return /* @__PURE__ */ jsxDEV(Fragment, {
294
+ children: props.children
295
+ }, undefined, false, undefined, this);
296
+ }
297
+ return /* @__PURE__ */ jsxDEV(Fragment, {
298
+ children: renderedEntries
299
+ }, undefined, false, undefined, this);
300
+ }
301
+ return /* @__PURE__ */ jsxDEV(Fragment, {
302
+ children: [
303
+ props.children,
304
+ entries.map((entry) => renderEntry(entry))
305
+ ]
306
+ }, undefined, true, undefined, this);
307
+ }
99
308
  // src/time-to-first-draw.tsx
100
309
  import { TimeToFirstDrawRenderable } from "@opentui/core";
101
310
  import { createElement } from "react";
@@ -116,11 +325,14 @@ export {
116
325
  getComponentCatalogue,
117
326
  flushSync,
118
327
  extend,
328
+ createSlot,
119
329
  createRoot,
330
+ createReactSlotRegistry,
120
331
  createPortal,
121
332
  createElement2 as createElement,
122
333
  componentCatalogue,
123
334
  baseComponents,
124
335
  TimeToFirstDraw,
336
+ Slot,
125
337
  AppContext
126
338
  };
@@ -17,12 +17,12 @@ import type {
17
17
  TabSelectProps,
18
18
  TextareaProps,
19
19
  TextProps,
20
- } from "./src/types/components"
20
+ } from "./src/types/components.js"
21
21
 
22
22
  export namespace JSX {
23
23
  type Element = React.ReactNode
24
24
 
25
- interface ElementClass extends React.ComponentClass<any> {
25
+ interface ElementClass extends React.Component<any> {
26
26
  render(): React.ReactNode
27
27
  }
28
28
 
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.87",
7
+ "version": "0.1.89",
8
8
  "description": "React renderer for building terminal user interfaces using OpenTUI core",
9
9
  "license": "MIT",
10
10
  "repository": {
@@ -28,6 +28,10 @@
28
28
  "import": "./test-utils.js",
29
29
  "require": "./test-utils.js"
30
30
  },
31
+ "./runtime-plugin-support": {
32
+ "types": "./scripts/runtime-plugin-support.d.ts",
33
+ "import": "./scripts/runtime-plugin-support.ts"
34
+ },
31
35
  "./jsx-runtime": {
32
36
  "types": "./jsx-runtime.d.ts",
33
37
  "import": "./jsx-runtime.js",
@@ -40,7 +44,7 @@
40
44
  }
41
45
  },
42
46
  "dependencies": {
43
- "@opentui/core": "0.1.87",
47
+ "@opentui/core": "0.1.89",
44
48
  "react-reconciler": "^0.32.0"
45
49
  },
46
50
  "devDependencies": {
@@ -0,0 +1 @@
1
+ export declare function ensureRuntimePluginSupport(): boolean;
@@ -0,0 +1,42 @@
1
+ import { plugin as registerBunPlugin } from "bun"
2
+ import * as coreRuntime from "@opentui/core"
3
+ import { createRuntimePlugin, type RuntimeModuleEntry } from "@opentui/core/runtime-plugin"
4
+ import * as reactRuntime from "react"
5
+ import * as reactJsxRuntime from "react/jsx-runtime"
6
+ import * as reactJsxDevRuntime from "react/jsx-dev-runtime"
7
+ import * as opentuiReactRuntime from "../src/index"
8
+
9
+ const runtimePluginSupportInstalledKey = "__opentuiReactRuntimePluginSupportInstalled__"
10
+
11
+ type RuntimePluginSupportState = typeof globalThis & {
12
+ [runtimePluginSupportInstalledKey]?: boolean
13
+ }
14
+
15
+ const additionalRuntimeModules: Record<string, RuntimeModuleEntry> = {
16
+ "@opentui/react": opentuiReactRuntime as Record<string, unknown>,
17
+ "@opentui/react/jsx-runtime": reactJsxRuntime as Record<string, unknown>,
18
+ "@opentui/react/jsx-dev-runtime": reactJsxDevRuntime as Record<string, unknown>,
19
+ react: reactRuntime as Record<string, unknown>,
20
+ "react/jsx-runtime": reactJsxRuntime as Record<string, unknown>,
21
+ "react/jsx-dev-runtime": reactJsxDevRuntime as Record<string, unknown>,
22
+ }
23
+
24
+ export function ensureRuntimePluginSupport(): boolean {
25
+ const state = globalThis as RuntimePluginSupportState
26
+
27
+ if (state[runtimePluginSupportInstalledKey]) {
28
+ return false
29
+ }
30
+
31
+ registerBunPlugin(
32
+ createRuntimePlugin({
33
+ core: coreRuntime as Record<string, unknown>,
34
+ additional: additionalRuntimeModules,
35
+ }),
36
+ )
37
+
38
+ state[runtimePluginSupportInstalledKey] = true
39
+ return true
40
+ }
41
+
42
+ ensureRuntimePluginSupport()
@@ -1,6 +1,6 @@
1
1
  import { ASCIIFontRenderable, BoxRenderable, CodeRenderable, DiffRenderable, InputRenderable, LineNumberRenderable, MarkdownRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextareaRenderable, TextRenderable } from "@opentui/core";
2
- import type { RenderableConstructor } from "../types/components";
3
- import { BoldSpanRenderable, ItalicSpanRenderable, LineBreakRenderable, LinkRenderable, SpanRenderable, UnderlineSpanRenderable } from "./text";
2
+ import type { RenderableConstructor } from "../types/components.js";
3
+ import { BoldSpanRenderable, ItalicSpanRenderable, LineBreakRenderable, LinkRenderable, SpanRenderable, UnderlineSpanRenderable } from "./text.js";
4
4
  export declare const baseComponents: {
5
5
  box: typeof BoxRenderable;
6
6
  text: typeof TextRenderable;
@@ -39,4 +39,4 @@ export declare const componentCatalogue: ComponentCatalogue;
39
39
  */
40
40
  export declare function extend<T extends ComponentCatalogue>(objects: T): void;
41
41
  export declare function getComponentCatalogue(): ComponentCatalogue;
42
- export type { ExtendedComponentProps, ExtendedIntrinsicElements, RenderableConstructor } from "../types/components";
42
+ export type { ExtendedComponentProps, ExtendedIntrinsicElements, RenderableConstructor } from "../types/components.js";
@@ -1,5 +1,5 @@
1
- export * from "./use-keyboard";
2
- export * from "./use-renderer";
3
- export * from "./use-resize";
4
- export * from "./use-terminal-dimensions";
5
- export * from "./use-timeline";
1
+ export * from "./use-keyboard.js";
2
+ export * from "./use-renderer.js";
3
+ export * from "./use-resize.js";
4
+ export * from "./use-terminal-dimensions.js";
5
+ export * from "./use-timeline.js";
package/src/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export * from "./components";
2
- export * from "./components/app";
3
- export * from "./hooks";
4
- export * from "./reconciler/renderer";
5
- export * from "./time-to-first-draw";
6
- export * from "./types/components";
1
+ export * from "./components/index.js";
2
+ export * from "./components/app.js";
3
+ export * from "./hooks/index.js";
4
+ export * from "./plugins/slot.js";
5
+ export * from "./reconciler/renderer.js";
6
+ export * from "./time-to-first-draw.js";
7
+ export * from "./types/components.js";
7
8
  export { createElement } from "react";
@@ -0,0 +1,25 @@
1
+ import { SlotRegistry, type CliRenderer, type Plugin, type PluginContext, type PluginErrorEvent, type SlotMode, type SlotRegistryOptions } from "@opentui/core";
2
+ import type { ReactNode } from "react";
3
+ export type { SlotMode };
4
+ type SlotMap = Record<string, object>;
5
+ export type ReactPlugin<TSlots extends SlotMap, TContext extends PluginContext = PluginContext> = Plugin<ReactNode, TSlots, TContext>;
6
+ export type ReactSlotProps<TSlots extends SlotMap, K extends keyof TSlots, TContext extends PluginContext = PluginContext> = {
7
+ registry: SlotRegistry<ReactNode, TSlots, TContext>;
8
+ name: K;
9
+ mode?: SlotMode;
10
+ children?: ReactNode;
11
+ pluginFailurePlaceholder?: (failure: PluginErrorEvent) => ReactNode;
12
+ } & TSlots[K];
13
+ export type ReactBoundSlotProps<TSlots extends SlotMap, K extends keyof TSlots> = {
14
+ name: K;
15
+ mode?: SlotMode;
16
+ children?: ReactNode;
17
+ } & TSlots[K];
18
+ export type ReactRegistrySlotComponent<TSlots extends SlotMap, TContext extends PluginContext = PluginContext> = <K extends keyof TSlots>(props: ReactSlotProps<TSlots, K, TContext>) => ReactNode;
19
+ export type ReactSlotComponent<TSlots extends SlotMap> = <K extends keyof TSlots>(props: ReactBoundSlotProps<TSlots, K>) => ReactNode;
20
+ export interface ReactSlotOptions {
21
+ pluginFailurePlaceholder?: (failure: PluginErrorEvent) => ReactNode;
22
+ }
23
+ export declare function createReactSlotRegistry<TSlots extends SlotMap, TContext extends PluginContext = PluginContext>(renderer: CliRenderer, context: TContext, options?: SlotRegistryOptions): SlotRegistry<ReactNode, TSlots, TContext>;
24
+ export declare function createSlot<TSlots extends SlotMap, TContext extends PluginContext = PluginContext>(registry: SlotRegistry<ReactNode, TSlots, TContext>, options?: ReactSlotOptions): ReactSlotComponent<TSlots>;
25
+ export declare function Slot<TSlots extends SlotMap, TContext extends PluginContext = PluginContext, K extends keyof TSlots = keyof TSlots>(props: ReactSlotProps<TSlots, K, TContext>): ReactNode;
@@ -1,5 +1,5 @@
1
1
  import type { HostConfig } from "react-reconciler";
2
- import type { Container, HostContext, Instance, Props, PublicInstance, TextInstance, Type } from "../types/host";
2
+ import type { Container, HostContext, Instance, Props, PublicInstance, TextInstance, Type } from "../types/host.js";
3
3
  export declare const hostConfig: HostConfig<Type, Props, Container, Instance, TextInstance, unknown, // SuspenseInstance
4
4
  unknown, // HydratableInstance
5
5
  unknown, // FormInstance
@@ -1,5 +1,5 @@
1
1
  import type { BaseRenderable, RootRenderable, TextNodeRenderable } from "@opentui/core";
2
- import { baseComponents } from "../components";
2
+ import { baseComponents } from "../components/index.js";
3
3
  export type Type = keyof typeof baseComponents;
4
4
  export type Props = Record<string, any>;
5
5
  export type Container = RootRenderable;
package/src/utils/id.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { Type } from "../types/host";
1
+ import type { Type } from "../types/host.js";
2
2
  export declare function getNextId(type: Type): string;
@@ -1,3 +1,3 @@
1
- import type { Instance, Props, Type } from "../types/host";
1
+ import type { Instance, Props, Type } from "../types/host.js";
2
2
  export declare function setInitialProperties(instance: Instance, type: Type, props: Props): void;
3
3
  export declare function updateProperties(instance: Instance, type: Type, oldProps: Props, newProps: Props): void;
package/test-utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  createRoot
4
- } from "./chunk-6x3h6qt2.js";
4
+ } from "./chunk-m242ttx8.js";
5
5
  import"./chunk-2mx7fq49.js";
6
6
 
7
7
  // src/test-utils.ts