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/dist/index.js
CHANGED
|
@@ -1,427 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const [activeBranchPath, setActiveBranchPath] = useState([]);
|
|
19
|
-
const focusNode = useCallback((id) => {
|
|
20
|
-
const nodes = nodesRef.current;
|
|
21
|
-
if (!nodes.has(id)) return;
|
|
22
|
-
setFocusedId((current) => {
|
|
23
|
-
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
|
-
return id;
|
|
36
|
-
});
|
|
37
|
-
}, []);
|
|
38
|
-
const focusFirstChild = useCallback(
|
|
39
|
-
(parentId) => {
|
|
40
|
-
const nodes = nodesRef.current;
|
|
41
|
-
const parent = nodes.get(parentId);
|
|
42
|
-
if (parent && parent.childrenIds.length > 0) {
|
|
43
|
-
focusNode(parent.childrenIds[0]);
|
|
44
|
-
} else {
|
|
45
|
-
pendingFocusFirstChildRef.current.add(parentId);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
[focusNode]
|
|
49
|
-
);
|
|
50
|
-
const registerNode = useCallback(
|
|
51
|
-
(id, parentId) => {
|
|
52
|
-
const nodes = nodesRef.current;
|
|
53
|
-
const node = {
|
|
54
|
-
id,
|
|
55
|
-
parentId,
|
|
56
|
-
childrenIds: []
|
|
57
|
-
};
|
|
58
|
-
nodes.set(id, node);
|
|
59
|
-
if (parentId) {
|
|
60
|
-
const parent = nodes.get(parentId);
|
|
61
|
-
if (parent && !parent.childrenIds.includes(id)) {
|
|
62
|
-
const wasEmpty = parent.childrenIds.length === 0;
|
|
63
|
-
parent.childrenIds.push(id);
|
|
64
|
-
if (wasEmpty && pendingFocusFirstChildRef.current.has(parentId)) {
|
|
65
|
-
pendingFocusFirstChildRef.current.delete(parentId);
|
|
66
|
-
focusNode(id);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
nodes.forEach((existingNode) => {
|
|
71
|
-
if (existingNode.parentId === id && !node.childrenIds.includes(existingNode.id)) {
|
|
72
|
-
node.childrenIds.push(existingNode.id);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
if (nodes.size === 1) {
|
|
76
|
-
focusNode(id);
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
[focusNode]
|
|
80
|
-
);
|
|
81
|
-
const unregisterNode = useCallback((id) => {
|
|
82
|
-
const nodes = nodesRef.current;
|
|
83
|
-
const node = nodes.get(id);
|
|
84
|
-
if (!node) return;
|
|
85
|
-
if (node.parentId) {
|
|
86
|
-
const parent = nodes.get(node.parentId);
|
|
87
|
-
if (parent) {
|
|
88
|
-
parent.childrenIds = parent.childrenIds.filter((childId) => childId !== id);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
nodes.delete(id);
|
|
92
|
-
pendingFocusFirstChildRef.current.delete(id);
|
|
93
|
-
setFocusedId((current) => {
|
|
94
|
-
if (current !== id) return current;
|
|
95
|
-
if (node.parentId) {
|
|
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;
|
|
112
|
-
});
|
|
113
|
-
}, []);
|
|
114
|
-
const isFocused = useCallback(
|
|
115
|
-
(id) => {
|
|
116
|
-
return id === focusedId;
|
|
117
|
-
},
|
|
118
|
-
[focusedId]
|
|
119
|
-
);
|
|
120
|
-
const getFocusedId = useCallback(() => {
|
|
121
|
-
return focusedId;
|
|
122
|
-
}, [focusedId]);
|
|
123
|
-
const isInActiveBranch = useCallback(
|
|
124
|
-
(id) => {
|
|
125
|
-
return activeBranchNodes.has(id);
|
|
126
|
-
},
|
|
127
|
-
[activeBranchNodes]
|
|
128
|
-
);
|
|
129
|
-
const getActiveBranchPath = useCallback(() => {
|
|
130
|
-
return activeBranchPath;
|
|
131
|
-
}, [activeBranchPath]);
|
|
132
|
-
const navigateSibling = useCallback(
|
|
133
|
-
(direction, wrap = true) => {
|
|
134
|
-
const currentId = focusedId;
|
|
135
|
-
if (!currentId) return;
|
|
136
|
-
const nodes = nodesRef.current;
|
|
137
|
-
const currentNode = nodes.get(currentId);
|
|
138
|
-
if (!(currentNode == null ? void 0 : currentNode.parentId)) return;
|
|
139
|
-
const parent = nodes.get(currentNode.parentId);
|
|
140
|
-
if (!parent || parent.childrenIds.length === 0) return;
|
|
141
|
-
const siblings = parent.childrenIds;
|
|
142
|
-
const currentIndex = siblings.indexOf(currentId);
|
|
143
|
-
if (currentIndex === -1) return;
|
|
144
|
-
let nextIndex;
|
|
145
|
-
if (wrap) {
|
|
146
|
-
nextIndex = direction === "next" ? (currentIndex + 1) % siblings.length : (currentIndex - 1 + siblings.length) % siblings.length;
|
|
147
|
-
} else {
|
|
148
|
-
nextIndex = direction === "next" ? Math.min(currentIndex + 1, siblings.length - 1) : Math.max(currentIndex - 1, 0);
|
|
149
|
-
}
|
|
150
|
-
focusNode(siblings[nextIndex]);
|
|
151
|
-
},
|
|
152
|
-
[focusedId, focusNode]
|
|
153
|
-
);
|
|
154
|
-
return /* @__PURE__ */ jsx(
|
|
155
|
-
FocusContext.Provider,
|
|
156
|
-
{
|
|
157
|
-
value: {
|
|
158
|
-
registerNode,
|
|
159
|
-
unregisterNode,
|
|
160
|
-
focusNode,
|
|
161
|
-
focusFirstChild,
|
|
162
|
-
isFocused,
|
|
163
|
-
getFocusedId,
|
|
164
|
-
isInActiveBranch,
|
|
165
|
-
getActiveBranchPath,
|
|
166
|
-
navigateSibling
|
|
167
|
-
},
|
|
168
|
-
children
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
};
|
|
172
|
-
var FocusNodeContext = createContext(null);
|
|
173
|
-
var useFocusContext = () => {
|
|
174
|
-
const context = useContext(FocusContext);
|
|
175
|
-
if (!context) {
|
|
176
|
-
throw new GigglesError("useFocusContext must be used within a FocusProvider");
|
|
177
|
-
}
|
|
178
|
-
return context;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// src/core/focus/FocusGroup.tsx
|
|
182
|
-
import { useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as useRef3 } from "react";
|
|
183
|
-
|
|
184
|
-
// src/core/input/InputContext.tsx
|
|
185
|
-
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useRef as useRef2 } from "react";
|
|
186
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
187
|
-
var InputContext = createContext2(null);
|
|
188
|
-
var InputProvider = ({ children }) => {
|
|
189
|
-
const bindingsRef = useRef2(/* @__PURE__ */ new Map());
|
|
190
|
-
const trapNodeIdRef = useRef2(null);
|
|
191
|
-
const registerKeybindings = useCallback2((nodeId, bindings, options) => {
|
|
192
|
-
const registration = {
|
|
193
|
-
bindings: new Map(Object.entries(bindings).filter((entry) => entry[1] != null)),
|
|
194
|
-
capture: (options == null ? void 0 : options.capture) ?? false,
|
|
195
|
-
onKeypress: options == null ? void 0 : options.onKeypress,
|
|
196
|
-
layer: options == null ? void 0 : options.layer
|
|
197
|
-
};
|
|
198
|
-
bindingsRef.current.set(nodeId, registration);
|
|
199
|
-
}, []);
|
|
200
|
-
const unregisterKeybindings = useCallback2((nodeId) => {
|
|
201
|
-
bindingsRef.current.delete(nodeId);
|
|
202
|
-
}, []);
|
|
203
|
-
const getNodeBindings = useCallback2((nodeId) => {
|
|
204
|
-
return bindingsRef.current.get(nodeId);
|
|
205
|
-
}, []);
|
|
206
|
-
const setTrap = useCallback2((nodeId) => {
|
|
207
|
-
trapNodeIdRef.current = nodeId;
|
|
208
|
-
}, []);
|
|
209
|
-
const clearTrap = useCallback2((nodeId) => {
|
|
210
|
-
if (trapNodeIdRef.current === nodeId) {
|
|
211
|
-
trapNodeIdRef.current = null;
|
|
212
|
-
}
|
|
213
|
-
}, []);
|
|
214
|
-
const getTrapNodeId = useCallback2(() => {
|
|
215
|
-
return trapNodeIdRef.current;
|
|
216
|
-
}, []);
|
|
217
|
-
const getAllBindings = useCallback2(() => {
|
|
218
|
-
const allBindings = [];
|
|
219
|
-
bindingsRef.current.forEach((nodeBindings, nodeId) => {
|
|
220
|
-
nodeBindings.bindings.forEach((handler, key) => {
|
|
221
|
-
allBindings.push({
|
|
222
|
-
nodeId,
|
|
223
|
-
key,
|
|
224
|
-
handler,
|
|
225
|
-
layer: nodeBindings.layer
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
return allBindings;
|
|
230
|
-
}, []);
|
|
231
|
-
return /* @__PURE__ */ jsx2(
|
|
232
|
-
InputContext.Provider,
|
|
233
|
-
{
|
|
234
|
-
value: {
|
|
235
|
-
registerKeybindings,
|
|
236
|
-
unregisterKeybindings,
|
|
237
|
-
getNodeBindings,
|
|
238
|
-
setTrap,
|
|
239
|
-
clearTrap,
|
|
240
|
-
getTrapNodeId,
|
|
241
|
-
getAllBindings
|
|
242
|
-
},
|
|
243
|
-
children
|
|
244
|
-
}
|
|
245
|
-
);
|
|
246
|
-
};
|
|
247
|
-
function useInputContext() {
|
|
248
|
-
const context = useContext2(InputContext);
|
|
249
|
-
if (!context) {
|
|
250
|
-
throw new GigglesError("useInputContext must be used within an InputProvider");
|
|
251
|
-
}
|
|
252
|
-
return context;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// src/core/input/InputRouter.tsx
|
|
256
|
-
import { useInput } from "ink";
|
|
257
|
-
|
|
258
|
-
// src/core/input/normalizeKey.ts
|
|
259
|
-
function normalizeKey(input, key) {
|
|
260
|
-
if (key.downArrow) return "down";
|
|
261
|
-
if (key.upArrow) return "up";
|
|
262
|
-
if (key.leftArrow) return "left";
|
|
263
|
-
if (key.rightArrow) return "right";
|
|
264
|
-
if (key.return) return "enter";
|
|
265
|
-
if (key.escape) return "escape";
|
|
266
|
-
if (key.tab) return "tab";
|
|
267
|
-
if (key.backspace) return "backspace";
|
|
268
|
-
if (key.delete) return "delete";
|
|
269
|
-
if (key.pageUp) return "pageup";
|
|
270
|
-
if (key.pageDown) return "pagedown";
|
|
271
|
-
if (key.home) return "home";
|
|
272
|
-
if (key.end) return "end";
|
|
273
|
-
return input;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// src/core/input/InputRouter.tsx
|
|
277
|
-
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
278
|
-
function InputRouter({ children }) {
|
|
279
|
-
const { getFocusedId, getActiveBranchPath } = useFocusContext();
|
|
280
|
-
const { getNodeBindings, getTrapNodeId } = useInputContext();
|
|
281
|
-
useInput((input, key) => {
|
|
282
|
-
const focusedId = getFocusedId();
|
|
283
|
-
if (!focusedId) return;
|
|
284
|
-
const path = getActiveBranchPath();
|
|
285
|
-
const trapNodeId = getTrapNodeId();
|
|
286
|
-
const keyName = normalizeKey(input, key);
|
|
287
|
-
for (const nodeId of path) {
|
|
288
|
-
const nodeBindings = getNodeBindings(nodeId);
|
|
289
|
-
if (!nodeBindings) continue;
|
|
290
|
-
const handler = nodeBindings.bindings.get(keyName);
|
|
291
|
-
if (handler) {
|
|
292
|
-
handler(input, key);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (nodeBindings.capture && nodeBindings.onKeypress) {
|
|
296
|
-
nodeBindings.onKeypress(input, key);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (nodeId === trapNodeId) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// src/core/input/useKeybindings.tsx
|
|
308
|
-
import { useEffect } from "react";
|
|
309
|
-
function useKeybindings(focus, bindings, options) {
|
|
310
|
-
const { registerKeybindings, unregisterKeybindings } = useInputContext();
|
|
311
|
-
registerKeybindings(focus.id, bindings, options);
|
|
312
|
-
useEffect(() => {
|
|
313
|
-
return () => unregisterKeybindings(focus.id);
|
|
314
|
-
}, [focus.id, unregisterKeybindings]);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/core/input/FocusTrap.tsx
|
|
318
|
-
import { useEffect as useEffect2 } from "react";
|
|
319
|
-
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
|
|
320
|
-
function FocusTrap({ children }) {
|
|
321
|
-
const { id } = useFocus();
|
|
322
|
-
const { setTrap, clearTrap } = useInputContext();
|
|
323
|
-
useEffect2(() => {
|
|
324
|
-
setTrap(id);
|
|
325
|
-
return () => clearTrap(id);
|
|
326
|
-
}, [id, setTrap, clearTrap]);
|
|
327
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children });
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// src/core/focus/FocusBindContext.tsx
|
|
331
|
-
import { createContext as createContext3 } from "react";
|
|
332
|
-
var FocusBindContext = createContext3(null);
|
|
333
|
-
|
|
334
|
-
// src/core/focus/useFocus.ts
|
|
335
|
-
import { useContext as useContext3, useEffect as useEffect3, useId } from "react";
|
|
336
|
-
var useFocus = (id) => {
|
|
337
|
-
const nodeId = useId();
|
|
338
|
-
const parentId = useContext3(FocusNodeContext);
|
|
339
|
-
const bindContext = useContext3(FocusBindContext);
|
|
340
|
-
const { focusNode, registerNode, unregisterNode, isFocused } = useFocusContext();
|
|
341
|
-
useEffect3(() => {
|
|
342
|
-
registerNode(nodeId, parentId);
|
|
343
|
-
if (id && bindContext) {
|
|
344
|
-
bindContext.register(id, nodeId);
|
|
345
|
-
}
|
|
346
|
-
return () => {
|
|
347
|
-
unregisterNode(nodeId);
|
|
348
|
-
if (id && bindContext) {
|
|
349
|
-
bindContext.unregister(id);
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
}, [nodeId, parentId, id, bindContext, registerNode, unregisterNode]);
|
|
353
|
-
return {
|
|
354
|
-
id: nodeId,
|
|
355
|
-
focused: isFocused(nodeId),
|
|
356
|
-
focus: () => focusNode(nodeId)
|
|
357
|
-
};
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
// src/core/focus/FocusGroup.tsx
|
|
361
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
362
|
-
function FocusGroup({
|
|
363
|
-
children,
|
|
364
|
-
direction = "vertical",
|
|
365
|
-
value,
|
|
366
|
-
wrap = true,
|
|
367
|
-
navigable = true
|
|
368
|
-
}) {
|
|
369
|
-
const focus = useFocus();
|
|
370
|
-
const { focusNode, navigateSibling } = useFocusContext();
|
|
371
|
-
const bindMapRef = useRef3(/* @__PURE__ */ new Map());
|
|
372
|
-
const register = useCallback3((logicalId, nodeId) => {
|
|
373
|
-
if (bindMapRef.current.has(logicalId)) {
|
|
374
|
-
throw new GigglesError(`FocusGroup: Duplicate id "${logicalId}". Each child must have a unique id.`);
|
|
375
|
-
}
|
|
376
|
-
bindMapRef.current.set(logicalId, nodeId);
|
|
377
|
-
}, []);
|
|
378
|
-
const unregister = useCallback3((logicalId) => {
|
|
379
|
-
bindMapRef.current.delete(logicalId);
|
|
380
|
-
}, []);
|
|
381
|
-
useEffect4(() => {
|
|
382
|
-
if (value) {
|
|
383
|
-
const nodeId = bindMapRef.current.get(value);
|
|
384
|
-
if (nodeId) {
|
|
385
|
-
focusNode(nodeId);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}, [value, focusNode]);
|
|
389
|
-
const bindContextValue = value ? { register, unregister } : null;
|
|
390
|
-
const navigationKeys = useMemo(() => {
|
|
391
|
-
if (!navigable) return {};
|
|
392
|
-
const next = () => navigateSibling("next", wrap);
|
|
393
|
-
const prev = () => navigateSibling("prev", wrap);
|
|
394
|
-
return direction === "vertical" ? {
|
|
395
|
-
j: next,
|
|
396
|
-
k: prev,
|
|
397
|
-
down: next,
|
|
398
|
-
up: prev
|
|
399
|
-
} : {
|
|
400
|
-
l: next,
|
|
401
|
-
h: prev,
|
|
402
|
-
right: next,
|
|
403
|
-
left: prev
|
|
404
|
-
};
|
|
405
|
-
}, [navigable, direction, wrap, navigateSibling]);
|
|
406
|
-
useKeybindings(focus, navigationKeys);
|
|
407
|
-
return /* @__PURE__ */ jsx5(FocusNodeContext.Provider, { value: focus.id, children: /* @__PURE__ */ jsx5(FocusBindContext.Provider, { value: bindContextValue, children }) });
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// src/core/focus/useFocusState.ts
|
|
411
|
-
import { useState as useState2 } from "react";
|
|
412
|
-
function useFocusState(initial) {
|
|
413
|
-
const [focused, setFocused] = useState2(initial);
|
|
414
|
-
return [focused, setFocused];
|
|
415
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
AlternateScreen
|
|
3
|
+
} from "./chunk-7PDVDYFB.js";
|
|
4
|
+
import {
|
|
5
|
+
FocusGroup,
|
|
6
|
+
FocusNodeContext,
|
|
7
|
+
FocusProvider,
|
|
8
|
+
FocusTrap,
|
|
9
|
+
GigglesError,
|
|
10
|
+
InputProvider,
|
|
11
|
+
InputRouter,
|
|
12
|
+
useFocus,
|
|
13
|
+
useFocusContext,
|
|
14
|
+
useFocusState,
|
|
15
|
+
useKeybindingRegistry,
|
|
16
|
+
useKeybindings
|
|
17
|
+
} from "./chunk-4LED4GXQ.js";
|
|
416
18
|
|
|
417
19
|
// src/core/GigglesProvider.tsx
|
|
418
|
-
import { jsx
|
|
20
|
+
import { jsx } from "react/jsx-runtime";
|
|
419
21
|
function GigglesProvider({ children }) {
|
|
420
|
-
return /* @__PURE__ */
|
|
22
|
+
return /* @__PURE__ */ jsx(AlternateScreen, { children: /* @__PURE__ */ jsx(FocusProvider, { children: /* @__PURE__ */ jsx(InputProvider, { children: /* @__PURE__ */ jsx(InputRouter, { children }) }) }) });
|
|
421
23
|
}
|
|
422
24
|
|
|
423
25
|
// src/core/router/Router.tsx
|
|
424
|
-
import
|
|
26
|
+
import React2, { useCallback, useReducer, useRef as useRef2 } from "react";
|
|
425
27
|
|
|
426
28
|
// src/core/router/Screen.tsx
|
|
427
29
|
function Screen(_props) {
|
|
@@ -429,14 +31,14 @@ function Screen(_props) {
|
|
|
429
31
|
}
|
|
430
32
|
|
|
431
33
|
// src/core/router/ScreenEntry.tsx
|
|
432
|
-
import
|
|
34
|
+
import React, { useEffect, useId, useMemo, useRef } from "react";
|
|
433
35
|
import { Box } from "ink";
|
|
434
36
|
|
|
435
37
|
// src/core/router/NavigationContext.tsx
|
|
436
|
-
import { createContext
|
|
437
|
-
var NavigationContext =
|
|
38
|
+
import { createContext, useContext } from "react";
|
|
39
|
+
var NavigationContext = createContext(null);
|
|
438
40
|
var useNavigation = () => {
|
|
439
|
-
const context =
|
|
41
|
+
const context = useContext(NavigationContext);
|
|
440
42
|
if (!context) {
|
|
441
43
|
throw new GigglesError("useNavigation must be used within a Router");
|
|
442
44
|
}
|
|
@@ -444,7 +46,7 @@ var useNavigation = () => {
|
|
|
444
46
|
};
|
|
445
47
|
|
|
446
48
|
// src/core/router/ScreenEntry.tsx
|
|
447
|
-
import { jsx as
|
|
49
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
448
50
|
function ScreenEntry({
|
|
449
51
|
entry,
|
|
450
52
|
isTop,
|
|
@@ -456,18 +58,18 @@ function ScreenEntry({
|
|
|
456
58
|
replace,
|
|
457
59
|
reset
|
|
458
60
|
}) {
|
|
459
|
-
const screenNodeId =
|
|
460
|
-
const parentId =
|
|
61
|
+
const screenNodeId = useId();
|
|
62
|
+
const parentId = React.useContext(FocusNodeContext);
|
|
461
63
|
const { registerNode, unregisterNode, focusFirstChild, focusNode, getFocusedId } = useFocusContext();
|
|
462
|
-
const lastFocusedChildRef =
|
|
463
|
-
const wasTopRef =
|
|
464
|
-
|
|
64
|
+
const lastFocusedChildRef = useRef(null);
|
|
65
|
+
const wasTopRef = useRef(isTop);
|
|
66
|
+
useEffect(() => {
|
|
465
67
|
registerNode(screenNodeId, parentId);
|
|
466
68
|
return () => {
|
|
467
69
|
unregisterNode(screenNodeId);
|
|
468
70
|
};
|
|
469
71
|
}, [screenNodeId, parentId, registerNode, unregisterNode]);
|
|
470
|
-
|
|
72
|
+
useEffect(() => {
|
|
471
73
|
if (!wasTopRef.current && isTop) {
|
|
472
74
|
const saved = restoreFocus ? lastFocusedChildRef.current : null;
|
|
473
75
|
if (saved) {
|
|
@@ -482,15 +84,15 @@ function ScreenEntry({
|
|
|
482
84
|
}
|
|
483
85
|
wasTopRef.current = isTop;
|
|
484
86
|
}, [isTop, screenNodeId, restoreFocus, focusFirstChild, focusNode, getFocusedId]);
|
|
485
|
-
const value =
|
|
87
|
+
const value = useMemo(
|
|
486
88
|
() => ({ currentRoute: entry, active: isTop, canGoBack, push, pop, replace, reset }),
|
|
487
89
|
[entry, isTop, canGoBack, push, pop, replace, reset]
|
|
488
90
|
);
|
|
489
|
-
return /* @__PURE__ */
|
|
91
|
+
return /* @__PURE__ */ jsx2(NavigationContext.Provider, { value, children: /* @__PURE__ */ jsx2(FocusNodeContext.Provider, { value: screenNodeId, children: /* @__PURE__ */ jsx2(Box, { display: isTop ? "flex" : "none", children: /* @__PURE__ */ jsx2(Component, { ...entry.params }) }) }) });
|
|
490
92
|
}
|
|
491
93
|
|
|
492
94
|
// src/core/router/Router.tsx
|
|
493
|
-
import { Fragment
|
|
95
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
494
96
|
function routerReducer(stack, action) {
|
|
495
97
|
switch (action.type) {
|
|
496
98
|
case "push":
|
|
@@ -504,11 +106,11 @@ function routerReducer(stack, action) {
|
|
|
504
106
|
}
|
|
505
107
|
}
|
|
506
108
|
function Router({ children, initialScreen, initialParams, restoreFocus = true }) {
|
|
507
|
-
const screenId =
|
|
508
|
-
const routes =
|
|
509
|
-
const screenNamesRef =
|
|
109
|
+
const screenId = useRef2(0);
|
|
110
|
+
const routes = React2.Children.toArray(children).filter((child) => React2.isValidElement(child) && child.type === Screen).map((child) => child.props);
|
|
111
|
+
const screenNamesRef = useRef2(/* @__PURE__ */ new Set());
|
|
510
112
|
screenNamesRef.current = new Set(routes.map((r) => r.name));
|
|
511
|
-
const assertScreen =
|
|
113
|
+
const assertScreen = useCallback((name) => {
|
|
512
114
|
if (!screenNamesRef.current.has(name)) {
|
|
513
115
|
throw new GigglesError(
|
|
514
116
|
`Screen "${name}" is not registered. Available screens: ${[...screenNamesRef.current].join(", ")}`
|
|
@@ -523,24 +125,24 @@ function Router({ children, initialScreen, initialParams, restoreFocus = true })
|
|
|
523
125
|
}
|
|
524
126
|
return [{ id: screenId.current++, name, params: initialParams }];
|
|
525
127
|
});
|
|
526
|
-
const push =
|
|
128
|
+
const push = useCallback(
|
|
527
129
|
(name, params) => {
|
|
528
130
|
assertScreen(name);
|
|
529
131
|
dispatch({ type: "push", route: { id: screenId.current++, name, params } });
|
|
530
132
|
},
|
|
531
133
|
[assertScreen]
|
|
532
134
|
);
|
|
533
|
-
const pop =
|
|
135
|
+
const pop = useCallback(() => {
|
|
534
136
|
dispatch({ type: "pop" });
|
|
535
137
|
}, []);
|
|
536
|
-
const replace =
|
|
138
|
+
const replace = useCallback(
|
|
537
139
|
(name, params) => {
|
|
538
140
|
assertScreen(name);
|
|
539
141
|
dispatch({ type: "replace", route: { id: screenId.current++, name, params } });
|
|
540
142
|
},
|
|
541
143
|
[assertScreen]
|
|
542
144
|
);
|
|
543
|
-
const reset =
|
|
145
|
+
const reset = useCallback(
|
|
544
146
|
(name, params) => {
|
|
545
147
|
assertScreen(name);
|
|
546
148
|
dispatch({ type: "reset", route: { id: screenId.current++, name, params } });
|
|
@@ -552,10 +154,10 @@ function Router({ children, initialScreen, initialParams, restoreFocus = true })
|
|
|
552
154
|
components.set(route.name, route.component);
|
|
553
155
|
}
|
|
554
156
|
const canGoBack = stack.length > 1;
|
|
555
|
-
return /* @__PURE__ */
|
|
157
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: stack.map((entry, i) => {
|
|
556
158
|
const Component = components.get(entry.name);
|
|
557
159
|
if (!Component) return null;
|
|
558
|
-
return /* @__PURE__ */
|
|
160
|
+
return /* @__PURE__ */ jsx3(
|
|
559
161
|
ScreenEntry,
|
|
560
162
|
{
|
|
561
163
|
entry,
|
|
@@ -581,6 +183,7 @@ export {
|
|
|
581
183
|
Screen,
|
|
582
184
|
useFocus,
|
|
583
185
|
useFocusState,
|
|
186
|
+
useKeybindingRegistry,
|
|
584
187
|
useKeybindings,
|
|
585
188
|
useNavigation
|
|
586
189
|
};
|
|
@@ -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
|
+
};
|