giggles 0.2.3 → 0.3.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 +2 -2
- package/dist/chunk-7PDVDYFB.js +23 -0
- package/dist/index.d.ts +19 -18
- package/dist/index.js +51 -58
- package/dist/terminal/index.d.ts +23 -0
- package/dist/terminal/index.js +83 -0
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[](https://github.com/zion-off/giggles/actions/workflows/giggles-lint.yml)
|
|
2
|
+
[](https://github.com/zion-off/giggles/actions/workflows/giggles-cd.yml)
|
|
3
3
|
[](https://giggles.zzzzion.com)
|
|
4
4
|
|
|
5
5
|
# giggles
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/terminal/components/AlternateScreen.tsx
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
var _a;
|
|
5
|
+
var isTTY = typeof process !== "undefined" && ((_a = process.stdout) == null ? void 0 : _a.write);
|
|
6
|
+
function AlternateScreen({ children }) {
|
|
7
|
+
const [ready, setReady] = useState(!isTTY);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!isTTY) return;
|
|
10
|
+
process.stdout.write("\x1B[?1049h");
|
|
11
|
+
process.stdout.write("\x1B[2J");
|
|
12
|
+
process.stdout.write("\x1B[H");
|
|
13
|
+
setReady(true);
|
|
14
|
+
return () => {
|
|
15
|
+
process.stdout.write("\x1B[?1049l");
|
|
16
|
+
};
|
|
17
|
+
}, []);
|
|
18
|
+
return ready ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
AlternateScreen
|
|
23
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -13,14 +13,32 @@ type GigglesProviderProps = {
|
|
|
13
13
|
};
|
|
14
14
|
declare function GigglesProvider({ children }: GigglesProviderProps): react_jsx_runtime.JSX.Element;
|
|
15
15
|
|
|
16
|
+
type KeyHandler = (input: string, key: Key) => void;
|
|
17
|
+
type SpecialKey = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'tab' | 'backspace' | 'delete' | 'pageup' | 'pagedown' | 'home' | 'end';
|
|
18
|
+
type KeyName = SpecialKey | (string & {});
|
|
19
|
+
type Keybindings = Partial<Record<KeyName, KeyHandler>>;
|
|
20
|
+
type KeybindingOptions = {
|
|
21
|
+
capture?: boolean;
|
|
22
|
+
onKeypress?: (input: string, key: Key) => void;
|
|
23
|
+
layer?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
declare function useKeybindings(focus: FocusHandle, bindings: Keybindings, options?: KeybindingOptions): void;
|
|
27
|
+
|
|
28
|
+
type FocusTrapProps = {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
};
|
|
31
|
+
declare function FocusTrap({ children }: FocusTrapProps): react_jsx_runtime.JSX.Element;
|
|
32
|
+
|
|
16
33
|
type FocusGroupProps = {
|
|
17
34
|
children: React__default.ReactNode;
|
|
18
35
|
direction?: 'vertical' | 'horizontal';
|
|
19
36
|
value?: string;
|
|
20
37
|
wrap?: boolean;
|
|
21
38
|
navigable?: boolean;
|
|
39
|
+
keybindings?: Keybindings;
|
|
22
40
|
};
|
|
23
|
-
declare function FocusGroup({ children, direction, value, wrap, navigable }: FocusGroupProps): react_jsx_runtime.JSX.Element;
|
|
41
|
+
declare function FocusGroup({ children, direction, value, wrap, navigable, keybindings: customBindings }: FocusGroupProps): react_jsx_runtime.JSX.Element;
|
|
24
42
|
|
|
25
43
|
type FocusHandle = {
|
|
26
44
|
id: string;
|
|
@@ -32,23 +50,6 @@ declare const useFocus: (id?: string) => FocusHandle;
|
|
|
32
50
|
|
|
33
51
|
declare function useFocusState<T extends string>(initial: T): readonly [T, React$1.Dispatch<React$1.SetStateAction<T>>];
|
|
34
52
|
|
|
35
|
-
type KeyHandler = (input: string, key: Key) => void;
|
|
36
|
-
type SpecialKey = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'tab' | 'backspace' | 'delete' | 'pageup' | 'pagedown' | 'home' | 'end';
|
|
37
|
-
type KeyName = SpecialKey | (string & {});
|
|
38
|
-
type Keybindings = Partial<Record<KeyName, KeyHandler>>;
|
|
39
|
-
type KeybindingOptions = {
|
|
40
|
-
capture?: boolean;
|
|
41
|
-
onKeypress?: (input: string, key: Key) => void;
|
|
42
|
-
layer?: string;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
declare function useKeybindings(focus: FocusHandle, bindings: Keybindings, options?: KeybindingOptions): void;
|
|
46
|
-
|
|
47
|
-
type FocusTrapProps = {
|
|
48
|
-
children: React.ReactNode;
|
|
49
|
-
};
|
|
50
|
-
declare function FocusTrap({ children }: FocusTrapProps): react_jsx_runtime.JSX.Element;
|
|
51
|
-
|
|
52
53
|
type RouterProps = {
|
|
53
54
|
children: React__default.ReactNode;
|
|
54
55
|
initialScreen: string;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlternateScreen
|
|
3
|
+
} from "./chunk-7PDVDYFB.js";
|
|
4
|
+
|
|
1
5
|
// src/core/GigglesError.ts
|
|
2
6
|
var GigglesError = class extends Error {
|
|
3
7
|
constructor(message) {
|
|
@@ -14,24 +18,11 @@ var FocusProvider = ({ children }) => {
|
|
|
14
18
|
const nodesRef = useRef(/* @__PURE__ */ new Map());
|
|
15
19
|
const pendingFocusFirstChildRef = useRef(/* @__PURE__ */ new Set());
|
|
16
20
|
const [focusedId, setFocusedId] = useState(null);
|
|
17
|
-
const [activeBranchNodes, setActiveBranchNodes] = useState(/* @__PURE__ */ new Set());
|
|
18
|
-
const [activeBranchPath, setActiveBranchPath] = useState([]);
|
|
19
21
|
const focusNode = useCallback((id) => {
|
|
20
22
|
const nodes = nodesRef.current;
|
|
21
23
|
if (!nodes.has(id)) return;
|
|
22
24
|
setFocusedId((current) => {
|
|
23
25
|
if (current === id) return current;
|
|
24
|
-
const pathSet = /* @__PURE__ */ new Set();
|
|
25
|
-
const pathArray = [];
|
|
26
|
-
let currentNode = id;
|
|
27
|
-
while (currentNode) {
|
|
28
|
-
pathSet.add(currentNode);
|
|
29
|
-
pathArray.push(currentNode);
|
|
30
|
-
const node = nodes.get(currentNode);
|
|
31
|
-
currentNode = (node == null ? void 0 : node.parentId) ?? null;
|
|
32
|
-
}
|
|
33
|
-
setActiveBranchNodes(pathSet);
|
|
34
|
-
setActiveBranchPath(pathArray);
|
|
35
26
|
return id;
|
|
36
27
|
});
|
|
37
28
|
}, []);
|
|
@@ -92,23 +83,7 @@ var FocusProvider = ({ children }) => {
|
|
|
92
83
|
pendingFocusFirstChildRef.current.delete(id);
|
|
93
84
|
setFocusedId((current) => {
|
|
94
85
|
if (current !== id) return current;
|
|
95
|
-
|
|
96
|
-
const pathSet = /* @__PURE__ */ new Set();
|
|
97
|
-
const pathArray = [];
|
|
98
|
-
let currentNode = node.parentId;
|
|
99
|
-
while (currentNode) {
|
|
100
|
-
pathSet.add(currentNode);
|
|
101
|
-
pathArray.push(currentNode);
|
|
102
|
-
const n = nodes.get(currentNode);
|
|
103
|
-
currentNode = (n == null ? void 0 : n.parentId) ?? null;
|
|
104
|
-
}
|
|
105
|
-
setActiveBranchNodes(pathSet);
|
|
106
|
-
setActiveBranchPath(pathArray);
|
|
107
|
-
return node.parentId;
|
|
108
|
-
}
|
|
109
|
-
setActiveBranchNodes(/* @__PURE__ */ new Set());
|
|
110
|
-
setActiveBranchPath([]);
|
|
111
|
-
return null;
|
|
86
|
+
return node.parentId ?? null;
|
|
112
87
|
});
|
|
113
88
|
}, []);
|
|
114
89
|
const isFocused = useCallback(
|
|
@@ -120,15 +95,18 @@ var FocusProvider = ({ children }) => {
|
|
|
120
95
|
const getFocusedId = useCallback(() => {
|
|
121
96
|
return focusedId;
|
|
122
97
|
}, [focusedId]);
|
|
123
|
-
const isInActiveBranch = useCallback(
|
|
124
|
-
(id) => {
|
|
125
|
-
return activeBranchNodes.has(id);
|
|
126
|
-
},
|
|
127
|
-
[activeBranchNodes]
|
|
128
|
-
);
|
|
129
98
|
const getActiveBranchPath = useCallback(() => {
|
|
130
|
-
return
|
|
131
|
-
|
|
99
|
+
if (!focusedId) return [];
|
|
100
|
+
const nodes = nodesRef.current;
|
|
101
|
+
const pathArray = [];
|
|
102
|
+
let node = focusedId;
|
|
103
|
+
while (node) {
|
|
104
|
+
pathArray.push(node);
|
|
105
|
+
const n = nodes.get(node);
|
|
106
|
+
node = (n == null ? void 0 : n.parentId) ?? null;
|
|
107
|
+
}
|
|
108
|
+
return pathArray;
|
|
109
|
+
}, [focusedId]);
|
|
132
110
|
const navigateSibling = useCallback(
|
|
133
111
|
(direction, wrap = true) => {
|
|
134
112
|
const currentId = focusedId;
|
|
@@ -161,7 +139,6 @@ var FocusProvider = ({ children }) => {
|
|
|
161
139
|
focusFirstChild,
|
|
162
140
|
isFocused,
|
|
163
141
|
getFocusedId,
|
|
164
|
-
isInActiveBranch,
|
|
165
142
|
getActiveBranchPath,
|
|
166
143
|
navigateSibling
|
|
167
144
|
},
|
|
@@ -179,7 +156,7 @@ var useFocusContext = () => {
|
|
|
179
156
|
};
|
|
180
157
|
|
|
181
158
|
// src/core/focus/FocusGroup.tsx
|
|
182
|
-
import { useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as
|
|
159
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as useRef4 } from "react";
|
|
183
160
|
|
|
184
161
|
// src/core/input/InputContext.tsx
|
|
185
162
|
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useRef as useRef2 } from "react";
|
|
@@ -257,6 +234,7 @@ import { useInput } from "ink";
|
|
|
257
234
|
|
|
258
235
|
// src/core/input/normalizeKey.ts
|
|
259
236
|
function normalizeKey(input, key) {
|
|
237
|
+
if (input === "\x1B[I" || input === "\x1B[O") return "";
|
|
260
238
|
if (key.downArrow) return "down";
|
|
261
239
|
if (key.upArrow) return "up";
|
|
262
240
|
if (key.leftArrow) return "left";
|
|
@@ -284,6 +262,7 @@ function InputRouter({ children }) {
|
|
|
284
262
|
const path = getActiveBranchPath();
|
|
285
263
|
const trapNodeId = getTrapNodeId();
|
|
286
264
|
const keyName = normalizeKey(input, key);
|
|
265
|
+
if (!keyName) return;
|
|
287
266
|
for (const nodeId of path) {
|
|
288
267
|
const nodeBindings = getNodeBindings(nodeId);
|
|
289
268
|
if (!nodeBindings) continue;
|
|
@@ -315,16 +294,25 @@ function useKeybindings(focus, bindings, options) {
|
|
|
315
294
|
}
|
|
316
295
|
|
|
317
296
|
// src/core/input/FocusTrap.tsx
|
|
318
|
-
import { useEffect as useEffect2 } from "react";
|
|
319
|
-
import {
|
|
297
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
298
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
320
299
|
function FocusTrap({ children }) {
|
|
321
300
|
const { id } = useFocus();
|
|
322
301
|
const { setTrap, clearTrap } = useInputContext();
|
|
302
|
+
const { focusFirstChild, getFocusedId, focusNode } = useFocusContext();
|
|
303
|
+
const previousFocusRef = useRef3(getFocusedId());
|
|
323
304
|
useEffect2(() => {
|
|
305
|
+
const previousFocus = previousFocusRef.current;
|
|
324
306
|
setTrap(id);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
307
|
+
focusFirstChild(id);
|
|
308
|
+
return () => {
|
|
309
|
+
clearTrap(id);
|
|
310
|
+
if (previousFocus) {
|
|
311
|
+
focusNode(previousFocus);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}, [id]);
|
|
315
|
+
return /* @__PURE__ */ jsx4(FocusNodeContext.Provider, { value: id, children });
|
|
328
316
|
}
|
|
329
317
|
|
|
330
318
|
// src/core/focus/FocusBindContext.tsx
|
|
@@ -364,11 +352,12 @@ function FocusGroup({
|
|
|
364
352
|
direction = "vertical",
|
|
365
353
|
value,
|
|
366
354
|
wrap = true,
|
|
367
|
-
navigable = true
|
|
355
|
+
navigable = true,
|
|
356
|
+
keybindings: customBindings
|
|
368
357
|
}) {
|
|
369
358
|
const focus = useFocus();
|
|
370
359
|
const { focusNode, navigateSibling } = useFocusContext();
|
|
371
|
-
const bindMapRef =
|
|
360
|
+
const bindMapRef = useRef4(/* @__PURE__ */ new Map());
|
|
372
361
|
const register = useCallback3((logicalId, nodeId) => {
|
|
373
362
|
if (bindMapRef.current.has(logicalId)) {
|
|
374
363
|
throw new GigglesError(`FocusGroup: Duplicate id "${logicalId}". Each child must have a unique id.`);
|
|
@@ -386,7 +375,7 @@ function FocusGroup({
|
|
|
386
375
|
}
|
|
387
376
|
}
|
|
388
377
|
}, [value, focusNode]);
|
|
389
|
-
const bindContextValue = value ? { register, unregister } : null;
|
|
378
|
+
const bindContextValue = useMemo(() => value ? { register, unregister } : null, [value, register, unregister]);
|
|
390
379
|
const navigationKeys = useMemo(() => {
|
|
391
380
|
if (!navigable) return {};
|
|
392
381
|
const next = () => navigateSibling("next", wrap);
|
|
@@ -403,7 +392,11 @@ function FocusGroup({
|
|
|
403
392
|
left: prev
|
|
404
393
|
};
|
|
405
394
|
}, [navigable, direction, wrap, navigateSibling]);
|
|
406
|
-
|
|
395
|
+
const mergedBindings = useMemo(
|
|
396
|
+
() => ({ ...navigationKeys, ...customBindings }),
|
|
397
|
+
[navigationKeys, customBindings]
|
|
398
|
+
);
|
|
399
|
+
useKeybindings(focus, mergedBindings);
|
|
407
400
|
return /* @__PURE__ */ jsx5(FocusNodeContext.Provider, { value: focus.id, children: /* @__PURE__ */ jsx5(FocusBindContext.Provider, { value: bindContextValue, children }) });
|
|
408
401
|
}
|
|
409
402
|
|
|
@@ -417,11 +410,11 @@ function useFocusState(initial) {
|
|
|
417
410
|
// src/core/GigglesProvider.tsx
|
|
418
411
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
419
412
|
function GigglesProvider({ children }) {
|
|
420
|
-
return /* @__PURE__ */ jsx6(FocusProvider, { children: /* @__PURE__ */ jsx6(InputProvider, { children: /* @__PURE__ */ jsx6(InputRouter, { children }) }) });
|
|
413
|
+
return /* @__PURE__ */ jsx6(AlternateScreen, { children: /* @__PURE__ */ jsx6(FocusProvider, { children: /* @__PURE__ */ jsx6(InputProvider, { children: /* @__PURE__ */ jsx6(InputRouter, { children }) }) }) });
|
|
421
414
|
}
|
|
422
415
|
|
|
423
416
|
// src/core/router/Router.tsx
|
|
424
|
-
import React4, { useCallback as useCallback4, useReducer, useRef as
|
|
417
|
+
import React4, { useCallback as useCallback4, useReducer, useRef as useRef6 } from "react";
|
|
425
418
|
|
|
426
419
|
// src/core/router/Screen.tsx
|
|
427
420
|
function Screen(_props) {
|
|
@@ -429,7 +422,7 @@ function Screen(_props) {
|
|
|
429
422
|
}
|
|
430
423
|
|
|
431
424
|
// src/core/router/ScreenEntry.tsx
|
|
432
|
-
import React3, { useEffect as useEffect5, useId as useId2, useMemo as useMemo2, useRef as
|
|
425
|
+
import React3, { useEffect as useEffect5, useId as useId2, useMemo as useMemo2, useRef as useRef5 } from "react";
|
|
433
426
|
import { Box } from "ink";
|
|
434
427
|
|
|
435
428
|
// src/core/router/NavigationContext.tsx
|
|
@@ -459,8 +452,8 @@ function ScreenEntry({
|
|
|
459
452
|
const screenNodeId = useId2();
|
|
460
453
|
const parentId = React3.useContext(FocusNodeContext);
|
|
461
454
|
const { registerNode, unregisterNode, focusFirstChild, focusNode, getFocusedId } = useFocusContext();
|
|
462
|
-
const lastFocusedChildRef =
|
|
463
|
-
const wasTopRef =
|
|
455
|
+
const lastFocusedChildRef = useRef5(null);
|
|
456
|
+
const wasTopRef = useRef5(isTop);
|
|
464
457
|
useEffect5(() => {
|
|
465
458
|
registerNode(screenNodeId, parentId);
|
|
466
459
|
return () => {
|
|
@@ -490,7 +483,7 @@ function ScreenEntry({
|
|
|
490
483
|
}
|
|
491
484
|
|
|
492
485
|
// src/core/router/Router.tsx
|
|
493
|
-
import { Fragment as
|
|
486
|
+
import { Fragment as Fragment2, jsx as jsx8 } from "react/jsx-runtime";
|
|
494
487
|
function routerReducer(stack, action) {
|
|
495
488
|
switch (action.type) {
|
|
496
489
|
case "push":
|
|
@@ -504,9 +497,9 @@ function routerReducer(stack, action) {
|
|
|
504
497
|
}
|
|
505
498
|
}
|
|
506
499
|
function Router({ children, initialScreen, initialParams, restoreFocus = true }) {
|
|
507
|
-
const screenId =
|
|
500
|
+
const screenId = useRef6(0);
|
|
508
501
|
const routes = React4.Children.toArray(children).filter((child) => React4.isValidElement(child) && child.type === Screen).map((child) => child.props);
|
|
509
|
-
const screenNamesRef =
|
|
502
|
+
const screenNamesRef = useRef6(/* @__PURE__ */ new Set());
|
|
510
503
|
screenNamesRef.current = new Set(routes.map((r) => r.name));
|
|
511
504
|
const assertScreen = useCallback4((name) => {
|
|
512
505
|
if (!screenNamesRef.current.has(name)) {
|
|
@@ -552,7 +545,7 @@ function Router({ children, initialScreen, initialParams, restoreFocus = true })
|
|
|
552
545
|
components.set(route.name, route.component);
|
|
553
546
|
}
|
|
554
547
|
const canGoBack = stack.length > 1;
|
|
555
|
-
return /* @__PURE__ */ jsx8(
|
|
548
|
+
return /* @__PURE__ */ jsx8(Fragment2, { children: stack.map((entry, i) => {
|
|
556
549
|
const Component = components.get(entry.name);
|
|
557
550
|
if (!Component) return null;
|
|
558
551
|
return /* @__PURE__ */ jsx8(
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type TerminalSize = {
|
|
5
|
+
rows: number;
|
|
6
|
+
columns: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
declare function useTerminalSize(): TerminalSize;
|
|
10
|
+
|
|
11
|
+
declare function useTerminalFocus(callback: (focused: boolean) => void): void;
|
|
12
|
+
|
|
13
|
+
declare function AlternateScreen({ children }: {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
16
|
+
|
|
17
|
+
declare function useShellOut(): {
|
|
18
|
+
run: (command: string) => Promise<{
|
|
19
|
+
exitCode: number;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { AlternateScreen, type TerminalSize, useShellOut, useTerminalFocus, useTerminalSize };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlternateScreen
|
|
3
|
+
} from "../chunk-7PDVDYFB.js";
|
|
4
|
+
|
|
5
|
+
// src/terminal/hooks/useTerminalSize.ts
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
function useTerminalSize() {
|
|
8
|
+
const [size, setSize] = useState({
|
|
9
|
+
rows: process.stdout.rows,
|
|
10
|
+
columns: process.stdout.columns
|
|
11
|
+
});
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const handleResize = () => {
|
|
14
|
+
setSize({
|
|
15
|
+
rows: process.stdout.rows,
|
|
16
|
+
columns: process.stdout.columns
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
process.stdout.on("resize", handleResize);
|
|
20
|
+
return () => {
|
|
21
|
+
process.stdout.off("resize", handleResize);
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
return size;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/terminal/hooks/useTerminalFocus.ts
|
|
28
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
29
|
+
function useTerminalFocus(callback) {
|
|
30
|
+
const callbackRef = useRef(callback);
|
|
31
|
+
callbackRef.current = callback;
|
|
32
|
+
useEffect2(() => {
|
|
33
|
+
const handler = (data) => {
|
|
34
|
+
const str = data.toString();
|
|
35
|
+
if (str.includes("\x1B[I")) callbackRef.current(true);
|
|
36
|
+
if (str.includes("\x1B[O")) callbackRef.current(false);
|
|
37
|
+
};
|
|
38
|
+
process.stdin.on("data", handler);
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
process.stdout.write("\x1B[?1004h");
|
|
41
|
+
}, 0);
|
|
42
|
+
return () => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
process.stdout.write("\x1B[?1004l");
|
|
45
|
+
process.stdin.off("data", handler);
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/terminal/hooks/useShellout.ts
|
|
51
|
+
import { execa } from "execa";
|
|
52
|
+
import { useCallback, useState as useState2 } from "react";
|
|
53
|
+
import { useStdin } from "ink";
|
|
54
|
+
function useShellOut() {
|
|
55
|
+
const [, setRedrawCount] = useState2(0);
|
|
56
|
+
const { setRawMode } = useStdin();
|
|
57
|
+
const run = useCallback(
|
|
58
|
+
async (command) => {
|
|
59
|
+
process.stdout.write("\x1B[?1049l");
|
|
60
|
+
setRawMode(false);
|
|
61
|
+
try {
|
|
62
|
+
const result = await execa(command, { stdio: "inherit", shell: true, reject: false });
|
|
63
|
+
return { exitCode: result.exitCode ?? 0 };
|
|
64
|
+
} finally {
|
|
65
|
+
setRawMode(true);
|
|
66
|
+
process.stdout.write("\x1B[?1049h");
|
|
67
|
+
process.stdout.write("\x1B[2J");
|
|
68
|
+
process.stdout.write("\x1B[H");
|
|
69
|
+
setRedrawCount((c) => c + 1);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
[setRawMode]
|
|
73
|
+
);
|
|
74
|
+
return {
|
|
75
|
+
run
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
AlternateScreen,
|
|
80
|
+
useShellOut,
|
|
81
|
+
useTerminalFocus,
|
|
82
|
+
useTerminalSize
|
|
83
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "giggles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./terminal": {
|
|
18
|
+
"import": "./dist/terminal/index.js",
|
|
19
|
+
"types": "./dist/terminal/index.d.ts"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"keywords": [
|
|
@@ -54,5 +58,8 @@
|
|
|
54
58
|
"tsx": "^4.21.0",
|
|
55
59
|
"typescript": "^5.0.3",
|
|
56
60
|
"typescript-eslint": "^8.0.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"execa": "^9.6.1"
|
|
57
64
|
}
|
|
58
65
|
}
|