giggles 0.2.4 → 0.3.1
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 +1 -1
- package/dist/chunk-4LED4GXQ.js +460 -0
- package/dist/chunk-7PDVDYFB.js +23 -0
- package/dist/index.d.ts +19 -20
- package/dist/index.js +45 -442
- package/dist/terminal/index.d.ts +23 -0
- package/dist/terminal/index.js +83 -0
- package/dist/types-ClgDW3fy.d.ts +26 -0
- package/dist/ui/index.d.ts +18 -0
- package/dist/ui/index.js +100 -0
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/zion-off/giggles/actions/workflows/giggles-ci.yml)
|
|
2
2
|
[](https://github.com/zion-off/giggles/actions/workflows/giggles-cd.yml)
|
|
3
3
|
[](https://giggles.zzzzion.com)
|
|
4
4
|
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// src/core/GigglesError.ts
|
|
2
|
+
var GigglesError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(`[giggles] ${message}`);
|
|
5
|
+
this.name = "GigglesError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/core/input/useKeybindings.tsx
|
|
10
|
+
import { useEffect } from "react";
|
|
11
|
+
|
|
12
|
+
// src/core/input/InputContext.tsx
|
|
13
|
+
import { createContext, useCallback, useContext, useRef } from "react";
|
|
14
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
|
+
var InputContext = createContext(null);
|
|
16
|
+
var InputProvider = ({ children }) => {
|
|
17
|
+
const bindingsRef = useRef(/* @__PURE__ */ new Map());
|
|
18
|
+
const trapNodeIdRef = useRef(null);
|
|
19
|
+
const registerKeybindings = useCallback((nodeId, bindings, options) => {
|
|
20
|
+
const entries = Object.entries(bindings).filter((entry) => entry[1] != null).map(([key, def]) => {
|
|
21
|
+
if (typeof def === "function") {
|
|
22
|
+
return [key, { handler: def }];
|
|
23
|
+
}
|
|
24
|
+
return [key, { handler: def.action, name: def.name, when: def.when }];
|
|
25
|
+
});
|
|
26
|
+
const registration = {
|
|
27
|
+
bindings: new Map(entries),
|
|
28
|
+
capture: (options == null ? void 0 : options.capture) ?? false,
|
|
29
|
+
onKeypress: options == null ? void 0 : options.onKeypress,
|
|
30
|
+
layer: options == null ? void 0 : options.layer
|
|
31
|
+
};
|
|
32
|
+
bindingsRef.current.set(nodeId, registration);
|
|
33
|
+
}, []);
|
|
34
|
+
const unregisterKeybindings = useCallback((nodeId) => {
|
|
35
|
+
bindingsRef.current.delete(nodeId);
|
|
36
|
+
}, []);
|
|
37
|
+
const getNodeBindings = useCallback((nodeId) => {
|
|
38
|
+
return bindingsRef.current.get(nodeId);
|
|
39
|
+
}, []);
|
|
40
|
+
const setTrap = useCallback((nodeId) => {
|
|
41
|
+
trapNodeIdRef.current = nodeId;
|
|
42
|
+
}, []);
|
|
43
|
+
const clearTrap = useCallback((nodeId) => {
|
|
44
|
+
if (trapNodeIdRef.current === nodeId) {
|
|
45
|
+
trapNodeIdRef.current = null;
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
const getTrapNodeId = useCallback(() => {
|
|
49
|
+
return trapNodeIdRef.current;
|
|
50
|
+
}, []);
|
|
51
|
+
const getAllBindings = useCallback(() => {
|
|
52
|
+
const allBindings = [];
|
|
53
|
+
bindingsRef.current.forEach((nodeBindings, nodeId) => {
|
|
54
|
+
nodeBindings.bindings.forEach((entry, key) => {
|
|
55
|
+
allBindings.push({
|
|
56
|
+
nodeId,
|
|
57
|
+
key,
|
|
58
|
+
handler: entry.handler,
|
|
59
|
+
name: entry.name,
|
|
60
|
+
when: entry.when,
|
|
61
|
+
layer: nodeBindings.layer
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return allBindings;
|
|
66
|
+
}, []);
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
InputContext.Provider,
|
|
69
|
+
{
|
|
70
|
+
value: {
|
|
71
|
+
registerKeybindings,
|
|
72
|
+
unregisterKeybindings,
|
|
73
|
+
getNodeBindings,
|
|
74
|
+
setTrap,
|
|
75
|
+
clearTrap,
|
|
76
|
+
getTrapNodeId,
|
|
77
|
+
getAllBindings
|
|
78
|
+
},
|
|
79
|
+
children
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
function useInputContext() {
|
|
84
|
+
const context = useContext(InputContext);
|
|
85
|
+
if (!context) {
|
|
86
|
+
throw new GigglesError("useInputContext must be used within an InputProvider");
|
|
87
|
+
}
|
|
88
|
+
return context;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/core/input/useKeybindings.tsx
|
|
92
|
+
function useKeybindings(focus, bindings, options) {
|
|
93
|
+
const { registerKeybindings, unregisterKeybindings } = useInputContext();
|
|
94
|
+
registerKeybindings(focus.id, bindings, options);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
return () => unregisterKeybindings(focus.id);
|
|
97
|
+
}, [focus.id, unregisterKeybindings]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/core/focus/FocusContext.tsx
|
|
101
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useRef as useRef2, useState } from "react";
|
|
102
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
103
|
+
var FocusContext = createContext2(null);
|
|
104
|
+
var FocusProvider = ({ children }) => {
|
|
105
|
+
const nodesRef = useRef2(/* @__PURE__ */ new Map());
|
|
106
|
+
const pendingFocusFirstChildRef = useRef2(/* @__PURE__ */ new Set());
|
|
107
|
+
const [focusedId, setFocusedId] = useState(null);
|
|
108
|
+
const focusNode = useCallback2((id) => {
|
|
109
|
+
const nodes = nodesRef.current;
|
|
110
|
+
if (!nodes.has(id)) return;
|
|
111
|
+
setFocusedId((current) => {
|
|
112
|
+
if (current === id) return current;
|
|
113
|
+
return id;
|
|
114
|
+
});
|
|
115
|
+
}, []);
|
|
116
|
+
const focusFirstChild = useCallback2(
|
|
117
|
+
(parentId) => {
|
|
118
|
+
const nodes = nodesRef.current;
|
|
119
|
+
const parent = nodes.get(parentId);
|
|
120
|
+
if (parent && parent.childrenIds.length > 0) {
|
|
121
|
+
focusNode(parent.childrenIds[0]);
|
|
122
|
+
} else {
|
|
123
|
+
pendingFocusFirstChildRef.current.add(parentId);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[focusNode]
|
|
127
|
+
);
|
|
128
|
+
const registerNode = useCallback2(
|
|
129
|
+
(id, parentId) => {
|
|
130
|
+
const nodes = nodesRef.current;
|
|
131
|
+
const node = {
|
|
132
|
+
id,
|
|
133
|
+
parentId,
|
|
134
|
+
childrenIds: []
|
|
135
|
+
};
|
|
136
|
+
nodes.set(id, node);
|
|
137
|
+
if (parentId) {
|
|
138
|
+
const parent = nodes.get(parentId);
|
|
139
|
+
if (parent && !parent.childrenIds.includes(id)) {
|
|
140
|
+
const wasEmpty = parent.childrenIds.length === 0;
|
|
141
|
+
parent.childrenIds.push(id);
|
|
142
|
+
if (wasEmpty && pendingFocusFirstChildRef.current.has(parentId)) {
|
|
143
|
+
pendingFocusFirstChildRef.current.delete(parentId);
|
|
144
|
+
focusNode(id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
nodes.forEach((existingNode) => {
|
|
149
|
+
if (existingNode.parentId === id && !node.childrenIds.includes(existingNode.id)) {
|
|
150
|
+
node.childrenIds.push(existingNode.id);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
if (nodes.size === 1) {
|
|
154
|
+
focusNode(id);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
[focusNode]
|
|
158
|
+
);
|
|
159
|
+
const unregisterNode = useCallback2((id) => {
|
|
160
|
+
const nodes = nodesRef.current;
|
|
161
|
+
const node = nodes.get(id);
|
|
162
|
+
if (!node) return;
|
|
163
|
+
if (node.parentId) {
|
|
164
|
+
const parent = nodes.get(node.parentId);
|
|
165
|
+
if (parent) {
|
|
166
|
+
parent.childrenIds = parent.childrenIds.filter((childId) => childId !== id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
nodes.delete(id);
|
|
170
|
+
pendingFocusFirstChildRef.current.delete(id);
|
|
171
|
+
setFocusedId((current) => {
|
|
172
|
+
if (current !== id) return current;
|
|
173
|
+
return node.parentId ?? null;
|
|
174
|
+
});
|
|
175
|
+
}, []);
|
|
176
|
+
const isFocused = useCallback2(
|
|
177
|
+
(id) => {
|
|
178
|
+
return id === focusedId;
|
|
179
|
+
},
|
|
180
|
+
[focusedId]
|
|
181
|
+
);
|
|
182
|
+
const getFocusedId = useCallback2(() => {
|
|
183
|
+
return focusedId;
|
|
184
|
+
}, [focusedId]);
|
|
185
|
+
const getActiveBranchPath = useCallback2(() => {
|
|
186
|
+
if (!focusedId) return [];
|
|
187
|
+
const nodes = nodesRef.current;
|
|
188
|
+
const pathArray = [];
|
|
189
|
+
let node = focusedId;
|
|
190
|
+
while (node) {
|
|
191
|
+
pathArray.push(node);
|
|
192
|
+
const n = nodes.get(node);
|
|
193
|
+
node = (n == null ? void 0 : n.parentId) ?? null;
|
|
194
|
+
}
|
|
195
|
+
return pathArray;
|
|
196
|
+
}, [focusedId]);
|
|
197
|
+
const navigateSibling = useCallback2(
|
|
198
|
+
(direction, wrap = true) => {
|
|
199
|
+
const currentId = focusedId;
|
|
200
|
+
if (!currentId) return;
|
|
201
|
+
const nodes = nodesRef.current;
|
|
202
|
+
const currentNode = nodes.get(currentId);
|
|
203
|
+
if (!(currentNode == null ? void 0 : currentNode.parentId)) return;
|
|
204
|
+
const parent = nodes.get(currentNode.parentId);
|
|
205
|
+
if (!parent || parent.childrenIds.length === 0) return;
|
|
206
|
+
const siblings = parent.childrenIds;
|
|
207
|
+
const currentIndex = siblings.indexOf(currentId);
|
|
208
|
+
if (currentIndex === -1) return;
|
|
209
|
+
let nextIndex;
|
|
210
|
+
if (wrap) {
|
|
211
|
+
nextIndex = direction === "next" ? (currentIndex + 1) % siblings.length : (currentIndex - 1 + siblings.length) % siblings.length;
|
|
212
|
+
} else {
|
|
213
|
+
nextIndex = direction === "next" ? Math.min(currentIndex + 1, siblings.length - 1) : Math.max(currentIndex - 1, 0);
|
|
214
|
+
}
|
|
215
|
+
focusNode(siblings[nextIndex]);
|
|
216
|
+
},
|
|
217
|
+
[focusedId, focusNode]
|
|
218
|
+
);
|
|
219
|
+
return /* @__PURE__ */ jsx2(
|
|
220
|
+
FocusContext.Provider,
|
|
221
|
+
{
|
|
222
|
+
value: {
|
|
223
|
+
registerNode,
|
|
224
|
+
unregisterNode,
|
|
225
|
+
focusNode,
|
|
226
|
+
focusFirstChild,
|
|
227
|
+
isFocused,
|
|
228
|
+
getFocusedId,
|
|
229
|
+
getActiveBranchPath,
|
|
230
|
+
navigateSibling
|
|
231
|
+
},
|
|
232
|
+
children
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
var FocusNodeContext = createContext2(null);
|
|
237
|
+
var useFocusContext = () => {
|
|
238
|
+
const context = useContext2(FocusContext);
|
|
239
|
+
if (!context) {
|
|
240
|
+
throw new GigglesError("useFocusContext must be used within a FocusProvider");
|
|
241
|
+
}
|
|
242
|
+
return context;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/core/input/useKeybindingRegistry.ts
|
|
246
|
+
function useKeybindingRegistry(focus) {
|
|
247
|
+
const { getAllBindings, getTrapNodeId } = useInputContext();
|
|
248
|
+
const { getActiveBranchPath } = useFocusContext();
|
|
249
|
+
const all = getAllBindings().filter((b) => b.name != null);
|
|
250
|
+
const branchPath = getActiveBranchPath();
|
|
251
|
+
const branchSet = new Set(branchPath);
|
|
252
|
+
const trapNodeId = getTrapNodeId();
|
|
253
|
+
const withinTrapSet = (() => {
|
|
254
|
+
if (!trapNodeId) return null;
|
|
255
|
+
const trapIndex = branchPath.indexOf(trapNodeId);
|
|
256
|
+
return trapIndex >= 0 ? new Set(branchPath.slice(0, trapIndex + 1)) : null;
|
|
257
|
+
})();
|
|
258
|
+
const available = all.filter((b) => {
|
|
259
|
+
if (b.when === "mounted") return withinTrapSet ? withinTrapSet.has(b.nodeId) : true;
|
|
260
|
+
return (withinTrapSet ?? branchSet).has(b.nodeId);
|
|
261
|
+
});
|
|
262
|
+
const local = focus ? all.filter((b) => b.nodeId === focus.id) : [];
|
|
263
|
+
return { all, available, local };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/core/input/FocusTrap.tsx
|
|
267
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
268
|
+
|
|
269
|
+
// src/core/focus/FocusGroup.tsx
|
|
270
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
|
|
271
|
+
|
|
272
|
+
// src/core/input/InputRouter.tsx
|
|
273
|
+
import { useInput } from "ink";
|
|
274
|
+
|
|
275
|
+
// src/core/input/normalizeKey.ts
|
|
276
|
+
function normalizeKey(input, key) {
|
|
277
|
+
if (input === "\x1B[I" || input === "\x1B[O") return "";
|
|
278
|
+
if (key.downArrow) return "down";
|
|
279
|
+
if (key.upArrow) return "up";
|
|
280
|
+
if (key.leftArrow) return "left";
|
|
281
|
+
if (key.rightArrow) return "right";
|
|
282
|
+
if (key.return) return "enter";
|
|
283
|
+
if (key.escape) return "escape";
|
|
284
|
+
if (key.tab) return "tab";
|
|
285
|
+
if (key.backspace || key.delete) return "backspace";
|
|
286
|
+
if (key.pageUp) return "pageup";
|
|
287
|
+
if (key.pageDown) return "pagedown";
|
|
288
|
+
if (key.home) return "home";
|
|
289
|
+
if (key.end) return "end";
|
|
290
|
+
if (key.ctrl && input.length === 1) {
|
|
291
|
+
return `ctrl+${input}`;
|
|
292
|
+
}
|
|
293
|
+
return input;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/core/input/InputRouter.tsx
|
|
297
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
298
|
+
function InputRouter({ children }) {
|
|
299
|
+
const { getFocusedId, getActiveBranchPath } = useFocusContext();
|
|
300
|
+
const { getNodeBindings, getTrapNodeId, getAllBindings } = useInputContext();
|
|
301
|
+
useInput((input, key) => {
|
|
302
|
+
const focusedId = getFocusedId();
|
|
303
|
+
if (!focusedId) return;
|
|
304
|
+
const path = getActiveBranchPath();
|
|
305
|
+
const trapNodeId = getTrapNodeId();
|
|
306
|
+
const keyName = normalizeKey(input, key);
|
|
307
|
+
if (!keyName) return;
|
|
308
|
+
for (const nodeId of path) {
|
|
309
|
+
const nodeBindings = getNodeBindings(nodeId);
|
|
310
|
+
if (!nodeBindings) continue;
|
|
311
|
+
const entry = nodeBindings.bindings.get(keyName);
|
|
312
|
+
if (entry && entry.when !== "mounted") {
|
|
313
|
+
entry.handler(input, key);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (nodeBindings.capture && nodeBindings.onKeypress) {
|
|
317
|
+
nodeBindings.onKeypress(input, key);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (nodeId === trapNodeId) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const binding of getAllBindings()) {
|
|
325
|
+
if (binding.key === keyName && binding.when === "mounted") {
|
|
326
|
+
binding.handler(input, key);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/core/focus/FocusBindContext.tsx
|
|
335
|
+
import { createContext as createContext3 } from "react";
|
|
336
|
+
var FocusBindContext = createContext3(null);
|
|
337
|
+
|
|
338
|
+
// src/core/focus/useFocus.ts
|
|
339
|
+
import { useContext as useContext3, useEffect as useEffect2, useId } from "react";
|
|
340
|
+
var useFocus = (id) => {
|
|
341
|
+
const nodeId = useId();
|
|
342
|
+
const parentId = useContext3(FocusNodeContext);
|
|
343
|
+
const bindContext = useContext3(FocusBindContext);
|
|
344
|
+
const { focusNode, registerNode, unregisterNode, isFocused } = useFocusContext();
|
|
345
|
+
useEffect2(() => {
|
|
346
|
+
registerNode(nodeId, parentId);
|
|
347
|
+
if (id && bindContext) {
|
|
348
|
+
bindContext.register(id, nodeId);
|
|
349
|
+
}
|
|
350
|
+
return () => {
|
|
351
|
+
unregisterNode(nodeId);
|
|
352
|
+
if (id && bindContext) {
|
|
353
|
+
bindContext.unregister(id);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}, [nodeId, parentId, id, bindContext, registerNode, unregisterNode]);
|
|
357
|
+
return {
|
|
358
|
+
id: nodeId,
|
|
359
|
+
focused: isFocused(nodeId),
|
|
360
|
+
focus: () => focusNode(nodeId)
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// src/core/focus/FocusGroup.tsx
|
|
365
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
366
|
+
function FocusGroup({
|
|
367
|
+
children,
|
|
368
|
+
direction = "vertical",
|
|
369
|
+
value,
|
|
370
|
+
wrap = true,
|
|
371
|
+
navigable = true,
|
|
372
|
+
keybindings: customBindings
|
|
373
|
+
}) {
|
|
374
|
+
const focus = useFocus();
|
|
375
|
+
const { focusNode, navigateSibling } = useFocusContext();
|
|
376
|
+
const bindMapRef = useRef3(/* @__PURE__ */ new Map());
|
|
377
|
+
const register = useCallback3((logicalId, nodeId) => {
|
|
378
|
+
if (bindMapRef.current.has(logicalId)) {
|
|
379
|
+
throw new GigglesError(`FocusGroup: Duplicate id "${logicalId}". Each child must have a unique id.`);
|
|
380
|
+
}
|
|
381
|
+
bindMapRef.current.set(logicalId, nodeId);
|
|
382
|
+
}, []);
|
|
383
|
+
const unregister = useCallback3((logicalId) => {
|
|
384
|
+
bindMapRef.current.delete(logicalId);
|
|
385
|
+
}, []);
|
|
386
|
+
useEffect3(() => {
|
|
387
|
+
if (value) {
|
|
388
|
+
const nodeId = bindMapRef.current.get(value);
|
|
389
|
+
if (nodeId) {
|
|
390
|
+
focusNode(nodeId);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}, [value, focusNode]);
|
|
394
|
+
const bindContextValue = useMemo(() => value ? { register, unregister } : null, [value, register, unregister]);
|
|
395
|
+
const navigationKeys = useMemo(() => {
|
|
396
|
+
if (!navigable) return {};
|
|
397
|
+
const next = () => navigateSibling("next", wrap);
|
|
398
|
+
const prev = () => navigateSibling("prev", wrap);
|
|
399
|
+
return direction === "vertical" ? {
|
|
400
|
+
j: next,
|
|
401
|
+
k: prev,
|
|
402
|
+
down: next,
|
|
403
|
+
up: prev
|
|
404
|
+
} : {
|
|
405
|
+
l: next,
|
|
406
|
+
h: prev,
|
|
407
|
+
right: next,
|
|
408
|
+
left: prev
|
|
409
|
+
};
|
|
410
|
+
}, [navigable, direction, wrap, navigateSibling]);
|
|
411
|
+
const mergedBindings = useMemo(
|
|
412
|
+
() => ({ ...navigationKeys, ...customBindings }),
|
|
413
|
+
[navigationKeys, customBindings]
|
|
414
|
+
);
|
|
415
|
+
useKeybindings(focus, mergedBindings);
|
|
416
|
+
return /* @__PURE__ */ jsx4(FocusNodeContext.Provider, { value: focus.id, children: /* @__PURE__ */ jsx4(FocusBindContext.Provider, { value: bindContextValue, children }) });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/core/focus/useFocusState.ts
|
|
420
|
+
import { useState as useState2 } from "react";
|
|
421
|
+
function useFocusState(initial) {
|
|
422
|
+
const [focused, setFocused] = useState2(initial);
|
|
423
|
+
return [focused, setFocused];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/core/input/FocusTrap.tsx
|
|
427
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
428
|
+
function FocusTrap({ children }) {
|
|
429
|
+
const { id } = useFocus();
|
|
430
|
+
const { setTrap, clearTrap } = useInputContext();
|
|
431
|
+
const { focusFirstChild, getFocusedId, focusNode } = useFocusContext();
|
|
432
|
+
const previousFocusRef = useRef4(getFocusedId());
|
|
433
|
+
useEffect4(() => {
|
|
434
|
+
const previousFocus = previousFocusRef.current;
|
|
435
|
+
setTrap(id);
|
|
436
|
+
focusFirstChild(id);
|
|
437
|
+
return () => {
|
|
438
|
+
clearTrap(id);
|
|
439
|
+
if (previousFocus) {
|
|
440
|
+
focusNode(previousFocus);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}, [id]);
|
|
444
|
+
return /* @__PURE__ */ jsx5(FocusNodeContext.Provider, { value: id, children });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export {
|
|
448
|
+
GigglesError,
|
|
449
|
+
FocusProvider,
|
|
450
|
+
FocusNodeContext,
|
|
451
|
+
useFocusContext,
|
|
452
|
+
InputProvider,
|
|
453
|
+
InputRouter,
|
|
454
|
+
useKeybindings,
|
|
455
|
+
useKeybindingRegistry,
|
|
456
|
+
FocusTrap,
|
|
457
|
+
useFocus,
|
|
458
|
+
FocusGroup,
|
|
459
|
+
useFocusState
|
|
460
|
+
};
|
|
@@ -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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React$1 from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { K as Keybindings, a as KeybindingOptions, R as RegisteredKeybinding } from './types-ClgDW3fy.js';
|
|
5
|
+
export { b as KeyHandler } from './types-ClgDW3fy.js';
|
|
5
6
|
export { Key } from 'ink';
|
|
6
7
|
|
|
7
8
|
declare class GigglesError extends Error {
|
|
@@ -13,14 +14,29 @@ type GigglesProviderProps = {
|
|
|
13
14
|
};
|
|
14
15
|
declare function GigglesProvider({ children }: GigglesProviderProps): react_jsx_runtime.JSX.Element;
|
|
15
16
|
|
|
17
|
+
declare function useKeybindings(focus: FocusHandle, bindings: Keybindings, options?: KeybindingOptions): void;
|
|
18
|
+
|
|
19
|
+
type KeybindingRegistry = {
|
|
20
|
+
all: RegisteredKeybinding[];
|
|
21
|
+
available: RegisteredKeybinding[];
|
|
22
|
+
local: RegisteredKeybinding[];
|
|
23
|
+
};
|
|
24
|
+
declare function useKeybindingRegistry(focus?: FocusHandle): KeybindingRegistry;
|
|
25
|
+
|
|
26
|
+
type FocusTrapProps = {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
declare function FocusTrap({ children }: FocusTrapProps): react_jsx_runtime.JSX.Element;
|
|
30
|
+
|
|
16
31
|
type FocusGroupProps = {
|
|
17
32
|
children: React__default.ReactNode;
|
|
18
33
|
direction?: 'vertical' | 'horizontal';
|
|
19
34
|
value?: string;
|
|
20
35
|
wrap?: boolean;
|
|
21
36
|
navigable?: boolean;
|
|
37
|
+
keybindings?: Keybindings;
|
|
22
38
|
};
|
|
23
|
-
declare function FocusGroup({ children, direction, value, wrap, navigable }: FocusGroupProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
declare function FocusGroup({ children, direction, value, wrap, navigable, keybindings: customBindings }: FocusGroupProps): react_jsx_runtime.JSX.Element;
|
|
24
40
|
|
|
25
41
|
type FocusHandle = {
|
|
26
42
|
id: string;
|
|
@@ -32,23 +48,6 @@ declare const useFocus: (id?: string) => FocusHandle;
|
|
|
32
48
|
|
|
33
49
|
declare function useFocusState<T extends string>(initial: T): readonly [T, React$1.Dispatch<React$1.SetStateAction<T>>];
|
|
34
50
|
|
|
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
51
|
type RouterProps = {
|
|
53
52
|
children: React__default.ReactNode;
|
|
54
53
|
initialScreen: string;
|
|
@@ -80,4 +79,4 @@ type NavigationContextValue = {
|
|
|
80
79
|
|
|
81
80
|
declare const useNavigation: () => NavigationContextValue;
|
|
82
81
|
|
|
83
|
-
export { FocusGroup, type FocusHandle, FocusTrap, GigglesError, GigglesProvider,
|
|
82
|
+
export { FocusGroup, type FocusHandle, FocusTrap, GigglesError, GigglesProvider, KeybindingOptions, type KeybindingRegistry, Keybindings, type NavigationContextValue, RegisteredKeybinding, Router, Screen, useFocus, useFocusState, useKeybindingRegistry, useKeybindings, useNavigation };
|