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 CHANGED
@@ -1 +1,6 @@
1
+ [![CI](https://github.com/zion-off/giggles/actions/workflows/giggles-lint.yml/badge.svg)](https://github.com/zion-off/giggles/actions/workflows/giggles-lint.yml)
2
+ [![CD](https://github.com/zion-off/giggles/actions/workflows/giggles-cd.yml/badge.svg)](https://github.com/zion-off/giggles/actions/workflows/giggles-cd.yml)
3
+ [![npm version](https://img.shields.io/npm/v/giggles)](https://www.npmjs.com/package/giggles)
4
+ [![npm downloads](https://img.shields.io/npm/dm/giggles)](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
- export { FocusGroup, type FocusHandle, FocusTrap, GigglesProvider, type KeyHandler, type KeybindingOptions, type Keybindings, useFocus, useFocusState, useKeybindings };
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 Error("useFocusContext must be used within a FocusProvider");
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 Error("useInputContext must be used within an InputProvider");
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, useContext as useContext3 } from "react";
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 useContext4, useEffect as useEffect3, useId } from "react";
315
+ import { useContext as useContext3, useEffect as useEffect3, useId } from "react";
308
316
  var useFocus = (id) => {
309
317
  const nodeId = useId();
310
- const parentId = useContext4(FocusNodeContext);
311
- const bindContext = useContext4(FocusBindContext);
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 Error(`FocusGroup: Duplicate id "${logicalId}". Each child must have a unique id.`);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",