@opentui/react 0.0.0-20260412-2fcfd0af → 0.0.0-20260419-49f00f96
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/{chunk-cecjmcj8.js → chunk-mq7v1x2n.js} +5 -2
- package/index.js +161 -25
- package/package.json +2 -2
- package/src/hooks/index.d.ts +1 -0
- package/src/hooks/use-keymap.d.ts +36 -0
- package/src/types/components.d.ts +5 -1
- package/test-utils.js +1 -1
|
@@ -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.0.0-
|
|
169
|
+
version: "0.0.0-20260419-49f00f96",
|
|
170
170
|
description: "React renderer for building terminal user interfaces using OpenTUI core",
|
|
171
171
|
license: "MIT",
|
|
172
172
|
repository: {
|
|
@@ -260,7 +260,8 @@ import {
|
|
|
260
260
|
SelectRenderable as SelectRenderable2,
|
|
261
261
|
SelectRenderableEvents,
|
|
262
262
|
TabSelectRenderable as TabSelectRenderable2,
|
|
263
|
-
TabSelectRenderableEvents
|
|
263
|
+
TabSelectRenderableEvents,
|
|
264
|
+
TextareaRenderable as TextareaRenderable2
|
|
264
265
|
} from "@opentui/core";
|
|
265
266
|
function initEventListeners(instance, eventName, listener, previousListener) {
|
|
266
267
|
if (previousListener) {
|
|
@@ -311,6 +312,8 @@ function setProperty(instance, type, propKey, propValue, oldPropValue) {
|
|
|
311
312
|
case "onSubmit":
|
|
312
313
|
if (instance instanceof InputRenderable2) {
|
|
313
314
|
initEventListeners(instance, InputRenderableEvents.ENTER, propValue, oldPropValue);
|
|
315
|
+
} else if (instance instanceof TextareaRenderable2) {
|
|
316
|
+
instance.onSubmit = propValue;
|
|
314
317
|
}
|
|
315
318
|
break;
|
|
316
319
|
case "onSelect":
|
package/index.js
CHANGED
|
@@ -11,19 +11,158 @@ import {
|
|
|
11
11
|
getComponentCatalogue,
|
|
12
12
|
jsxDEV,
|
|
13
13
|
useAppContext
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-mq7v1x2n.js";
|
|
15
15
|
import"./chunk-2mx7fq49.js";
|
|
16
|
+
// src/hooks/use-keymap.ts
|
|
17
|
+
import {
|
|
18
|
+
getKeymap
|
|
19
|
+
} from "@opentui/core/extras";
|
|
20
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef } from "react";
|
|
21
|
+
|
|
22
|
+
// src/hooks/use-renderer.ts
|
|
23
|
+
var useRenderer = () => {
|
|
24
|
+
const { renderer } = useAppContext();
|
|
25
|
+
if (!renderer) {
|
|
26
|
+
throw new Error("Renderer not found.");
|
|
27
|
+
}
|
|
28
|
+
return renderer;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/hooks/use-keymap.ts
|
|
32
|
+
function resolveBindingsTarget(target) {
|
|
33
|
+
if (typeof target === "function") {
|
|
34
|
+
return target() ?? undefined;
|
|
35
|
+
}
|
|
36
|
+
return target ?? undefined;
|
|
37
|
+
}
|
|
38
|
+
var useKeymap = () => {
|
|
39
|
+
const renderer = useRenderer();
|
|
40
|
+
return useMemo(() => getKeymap(renderer), [renderer]);
|
|
41
|
+
};
|
|
42
|
+
function useKeymapStateVersion(keymap) {
|
|
43
|
+
const [version, bumpVersion] = useReducer((value) => value + 1, 0);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
const dispose = keymap.on("state", () => {
|
|
46
|
+
bumpVersion();
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
dispose();
|
|
50
|
+
};
|
|
51
|
+
}, [keymap]);
|
|
52
|
+
return version;
|
|
53
|
+
}
|
|
54
|
+
var useActiveKeys = (options) => {
|
|
55
|
+
const keymap = useKeymap();
|
|
56
|
+
const version = useKeymapStateVersion(keymap);
|
|
57
|
+
return useMemo(() => {
|
|
58
|
+
return keymap.getActiveKeys(options);
|
|
59
|
+
}, [keymap, options, version]);
|
|
60
|
+
};
|
|
61
|
+
var usePendingSequence = () => {
|
|
62
|
+
const keymap = useKeymap();
|
|
63
|
+
const version = useKeymapStateVersion(keymap);
|
|
64
|
+
return useMemo(() => {
|
|
65
|
+
return keymap.getPendingSequence();
|
|
66
|
+
}, [keymap, version]);
|
|
67
|
+
};
|
|
68
|
+
function useBindings(createLayer, deps = []) {
|
|
69
|
+
const keymap = useKeymap();
|
|
70
|
+
const layer = useMemo(createLayer, deps);
|
|
71
|
+
const layerRef = useRef(layer);
|
|
72
|
+
const refTargetRef = useRef(undefined);
|
|
73
|
+
const disposeRef = useRef(undefined);
|
|
74
|
+
const mountedRef = useRef(false);
|
|
75
|
+
const registeredScopeRef = useRef(undefined);
|
|
76
|
+
const registeredTargetRef = useRef(undefined);
|
|
77
|
+
layerRef.current = layer;
|
|
78
|
+
const unregister = useCallback(() => {
|
|
79
|
+
disposeRef.current?.();
|
|
80
|
+
disposeRef.current = undefined;
|
|
81
|
+
registeredScopeRef.current = undefined;
|
|
82
|
+
registeredTargetRef.current = undefined;
|
|
83
|
+
}, []);
|
|
84
|
+
const register = useCallback(() => {
|
|
85
|
+
if (disposeRef.current) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const currentLayer = layerRef.current;
|
|
89
|
+
const explicitTarget = resolveBindingsTarget(currentLayer.target);
|
|
90
|
+
const resolvedTarget = explicitTarget ?? refTargetRef.current;
|
|
91
|
+
const resolvedScope = currentLayer.scope ?? (resolvedTarget ? "focus-within" : "global");
|
|
92
|
+
if (currentLayer.target !== undefined && !explicitTarget) {
|
|
93
|
+
throw new Error("useBindings target was not available during mount");
|
|
94
|
+
}
|
|
95
|
+
if (resolvedScope !== "global" && !resolvedTarget) {
|
|
96
|
+
throw new Error("useBindings local bindings need a target or the returned ref callback attached to a renderable");
|
|
97
|
+
}
|
|
98
|
+
const { scope: _scope, target: _target, ...baseLayer } = currentLayer;
|
|
99
|
+
const resolvedLayer = resolvedScope === "global" ? {
|
|
100
|
+
...baseLayer,
|
|
101
|
+
scope: "global"
|
|
102
|
+
} : {
|
|
103
|
+
...baseLayer,
|
|
104
|
+
scope: resolvedScope,
|
|
105
|
+
target: resolvedTarget
|
|
106
|
+
};
|
|
107
|
+
disposeRef.current = keymap.registerLayer(resolvedLayer);
|
|
108
|
+
registeredScopeRef.current = resolvedScope;
|
|
109
|
+
registeredTargetRef.current = resolvedScope === "global" ? undefined : resolvedTarget;
|
|
110
|
+
}, [keymap]);
|
|
111
|
+
const ref = useCallback((value) => {
|
|
112
|
+
refTargetRef.current = value ?? undefined;
|
|
113
|
+
}, []);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
mountedRef.current = true;
|
|
116
|
+
unregister();
|
|
117
|
+
register();
|
|
118
|
+
return () => {
|
|
119
|
+
mountedRef.current = false;
|
|
120
|
+
unregister();
|
|
121
|
+
};
|
|
122
|
+
}, [layer, register, unregister]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!mountedRef.current) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const currentLayer = layerRef.current;
|
|
128
|
+
if (currentLayer.target !== undefined || currentLayer.scope === "global") {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const resolvedTarget = refTargetRef.current;
|
|
132
|
+
const resolvedScope = currentLayer.scope ?? (resolvedTarget ? "focus-within" : "global");
|
|
133
|
+
const nextTarget = resolvedScope === "global" ? undefined : resolvedTarget;
|
|
134
|
+
if (registeredScopeRef.current === resolvedScope && registeredTargetRef.current === nextTarget) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
unregister();
|
|
138
|
+
if (!nextTarget && currentLayer.scope !== undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
register();
|
|
142
|
+
});
|
|
143
|
+
return ref;
|
|
144
|
+
}
|
|
145
|
+
function reactiveMatcherFromStore(subscribe, getSnapshot, predicate) {
|
|
146
|
+
return {
|
|
147
|
+
get() {
|
|
148
|
+
return predicate ? predicate(getSnapshot()) : Boolean(getSnapshot());
|
|
149
|
+
},
|
|
150
|
+
subscribe(onChange) {
|
|
151
|
+
return subscribe(onChange);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
16
155
|
// src/hooks/use-keyboard.ts
|
|
17
|
-
import { useEffect } from "react";
|
|
156
|
+
import { useEffect as useEffect2 } from "react";
|
|
18
157
|
|
|
19
158
|
// src/hooks/use-event.ts
|
|
20
|
-
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
159
|
+
import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
|
|
21
160
|
function useEffectEvent(handler) {
|
|
22
|
-
const handlerRef =
|
|
23
|
-
|
|
161
|
+
const handlerRef = useRef2(handler);
|
|
162
|
+
useLayoutEffect2(() => {
|
|
24
163
|
handlerRef.current = handler;
|
|
25
164
|
});
|
|
26
|
-
return
|
|
165
|
+
return useCallback2((...args) => {
|
|
27
166
|
const fn = handlerRef.current;
|
|
28
167
|
return fn(...args);
|
|
29
168
|
}, []);
|
|
@@ -33,7 +172,7 @@ function useEffectEvent(handler) {
|
|
|
33
172
|
var useKeyboard = (handler, options = { release: false }) => {
|
|
34
173
|
const { keyHandler } = useAppContext();
|
|
35
174
|
const stableHandler = useEffectEvent(handler);
|
|
36
|
-
|
|
175
|
+
useEffect2(() => {
|
|
37
176
|
keyHandler?.on("keypress", stableHandler);
|
|
38
177
|
if (options?.release) {
|
|
39
178
|
keyHandler?.on("keyrelease", stableHandler);
|
|
@@ -46,20 +185,12 @@ var useKeyboard = (handler, options = { release: false }) => {
|
|
|
46
185
|
};
|
|
47
186
|
}, [keyHandler, options.release]);
|
|
48
187
|
};
|
|
49
|
-
// src/hooks/use-renderer.ts
|
|
50
|
-
var useRenderer = () => {
|
|
51
|
-
const { renderer } = useAppContext();
|
|
52
|
-
if (!renderer) {
|
|
53
|
-
throw new Error("Renderer not found.");
|
|
54
|
-
}
|
|
55
|
-
return renderer;
|
|
56
|
-
};
|
|
57
188
|
// src/hooks/use-resize.ts
|
|
58
|
-
import { useEffect as
|
|
189
|
+
import { useEffect as useEffect3 } from "react";
|
|
59
190
|
var useOnResize = (callback) => {
|
|
60
191
|
const renderer = useRenderer();
|
|
61
192
|
const stableCallback = useEffectEvent(callback);
|
|
62
|
-
|
|
193
|
+
useEffect3(() => {
|
|
63
194
|
renderer.on("resize", stableCallback);
|
|
64
195
|
return () => {
|
|
65
196
|
renderer.off("resize", stableCallback);
|
|
@@ -83,10 +214,10 @@ var useTerminalDimensions = () => {
|
|
|
83
214
|
};
|
|
84
215
|
// src/hooks/use-timeline.ts
|
|
85
216
|
import { engine, Timeline } from "@opentui/core";
|
|
86
|
-
import { useEffect as
|
|
217
|
+
import { useEffect as useEffect4 } from "react";
|
|
87
218
|
var useTimeline = (options = {}) => {
|
|
88
219
|
const timeline = new Timeline(options);
|
|
89
|
-
|
|
220
|
+
useEffect4(() => {
|
|
90
221
|
if (!options.autoplay) {
|
|
91
222
|
timeline.play();
|
|
92
223
|
}
|
|
@@ -102,7 +233,7 @@ var useTimeline = (options = {}) => {
|
|
|
102
233
|
import {
|
|
103
234
|
createSlotRegistry
|
|
104
235
|
} from "@opentui/core";
|
|
105
|
-
import React, { Fragment as Fragment2, useEffect as
|
|
236
|
+
import React, { Fragment as Fragment2, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
106
237
|
function createReactSlotRegistry(renderer, context, options = {}) {
|
|
107
238
|
return createSlotRegistry(renderer, "react:slot-registry", context, options);
|
|
108
239
|
}
|
|
@@ -179,14 +310,14 @@ function Slot(props) {
|
|
|
179
310
|
const [version, setVersion] = useState2(0);
|
|
180
311
|
const registry = props.registry;
|
|
181
312
|
const slotName = String(props.name);
|
|
182
|
-
const renderFailuresByPluginRef =
|
|
183
|
-
const pendingRenderReportsRef =
|
|
184
|
-
|
|
313
|
+
const renderFailuresByPluginRef = useRef3(new Map);
|
|
314
|
+
const pendingRenderReportsRef = useRef3(new Map);
|
|
315
|
+
useEffect5(() => {
|
|
185
316
|
return registry.subscribe(() => {
|
|
186
317
|
setVersion((current) => current + 1);
|
|
187
318
|
});
|
|
188
319
|
}, [registry]);
|
|
189
|
-
|
|
320
|
+
useEffect5(() => {
|
|
190
321
|
if (pendingRenderReportsRef.current.size === 0) {
|
|
191
322
|
return;
|
|
192
323
|
}
|
|
@@ -203,7 +334,7 @@ function Slot(props) {
|
|
|
203
334
|
renderFailuresByPluginRef.current.set(`${report.slot}:${report.pluginId}:render`, failure);
|
|
204
335
|
}
|
|
205
336
|
});
|
|
206
|
-
const entries =
|
|
337
|
+
const entries = useMemo2(() => registry.resolveEntries(props.name), [registry, props.name, version]);
|
|
207
338
|
const slotProps = getSlotProps(props);
|
|
208
339
|
const renderEntry = (entry, fallbackOnFailure) => {
|
|
209
340
|
const key = `${slotName}:${entry.id}`;
|
|
@@ -319,9 +450,14 @@ export {
|
|
|
319
450
|
useTimeline,
|
|
320
451
|
useTerminalDimensions,
|
|
321
452
|
useRenderer,
|
|
453
|
+
usePendingSequence,
|
|
322
454
|
useOnResize,
|
|
455
|
+
useKeymap,
|
|
323
456
|
useKeyboard,
|
|
457
|
+
useBindings,
|
|
324
458
|
useAppContext,
|
|
459
|
+
useActiveKeys,
|
|
460
|
+
reactiveMatcherFromStore,
|
|
325
461
|
getComponentCatalogue,
|
|
326
462
|
flushSync,
|
|
327
463
|
extend,
|
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.0.0-
|
|
7
|
+
"version": "0.0.0-20260419-49f00f96",
|
|
8
8
|
"description": "React renderer for building terminal user interfaces using OpenTUI core",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@opentui/core": "0.0.0-
|
|
47
|
+
"@opentui/core": "0.0.0-20260419-49f00f96",
|
|
48
48
|
"react-reconciler": "^0.32.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
package/src/hooks/index.d.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Renderable } from "@opentui/core";
|
|
2
|
+
import { type ActiveKey, type ActiveKeyOptions, type LayerFields, type Keymap, type ReactiveMatcher, type KeySequencePart } from "@opentui/core/extras";
|
|
3
|
+
import { type DependencyList } from "react";
|
|
4
|
+
export type UseBindingsTarget<TRenderable extends Renderable = Renderable> = TRenderable | null | undefined | (() => TRenderable | null | undefined);
|
|
5
|
+
type UseBindingsLayerBase = LayerFields;
|
|
6
|
+
export type BindingsRef<TRenderable extends Renderable = Renderable> = (value: TRenderable | null) => void;
|
|
7
|
+
export interface UseGlobalBindingsLayer extends UseBindingsLayerBase {
|
|
8
|
+
scope?: "global";
|
|
9
|
+
target?: undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface UseFocusBindingsLayer<TRenderable extends Renderable = Renderable> extends UseBindingsLayerBase {
|
|
12
|
+
scope: "focus";
|
|
13
|
+
target?: UseBindingsTarget<TRenderable>;
|
|
14
|
+
}
|
|
15
|
+
export interface UseFocusWithinBindingsLayer<TRenderable extends Renderable = Renderable> extends UseBindingsLayerBase {
|
|
16
|
+
scope: "focus-within";
|
|
17
|
+
target?: UseBindingsTarget<TRenderable>;
|
|
18
|
+
}
|
|
19
|
+
export interface UseInferredFocusWithinBindingsLayer<TRenderable extends Renderable = Renderable> extends UseBindingsLayerBase {
|
|
20
|
+
scope?: undefined;
|
|
21
|
+
target: UseBindingsTarget<TRenderable>;
|
|
22
|
+
}
|
|
23
|
+
export type UseTargetBindingsLayer<TRenderable extends Renderable = Renderable> = UseFocusBindingsLayer<TRenderable> | UseFocusWithinBindingsLayer<TRenderable> | UseInferredFocusWithinBindingsLayer<TRenderable>;
|
|
24
|
+
export type UseBindingsLayer<TRenderable extends Renderable = Renderable> = UseGlobalBindingsLayer | UseTargetBindingsLayer<TRenderable>;
|
|
25
|
+
export declare const useKeymap: () => Keymap;
|
|
26
|
+
export declare const useActiveKeys: (options?: ActiveKeyOptions) => readonly ActiveKey[];
|
|
27
|
+
export declare const usePendingSequence: () => readonly KeySequencePart[];
|
|
28
|
+
export declare function useBindings<TRenderable extends Renderable = Renderable>(createLayer: () => UseGlobalBindingsLayer, deps?: DependencyList): BindingsRef<TRenderable>;
|
|
29
|
+
export declare function useBindings<TRenderable extends Renderable = Renderable>(createLayer: () => UseTargetBindingsLayer<TRenderable>, deps?: DependencyList): BindingsRef<TRenderable>;
|
|
30
|
+
/**
|
|
31
|
+
* Adapts any `subscribe` + `getSnapshot` store to
|
|
32
|
+
* `ReactiveMatcher`. Pass `predicate` when the snapshot value is not
|
|
33
|
+
* already boolean.
|
|
34
|
+
*/
|
|
35
|
+
export declare function reactiveMatcherFromStore<T>(subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T, predicate?: (value: T) => boolean): ReactiveMatcher;
|
|
36
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ASCIIFontOptions, ASCIIFontRenderable, BaseRenderable, BoxOptions, BoxRenderable, CodeOptions, CodeRenderable, DiffRenderable, DiffRenderableOptions, InputRenderable, InputRenderableOptions, LineNumberOptions, LineNumberRenderable, MarkdownOptions, MarkdownRenderable, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextareaOptions, TextareaRenderable, TextNodeOptions, TextNodeRenderable, TextOptions, TextRenderable } from "@opentui/core";
|
|
1
|
+
import type { ASCIIFontOptions, ASCIIFontRenderable, BaseRenderable, BoxOptions, BoxRenderable, CodeOptions, CodeRenderable, DiffRenderable, DiffRenderableOptions, InputRenderable, InputRenderableOptions, LineNumberOptions, LineNumberRenderable, MarkdownOptions, MarkdownRenderable, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextareaOptions, TextareaRenderable, TextNodeOptions, TextNodeRenderable, TextOptions, TextRenderable, CursorChangeEvent, ContentChangeEvent, KeyEvent } 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}`;
|
|
@@ -46,6 +46,10 @@ export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable>
|
|
|
46
46
|
};
|
|
47
47
|
export type TextareaProps = ComponentProps<TextareaOptions, TextareaRenderable> & {
|
|
48
48
|
focused?: boolean;
|
|
49
|
+
onSubmit?: () => void;
|
|
50
|
+
onContentChange?: (event: ContentChangeEvent) => void;
|
|
51
|
+
onCursorChange?: (event: CursorChangeEvent) => void;
|
|
52
|
+
onKeyDown?: (event: KeyEvent) => void;
|
|
49
53
|
};
|
|
50
54
|
export type CodeProps = ComponentProps<CodeOptions, CodeRenderable>;
|
|
51
55
|
export type MarkdownProps = ComponentProps<MarkdownOptions, MarkdownRenderable>;
|