@buzola/router 0.0.3 → 0.0.4

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.
Files changed (95) hide show
  1. package/dist/define/create-page.d.ts +17 -5
  2. package/dist/define/create-page.d.ts.map +1 -1
  3. package/dist/define/create-page.js +78 -38
  4. package/dist/define/create-page.js.map +1 -1
  5. package/dist/engine/loader-cache.d.ts +18 -0
  6. package/dist/engine/loader-cache.d.ts.map +1 -0
  7. package/dist/engine/loader-cache.js +48 -0
  8. package/dist/engine/loader-cache.js.map +1 -0
  9. package/dist/engine/matcher.d.ts +1 -1
  10. package/dist/engine/matcher.d.ts.map +1 -1
  11. package/dist/engine/navigation-adapter.d.ts +1 -1
  12. package/dist/engine/navigation-adapter.d.ts.map +1 -1
  13. package/dist/engine/route-tree.d.ts +1 -2
  14. package/dist/engine/route-tree.d.ts.map +1 -1
  15. package/dist/engine/route-tree.js +5 -4
  16. package/dist/engine/route-tree.js.map +1 -1
  17. package/dist/engine/router.d.ts +7 -10
  18. package/dist/engine/router.d.ts.map +1 -1
  19. package/dist/engine/router.js +23 -67
  20. package/dist/engine/router.js.map +1 -1
  21. package/dist/engine/schema.d.ts +5 -1
  22. package/dist/engine/schema.d.ts.map +1 -1
  23. package/dist/engine/schema.js +11 -1
  24. package/dist/engine/schema.js.map +1 -1
  25. package/dist/engine/types.d.ts +2 -15
  26. package/dist/engine/types.d.ts.map +1 -1
  27. package/dist/engine/utils.d.ts +8 -0
  28. package/dist/engine/utils.d.ts.map +1 -1
  29. package/dist/engine/utils.js +10 -3
  30. package/dist/engine/utils.js.map +1 -1
  31. package/dist/index.d.ts +14 -13
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +8 -7
  34. package/dist/index.js.map +1 -1
  35. package/dist/react/context.d.ts +2 -2
  36. package/dist/react/context.d.ts.map +1 -1
  37. package/dist/react/hooks.d.ts +3 -3
  38. package/dist/react/hooks.d.ts.map +1 -1
  39. package/dist/react/hooks.js +11 -9
  40. package/dist/react/hooks.js.map +1 -1
  41. package/dist/react/index.d.ts +11 -11
  42. package/dist/react/index.d.ts.map +1 -1
  43. package/dist/react/index.js +6 -6
  44. package/dist/react/index.js.map +1 -1
  45. package/dist/react/link.d.ts +5 -0
  46. package/dist/react/link.d.ts.map +1 -1
  47. package/dist/react/link.js +10 -1
  48. package/dist/react/link.js.map +1 -1
  49. package/dist/react/outlet.d.ts +3 -1
  50. package/dist/react/outlet.d.ts.map +1 -1
  51. package/dist/react/outlet.js +25 -1
  52. package/dist/react/outlet.js.map +1 -1
  53. package/dist/react/provider.d.ts +15 -2
  54. package/dist/react/provider.d.ts.map +1 -1
  55. package/dist/react/provider.js +15 -4
  56. package/dist/react/provider.js.map +1 -1
  57. package/package.json +6 -3
  58. package/src/__tests__/create-page.test.ts +42 -6
  59. package/src/__tests__/loader-cache.test.ts +115 -0
  60. package/src/__tests__/matcher.test.ts +3 -3
  61. package/src/__tests__/react.test.tsx +1020 -0
  62. package/src/__tests__/router.test.ts +4 -214
  63. package/src/__tests__/setup-dom.ts +21 -0
  64. package/src/define/create-page.tsx +94 -46
  65. package/src/engine/loader-cache.ts +55 -0
  66. package/src/engine/matcher.ts +1 -1
  67. package/src/engine/navigation-adapter.ts +1 -1
  68. package/src/engine/route-tree.ts +4 -6
  69. package/src/engine/router.ts +27 -73
  70. package/src/engine/schema.ts +13 -2
  71. package/src/engine/types.ts +2 -18
  72. package/src/engine/utils.ts +11 -3
  73. package/src/index.ts +15 -17
  74. package/src/react/context.ts +2 -2
  75. package/src/react/hooks.ts +14 -13
  76. package/src/react/index.ts +21 -11
  77. package/src/react/link.tsx +16 -0
  78. package/src/react/outlet.tsx +32 -1
  79. package/src/react/provider.tsx +33 -5
  80. package/dist/__tests__/create-page.test.d.ts +0 -2
  81. package/dist/__tests__/create-page.test.d.ts.map +0 -1
  82. package/dist/__tests__/create-page.test.js +0 -457
  83. package/dist/__tests__/create-page.test.js.map +0 -1
  84. package/dist/__tests__/matcher.test.d.ts +0 -2
  85. package/dist/__tests__/matcher.test.d.ts.map +0 -1
  86. package/dist/__tests__/matcher.test.js +0 -108
  87. package/dist/__tests__/matcher.test.js.map +0 -1
  88. package/dist/__tests__/router.test.d.ts +0 -2
  89. package/dist/__tests__/router.test.d.ts.map +0 -1
  90. package/dist/__tests__/router.test.js +0 -327
  91. package/dist/__tests__/router.test.js.map +0 -1
  92. package/dist/__tests__/type-safety.test.d.ts +0 -15
  93. package/dist/__tests__/type-safety.test.d.ts.map +0 -1
  94. package/dist/__tests__/type-safety.test.js +0 -163
  95. package/dist/__tests__/type-safety.test.js.map +0 -1
@@ -1,12 +1,12 @@
1
- export { BuzolaProvider } from './provider';
2
- export type { BuzolaProviderProps } from './provider';
3
- export { Outlet } from './outlet';
4
- export type { OutletProps } from './outlet';
5
- export { Link } from './link';
6
- export type { LinkProps } from './link';
7
- export { ErrorBoundary } from './error-boundary';
8
- export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams } from './hooks';
9
- export type { BlockerState } from './hooks';
10
- export { RouteContext, RouterContext } from './context';
11
- export type { RouteContextValue } from './context';
1
+ export { BuzolaProvider } from './provider.js';
2
+ export type { BuzolaProviderProps, MiddlewareComponent, MiddlewareProps } from './provider.js';
3
+ export { Outlet } from './outlet.js';
4
+ export type { OutletProps } from './outlet.js';
5
+ export { Link } from './link.js';
6
+ export type { LinkProps } from './link.js';
7
+ export { ErrorBoundary } from './error-boundary.js';
8
+ export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams, } from './hooks.js';
9
+ export type { BlockerState } from './hooks.js';
10
+ export { RouteContext, RouterContext } from './context.js';
11
+ export type { RouteContextValue } from './context.js';
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACrJ,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE9F,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EACN,UAAU,EACV,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,QAAQ,EACR,SAAS,EACT,cAAc,EACd,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"}
@@ -1,7 +1,7 @@
1
- export { BuzolaProvider } from './provider';
2
- export { Outlet } from './outlet';
3
- export { Link } from './link';
4
- export { ErrorBoundary } from './error-boundary';
5
- export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams } from './hooks';
6
- export { RouteContext, RouterContext } from './context';
1
+ export { BuzolaProvider } from './provider.js';
2
+ export { Outlet } from './outlet.js';
3
+ export { Link } from './link.js';
4
+ export { ErrorBoundary } from './error-boundary.js';
5
+ export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams, } from './hooks.js';
6
+ export { RouteContext, RouterContext } from './context.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGrJ,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EACN,UAAU,EACV,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,QAAQ,EACR,SAAS,EACT,cAAc,EACd,eAAe,GACf,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
@@ -18,6 +18,11 @@ type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
18
18
  * When `false`, `data-active` is also set when the current path starts with the link target.
19
19
  */
20
20
  activeExact?: boolean;
21
+ /**
22
+ * Render as child element instead of `<a>`.
23
+ * When `true`, clones the single child element and merges `href`, `onClick`, and `data-active` props into it.
24
+ */
25
+ asChild?: boolean;
21
26
  };
22
27
  export type LinkProps<P extends RegisteredPage = RegisteredPage> = [keyof EffectivePageParams<P>] extends [never] ? LinkPropsBase & {
23
28
  to: P;
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAK9F,KAAK,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,GAAG;IAClF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9G,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAEzC,EAAE,SAAS,mBAAmB,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,GAC9F,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAoDtF"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAK9F,KAAK,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,GAAG;IAClF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9G,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAEzC,EAAE,SAAS,mBAAmB,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,GAC9F,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CA+DtF"}
@@ -10,7 +10,7 @@ import { useRouter, useRouterState } from './hooks.js';
10
10
  * Sets `data-active` attribute when the link target matches the current URL.
11
11
  */
12
12
  export function Link(props) {
13
- const { to, params, replace, state, viewTransition, resetScroll, activeExact = true, onClick, children, ...rest } = props;
13
+ const { to, params, replace, state, viewTransition, resetScroll, activeExact = true, asChild, onClick, children, ...rest } = props;
14
14
  const router = useRouter();
15
15
  const routerState = useRouterState();
16
16
  const href = router.buildPagePath(to, params);
@@ -33,6 +33,15 @@ export function Link(props) {
33
33
  router.navigateToPage(to, params, { replace, state, viewTransition, resetScroll });
34
34
  }
35
35
  }, [onClick, needsProgrammaticNav, router, to, params, replace, state, viewTransition, resetScroll]);
36
+ if (asChild) {
37
+ const child = React.Children.only(children);
38
+ return React.cloneElement(child, {
39
+ href,
40
+ onClick: handleClick,
41
+ 'data-active': isActive ? '' : undefined,
42
+ ...rest,
43
+ });
44
+ }
36
45
  return (_jsx("a", { href: href, onClick: handleClick, "data-active": isActive ? '' : undefined, ...rest, children: children }));
37
46
  }
38
47
  //# sourceMappingURL=link.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA8BtD;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAA2B,KAAmB;IACjE,MAAM,EACL,EAAE,EACF,MAAM,EACN,OAAO,EACP,KAAK,EACL,cAAc,EACd,WAAW,EACX,WAAW,GAAG,IAAI,EAClB,OAAO,EACP,QAAQ,EACR,GAAG,IAAI,EACP,GAAG,KAAwE,CAAA;IAE5E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IAEpC,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IAC7C,MAAM,oBAAoB,GAAG,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,cAAc,IAAI,WAAW,KAAK,KAAK,CAAA;IAEtG,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG,WAAW,KAAK,UAAU,CAAA;IAC1C,MAAM,QAAQ,GAAG,UAAU,KAAK,GAAG,IAAI,WAAW,CAAC,UAAU,CAAC,UAAU,GAAG,GAAG,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAA;IAEtD,MAAM,WAAW,GAAG,WAAW,CAC9B,CAAC,CAAsC,EAAE,EAAE;QAC1C,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;QACZ,IAAI,CAAC,CAAC,gBAAgB;YAAE,OAAM;QAE9B,gDAAgD;QAChD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAM;QAE9E,IAAI,oBAAoB,EAAE,CAAC;YAC1B,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,MAAM,CAAC,cAAc,CACpB,EAAE,EACF,MAAM,EACN,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAqB,CAClE,CAAA;QACF,CAAC;IACF,CAAC,EACD,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,CAChG,CAAA;IAED,OAAO,CACN,YAAG,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,iBAAe,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAM,IAAI,YACnF,QAAQ,GACN,CACJ,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAmCtD;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAA2B,KAAmB;IACjE,MAAM,EACL,EAAE,EACF,MAAM,EACN,OAAO,EACP,KAAK,EACL,cAAc,EACd,WAAW,EACX,WAAW,GAAG,IAAI,EAClB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,GAAG,IAAI,EACP,GAAG,KAAwE,CAAA;IAE5E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IAEpC,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IAC7C,MAAM,oBAAoB,GAAG,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,cAAc,IAAI,WAAW,KAAK,KAAK,CAAA;IAEtG,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG,WAAW,KAAK,UAAU,CAAA;IAC1C,MAAM,QAAQ,GAAG,UAAU,KAAK,GAAG,IAAI,WAAW,CAAC,UAAU,CAAC,UAAU,GAAG,GAAG,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAA;IAEtD,MAAM,WAAW,GAAG,WAAW,CAC9B,CAAC,CAAsC,EAAE,EAAE;QAC1C,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;QACZ,IAAI,CAAC,CAAC,gBAAgB;YAAE,OAAM;QAE9B,gDAAgD;QAChD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAM;QAE9E,IAAI,oBAAoB,EAAE,CAAC;YAC1B,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,MAAM,CAAC,cAAc,CACpB,EAAE,EACF,MAAM,EACN,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAqB,CAClE,CAAA;QACF,CAAC;IACF,CAAC,EACD,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,CAChG,CAAA;IAED,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAA4B,CAAA;QACtE,OAAO,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE;YAChC,IAAI;YACJ,OAAO,EAAE,WAAW;YACpB,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACxC,GAAG,IAAI;SACP,CAAC,CAAA;IACH,CAAC;IAED,OAAO,CACN,YAAG,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,iBAAe,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAM,IAAI,YACnF,QAAQ,GACN,CACJ,CAAA;AACF,CAAC"}
@@ -4,10 +4,12 @@ export interface OutletProps {
4
4
  fallback?: React.ReactNode;
5
5
  /** Fallback to show when a route component throws an error. */
6
6
  errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);
7
+ /** Content to show when no route matches. Defaults to a built-in 404 page. Pass `null` to render nothing. */
8
+ notFound?: React.ReactNode;
7
9
  }
8
10
  /**
9
11
  * Renders the component for the current depth in the route match chain.
10
12
  * Each Outlet increments the depth and renders the next matched route's component.
11
13
  */
12
- export declare function Outlet({ fallback, errorFallback }: OutletProps): React.ReactElement | null;
14
+ export declare function Outlet({ fallback, errorFallback, notFound }: OutletProps): React.ReactElement | null;
13
15
  //# sourceMappingURL=outlet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"outlet.d.ts","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiC,MAAM,OAAO,CAAA;AAIrD,MAAM,WAAW,WAAW;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC,CAAA;CACrE;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAmD1F"}
1
+ {"version":3,"file":"outlet.d.ts","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiC,MAAM,OAAO,CAAA;AA4BrD,MAAM,WAAW,WAAW;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC,CAAA;IACrE,6GAA6G;IAC7G,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAwDpG"}
@@ -2,11 +2,29 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { Suspense, use, useMemo } from 'react';
3
3
  import { RouteContext } from './context.js';
4
4
  import { ErrorBoundary } from './error-boundary.js';
5
+ const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
6
+ function DefaultNotFound() {
7
+ return React.createElement('div', {
8
+ style: { fontFamily: 'system-ui, sans-serif', padding: '2rem', maxWidth: '600px', margin: '0 auto' },
9
+ }, isDev
10
+ ? [
11
+ React.createElement('h1', { key: 'h' }, '404 — Page Not Found'),
12
+ React.createElement('p', { key: 'p1' }, `No route matches "${typeof window !== 'undefined' ? window.location.pathname : ''}".`),
13
+ React.createElement('p', {
14
+ key: 'p2',
15
+ style: { color: '#666', fontSize: '0.9rem' },
16
+ }, 'To handle this, add a _404.tsx catch-all route or pass a notFound prop to <Outlet />.'),
17
+ ]
18
+ : [
19
+ React.createElement('h1', { key: 'h' }, '404'),
20
+ React.createElement('p', { key: 'p' }, 'Page not found.'),
21
+ ]);
22
+ }
5
23
  /**
6
24
  * Renders the component for the current depth in the route match chain.
7
25
  * Each Outlet increments the depth and renders the next matched route's component.
8
26
  */
9
- export function Outlet({ fallback, errorFallback }) {
27
+ export function Outlet({ fallback, errorFallback, notFound }) {
10
28
  const routeContext = use(RouteContext);
11
29
  if (!routeContext) {
12
30
  throw new Error('<Outlet /> must be used within a <BuzolaProvider>');
@@ -38,6 +56,12 @@ export function Outlet({ fallback, errorFallback }) {
38
56
  };
39
57
  }, [state, depth, effectiveDepth, matches, match, routeContext.params]);
40
58
  if (!match) {
59
+ // At root depth with no matches — show 404
60
+ if (depth === 0 && matches.length === 0) {
61
+ if (notFound === null)
62
+ return null;
63
+ return (notFound ?? React.createElement(DefaultNotFound, null));
64
+ }
41
65
  return null;
42
66
  }
43
67
  const Component = match.node.component;
@@ -1 +1 @@
1
- {"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,YAAY,EAA0B,MAAM,cAAc,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AASnD;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAe;IAC9D,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC,CAAA;IACtC,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACrE,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,CAAA;IAE9C,yEAAyE;IACzE,2DAA2D;IAC3D,IAAI,cAAc,GAAG,KAAK,CAAA;IAC1B,IAAI,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnC,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,cAAc,EAAE,CAAA;QAChB,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAA2B,GAAG,EAAE;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,gEAAgE;QAChE,IAAI,YAAY,GAAG,EAAE,GAAG,YAAY,CAAC,MAAM,EAAE,CAAA;QAC7C,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,YAAY,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;YACzD,CAAC;QACF,CAAC;QACD,OAAO;YACN,KAAK;YACL,KAAK,EAAE,cAAc,GAAG,CAAC;YACzB,OAAO;YACP,MAAM,EAAE,YAAY;SACpB,CAAA;IACF,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAA;IAEvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAU,CAAA;IAEvC,OAAO,CACN,KAAC,aAAa,IAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,YAC9D,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,IAAI,IAAI,YAClC,WAAW,IAAI,CACf,KAAC,YAAY,IAAC,KAAK,EAAE,WAAW,YAC/B,KAAC,SAAS,KAAG,GACC,CACf,GACS,GACI,CAChB,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,YAAY,EAA0B,MAAM,cAAc,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,KAAK,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAA;AAErF,SAAS,eAAe;IACvB,OAAO,KAAK,CAAC,aAAa,CACzB,KAAK,EACL;QACC,KAAK,EAAE,EAAE,UAAU,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;KACpG,EACD,KAAK;QACJ,CAAC,CAAC;YACD,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,sBAAsB,CAAC;YAC/D,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,qBAAqB,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;YAC/H,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE;gBACxB,GAAG,EAAE,IAAI;gBACT,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE;aAC5C,EAAE,uFAAuF,CAAC;SAC3F;QACD,CAAC,CAAC;YACD,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,CAAC;YAC9C,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,iBAAiB,CAAC;SACzD,CACF,CAAA;AACF,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAe;IACxE,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC,CAAA;IACtC,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACrE,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,CAAA;IAE9C,yEAAyE;IACzE,2DAA2D;IAC3D,IAAI,cAAc,GAAG,KAAK,CAAA;IAC1B,IAAI,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnC,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,cAAc,EAAE,CAAA;QAChB,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAA2B,GAAG,EAAE;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,gEAAgE;QAChE,IAAI,YAAY,GAAG,EAAE,GAAG,YAAY,CAAC,MAAM,EAAE,CAAA;QAC7C,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,YAAY,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;YACzD,CAAC;QACF,CAAC;QACD,OAAO;YACN,KAAK;YACL,KAAK,EAAE,cAAc,GAAG,CAAC;YACzB,OAAO;YACP,MAAM,EAAE,YAAY;SACpB,CAAA;IACF,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAA;IAEvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,2CAA2C;QAC3C,IAAI,KAAK,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,IAAI,QAAQ,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAA;YAClC,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,aAAa,CAAC,eAAe,EAAE,IAAI,CAAC,CAAuB,CAAA;QACtF,CAAC;QACD,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAU,CAAA;IAEvC,OAAO,CACN,KAAC,aAAa,IAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,YAC9D,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,IAAI,IAAI,YAClC,WAAW,IAAI,CACf,KAAC,YAAY,IAAC,KAAK,EAAE,WAAW,YAC/B,KAAC,SAAS,KAAG,GACC,CACf,GACS,GACI,CAChB,CAAA;AACF,CAAC"}
@@ -1,8 +1,21 @@
1
1
  import React from 'react';
2
2
  import { Router } from '../engine/router.js';
3
- import type { RouteNode } from '../engine/types.js';
3
+ import type { RouteMatch, RouteNode } from '../engine/types.js';
4
+ /** Props passed to the middleware component. */
5
+ export interface MiddlewareProps {
6
+ /** Merged params from all matched routes. */
7
+ params: Record<string, string>;
8
+ /** All route matches from root to leaf. */
9
+ matches: RouteMatch[];
10
+ /** The rendered route tree to wrap. */
11
+ children: React.ReactNode;
12
+ }
13
+ /** Middleware component that wraps the entire router output. */
14
+ export type MiddlewareComponent = React.ComponentType<MiddlewareProps>;
4
15
  export type BuzolaProviderProps = {
5
16
  children?: React.ReactNode;
17
+ /** Global middleware that wraps the rendered route tree. Has access to all matched params. */
18
+ middleware?: MiddlewareComponent;
6
19
  } & ({
7
20
  router: Router;
8
21
  routes?: never;
@@ -18,5 +31,5 @@ export type BuzolaProviderProps = {
18
31
  * Root provider for Buzola router.
19
32
  * Subscribes to router state and provides context to the React tree.
20
33
  */
21
- export declare function BuzolaProvider({ routes, children, ...props }: BuzolaProviderProps): React.ReactElement;
34
+ export declare function BuzolaProvider({ routes, children, middleware: Middleware, ...props }: BuzolaProviderProps): React.ReactElement;
22
35
  //# sourceMappingURL=provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwE,MAAM,OAAO,CAAA;AAE5F,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAInD,MAAM,MAAM,mBAAmB,GAC5B;IACD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,GACC,CACC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,GAClF;IACD,MAAM,CAAC,EAAE,KAAK,CAAA;IACd,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC,CACD,CAAA;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAkCtG"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAA;AAElG,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAI/D,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC/B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,2CAA2C;IAC3C,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,uCAAuC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED,gEAAgE;AAChE,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,aAAa,CAAC,eAAe,CAAC,CAAA;AAEtE,MAAM,MAAM,mBAAmB,GAC5B;IACD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,8FAA8F;IAC9F,UAAU,CAAC,EAAE,mBAAmB,CAAA;CAChC,GACC,CACC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,GAClF;IACD,MAAM,CAAC,EAAE,KAAK,CAAA;IACd,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC,CACD,CAAA;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CA+C9H"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
2
+ import React, { useCallback, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
3
3
  import { createBrowserNavigationAdapter } from '../engine/navigation-adapter.js';
4
4
  import { Router } from '../engine/router.js';
5
5
  import { RouteContext, RouterContext } from './context.js';
@@ -8,7 +8,7 @@ import { Outlet } from './outlet.js';
8
8
  * Root provider for Buzola router.
9
9
  * Subscribes to router state and provides context to the React tree.
10
10
  */
11
- export function BuzolaProvider({ routes, children, ...props }) {
11
+ export function BuzolaProvider({ routes, children, middleware: Middleware, ...props }) {
12
12
  const routerRef = useRef(props.router);
13
13
  if (!routerRef.current) {
14
14
  routerRef.current = new Router({
@@ -23,7 +23,7 @@ export function BuzolaProvider({ routes, children, ...props }) {
23
23
  const getSnapshot = useCallback(() => router.getState(), [router]);
24
24
  const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
25
25
  // Start navigation interception
26
- useEffect(() => {
26
+ useLayoutEffect(() => {
27
27
  return router.start();
28
28
  }, [router]);
29
29
  const routeContextValue = useMemo(() => ({
@@ -32,6 +32,17 @@ export function BuzolaProvider({ routes, children, ...props }) {
32
32
  matches: state.matches,
33
33
  params: {},
34
34
  }), [state]);
35
- return (_jsx(RouterContext, { value: router, children: _jsx(RouteContext, { value: routeContextValue, children: children ?? _jsx(Outlet, {}) }) }));
35
+ // Merge params from all matches for middleware
36
+ const allParams = useMemo(() => {
37
+ let merged = {};
38
+ for (const match of state.matches) {
39
+ merged = { ...merged, ...match.params };
40
+ }
41
+ return merged;
42
+ }, [state.matches]);
43
+ const content = children ?? _jsx(Outlet, {});
44
+ return (_jsx(RouterContext, { value: router, children: _jsx(RouteContext, { value: routeContextValue, children: Middleware
45
+ ? _jsx(Middleware, { params: allParams, matches: state.matches, children: content })
46
+ : content }) }));
36
47
  }
37
48
  //# sourceMappingURL=provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAC5F,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EAAE,YAAY,EAA0B,aAAa,EAAE,MAAM,cAAc,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAgBpC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAuB;IACjF,MAAM,SAAS,GAAG,MAAM,CAAqB,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1D,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC;YAC9B,MAAM,EAAE,MAAO;YACf,OAAO,EAAE,8BAA8B,EAAE;YACzC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,YAAY,EAAE,KAAK,CAAC,YAAY;SAChC,CAAC,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IACjF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAClE,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;IAEvE,gCAAgC;IAChC,SAAS,CAAC,GAAG,EAAE;QACd,OAAO,MAAM,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,MAAM,iBAAiB,GAAG,OAAO,CAAoB,GAAG,EAAE,CAAC,CAAC;QAC3D,KAAK;QACL,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,EAAE;KACV,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEZ,OAAO,CACN,KAAC,aAAa,IAAC,KAAK,EAAE,MAAM,YAC3B,KAAC,YAAY,IAAC,KAAK,EAAE,iBAAiB,YACpC,QAAQ,IAAI,KAAC,MAAM,KAAG,GACT,GACA,CAChB,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAClG,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EAAE,YAAY,EAA0B,aAAa,EAAE,MAAM,cAAc,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AA+BpC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,KAAK,EAAuB;IACzG,MAAM,SAAS,GAAG,MAAM,CAAqB,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1D,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC;YAC9B,MAAM,EAAE,MAAO;YACf,OAAO,EAAE,8BAA8B,EAAE;YACzC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,YAAY,EAAE,KAAK,CAAC,YAAY;SAChC,CAAC,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IACjF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAClE,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;IAEvE,gCAAgC;IAChC,eAAe,CAAC,GAAG,EAAE;QACpB,OAAO,MAAM,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,MAAM,iBAAiB,GAAG,OAAO,CAAoB,GAAG,EAAE,CAAC,CAAC;QAC3D,KAAK;QACL,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,EAAE;KACV,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEZ,+CAA+C;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC9B,IAAI,MAAM,GAA2B,EAAE,CAAA;QACvC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAA;QACxC,CAAC;QACD,OAAO,MAAM,CAAA;IACd,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IAEnB,MAAM,OAAO,GAAG,QAAQ,IAAI,KAAC,MAAM,KAAG,CAAA;IAEtC,OAAO,CACN,KAAC,aAAa,IAAC,KAAK,EAAE,MAAM,YAC3B,KAAC,YAAY,IAAC,KAAK,EAAE,iBAAiB,YACpC,UAAU;gBACV,CAAC,CAAC,KAAC,UAAU,IAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,YAAG,OAAO,GAAc;gBAC/E,CAAC,CAAC,OAAO,GACI,GACA,CAChB,CAAA;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buzola/router",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -29,9 +29,12 @@
29
29
  "react": "^18.0.0 || ^19.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "react": "^19.0.0",
33
- "@types/react": "^19.0.0",
32
+ "@testing-library/dom": "^10.4.1",
33
+ "@testing-library/react": "^16.3.2",
34
34
  "@types/bun": "latest",
35
+ "@types/react": "^19.0.0",
36
+ "happy-dom": "^20.8.3",
37
+ "react": "^19.0.0",
35
38
  "typescript": "^5.7.0"
36
39
  }
37
40
  }
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
2
  import { createElement } from 'react'
3
- import { createPage } from '../define/create-page'
4
- import { createMemoryNavigationAdapter } from '../engine/navigation-adapter'
5
- import { buildRouteTree } from '../engine/route-tree'
6
- import { Router } from '../engine/router'
7
- import { s } from '../engine/schema'
8
- import type { RouteConfig, StandardSchema } from '../engine/types'
3
+ import { BuzolaRedirect, createPage, redirect } from '../define/create-page.js'
4
+ import { createMemoryNavigationAdapter } from '../engine/navigation-adapter.js'
5
+ import { buildRouteTree } from '../engine/route-tree.js'
6
+ import { Router } from '../engine/router.js'
7
+ import { s } from '../engine/schema.js'
8
+ import type { RouteConfig, StandardSchema } from '../engine/types.js'
9
9
 
10
10
  // ─── Helpers ─────────────────────────────────────────────────────────────────
11
11
 
@@ -586,3 +586,39 @@ describe('navigate with extra params (integration)', () => {
586
586
  expect(state.location.searchParams.get('tab')).toBe('posts')
587
587
  })
588
588
  })
589
+
590
+ describe('redirect()', () => {
591
+ it('returns a BuzolaRedirect instance', () => {
592
+ const r = redirect('LoginPage' as any)
593
+ expect(r).toBeInstanceOf(BuzolaRedirect)
594
+ expect(r.pageId).toBe('LoginPage')
595
+ })
596
+
597
+ it('stores params on the redirect', () => {
598
+ const r = redirect('UserPage' as any, { userId: '123' } as any)
599
+ expect(r).toBeInstanceOf(BuzolaRedirect)
600
+ expect(r.pageId).toBe('UserPage')
601
+ expect(r.params).toEqual({ userId: '123' })
602
+ })
603
+
604
+ it('loader receives redirect in context', async () => {
605
+ let receivedRedirect: unknown
606
+ const page = createPage()
607
+ .loader(async ({ redirect: r }) => {
608
+ receivedRedirect = r
609
+ return {}
610
+ })
611
+ .render(() => null)
612
+
613
+ expect(typeof receivedRedirect).toBe('undefined') // not called yet — component not rendered
614
+ // Verify the type is correct by checking the function signature
615
+ expect(page.__buzolaPage).toBe(true)
616
+ })
617
+
618
+ it('redirect() result is recognized as BuzolaRedirect', () => {
619
+ const r = redirect('DashboardPage' as any)
620
+ expect(r instanceof BuzolaRedirect).toBe(true)
621
+ expect(r.__buzolaRedirect).toBe(true)
622
+ expect(r.pageId).toBe('DashboardPage')
623
+ })
624
+ })
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { LoaderCache } from '../engine/loader-cache.js'
3
+
4
+ describe('LoaderCache', () => {
5
+ it('stores and retrieves values', () => {
6
+ const cache = new LoaderCache(10)
7
+ cache.set('a', 1)
8
+ expect(cache.get('a')).toBe(1)
9
+ })
10
+
11
+ it('returns undefined for missing keys', () => {
12
+ const cache = new LoaderCache(10)
13
+ expect(cache.get('missing')).toBeUndefined()
14
+ })
15
+
16
+ it('has() returns true for existing keys', () => {
17
+ const cache = new LoaderCache(10)
18
+ cache.set('a', 1)
19
+ expect(cache.has('a')).toBe(true)
20
+ expect(cache.has('b')).toBe(false)
21
+ })
22
+
23
+ it('delete() removes entries', () => {
24
+ const cache = new LoaderCache(10)
25
+ cache.set('a', 1)
26
+ cache.delete('a')
27
+ expect(cache.has('a')).toBe(false)
28
+ expect(cache.size).toBe(0)
29
+ })
30
+
31
+ it('tracks size correctly', () => {
32
+ const cache = new LoaderCache(10)
33
+ expect(cache.size).toBe(0)
34
+ cache.set('a', 1)
35
+ cache.set('b', 2)
36
+ expect(cache.size).toBe(2)
37
+ cache.delete('a')
38
+ expect(cache.size).toBe(1)
39
+ })
40
+
41
+ it('clear() removes all entries', () => {
42
+ const cache = new LoaderCache(10)
43
+ cache.set('a', 1)
44
+ cache.set('b', 2)
45
+ cache.clear()
46
+ expect(cache.size).toBe(0)
47
+ expect(cache.has('a')).toBe(false)
48
+ })
49
+
50
+ // ─── LRU eviction ────────────────────────────────────────────────────────
51
+
52
+ it('evicts oldest entry when exceeding maxSize', () => {
53
+ const cache = new LoaderCache(2)
54
+ cache.set('a', 1)
55
+ cache.set('b', 2)
56
+ cache.set('c', 3)
57
+
58
+ expect(cache.has('a')).toBe(false)
59
+ expect(cache.get('b')).toBe(2)
60
+ expect(cache.get('c')).toBe(3)
61
+ expect(cache.size).toBe(2)
62
+ })
63
+
64
+ it('evicts multiple entries when needed', () => {
65
+ const cache = new LoaderCache(1)
66
+ cache.set('a', 1)
67
+ cache.set('b', 2)
68
+
69
+ expect(cache.has('a')).toBe(false)
70
+ expect(cache.get('b')).toBe(2)
71
+ expect(cache.size).toBe(1)
72
+ })
73
+
74
+ it('get() refreshes LRU order', () => {
75
+ const cache = new LoaderCache(2)
76
+ cache.set('a', 1)
77
+ cache.set('b', 2)
78
+
79
+ // Access 'a' to move it to most recent
80
+ cache.get('a')
81
+
82
+ // Now 'b' is the oldest, should be evicted
83
+ cache.set('c', 3)
84
+ expect(cache.has('b')).toBe(false)
85
+ expect(cache.get('a')).toBe(1)
86
+ expect(cache.get('c')).toBe(3)
87
+ })
88
+
89
+ it('set() on existing key updates value and refreshes LRU order', () => {
90
+ const cache = new LoaderCache(2)
91
+ cache.set('a', 1)
92
+ cache.set('b', 2)
93
+
94
+ // Update 'a' — should move to most recent
95
+ cache.set('a', 10)
96
+
97
+ // Now 'b' is the oldest
98
+ cache.set('c', 3)
99
+ expect(cache.has('b')).toBe(false)
100
+ expect(cache.get('a')).toBe(10)
101
+ expect(cache.get('c')).toBe(3)
102
+ })
103
+
104
+ it('maxSize of 0 keeps no entries', () => {
105
+ const cache = new LoaderCache(0)
106
+ cache.set('a', 1)
107
+ expect(cache.size).toBe(0)
108
+ expect(cache.has('a')).toBe(false)
109
+ })
110
+
111
+ it('exposes maxSize', () => {
112
+ const cache = new LoaderCache(5)
113
+ expect(cache.maxSize).toBe(5)
114
+ })
115
+ })
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
- import { matchRoutes } from '../engine/matcher'
3
- import { buildRouteTree } from '../engine/route-tree'
4
- import type { RouteConfig } from '../engine/types'
2
+ import { matchRoutes } from '../engine/matcher.js'
3
+ import { buildRouteTree } from '../engine/route-tree.js'
4
+ import type { RouteConfig } from '../engine/types.js'
5
5
 
6
6
  function dummyComponent() {
7
7
  return null