@studiolambda/router 0.1.1 → 2.0.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.
Files changed (39) hide show
  1. package/dist/matcher-C4dv_pr6.cjs +2 -0
  2. package/dist/{matcher-CTRKcf7I.cjs.map → matcher-C4dv_pr6.cjs.map} +1 -1
  3. package/dist/{matcher-ehhvtyaT.js → matcher-CQObVRFk.js} +2 -1
  4. package/dist/{matcher-ehhvtyaT.js.map → matcher-CQObVRFk.js.map} +1 -1
  5. package/dist/router.cjs +1 -1
  6. package/dist/router.js +1 -1
  7. package/dist/router_react.cjs +2 -2
  8. package/dist/router_react.cjs.map +1 -1
  9. package/dist/router_react.js +233 -154
  10. package/dist/router_react.js.map +1 -1
  11. package/dist/src/react/components/NotFound.d.ts +5 -2
  12. package/dist/src/react/components/Router.d.ts +1 -1
  13. package/dist/src/react/context/NavigationContext.d.ts +1 -1
  14. package/dist/src/react/context/NavigationSignalContext.d.ts +6 -1
  15. package/dist/src/react/context/NavigationTypeContext.d.ts +6 -1
  16. package/dist/src/react/context/ParamsContext.d.ts +10 -0
  17. package/dist/src/react/context/PathnameContext.d.ts +5 -4
  18. package/dist/src/react/context/TransitionContext.d.ts +0 -6
  19. package/dist/src/react/context/UrlContext.d.ts +12 -0
  20. package/dist/src/react/createRouter.d.ts +5 -0
  21. package/dist/src/react/extractPathname.d.ts +1 -2
  22. package/dist/src/react/hooks/useBack.d.ts +7 -1
  23. package/dist/src/react/hooks/useForward.d.ts +8 -1
  24. package/dist/src/react/hooks/useNavigate.d.ts +15 -0
  25. package/dist/src/react/hooks/useNavigation.d.ts +10 -0
  26. package/dist/src/react/hooks/useNavigationEvents.d.ts +18 -0
  27. package/dist/src/react/hooks/useNavigationHandlers.d.ts +9 -0
  28. package/dist/src/react/hooks/useNavigationSignal.d.ts +15 -0
  29. package/dist/src/react/hooks/useNavigationType.d.ts +18 -0
  30. package/dist/src/react/hooks/useNextMatch.d.ts +11 -0
  31. package/dist/src/react/hooks/useParams.d.ts +1 -0
  32. package/dist/src/react/hooks/usePathname.d.ts +1 -0
  33. package/dist/src/react/hooks/usePrefetch.d.ts +57 -0
  34. package/dist/src/react/hooks/useSearchParams.d.ts +9 -5
  35. package/dist/src/react/index.d.ts +2 -1
  36. package/dist/src/react/navigation/createMemoryNavigation.d.ts +5 -1
  37. package/package.json +1 -1
  38. package/dist/matcher-CTRKcf7I.cjs +0 -2
  39. package/dist/src/react/context/PropsContext.d.ts +0 -10
@@ -153,12 +153,17 @@ export type RouteFactory = (path?: string) => RouteBuilder;
153
153
  * chaining, nested groups with path prefixing, redirects,
154
154
  * and all handler options (scroll, focusReset, formHandler).
155
155
  *
156
+ * After all routes are registered, static redirect targets
157
+ * are checked for cycles. Self-redirects and multi-hop loops
158
+ * throw an error at registration time.
159
+ *
156
160
  * Returns a `Matcher<Handler>` that plugs directly into the
157
161
  * `<Router matcher={...}>` component.
158
162
  *
159
163
  * @param callback - A function that defines routes using the
160
164
  * provided `route` factory.
161
165
  * @returns A populated matcher ready for the Router.
166
+ * @throws When a static redirect cycle is detected.
162
167
  *
163
168
  * @example
164
169
  * ```tsx
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Extracts the pathname portion from a URL string. Uses a
3
3
  * dummy base URL to handle both absolute and relative paths
4
- * correctly. Returns `'/'` when the input is null, undefined,
5
- * or an empty string.
4
+ * correctly. Returns `'/'` when the input is null or undefined.
6
5
  *
7
6
  * Used by the Router (to extract pathname from navigation
8
7
  * destination URLs), Link (for active link comparison), and
@@ -17,7 +17,8 @@ export interface UseBackResult {
17
17
  /**
18
18
  * Whether backward navigation is possible. Mirrors
19
19
  * `navigation.canGoBack` from the Navigation API.
20
- * When false, calling `back()` will throw.
20
+ * Reactively updates when navigations change the
21
+ * history stack. When false, calling `back()` will throw.
21
22
  */
22
23
  readonly canGoBack: boolean;
23
24
  }
@@ -27,6 +28,11 @@ export interface UseBackResult {
27
28
  * `canGoBack` boolean that reflects whether the history
28
29
  * stack has a previous entry to traverse to.
29
30
  *
31
+ * The `canGoBack` value is kept in React state and
32
+ * updated via the `currententrychange` event, ensuring
33
+ * it stays reactive across navigations — including those
34
+ * triggered outside of React (e.g. browser back button).
35
+ *
30
36
  * Must be used inside a `<Router>` component tree.
31
37
  *
32
38
  * @returns An object with `back` and `canGoBack`.
@@ -17,7 +17,9 @@ export interface UseForwardResult {
17
17
  /**
18
18
  * Whether forward navigation is possible. Mirrors
19
19
  * `navigation.canGoForward` from the Navigation API.
20
- * When false, calling `forward()` will throw.
20
+ * Reactively updates when navigations change the
21
+ * history stack. When false, calling `forward()` will
22
+ * throw.
21
23
  */
22
24
  readonly canGoForward: boolean;
23
25
  }
@@ -27,6 +29,11 @@ export interface UseForwardResult {
27
29
  * `canGoForward` boolean that reflects whether the history
28
30
  * stack has a next entry to traverse to.
29
31
  *
32
+ * The `canGoForward` value is kept in React state and
33
+ * updated via the `currententrychange` event, ensuring
34
+ * it stays reactive across navigations — including those
35
+ * triggered outside of React (e.g. browser forward button).
36
+ *
30
37
  * Must be used inside a `<Router>` component tree.
31
38
  *
32
39
  * @returns An object with `forward` and `canGoForward`.
@@ -6,5 +6,20 @@
6
6
  *
7
7
  * @returns A navigate function that accepts a URL string and
8
8
  * optional `NavigationNavigateOptions`.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * function LogoutButton() {
13
+ * const navigate = useNavigate()
14
+ *
15
+ * return (
16
+ * <button onClick={function () {
17
+ * navigate('/login', { history: 'replace' })
18
+ * }}>
19
+ * Log Out
20
+ * </button>
21
+ * )
22
+ * }
23
+ * ```
9
24
  */
10
25
  export declare function useNavigate(): (url: string, options?: NavigationNavigateOptions) => NavigationResult;
@@ -9,5 +9,15 @@
9
9
  *
10
10
  * @returns The Navigation object from the nearest provider.
11
11
  * @throws When used outside a NavigationContext provider.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function HistoryDebug() {
16
+ * const navigation = useNavigation()
17
+ * const entries = navigation.entries()
18
+ *
19
+ * return <pre>{JSON.stringify(entries.map(e => e.url))}</pre>
20
+ * }
21
+ * ```
12
22
  */
13
23
  export declare function useNavigation(): Navigation;
@@ -39,5 +39,23 @@ export interface NavigationEventHandlers {
39
39
  * @param navigation - The Navigation object to subscribe to.
40
40
  * @param handlers - Callbacks for each navigation lifecycle
41
41
  * event. All are optional.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * function NavigationLogger() {
46
+ * const navigation = useNavigation()
47
+ *
48
+ * useNavigationEvents(navigation, {
49
+ * onNavigateSuccess() {
50
+ * console.log('navigation completed')
51
+ * },
52
+ * onNavigateError(error) {
53
+ * console.error('navigation failed', error)
54
+ * },
55
+ * })
56
+ *
57
+ * return null
58
+ * }
59
+ * ```
42
60
  */
43
61
  export declare function useNavigationEvents(navigation: Navigation, handlers: NavigationEventHandlers): void;
@@ -40,6 +40,15 @@ export interface PrecommitHandlerOptions {
40
40
  * provides TransitionContext.
41
41
  * @throws When no transition tuple is provided and the
42
42
  * hook is used outside a TransitionContext provider.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * function CustomRouter() {
47
+ * const transition = useTransition()
48
+ * const { createHandler } = useNavigationHandlers(transition)
49
+ * // use createHandler to build intercept handlers
50
+ * }
51
+ * ```
43
52
  */
44
53
  export declare function useNavigationHandlers(transition?: ReturnType<typeof useTransition>): {
45
54
  createPrecommitHandler: (options: PrecommitHandlerOptions) => ((controller: NavigationPrecommitController) => Promise<void>) | undefined;
@@ -8,6 +8,21 @@
8
8
  * Returns `null` before any navigation event has occurred
9
9
  * (i.e. on the initial render).
10
10
  *
11
+ * Must be used inside a `<Router>` component tree.
12
+ *
11
13
  * @returns The current AbortSignal or null.
14
+ * @throws When used outside a Router or NavigationSignalContext
15
+ * provider.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * function UserProfile({ id }: { id: string }) {
20
+ * const signal = useNavigationSignal()
21
+ *
22
+ * useEffect(function () {
23
+ * fetch(`/api/user/${id}`, { signal })
24
+ * }, [id, signal])
25
+ * }
26
+ * ```
12
27
  */
13
28
  export declare function useNavigationSignal(): AbortSignal | null;
@@ -7,6 +7,24 @@
7
7
  * Returns `null` before any navigation event has occurred
8
8
  * (i.e. on the initial render).
9
9
  *
10
+ * Must be used inside a `<Router>` component tree.
11
+ *
10
12
  * @returns The current NavigationType or null.
13
+ * @throws When used outside a Router or NavigationTypeContext
14
+ * provider.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function PageTransition({ children }: { children: ReactNode }) {
19
+ * const type = useNavigationType()
20
+ * const isTraversal = type === 'traverse'
21
+ *
22
+ * return (
23
+ * <div className={isTraversal ? 'slide' : 'fade'}>
24
+ * {children}
25
+ * </div>
26
+ * )
27
+ * }
28
+ * ```
11
29
  */
12
30
  export declare function useNavigationType(): NavigationType | null;
@@ -22,5 +22,16 @@ export interface NextMatchOptions {
22
22
  * @param options - Optional matcher override.
23
23
  * @returns A resolver function that takes a destination URL
24
24
  * and a not-found component, returning the resolved match.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function CustomRouter() {
29
+ * const resolve = useNextMatch()
30
+ * const match = resolve(window.location.href, NotFound)
31
+ * const Component = match.handler.component
32
+ *
33
+ * return <Component />
34
+ * }
35
+ * ```
25
36
  */
26
37
  export declare function useNextMatch(options?: NextMatchOptions): (destination: string | null, notFound: ComponentType) => Resolved<Handler>;
@@ -8,6 +8,7 @@
8
8
  * `ParamsContext` is provided.
9
9
  *
10
10
  * @returns A record of parameter names to their string values.
11
+ * @throws When used outside a Router or ParamsContext provider.
11
12
  *
12
13
  * @example
13
14
  * ```tsx
@@ -10,6 +10,7 @@
10
10
  * `PathnameContext` is provided.
11
11
  *
12
12
  * @returns The current pathname string (e.g. `"/user/42"`).
13
+ * @throws When used outside a Router or PathnameContext provider.
13
14
  *
14
15
  * @example
15
16
  * ```tsx
@@ -10,12 +10,53 @@ export interface PrefetchOptions {
10
10
  */
11
11
  matcher?: Matcher<Handler>;
12
12
  }
13
+ /**
14
+ * Clears the prefetch deduplication cache for a specific
15
+ * matcher instance. After clearing, subsequent calls to the
16
+ * prefetch function will re-execute handlers for pathnames
17
+ * that were previously skipped.
18
+ *
19
+ * Useful when cached data becomes stale — for example after
20
+ * a user logs out, a form submission invalidates server
21
+ * state, or a known data expiry occurs.
22
+ *
23
+ * When called without a matcher argument, has no effect.
24
+ * The cache is automatically garbage-collected when the
25
+ * matcher instance is no longer referenced, so explicit
26
+ * clearing is only needed for long-lived matchers.
27
+ *
28
+ * @param matcher - The matcher whose prefetch cache should
29
+ * be cleared.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * function LogoutButton() {
34
+ * const navigate = useNavigate()
35
+ * const matcher = use(MatcherContext)
36
+ *
37
+ * return (
38
+ * <button onClick={function () {
39
+ * clearPrefetchCache(matcher)
40
+ * navigate('/login', { history: 'replace' })
41
+ * }}>
42
+ * Log Out
43
+ * </button>
44
+ * )
45
+ * }
46
+ * ```
47
+ */
48
+ export declare function clearPrefetchCache(matcher: Matcher<Handler>): void;
13
49
  /**
14
50
  * Returns a function that triggers the prefetch logic for a
15
51
  * given URL by resolving it against the matcher and calling
16
52
  * the route's prefetch function. Used by the Link component
17
53
  * for hover and viewport prefetch strategies.
18
54
  *
55
+ * Each pathname is prefetched at most once per matcher
56
+ * instance per page session. Subsequent calls with the same
57
+ * pathname are no-ops, preventing thundering-herd problems
58
+ * when many Links point to the same destination.
59
+ *
19
60
  * Since prefetch triggered from Link happens outside of a
20
61
  * navigation event, a stub NavigationPrecommitController is
21
62
  * passed (the redirect capability is not meaningful here).
@@ -23,5 +64,21 @@ export interface PrefetchOptions {
23
64
  * @param options - Optional matcher override.
24
65
  * @returns A function that accepts a URL string and invokes
25
66
  * the matched route's prefetch handler, if any.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * function PrefetchOnHover({ href, children }: Props) {
71
+ * const prefetch = usePrefetch()
72
+ *
73
+ * return (
74
+ * <a
75
+ * href={href}
76
+ * onMouseEnter={function () { prefetch(href) }}
77
+ * >
78
+ * {children}
79
+ * </a>
80
+ * )
81
+ * }
82
+ * ```
26
83
  */
27
84
  export declare function usePrefetch(options?: PrefetchOptions): (url: string) => void | Promise<void>;
@@ -22,11 +22,15 @@ export interface SetSearchParamsOptions {
22
22
  * `URLSearchParams` instance and a setter function to
23
23
  * update them via navigation.
24
24
  *
25
- * The getter reads from `navigation.currentEntry.url`
26
- * on each render, so it always reflects the committed
27
- * URL. The setter performs a navigation with the updated
28
- * search string, defaulting to `history: 'replace'` to
29
- * avoid polluting the history stack with parameter changes.
25
+ * The getter derives search params from `UrlContext`
26
+ * React state managed by the Router rather than reading
27
+ * the mutable `navigation.currentEntry` during render.
28
+ * This prevents subscription tearing in concurrent mode
29
+ * where two components could otherwise see different
30
+ * search params if a navigation fires mid-render.
31
+ *
32
+ * The setter preserves the existing hash fragment when
33
+ * updating search parameters.
30
34
  *
31
35
  * The React Compiler handles memoization of the setter,
32
36
  * so no manual `useCallback` is needed.
@@ -4,11 +4,12 @@ export * from './components/NotFound';
4
4
  export * from './components/Link';
5
5
  export * from './context/MatcherContext';
6
6
  export * from './context/TransitionContext';
7
- export * from './context/PropsContext';
7
+ export * from './context/ParamsContext';
8
8
  export * from './context/NavigationContext';
9
9
  export * from './context/NavigationSignalContext';
10
10
  export * from './context/NavigationTypeContext';
11
11
  export * from './context/PathnameContext';
12
+ export * from './context/UrlContext';
12
13
  export * from './hooks/useNavigation';
13
14
  export * from './hooks/useNavigate';
14
15
  export * from './hooks/useNavigationSignal';
@@ -17,13 +17,17 @@ export interface MemoryNavigationOptions {
17
17
  * browser Navigation API is unavailable.
18
18
  *
19
19
  * The returned object satisfies the subset of the `Navigation`
20
- * interface consumed by the Router component:
20
+ * interface consumed by the Router component and hooks:
21
21
  *
22
22
  * - `currentEntry.url` — returns the initial URL
23
23
  * - `addEventListener` / `removeEventListener` — no-ops
24
24
  * (no events fire in a memory environment)
25
25
  * - `navigate()` — no-op that returns a NavigationResult
26
26
  * with immediately-resolved promises
27
+ * - `back()` / `forward()` — no-ops that return a
28
+ * NavigationResult with immediately-resolved promises
29
+ * - `traverseTo()` — no-op that returns a NavigationResult
30
+ * - `updateCurrentEntry()` — no-op
27
31
  * - `canGoBack` / `canGoForward` — always false
28
32
  * - `entries()` — returns a single-entry array
29
33
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiolambda/router",
3
- "version": "0.1.1",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "keywords": [
6
6
  "router",
@@ -1,2 +0,0 @@
1
- function e(e){let t=e?.root??{children:new Map};function n(e,n){let r=e.split(`/`).filter(Boolean),i=t;for(let t of r){if(t.startsWith(`*`)){let n=t.length>1?t.slice(1):`*`;if(!i.wildcard)i.wildcard={children:new Map,name:n};else if(i.wildcard.name!==n)throw Error(`conflicting wildcard param name at "${e}": existing "*${i.wildcard.name}" vs new "*${n}"`);i=i.wildcard;continue}if(t.startsWith(`:`)){let n=t.slice(1);if(!i.child)i.child={children:new Map,name:n};else if(i.child.name!==n)throw Error(`conflicting dynamic param name at "${e}": existing ":${i.child.name}" vs new ":${n}"`);i=i.child;continue}let n=i.children.get(t);n||(n={children:new Map},i.children.set(t,n)),i=n}i.handler=n}function r(e){let n=e.split(`/`).filter(Boolean);function r(e,t,i){if(t===n.length)return e.handler===void 0?null:{handler:e.handler,params:i};let a=n[t],o=e.children.get(a);if(o){let e=r(o,t+1,i);if(e)return e}if(e.child&&e.child.name){let n=r(e.child,t+1,{...i,[e.child.name]:a});if(n)return n}if(e.wildcard&&e.wildcard.name&&e.wildcard.handler!==void 0){let r=n.slice(t).join(`/`);return{handler:e.wildcard.handler,params:{...i,[e.wildcard.name]:r}}}return null}return r(t,0,{})}return{register:n,match:r}}Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return e}});
2
- //# sourceMappingURL=matcher-CTRKcf7I.cjs.map
@@ -1,10 +0,0 @@
1
- /**
2
- * Provides the route parameters extracted from the matched URL
3
- * pattern as a string-keyed record. Defaults to an empty object
4
- * when no route has been matched yet.
5
- *
6
- * Consumed via the `useParams` hook. The Router component
7
- * updates this context on every successful navigation with
8
- * the newly extracted parameters.
9
- */
10
- export declare const ParamsContext: import('react').Context<Record<string, string>>;