@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.
@@ -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-20260412-2fcfd0af",
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-cecjmcj8.js";
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 = useRef(handler);
23
- useLayoutEffect(() => {
161
+ const handlerRef = useRef2(handler);
162
+ useLayoutEffect2(() => {
24
163
  handlerRef.current = handler;
25
164
  });
26
- return useCallback((...args) => {
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
- useEffect(() => {
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 useEffect2 } from "react";
189
+ import { useEffect as useEffect3 } from "react";
59
190
  var useOnResize = (callback) => {
60
191
  const renderer = useRenderer();
61
192
  const stableCallback = useEffectEvent(callback);
62
- useEffect2(() => {
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 useEffect3 } from "react";
217
+ import { useEffect as useEffect4 } from "react";
87
218
  var useTimeline = (options = {}) => {
88
219
  const timeline = new Timeline(options);
89
- useEffect3(() => {
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 useEffect4, useMemo, useRef as useRef2, useState as useState2 } from "react";
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 = useRef2(new Map);
183
- const pendingRenderReportsRef = useRef2(new Map);
184
- useEffect4(() => {
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
- useEffect4(() => {
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 = useMemo(() => registry.resolveEntries(props.name), [registry, props.name, version]);
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-20260412-2fcfd0af",
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-20260412-2fcfd0af",
47
+ "@opentui/core": "0.0.0-20260419-49f00f96",
48
48
  "react-reconciler": "^0.32.0"
49
49
  },
50
50
  "devDependencies": {
@@ -1,3 +1,4 @@
1
+ export * from "./use-keymap.js";
1
2
  export * from "./use-keyboard.js";
2
3
  export * from "./use-renderer.js";
3
4
  export * from "./use-resize.js";
@@ -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>;
package/test-utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  createRoot
4
- } from "./chunk-cecjmcj8.js";
4
+ } from "./chunk-mq7v1x2n.js";
5
5
  import"./chunk-2mx7fq49.js";
6
6
 
7
7
  // src/test-utils.ts