giggles 0.1.1 → 0.2.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 +5 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +145 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,6 @@
|
|
|
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
|
+
[](https://www.npmjs.com/package/giggles)
|
|
4
|
+
[](https://www.npmjs.com/package/giggles)
|
|
5
|
+
|
|
1
6
|
wip -- see [giggles.zzzzion.com](https://giggles.zzzzion.com)!
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ import React__default from 'react';
|
|
|
4
4
|
import { Key } from 'ink';
|
|
5
5
|
export { Key } from 'ink';
|
|
6
6
|
|
|
7
|
+
declare class GigglesError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
type GigglesProviderProps = {
|
|
8
12
|
children: React__default.ReactNode;
|
|
9
13
|
};
|
|
@@ -23,6 +27,7 @@ type FocusHandle = {
|
|
|
23
27
|
focused: boolean;
|
|
24
28
|
focus: () => void;
|
|
25
29
|
};
|
|
30
|
+
|
|
26
31
|
declare const useFocus: (id?: string) => FocusHandle;
|
|
27
32
|
|
|
28
33
|
declare function useFocusState<T extends string>(initial: T): readonly [T, React$1.Dispatch<React$1.SetStateAction<T>>];
|
|
@@ -44,4 +49,34 @@ type FocusTrapProps = {
|
|
|
44
49
|
};
|
|
45
50
|
declare function FocusTrap({ children }: FocusTrapProps): react_jsx_runtime.JSX.Element;
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
type RouterProps = {
|
|
53
|
+
children: React__default.ReactNode;
|
|
54
|
+
initialScreen: string;
|
|
55
|
+
initialParams?: Record<string, unknown>;
|
|
56
|
+
};
|
|
57
|
+
declare function Router({ children, initialScreen, initialParams }: RouterProps): react_jsx_runtime.JSX.Element;
|
|
58
|
+
|
|
59
|
+
type ScreenProps = {
|
|
60
|
+
name: string;
|
|
61
|
+
component: React__default.ComponentType<Record<string, unknown>>;
|
|
62
|
+
};
|
|
63
|
+
declare function Screen(_props: ScreenProps): null;
|
|
64
|
+
|
|
65
|
+
type ScreenRoute = {
|
|
66
|
+
id: number;
|
|
67
|
+
name: string;
|
|
68
|
+
params?: Record<string, unknown>;
|
|
69
|
+
};
|
|
70
|
+
type NavigationContextValue = {
|
|
71
|
+
currentRoute: ScreenRoute;
|
|
72
|
+
canGoBack: boolean;
|
|
73
|
+
active: boolean;
|
|
74
|
+
push(name: string, params?: Record<string, unknown>): void;
|
|
75
|
+
pop(): void;
|
|
76
|
+
replace(name: string, params?: Record<string, unknown>): void;
|
|
77
|
+
reset(name: string, params?: Record<string, unknown>): void;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
declare const useNavigation: () => NavigationContextValue;
|
|
81
|
+
|
|
82
|
+
export { FocusGroup, type FocusHandle, FocusTrap, GigglesError, GigglesProvider, type KeyHandler, type KeybindingOptions, type Keybindings, type NavigationContextValue, Router, Screen, type ScreenRoute, useFocus, useFocusState, useKeybindings, useNavigation };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
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
|
+
|
|
1
9
|
// src/core/focus/FocusContext.tsx
|
|
2
10
|
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
|
3
11
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -145,7 +153,7 @@ var FocusNodeContext = createContext(null);
|
|
|
145
153
|
var useFocusContext = () => {
|
|
146
154
|
const context = useContext(FocusContext);
|
|
147
155
|
if (!context) {
|
|
148
|
-
throw new
|
|
156
|
+
throw new GigglesError("useFocusContext must be used within a FocusProvider");
|
|
149
157
|
}
|
|
150
158
|
return context;
|
|
151
159
|
};
|
|
@@ -219,7 +227,7 @@ var InputProvider = ({ children }) => {
|
|
|
219
227
|
function useInputContext() {
|
|
220
228
|
const context = useContext2(InputContext);
|
|
221
229
|
if (!context) {
|
|
222
|
-
throw new
|
|
230
|
+
throw new GigglesError("useInputContext must be used within an InputProvider");
|
|
223
231
|
}
|
|
224
232
|
return context;
|
|
225
233
|
}
|
|
@@ -300,15 +308,15 @@ function FocusTrap({ children }) {
|
|
|
300
308
|
}
|
|
301
309
|
|
|
302
310
|
// src/core/focus/FocusBindContext.tsx
|
|
303
|
-
import { createContext as createContext3
|
|
311
|
+
import { createContext as createContext3 } from "react";
|
|
304
312
|
var FocusBindContext = createContext3(null);
|
|
305
313
|
|
|
306
314
|
// src/core/focus/useFocus.ts
|
|
307
|
-
import { useContext as
|
|
315
|
+
import { useContext as useContext3, useEffect as useEffect3, useId } from "react";
|
|
308
316
|
var useFocus = (id) => {
|
|
309
317
|
const nodeId = useId();
|
|
310
|
-
const parentId =
|
|
311
|
-
const bindContext =
|
|
318
|
+
const parentId = useContext3(FocusNodeContext);
|
|
319
|
+
const bindContext = useContext3(FocusBindContext);
|
|
312
320
|
const { focusNode, registerNode, unregisterNode, isFocused } = useFocusContext();
|
|
313
321
|
useEffect3(() => {
|
|
314
322
|
registerNode(nodeId, parentId);
|
|
@@ -343,7 +351,7 @@ function FocusGroup({
|
|
|
343
351
|
const bindMapRef = useRef3(/* @__PURE__ */ new Map());
|
|
344
352
|
const register = useCallback3((logicalId, nodeId) => {
|
|
345
353
|
if (bindMapRef.current.has(logicalId)) {
|
|
346
|
-
throw new
|
|
354
|
+
throw new GigglesError(`FocusGroup: Duplicate id "${logicalId}". Each child must have a unique id.`);
|
|
347
355
|
}
|
|
348
356
|
bindMapRef.current.set(logicalId, nodeId);
|
|
349
357
|
}, []);
|
|
@@ -391,11 +399,140 @@ import { jsx as jsx6 } from "react/jsx-runtime";
|
|
|
391
399
|
function GigglesProvider({ children }) {
|
|
392
400
|
return /* @__PURE__ */ jsx6(FocusProvider, { children: /* @__PURE__ */ jsx6(InputProvider, { children: /* @__PURE__ */ jsx6(InputRouter, { children }) }) });
|
|
393
401
|
}
|
|
402
|
+
|
|
403
|
+
// src/core/router/Router.tsx
|
|
404
|
+
import React4, { useCallback as useCallback4, useReducer, useRef as useRef4 } from "react";
|
|
405
|
+
|
|
406
|
+
// src/core/router/Screen.tsx
|
|
407
|
+
function Screen(_props) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/core/router/ScreenEntry.tsx
|
|
412
|
+
import { useMemo as useMemo2 } from "react";
|
|
413
|
+
import { Box } from "ink";
|
|
414
|
+
|
|
415
|
+
// src/core/router/NavigationContext.tsx
|
|
416
|
+
import { createContext as createContext4, useContext as useContext4 } from "react";
|
|
417
|
+
var NavigationContext = createContext4(null);
|
|
418
|
+
var useNavigation = () => {
|
|
419
|
+
const context = useContext4(NavigationContext);
|
|
420
|
+
if (!context) {
|
|
421
|
+
throw new GigglesError("useNavigation must be used within a Router");
|
|
422
|
+
}
|
|
423
|
+
return context;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// src/core/router/ScreenEntry.tsx
|
|
427
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
428
|
+
function ScreenEntry({
|
|
429
|
+
entry,
|
|
430
|
+
isTop,
|
|
431
|
+
canGoBack,
|
|
432
|
+
component: Component,
|
|
433
|
+
push,
|
|
434
|
+
pop,
|
|
435
|
+
replace,
|
|
436
|
+
reset
|
|
437
|
+
}) {
|
|
438
|
+
const value = useMemo2(
|
|
439
|
+
() => ({ currentRoute: entry, active: isTop, canGoBack, push, pop, replace, reset }),
|
|
440
|
+
[entry, isTop, canGoBack, push, pop, replace, reset]
|
|
441
|
+
);
|
|
442
|
+
return /* @__PURE__ */ jsx7(NavigationContext.Provider, { value, children: /* @__PURE__ */ jsx7(Box, { display: isTop ? "flex" : "none", children: /* @__PURE__ */ jsx7(Component, { ...entry.params }) }) });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/core/router/Router.tsx
|
|
446
|
+
import { Fragment as Fragment3, jsx as jsx8 } from "react/jsx-runtime";
|
|
447
|
+
function routerReducer(stack, action) {
|
|
448
|
+
switch (action.type) {
|
|
449
|
+
case "push":
|
|
450
|
+
return [...stack, action.route];
|
|
451
|
+
case "pop":
|
|
452
|
+
return stack.length > 1 ? stack.slice(0, -1) : stack;
|
|
453
|
+
case "replace":
|
|
454
|
+
return [...stack.slice(0, -1), action.route];
|
|
455
|
+
case "reset":
|
|
456
|
+
return [action.route];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function Router({ children, initialScreen, initialParams }) {
|
|
460
|
+
const screenId = useRef4(0);
|
|
461
|
+
const routes = React4.Children.toArray(children).filter((child) => React4.isValidElement(child) && child.type === Screen).map((child) => child.props);
|
|
462
|
+
const screenNamesRef = useRef4(/* @__PURE__ */ new Set());
|
|
463
|
+
screenNamesRef.current = new Set(routes.map((r) => r.name));
|
|
464
|
+
const assertScreen = useCallback4((name) => {
|
|
465
|
+
if (!screenNamesRef.current.has(name)) {
|
|
466
|
+
throw new GigglesError(
|
|
467
|
+
`Screen "${name}" is not registered. Available screens: ${[...screenNamesRef.current].join(", ")}`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}, []);
|
|
471
|
+
const [stack, dispatch] = useReducer(routerReducer, initialScreen, (name) => {
|
|
472
|
+
if (!screenNamesRef.current.has(name)) {
|
|
473
|
+
throw new GigglesError(
|
|
474
|
+
`Initial screen "${name}" is not registered. Available screens: ${[...screenNamesRef.current].join(", ")}`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return [{ id: screenId.current++, name, params: initialParams }];
|
|
478
|
+
});
|
|
479
|
+
const push = useCallback4(
|
|
480
|
+
(name, params) => {
|
|
481
|
+
assertScreen(name);
|
|
482
|
+
dispatch({ type: "push", route: { id: screenId.current++, name, params } });
|
|
483
|
+
},
|
|
484
|
+
[assertScreen]
|
|
485
|
+
);
|
|
486
|
+
const pop = useCallback4(() => {
|
|
487
|
+
dispatch({ type: "pop" });
|
|
488
|
+
}, []);
|
|
489
|
+
const replace = useCallback4(
|
|
490
|
+
(name, params) => {
|
|
491
|
+
assertScreen(name);
|
|
492
|
+
dispatch({ type: "replace", route: { id: screenId.current++, name, params } });
|
|
493
|
+
},
|
|
494
|
+
[assertScreen]
|
|
495
|
+
);
|
|
496
|
+
const reset = useCallback4(
|
|
497
|
+
(name, params) => {
|
|
498
|
+
assertScreen(name);
|
|
499
|
+
dispatch({ type: "reset", route: { id: screenId.current++, name, params } });
|
|
500
|
+
},
|
|
501
|
+
[assertScreen]
|
|
502
|
+
);
|
|
503
|
+
const components = /* @__PURE__ */ new Map();
|
|
504
|
+
for (const route of routes) {
|
|
505
|
+
components.set(route.name, route.component);
|
|
506
|
+
}
|
|
507
|
+
const canGoBack = stack.length > 1;
|
|
508
|
+
return /* @__PURE__ */ jsx8(Fragment3, { children: stack.map((entry, i) => {
|
|
509
|
+
const Component = components.get(entry.name);
|
|
510
|
+
if (!Component) return null;
|
|
511
|
+
return /* @__PURE__ */ jsx8(
|
|
512
|
+
ScreenEntry,
|
|
513
|
+
{
|
|
514
|
+
entry,
|
|
515
|
+
isTop: i === stack.length - 1,
|
|
516
|
+
canGoBack,
|
|
517
|
+
component: Component,
|
|
518
|
+
push,
|
|
519
|
+
pop,
|
|
520
|
+
replace,
|
|
521
|
+
reset
|
|
522
|
+
},
|
|
523
|
+
entry.id
|
|
524
|
+
);
|
|
525
|
+
}) });
|
|
526
|
+
}
|
|
394
527
|
export {
|
|
395
528
|
FocusGroup,
|
|
396
529
|
FocusTrap,
|
|
530
|
+
GigglesError,
|
|
397
531
|
GigglesProvider,
|
|
532
|
+
Router,
|
|
533
|
+
Screen,
|
|
398
534
|
useFocus,
|
|
399
535
|
useFocusState,
|
|
400
|
-
useKeybindings
|
|
536
|
+
useKeybindings,
|
|
537
|
+
useNavigation
|
|
401
538
|
};
|