giggles 0.2.0 → 0.2.2
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/dist/index.d.ts +2 -1
- package/dist/index.js +54 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -53,8 +53,9 @@ type RouterProps = {
|
|
|
53
53
|
children: React__default.ReactNode;
|
|
54
54
|
initialScreen: string;
|
|
55
55
|
initialParams?: Record<string, unknown>;
|
|
56
|
+
restoreFocus?: boolean;
|
|
56
57
|
};
|
|
57
|
-
declare function Router({ children, initialScreen, initialParams }: RouterProps): react_jsx_runtime.JSX.Element;
|
|
58
|
+
declare function Router({ children, initialScreen, initialParams, restoreFocus }: RouterProps): react_jsx_runtime.JSX.Element;
|
|
58
59
|
|
|
59
60
|
type ScreenProps = {
|
|
60
61
|
name: string;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
12
12
|
var FocusContext = createContext(null);
|
|
13
13
|
var FocusProvider = ({ children }) => {
|
|
14
14
|
const nodesRef = useRef(/* @__PURE__ */ new Map());
|
|
15
|
+
const pendingFocusFirstChildRef = useRef(/* @__PURE__ */ new Set());
|
|
15
16
|
const [focusedId, setFocusedId] = useState(null);
|
|
16
17
|
const [activeBranchNodes, setActiveBranchNodes] = useState(/* @__PURE__ */ new Set());
|
|
17
18
|
const [activeBranchPath, setActiveBranchPath] = useState([]);
|
|
@@ -34,6 +35,18 @@ var FocusProvider = ({ children }) => {
|
|
|
34
35
|
return id;
|
|
35
36
|
});
|
|
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
|
+
);
|
|
37
50
|
const registerNode = useCallback(
|
|
38
51
|
(id, parentId) => {
|
|
39
52
|
const nodes = nodesRef.current;
|
|
@@ -46,7 +59,12 @@ var FocusProvider = ({ children }) => {
|
|
|
46
59
|
if (parentId) {
|
|
47
60
|
const parent = nodes.get(parentId);
|
|
48
61
|
if (parent && !parent.childrenIds.includes(id)) {
|
|
62
|
+
const wasEmpty = parent.childrenIds.length === 0;
|
|
49
63
|
parent.childrenIds.push(id);
|
|
64
|
+
if (wasEmpty && pendingFocusFirstChildRef.current.has(parentId)) {
|
|
65
|
+
pendingFocusFirstChildRef.current.delete(parentId);
|
|
66
|
+
focusNode(id);
|
|
67
|
+
}
|
|
50
68
|
}
|
|
51
69
|
}
|
|
52
70
|
nodes.forEach((existingNode) => {
|
|
@@ -71,6 +89,7 @@ var FocusProvider = ({ children }) => {
|
|
|
71
89
|
}
|
|
72
90
|
}
|
|
73
91
|
nodes.delete(id);
|
|
92
|
+
pendingFocusFirstChildRef.current.delete(id);
|
|
74
93
|
setFocusedId((current) => {
|
|
75
94
|
if (current !== id) return current;
|
|
76
95
|
if (node.parentId) {
|
|
@@ -139,6 +158,7 @@ var FocusProvider = ({ children }) => {
|
|
|
139
158
|
registerNode,
|
|
140
159
|
unregisterNode,
|
|
141
160
|
focusNode,
|
|
161
|
+
focusFirstChild,
|
|
142
162
|
isFocused,
|
|
143
163
|
getFocusedId,
|
|
144
164
|
isInActiveBranch,
|
|
@@ -401,7 +421,7 @@ function GigglesProvider({ children }) {
|
|
|
401
421
|
}
|
|
402
422
|
|
|
403
423
|
// src/core/router/Router.tsx
|
|
404
|
-
import React4, { useCallback as useCallback4, useReducer, useRef as
|
|
424
|
+
import React4, { useCallback as useCallback4, useReducer, useRef as useRef5 } from "react";
|
|
405
425
|
|
|
406
426
|
// src/core/router/Screen.tsx
|
|
407
427
|
function Screen(_props) {
|
|
@@ -409,7 +429,7 @@ function Screen(_props) {
|
|
|
409
429
|
}
|
|
410
430
|
|
|
411
431
|
// src/core/router/ScreenEntry.tsx
|
|
412
|
-
import { useMemo as useMemo2 } from "react";
|
|
432
|
+
import React3, { useEffect as useEffect5, useId as useId2, useMemo as useMemo2, useRef as useRef4 } from "react";
|
|
413
433
|
import { Box } from "ink";
|
|
414
434
|
|
|
415
435
|
// src/core/router/NavigationContext.tsx
|
|
@@ -429,17 +449,44 @@ function ScreenEntry({
|
|
|
429
449
|
entry,
|
|
430
450
|
isTop,
|
|
431
451
|
canGoBack,
|
|
452
|
+
restoreFocus,
|
|
432
453
|
component: Component,
|
|
433
454
|
push,
|
|
434
455
|
pop,
|
|
435
456
|
replace,
|
|
436
457
|
reset
|
|
437
458
|
}) {
|
|
459
|
+
const screenNodeId = useId2();
|
|
460
|
+
const parentId = React3.useContext(FocusNodeContext);
|
|
461
|
+
const { registerNode, unregisterNode, focusFirstChild, focusNode, getFocusedId } = useFocusContext();
|
|
462
|
+
const lastFocusedChildRef = useRef4(null);
|
|
463
|
+
const wasTopRef = useRef4(isTop);
|
|
464
|
+
useEffect5(() => {
|
|
465
|
+
registerNode(screenNodeId, parentId);
|
|
466
|
+
return () => {
|
|
467
|
+
unregisterNode(screenNodeId);
|
|
468
|
+
};
|
|
469
|
+
}, [screenNodeId, parentId, registerNode, unregisterNode]);
|
|
470
|
+
useEffect5(() => {
|
|
471
|
+
if (!wasTopRef.current && isTop) {
|
|
472
|
+
const saved = restoreFocus ? lastFocusedChildRef.current : null;
|
|
473
|
+
if (saved) {
|
|
474
|
+
focusNode(saved);
|
|
475
|
+
} else {
|
|
476
|
+
focusFirstChild(screenNodeId);
|
|
477
|
+
}
|
|
478
|
+
} else if (wasTopRef.current && !isTop) {
|
|
479
|
+
lastFocusedChildRef.current = getFocusedId();
|
|
480
|
+
} else if (isTop) {
|
|
481
|
+
focusFirstChild(screenNodeId);
|
|
482
|
+
}
|
|
483
|
+
wasTopRef.current = isTop;
|
|
484
|
+
}, [isTop, screenNodeId, restoreFocus, focusFirstChild, focusNode, getFocusedId]);
|
|
438
485
|
const value = useMemo2(
|
|
439
486
|
() => ({ currentRoute: entry, active: isTop, canGoBack, push, pop, replace, reset }),
|
|
440
487
|
[entry, isTop, canGoBack, push, pop, replace, reset]
|
|
441
488
|
);
|
|
442
|
-
return /* @__PURE__ */ jsx7(NavigationContext.Provider, { value, children: /* @__PURE__ */ jsx7(Box, { display: isTop ? "flex" : "none", children: /* @__PURE__ */ jsx7(Component, { ...entry.params }) }) });
|
|
489
|
+
return /* @__PURE__ */ jsx7(NavigationContext.Provider, { value, children: /* @__PURE__ */ jsx7(FocusNodeContext.Provider, { value: screenNodeId, children: /* @__PURE__ */ jsx7(Box, { display: isTop ? "flex" : "none", children: /* @__PURE__ */ jsx7(Component, { ...entry.params }) }) }) });
|
|
443
490
|
}
|
|
444
491
|
|
|
445
492
|
// src/core/router/Router.tsx
|
|
@@ -456,10 +503,10 @@ function routerReducer(stack, action) {
|
|
|
456
503
|
return [action.route];
|
|
457
504
|
}
|
|
458
505
|
}
|
|
459
|
-
function Router({ children, initialScreen, initialParams }) {
|
|
460
|
-
const screenId =
|
|
506
|
+
function Router({ children, initialScreen, initialParams, restoreFocus = true }) {
|
|
507
|
+
const screenId = useRef5(0);
|
|
461
508
|
const routes = React4.Children.toArray(children).filter((child) => React4.isValidElement(child) && child.type === Screen).map((child) => child.props);
|
|
462
|
-
const screenNamesRef =
|
|
509
|
+
const screenNamesRef = useRef5(/* @__PURE__ */ new Set());
|
|
463
510
|
screenNamesRef.current = new Set(routes.map((r) => r.name));
|
|
464
511
|
const assertScreen = useCallback4((name) => {
|
|
465
512
|
if (!screenNamesRef.current.has(name)) {
|
|
@@ -514,6 +561,7 @@ function Router({ children, initialScreen, initialParams }) {
|
|
|
514
561
|
entry,
|
|
515
562
|
isTop: i === stack.length - 1,
|
|
516
563
|
canGoBack,
|
|
564
|
+
restoreFocus,
|
|
517
565
|
component: Component,
|
|
518
566
|
push,
|
|
519
567
|
pop,
|