giggles 0.6.1 → 0.7.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 +81 -1
- package/dist/{chunk-A7BRQXWE.js → chunk-74PBSWEK.js} +84 -7
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -4
- package/dist/terminal/index.d.ts +7 -6
- package/dist/ui/index.d.ts +11 -5
- package/dist/ui/index.js +16 -13
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# giggles
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<img src="https://github.com/user-attachments/assets/c5c7ef05-232f-4180-8b85-0160fb0f083a" width="700" alt="giggles">
|
|
8
8
|
|
|
9
9
|
giggles is a batteries-included react framework for building terminal apps. built on ink, it handles focus, input routing, screen navigation, and theming out of the box so you can skip the plumbing and build.
|
|
10
10
|
|
|
@@ -30,3 +30,83 @@ npx create-giggles-app
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
see [giggles.zzzzion.com](https://giggles.zzzzion.com) for API documentation and live demos.
|
|
33
|
+
|
|
34
|
+
## giggles/ui
|
|
35
|
+
|
|
36
|
+
### [select](https://giggles.zzzzion.com/ui/select)
|
|
37
|
+
|
|
38
|
+
<img src="https://github.com/user-attachments/assets/8ce13f75-7a7b-4123-a973-f2992193bf84" width="500" alt="select">
|
|
39
|
+
|
|
40
|
+
### [multi select](https://giggles.zzzzion.com/ui/multi-select)
|
|
41
|
+
|
|
42
|
+
<img src="https://github.com/user-attachments/assets/24f5d625-6e46-4cb1-8d22-42d40eb48f56" width="500" alt="multi-select">
|
|
43
|
+
|
|
44
|
+
### [markdown](https://giggles.zzzzion.com/ui/markdown)
|
|
45
|
+
|
|
46
|
+
<img src="https://github.com/user-attachments/assets/1cdb6e84-c714-470a-8cf0-b6abf68b78a9" width="500" alt="markdown">
|
|
47
|
+
|
|
48
|
+
### [text input](https://giggles.zzzzion.com/ui/text-input)
|
|
49
|
+
|
|
50
|
+
<img src="https://github.com/user-attachments/assets/b56056ca-97e2-4dc2-a1eb-2ee3f4d559e7" width="500" alt="text-input">
|
|
51
|
+
|
|
52
|
+
### [viewport](https://giggles.zzzzion.com/ui/viewport)
|
|
53
|
+
|
|
54
|
+
<img src="https://github.com/user-attachments/assets/56c6cb6b-b2a7-4803-bed4-c6c6c34042c3" width="500" alt="viewport">
|
|
55
|
+
|
|
56
|
+
### [code block](https://giggles.zzzzion.com/ui/codeblock)
|
|
57
|
+
|
|
58
|
+
<img src="https://github.com/user-attachments/assets/283dbd7e-326c-4acb-b8c0-d3608722952b" width="500" alt="codeblock">
|
|
59
|
+
|
|
60
|
+
### [confirm](https://giggles.zzzzion.com/ui/confirm)
|
|
61
|
+
|
|
62
|
+
<img src="https://github.com/user-attachments/assets/b887da72-bf02-4084-b846-8b90cc3c3487" width="500" alt="confirm">
|
|
63
|
+
|
|
64
|
+
### [spinner](https://giggles.zzzzion.com/ui/spinner)
|
|
65
|
+
|
|
66
|
+
<img src="https://github.com/user-attachments/assets/71aef7f8-e53b-4876-864f-b9b1a5100c5d" width="500" alt="spinner">
|
|
67
|
+
|
|
68
|
+
### [modal](https://giggles.zzzzion.com/ui/modal)
|
|
69
|
+
|
|
70
|
+
<img src="https://github.com/user-attachments/assets/7415c554-927e-4b5c-91d7-7e3b1f4ea0ca" width="500" alt="modal">
|
|
71
|
+
|
|
72
|
+
### [paginator](https://giggles.zzzzion.com/ui/paginator)
|
|
73
|
+
|
|
74
|
+
<img src="https://github.com/user-attachments/assets/b0780f46-848e-4881-822a-86d7db02d212" width="500" alt="paginator">
|
|
75
|
+
|
|
76
|
+
### [autocomplete](https://giggles.zzzzion.com/ui/autocomplete)
|
|
77
|
+
|
|
78
|
+
<img src="https://github.com/user-attachments/assets/aa76dd7a-5357-4969-a979-95975b5ec578" width="500" alt="autocomplete">
|
|
79
|
+
|
|
80
|
+
### [command palette](https://giggles.zzzzion.com/ui/command-palette)
|
|
81
|
+
|
|
82
|
+
<img src="https://github.com/user-attachments/assets/30886cb5-986f-4477-85c1-61cba4b499aa" width="500" alt="command-palette">
|
|
83
|
+
|
|
84
|
+
### [virtual list](https://giggles.zzzzion.com/ui/virtual-list)
|
|
85
|
+
|
|
86
|
+
<img src="https://github.com/user-attachments/assets/d3ef1d92-813c-4546-8d60-2c38745ddbbc" width="500" alt="virtual-list">
|
|
87
|
+
|
|
88
|
+
### [badge](https://giggles.zzzzion.com/ui/badge)
|
|
89
|
+
|
|
90
|
+
<img src="https://github.com/user-attachments/assets/b144c3f5-8b3b-4236-abf2-fc239d23f0c6" width="500" alt="badge">
|
|
91
|
+
|
|
92
|
+
### [panel](https://giggles.zzzzion.com/ui/panel)
|
|
93
|
+
|
|
94
|
+
<img src="https://github.com/user-attachments/assets/9831d73a-baa9-410a-b933-e0dfd9433604" width="500" alt="panel">
|
|
95
|
+
|
|
96
|
+
## giggles/terminal
|
|
97
|
+
|
|
98
|
+
### [useShellOut](https://giggles.zzzzion.com/terminal#useshellout)
|
|
99
|
+
|
|
100
|
+
suspend the UI, hand off the terminal to an external program like `vim` or `less`, and resume cleanly when it exits
|
|
101
|
+
|
|
102
|
+
### [useSpawn](https://giggles.zzzzion.com/terminal#usespawn)
|
|
103
|
+
|
|
104
|
+
spawn a child process and stream its stdout/stderr output into your UI — with support for colored output via a pty
|
|
105
|
+
|
|
106
|
+
### [useTerminalSize](https://giggles.zzzzion.com/terminal#useterminalsize)
|
|
107
|
+
|
|
108
|
+
reactively track the terminal's current dimensions (rows and columns), updating on resize
|
|
109
|
+
|
|
110
|
+
### [useTerminalFocus](https://giggles.zzzzion.com/terminal#useterminalfocus)
|
|
111
|
+
|
|
112
|
+
detect when the terminal window gains or loses focus
|
|
@@ -76,13 +76,14 @@ function useFocusNode(options) {
|
|
|
76
76
|
const store = useStore();
|
|
77
77
|
const contextParentId = useContext2(ScopeIdContext);
|
|
78
78
|
const parentId = ((_a = options == null ? void 0 : options.parent) == null ? void 0 : _a.id) ?? contextParentId;
|
|
79
|
+
const focusKey = options == null ? void 0 : options.focusKey;
|
|
79
80
|
const subscribe = useMemo(() => store.subscribe.bind(store), [store]);
|
|
80
81
|
useEffect2(() => {
|
|
81
|
-
store.registerNode(id, parentId);
|
|
82
|
+
store.registerNode(id, parentId, focusKey);
|
|
82
83
|
return () => {
|
|
83
84
|
store.unregisterNode(id);
|
|
84
85
|
};
|
|
85
|
-
}, [id, parentId, store]);
|
|
86
|
+
}, [id, parentId, focusKey, store]);
|
|
86
87
|
const hasFocus = useSyncExternalStore2(subscribe, () => store.isFocused(id));
|
|
87
88
|
return { id, hasFocus };
|
|
88
89
|
}
|
|
@@ -128,13 +129,14 @@ function useFocusScope(options) {
|
|
|
128
129
|
const store = useStore();
|
|
129
130
|
const contextParentId = useContext3(ScopeIdContext);
|
|
130
131
|
const parentId = ((_a = options == null ? void 0 : options.parent) == null ? void 0 : _a.id) ?? contextParentId;
|
|
132
|
+
const focusKey = options == null ? void 0 : options.focusKey;
|
|
131
133
|
const subscribe = useMemo2(() => store.subscribe.bind(store), [store]);
|
|
132
134
|
useEffect4(() => {
|
|
133
|
-
store.registerNode(id, parentId);
|
|
135
|
+
store.registerNode(id, parentId, focusKey);
|
|
134
136
|
return () => {
|
|
135
137
|
store.unregisterNode(id);
|
|
136
138
|
};
|
|
137
|
-
}, [id, parentId, store]);
|
|
139
|
+
}, [id, parentId, focusKey, store]);
|
|
138
140
|
const hasFocus = useSyncExternalStore3(subscribe, () => store.isFocused(id));
|
|
139
141
|
const isPassive = useSyncExternalStore3(subscribe, () => store.isPassive(id));
|
|
140
142
|
const next = useCallback(() => store.navigateSibling("next", true, id), [store, id]);
|
|
@@ -143,19 +145,46 @@ function useFocusScope(options) {
|
|
|
143
145
|
const prevShallow = useCallback(() => store.navigateSibling("prev", true, id, true), [store, id]);
|
|
144
146
|
const escape = useCallback(() => store.makePassive(id), [store, id]);
|
|
145
147
|
const drillIn = useCallback(() => store.focusFirstChild(id), [store, id]);
|
|
146
|
-
const
|
|
148
|
+
const focusChild = useCallback((key) => store.focusChildByKey(id, key, false), [store, id]);
|
|
149
|
+
const focusChildShallow = useCallback((key) => store.focusChildByKey(id, key, true), [store, id]);
|
|
150
|
+
const resolvedBindings = typeof (options == null ? void 0 : options.keybindings) === "function" ? options.keybindings({ next, prev, nextShallow, prevShallow, escape, drillIn, focusChild, focusChildShallow }) : (options == null ? void 0 : options.keybindings) ?? {};
|
|
147
151
|
store.registerKeybindings(id, keybindingRegistrationId, resolvedBindings);
|
|
148
152
|
useEffect4(() => {
|
|
149
153
|
return () => {
|
|
150
154
|
store.unregisterKeybindings(id, keybindingRegistrationId);
|
|
151
155
|
};
|
|
152
156
|
}, [id, keybindingRegistrationId, store]);
|
|
153
|
-
|
|
157
|
+
useEffect4(() => {
|
|
158
|
+
if (!store.hasFocusScopeComponent(id)) {
|
|
159
|
+
throw new GigglesError(
|
|
160
|
+
"useFocusScope() was called but no <FocusScope handle={scope}> was rendered. Every useFocusScope() call requires a corresponding <FocusScope> in the render output \u2014 without it, child components register under the wrong parent scope and keyboard navigation silently breaks."
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}, [id, store]);
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
hasFocus,
|
|
167
|
+
isPassive,
|
|
168
|
+
next,
|
|
169
|
+
prev,
|
|
170
|
+
nextShallow,
|
|
171
|
+
prevShallow,
|
|
172
|
+
escape,
|
|
173
|
+
drillIn,
|
|
174
|
+
focusChild,
|
|
175
|
+
focusChildShallow
|
|
176
|
+
};
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
// src/core/focus/FocusScope.tsx
|
|
180
|
+
import { useEffect as useEffect5 } from "react";
|
|
157
181
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
158
182
|
function FocusScope({ handle, children }) {
|
|
183
|
+
const store = useStore();
|
|
184
|
+
useEffect5(() => {
|
|
185
|
+
store.registerFocusScopeComponent(handle.id);
|
|
186
|
+
return () => store.unregisterFocusScopeComponent(handle.id);
|
|
187
|
+
}, [handle.id, store]);
|
|
159
188
|
return /* @__PURE__ */ jsx3(ScopeIdContext.Provider, { value: handle.id, children });
|
|
160
189
|
}
|
|
161
190
|
|
|
@@ -191,6 +220,7 @@ var FocusStore = class {
|
|
|
191
220
|
passiveSet = /* @__PURE__ */ new Set();
|
|
192
221
|
pendingFocusFirstChild = /* @__PURE__ */ new Set();
|
|
193
222
|
trapNodeId = null;
|
|
223
|
+
renderedScopes = /* @__PURE__ */ new Set();
|
|
194
224
|
listeners = /* @__PURE__ */ new Set();
|
|
195
225
|
version = 0;
|
|
196
226
|
// nodeId → registrationId → BindingRegistration
|
|
@@ -198,6 +228,8 @@ var FocusStore = class {
|
|
|
198
228
|
// A keybinding may exist for a node that has not yet appeared in the node tree —
|
|
199
229
|
// this is safe because dispatch only walks nodes in the active branch path.
|
|
200
230
|
keybindings = /* @__PURE__ */ new Map();
|
|
231
|
+
// parentId → focusKey → childId
|
|
232
|
+
keyIndex = /* @__PURE__ */ new Map();
|
|
201
233
|
// ---------------------------------------------------------------------------
|
|
202
234
|
// Subscription
|
|
203
235
|
// ---------------------------------------------------------------------------
|
|
@@ -217,7 +249,7 @@ var FocusStore = class {
|
|
|
217
249
|
// ---------------------------------------------------------------------------
|
|
218
250
|
// Registration
|
|
219
251
|
// ---------------------------------------------------------------------------
|
|
220
|
-
registerNode(id, parentId) {
|
|
252
|
+
registerNode(id, parentId, focusKey) {
|
|
221
253
|
const node = { id, parentId, childrenIds: [] };
|
|
222
254
|
this.nodes.set(id, node);
|
|
223
255
|
this.parentMap.set(id, parentId);
|
|
@@ -237,6 +269,12 @@ var FocusStore = class {
|
|
|
237
269
|
node.childrenIds.push(existingId);
|
|
238
270
|
}
|
|
239
271
|
}
|
|
272
|
+
if (focusKey && parentId) {
|
|
273
|
+
if (!this.keyIndex.has(parentId)) {
|
|
274
|
+
this.keyIndex.set(parentId, /* @__PURE__ */ new Map());
|
|
275
|
+
}
|
|
276
|
+
this.keyIndex.get(parentId).set(focusKey, id);
|
|
277
|
+
}
|
|
240
278
|
if (this.nodes.size === 1) {
|
|
241
279
|
this.focusNode(id);
|
|
242
280
|
}
|
|
@@ -254,6 +292,21 @@ var FocusStore = class {
|
|
|
254
292
|
this.nodes.delete(id);
|
|
255
293
|
this.passiveSet.delete(id);
|
|
256
294
|
this.pendingFocusFirstChild.delete(id);
|
|
295
|
+
if (node.parentId) {
|
|
296
|
+
const parentKeys = this.keyIndex.get(node.parentId);
|
|
297
|
+
if (parentKeys) {
|
|
298
|
+
for (const [key, childId] of parentKeys) {
|
|
299
|
+
if (childId === id) {
|
|
300
|
+
parentKeys.delete(key);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (parentKeys.size === 0) {
|
|
305
|
+
this.keyIndex.delete(node.parentId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.keyIndex.delete(id);
|
|
257
310
|
if (this.focusedId === id) {
|
|
258
311
|
let candidate = node.parentId;
|
|
259
312
|
while (candidate !== null) {
|
|
@@ -301,6 +354,21 @@ var FocusStore = class {
|
|
|
301
354
|
this.pendingFocusFirstChild.add(parentId);
|
|
302
355
|
}
|
|
303
356
|
}
|
|
357
|
+
focusChildByKey(parentId, key, shallow) {
|
|
358
|
+
var _a;
|
|
359
|
+
const childId = (_a = this.keyIndex.get(parentId)) == null ? void 0 : _a.get(key);
|
|
360
|
+
if (!childId || !this.nodes.has(childId)) return;
|
|
361
|
+
if (shallow) {
|
|
362
|
+
this.focusNode(childId);
|
|
363
|
+
} else {
|
|
364
|
+
const child = this.nodes.get(childId);
|
|
365
|
+
if (child.childrenIds.length > 0) {
|
|
366
|
+
this.focusFirstChild(childId);
|
|
367
|
+
} else {
|
|
368
|
+
this.focusNode(childId);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
304
372
|
// ---------------------------------------------------------------------------
|
|
305
373
|
// Navigation
|
|
306
374
|
// ---------------------------------------------------------------------------
|
|
@@ -396,6 +464,15 @@ var FocusStore = class {
|
|
|
396
464
|
// ---------------------------------------------------------------------------
|
|
397
465
|
// Trap
|
|
398
466
|
// ---------------------------------------------------------------------------
|
|
467
|
+
registerFocusScopeComponent(id) {
|
|
468
|
+
this.renderedScopes.add(id);
|
|
469
|
+
}
|
|
470
|
+
unregisterFocusScopeComponent(id) {
|
|
471
|
+
this.renderedScopes.delete(id);
|
|
472
|
+
}
|
|
473
|
+
hasFocusScopeComponent(id) {
|
|
474
|
+
return this.renderedScopes.has(id);
|
|
475
|
+
}
|
|
399
476
|
setTrap(nodeId) {
|
|
400
477
|
this.trapNodeId = nodeId;
|
|
401
478
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,17 +41,19 @@ type FocusScopeHandle = {
|
|
|
41
41
|
id: string;
|
|
42
42
|
hasFocus: boolean;
|
|
43
43
|
isPassive: boolean;
|
|
44
|
-
};
|
|
45
|
-
type FocusScopeHelpers = {
|
|
46
44
|
next: () => void;
|
|
47
45
|
prev: () => void;
|
|
48
46
|
nextShallow: () => void;
|
|
49
47
|
prevShallow: () => void;
|
|
50
48
|
escape: () => void;
|
|
51
49
|
drillIn: () => void;
|
|
50
|
+
focusChild: (key: string) => void;
|
|
51
|
+
focusChildShallow: (key: string) => void;
|
|
52
52
|
};
|
|
53
|
+
type FocusScopeHelpers = Pick<FocusScopeHandle, 'next' | 'prev' | 'nextShallow' | 'prevShallow' | 'escape' | 'drillIn' | 'focusChild' | 'focusChildShallow'>;
|
|
53
54
|
type FocusScopeOptions = {
|
|
54
55
|
parent?: FocusScopeHandle;
|
|
56
|
+
focusKey?: string;
|
|
55
57
|
keybindings?: Keybindings | ((helpers: FocusScopeHelpers) => Keybindings);
|
|
56
58
|
};
|
|
57
59
|
declare function useFocusScope(options?: FocusScopeOptions): FocusScopeHandle;
|
|
@@ -68,6 +70,7 @@ type FocusNodeHandle = {
|
|
|
68
70
|
};
|
|
69
71
|
type FocusNodeOptions = {
|
|
70
72
|
parent?: FocusScopeHandle;
|
|
73
|
+
focusKey?: string;
|
|
71
74
|
};
|
|
72
75
|
declare function useFocusNode(options?: FocusNodeOptions): FocusNodeHandle;
|
|
73
76
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useTerminalSize
|
|
3
|
+
} from "./chunk-WNGBTD67.js";
|
|
1
4
|
import {
|
|
2
5
|
FocusScope,
|
|
3
6
|
FocusStore,
|
|
@@ -11,14 +14,11 @@ import {
|
|
|
11
14
|
useKeybindingRegistry,
|
|
12
15
|
useKeybindings,
|
|
13
16
|
useStore
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-74PBSWEK.js";
|
|
15
18
|
import {
|
|
16
19
|
ThemeProvider,
|
|
17
20
|
useTheme
|
|
18
21
|
} from "./chunk-C77VBSPK.js";
|
|
19
|
-
import {
|
|
20
|
-
useTerminalSize
|
|
21
|
-
} from "./chunk-WNGBTD67.js";
|
|
22
22
|
|
|
23
23
|
// src/core/GigglesProvider.tsx
|
|
24
24
|
import { useRef } from "react";
|
package/dist/terminal/index.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ type TerminalSize = {
|
|
|
4
4
|
rows: number;
|
|
5
5
|
columns: number;
|
|
6
6
|
};
|
|
7
|
+
type ShellOutHandle = {
|
|
8
|
+
run: (command: string) => Promise<{
|
|
9
|
+
exitCode: number;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
7
12
|
type SpawnOptions = SpawnOptionsWithoutStdio & {
|
|
8
13
|
/**
|
|
9
14
|
* Inject FORCE_COLOR=1 and TERM=xterm-256color into the child process
|
|
@@ -28,12 +33,8 @@ declare function useTerminalSize(): TerminalSize;
|
|
|
28
33
|
|
|
29
34
|
declare function useTerminalFocus(callback: (focused: boolean) => void): void;
|
|
30
35
|
|
|
31
|
-
declare function useShellOut():
|
|
32
|
-
run: (command: string) => Promise<{
|
|
33
|
-
exitCode: number;
|
|
34
|
-
}>;
|
|
35
|
-
};
|
|
36
|
+
declare function useShellOut(): ShellOutHandle;
|
|
36
37
|
|
|
37
38
|
declare function useSpawn(): SpawnHandle;
|
|
38
39
|
|
|
39
|
-
export { type SpawnHandle, type SpawnOptions, type SpawnOutputLine, type TerminalSize, useShellOut, useSpawn, useTerminalFocus, useTerminalSize };
|
|
40
|
+
export { type ShellOutHandle, type SpawnHandle, type SpawnOptions, type SpawnOutputLine, type TerminalSize, useShellOut, useSpawn, useTerminalFocus, useTerminalSize };
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -31,8 +31,9 @@ type TextInputProps = {
|
|
|
31
31
|
onSubmit?: (value: string) => void;
|
|
32
32
|
placeholder?: string;
|
|
33
33
|
render?: (props: TextInputRenderProps) => React$1.ReactNode;
|
|
34
|
+
focusKey?: string;
|
|
34
35
|
};
|
|
35
|
-
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
36
|
+
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render, focusKey }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
36
37
|
|
|
37
38
|
type PaginatorStyle = 'dots' | 'arrows' | 'scrollbar' | 'counter' | 'none';
|
|
38
39
|
type PaginatorProps = {
|
|
@@ -70,8 +71,9 @@ type SelectProps<T> = {
|
|
|
70
71
|
paginatorStyle?: PaginatorStyle;
|
|
71
72
|
wrap?: boolean;
|
|
72
73
|
render?: (props: SelectRenderProps<T>) => React$1.ReactNode;
|
|
74
|
+
focusKey?: string;
|
|
73
75
|
};
|
|
74
|
-
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, gap, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
76
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
75
77
|
|
|
76
78
|
type MultiSelectRenderProps<T> = {
|
|
77
79
|
option: SelectOption<T>;
|
|
@@ -92,15 +94,17 @@ type MultiSelectProps<T> = {
|
|
|
92
94
|
paginatorStyle?: PaginatorStyle;
|
|
93
95
|
wrap?: boolean;
|
|
94
96
|
render?: (props: MultiSelectRenderProps<T>) => React$1.ReactNode;
|
|
97
|
+
focusKey?: string;
|
|
95
98
|
};
|
|
96
|
-
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, gap, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
99
|
+
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
97
100
|
|
|
98
101
|
type ConfirmProps = {
|
|
99
102
|
message: string;
|
|
100
103
|
defaultValue?: boolean;
|
|
101
104
|
onSubmit: (value: boolean) => void;
|
|
105
|
+
focusKey?: string;
|
|
102
106
|
};
|
|
103
|
-
declare function Confirm({ message, defaultValue, onSubmit }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
107
|
+
declare function Confirm({ message, defaultValue, onSubmit, focusKey }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
104
108
|
|
|
105
109
|
type AutocompleteRenderProps<T> = {
|
|
106
110
|
option: SelectOption<T>;
|
|
@@ -122,8 +126,9 @@ type AutocompleteProps<T> = {
|
|
|
122
126
|
paginatorStyle?: PaginatorStyle;
|
|
123
127
|
wrap?: boolean;
|
|
124
128
|
render?: (props: AutocompleteRenderProps<T>) => React$1.ReactNode;
|
|
129
|
+
focusKey?: string;
|
|
125
130
|
};
|
|
126
|
-
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, gap, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
131
|
+
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
127
132
|
|
|
128
133
|
type VirtualListRenderProps<T> = {
|
|
129
134
|
item: T;
|
|
@@ -160,6 +165,7 @@ declare const Viewport: React$1.ForwardRefExoticComponent<Omit<BoxProps, "flexDi
|
|
|
160
165
|
height: number;
|
|
161
166
|
keybindings?: boolean;
|
|
162
167
|
footer?: React$1.ReactNode;
|
|
168
|
+
focusKey?: string;
|
|
163
169
|
} & React$1.RefAttributes<ViewportRef>>;
|
|
164
170
|
|
|
165
171
|
type ModalProps = Omit<BoxProps, 'children'> & {
|
package/dist/ui/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
useFocusNode,
|
|
5
5
|
useKeybindingRegistry,
|
|
6
6
|
useKeybindings
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-74PBSWEK.js";
|
|
8
8
|
import {
|
|
9
9
|
CodeBlock
|
|
10
10
|
} from "../chunk-SKSDNDQF.js";
|
|
@@ -136,8 +136,8 @@ function CommandPalette({ onClose, interactive = true, render }) {
|
|
|
136
136
|
import { useReducer, useRef } from "react";
|
|
137
137
|
import { Text as Text2 } from "ink";
|
|
138
138
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
139
|
-
function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
140
|
-
const focus = useFocusNode();
|
|
139
|
+
function TextInput({ label, value, onChange, onSubmit, placeholder, render, focusKey }) {
|
|
140
|
+
const focus = useFocusNode({ focusKey });
|
|
141
141
|
const cursorRef = useRef(value.length);
|
|
142
142
|
const [, forceRender] = useReducer((c) => c + 1, 0);
|
|
143
143
|
const cursor = Math.min(cursorRef.current, value.length);
|
|
@@ -351,7 +351,8 @@ function Select({
|
|
|
351
351
|
maxVisible,
|
|
352
352
|
paginatorStyle,
|
|
353
353
|
wrap = true,
|
|
354
|
-
render
|
|
354
|
+
render,
|
|
355
|
+
focusKey
|
|
355
356
|
}) {
|
|
356
357
|
const seen = /* @__PURE__ */ new Set();
|
|
357
358
|
for (const opt of options) {
|
|
@@ -361,7 +362,7 @@ function Select({
|
|
|
361
362
|
}
|
|
362
363
|
seen.add(key);
|
|
363
364
|
}
|
|
364
|
-
const focus = useFocusNode();
|
|
365
|
+
const focus = useFocusNode({ focusKey });
|
|
365
366
|
const theme = useTheme();
|
|
366
367
|
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
367
368
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
@@ -438,7 +439,8 @@ function MultiSelect({
|
|
|
438
439
|
maxVisible,
|
|
439
440
|
paginatorStyle,
|
|
440
441
|
wrap = true,
|
|
441
|
-
render
|
|
442
|
+
render,
|
|
443
|
+
focusKey
|
|
442
444
|
}) {
|
|
443
445
|
const seen = /* @__PURE__ */ new Set();
|
|
444
446
|
for (const opt of options) {
|
|
@@ -448,7 +450,7 @@ function MultiSelect({
|
|
|
448
450
|
}
|
|
449
451
|
seen.add(key);
|
|
450
452
|
}
|
|
451
|
-
const focus = useFocusNode();
|
|
453
|
+
const focus = useFocusNode({ focusKey });
|
|
452
454
|
const theme = useTheme();
|
|
453
455
|
const [highlightIndex, setHighlightIndex] = useState4(0);
|
|
454
456
|
const [internalSelected, setInternalSelected] = useState4([]);
|
|
@@ -515,8 +517,8 @@ function MultiSelect({
|
|
|
515
517
|
// src/ui/Confirm.tsx
|
|
516
518
|
import { Text as Text6 } from "ink";
|
|
517
519
|
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
518
|
-
function Confirm({ message, defaultValue = true, onSubmit }) {
|
|
519
|
-
const focus = useFocusNode();
|
|
520
|
+
function Confirm({ message, defaultValue = true, onSubmit, focusKey }) {
|
|
521
|
+
const focus = useFocusNode({ focusKey });
|
|
520
522
|
useKeybindings(focus, {
|
|
521
523
|
y: () => onSubmit(true),
|
|
522
524
|
n: () => onSubmit(false),
|
|
@@ -558,7 +560,8 @@ function Autocomplete({
|
|
|
558
560
|
maxVisible,
|
|
559
561
|
paginatorStyle,
|
|
560
562
|
wrap = true,
|
|
561
|
-
render
|
|
563
|
+
render,
|
|
564
|
+
focusKey
|
|
562
565
|
}) {
|
|
563
566
|
const seen = /* @__PURE__ */ new Set();
|
|
564
567
|
for (const opt of options) {
|
|
@@ -568,7 +571,7 @@ function Autocomplete({
|
|
|
568
571
|
}
|
|
569
572
|
seen.add(key);
|
|
570
573
|
}
|
|
571
|
-
const focus = useFocusNode();
|
|
574
|
+
const focus = useFocusNode({ focusKey });
|
|
572
575
|
const theme = useTheme();
|
|
573
576
|
const [query, setQuery] = useState5("");
|
|
574
577
|
const [highlightIndex, setHighlightIndex] = useState5(0);
|
|
@@ -718,8 +721,8 @@ function MeasurableItem({
|
|
|
718
721
|
}, [index, onMeasure, children]);
|
|
719
722
|
return /* @__PURE__ */ jsx8(Box7, { ref, flexShrink: 0, width: "100%", flexDirection: "column", children });
|
|
720
723
|
}
|
|
721
|
-
var Viewport = forwardRef(function Viewport2({ children, height, keybindings: enableKeybindings = true, footer, ...boxProps }, ref) {
|
|
722
|
-
const focus = useFocusNode();
|
|
724
|
+
var Viewport = forwardRef(function Viewport2({ children, height, keybindings: enableKeybindings = true, footer, focusKey, ...boxProps }, ref) {
|
|
725
|
+
const focus = useFocusNode({ focusKey });
|
|
723
726
|
const [scrollOffset, setScrollOffset] = useState6(0);
|
|
724
727
|
const contentHeightRef = useRef4(0);
|
|
725
728
|
const itemHeightsRef = useRef4({});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "giggles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup",
|
|
42
42
|
"build:watch": "nodemon --watch src --ext ts,tsx --exec tsup",
|
|
43
|
-
"play": "tsx --watch",
|
|
44
|
-
"record": "vhs",
|
|
43
|
+
"play": "f() { tsx --watch playground/examples/$1.tsx; }; f",
|
|
44
|
+
"record": "f() { vhs playground/tapes/$1.tape; }; f",
|
|
45
45
|
"dev:docs": "pnpm build && concurrently --kill-others \"pnpm build:watch\" \"pnpm --filter documentation dev\"",
|
|
46
|
-
"lint": "prettier --write . && eslint . --fix"
|
|
46
|
+
"lint": "prettier --write --loglevel warn . && eslint . --fix --quiet"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"dist"
|