giggles 0.3.15 → 0.3.17

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
@@ -10,6 +10,19 @@ giggles is a batteries-included react framework for building terminal apps. buil
10
10
 
11
11
  inspired by the [charmbracelet](https://github.com/charmbracelet) ecosystem, it comes with a rich set of UI components, hooks for focus and navigation management, and terminal utilities for things like running shell commands.
12
12
 
13
+ ## features
14
+
15
+ - no `useInput` hooks scattered across your app — focus, input routing, and keyboard navigation are handled for you
16
+ - navigate between views with a simple API; the previously focused component is restored when you return
17
+ - a full set of hooks and components — `useFocus`, `FocusGroup`, `FocusTrap`, `useNavigation`, and more — for building any interaction pattern without reimplementing the plumbing
18
+ - built-in keybinding registry so your app can always show users what keys do what, in the current context — context-aware and accessible via a hook
19
+ - a component library covering most TUI use cases, from text inputs and autocomplete to virtual lists for large datasets — with sensible defaults and render props for full customization
20
+ - render markdown in the terminal, with full formatting and syntax-highlighted code block and diff support
21
+ - hand off terminal control to external programs like `vim` or `less` and reclaim it cleanly when they exit, or spawn processes and stream their output directly into your UI
22
+ - a consistent look out of the box, customizable from a single theme object
23
+
24
+ ## your first TUI
25
+
13
26
  to get started, run
14
27
 
15
28
  ```bash
@@ -1,3 +1,4 @@
1
+ import { SpawnOptionsWithoutStdio } from 'child_process';
1
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
  import { ReactNode } from 'react';
3
4
 
@@ -5,6 +6,25 @@ type TerminalSize = {
5
6
  rows: number;
6
7
  columns: number;
7
8
  };
9
+ type SpawnOptions = SpawnOptionsWithoutStdio & {
10
+ /**
11
+ * Inject FORCE_COLOR=1 and TERM=xterm-256color into the child process
12
+ * environment so tools that detect isTTY emit ANSI color codes.
13
+ */
14
+ pty?: boolean;
15
+ };
16
+ type SpawnOutputLine = {
17
+ type: 'stdout' | 'stderr';
18
+ data: string;
19
+ };
20
+ type SpawnHandle = {
21
+ output: SpawnOutputLine[];
22
+ running: boolean;
23
+ exitCode: number | null;
24
+ error: Error | null;
25
+ run: (command: string, args?: string[], options?: SpawnOptions) => void;
26
+ kill: () => void;
27
+ };
8
28
 
9
29
  declare function useTerminalSize(): TerminalSize;
10
30
 
@@ -22,4 +42,6 @@ declare function useShellOut(): {
22
42
  }>;
23
43
  };
24
44
 
25
- export { AlternateScreen, type TerminalSize, useShellOut, useTerminalFocus, useTerminalSize };
45
+ declare function useSpawn(): SpawnHandle;
46
+
47
+ export { AlternateScreen, type SpawnHandle, type SpawnOptions, type SpawnOutputLine, type TerminalSize, useShellOut, useSpawn, useTerminalFocus, useTerminalSize };
@@ -54,9 +54,88 @@ function useShellOut() {
54
54
  run
55
55
  };
56
56
  }
57
+
58
+ // src/terminal/hooks/useSpawn.ts
59
+ import { spawn } from "child_process";
60
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
61
+ function useSpawn() {
62
+ const [output, setOutput] = useState2([]);
63
+ const [running, setRunning] = useState2(false);
64
+ const [exitCode, setExitCode] = useState2(null);
65
+ const [error, setError] = useState2(null);
66
+ const processRef = useRef2(null);
67
+ const cancelRef = useRef2(null);
68
+ useEffect2(() => {
69
+ return () => {
70
+ var _a, _b;
71
+ (_a = cancelRef.current) == null ? void 0 : _a.call(cancelRef);
72
+ (_b = processRef.current) == null ? void 0 : _b.kill();
73
+ };
74
+ }, []);
75
+ const run = useCallback2((command, args, options) => {
76
+ var _a, _b;
77
+ (_a = cancelRef.current) == null ? void 0 : _a.call(cancelRef);
78
+ (_b = processRef.current) == null ? void 0 : _b.kill();
79
+ processRef.current = null;
80
+ cancelRef.current = null;
81
+ let cancelled = false;
82
+ cancelRef.current = () => {
83
+ cancelled = true;
84
+ };
85
+ const { pty, ...spawnOptions } = options ?? {};
86
+ setOutput([]);
87
+ setRunning(true);
88
+ setExitCode(null);
89
+ setError(null);
90
+ const env = pty ? { ...process.env, FORCE_COLOR: "1", TERM: "xterm-256color", ...spawnOptions.env } : { ...process.env, ...spawnOptions.env };
91
+ let proc;
92
+ try {
93
+ proc = spawn(command, args ?? [], { ...spawnOptions, env, stdio: "pipe" });
94
+ } catch (err) {
95
+ setRunning(false);
96
+ setError(err instanceof Error ? err : new Error(String(err)));
97
+ cancelRef.current = null;
98
+ return;
99
+ }
100
+ processRef.current = proc;
101
+ proc.stdout.on("data", (data) => {
102
+ if (!cancelled) setOutput((prev) => [...prev, { type: "stdout", data: data.toString() }]);
103
+ });
104
+ proc.stderr.on("data", (data) => {
105
+ if (!cancelled) setOutput((prev) => [...prev, { type: "stderr", data: data.toString() }]);
106
+ });
107
+ proc.on("error", (err) => {
108
+ if (!cancelled) {
109
+ cancelled = true;
110
+ setError(err);
111
+ setRunning(false);
112
+ processRef.current = null;
113
+ cancelRef.current = null;
114
+ }
115
+ });
116
+ proc.on("close", (code) => {
117
+ if (!cancelled) {
118
+ setRunning(false);
119
+ setExitCode(code);
120
+ processRef.current = null;
121
+ cancelRef.current = null;
122
+ }
123
+ });
124
+ }, []);
125
+ const kill = useCallback2(() => {
126
+ var _a, _b;
127
+ (_a = cancelRef.current) == null ? void 0 : _a.call(cancelRef);
128
+ cancelRef.current = null;
129
+ (_b = processRef.current) == null ? void 0 : _b.kill();
130
+ processRef.current = null;
131
+ setRunning(false);
132
+ }, []);
133
+ return { output, running, exitCode, error, run, kill };
134
+ }
57
135
  export {
58
136
  AlternateScreen,
59
137
  useShellOut,
138
+ useSpawn,
60
139
  useTerminalFocus,
61
140
  useTerminalSize
62
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",