@studiolambda/router 0.1.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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/matcher-CSJ3hjzA.cjs +2 -0
- package/dist/matcher-CSJ3hjzA.cjs.map +1 -0
- package/dist/matcher-XNPYU-tD.js +69 -0
- package/dist/matcher-XNPYU-tD.js.map +1 -0
- package/dist/router.cjs +1 -0
- package/dist/router.js +2 -0
- package/dist/router_react.cjs +6 -0
- package/dist/router_react.cjs.map +1 -0
- package/dist/router_react.js +506 -0
- package/dist/router_react.js.map +1 -0
- package/dist/src/react/ExampleMain.d.ts +7 -0
- package/dist/src/react/components/Link.d.ts +91 -0
- package/dist/src/react/components/Middlewares.d.ts +28 -0
- package/dist/src/react/components/NotFound.d.ts +6 -0
- package/dist/src/react/components/Router.d.ts +81 -0
- package/dist/src/react/context/MatcherContext.d.ts +12 -0
- package/dist/src/react/context/NavigationContext.d.ts +11 -0
- package/dist/src/react/context/NavigationSignalContext.d.ts +7 -0
- package/dist/src/react/context/NavigationTypeContext.d.ts +6 -0
- package/dist/src/react/context/PathnameContext.d.ts +10 -0
- package/dist/src/react/context/PropsContext.d.ts +10 -0
- package/dist/src/react/context/TransitionContext.d.ts +19 -0
- package/dist/src/react/createRouter.d.ts +187 -0
- package/dist/src/react/example.d.ts +1 -0
- package/dist/src/react/examples/Auth.d.ts +11 -0
- package/dist/src/react/examples/HelloWorld.d.ts +5 -0
- package/dist/src/react/examples/Other.d.ts +6 -0
- package/dist/src/react/examples/User.d.ts +6 -0
- package/dist/src/react/extractPathname.d.ts +17 -0
- package/dist/src/react/hooks/useActiveLinkProps.d.ts +73 -0
- package/dist/src/react/hooks/useBack.d.ts +47 -0
- package/dist/src/react/hooks/useForward.d.ts +47 -0
- package/dist/src/react/hooks/useIsPending.d.ts +29 -0
- package/dist/src/react/hooks/useNavigate.d.ts +10 -0
- package/dist/src/react/hooks/useNavigation.d.ts +13 -0
- package/dist/src/react/hooks/useNavigationEvents.d.ts +43 -0
- package/dist/src/react/hooks/useNavigationHandlers.d.ts +47 -0
- package/dist/src/react/hooks/useNavigationSignal.d.ts +13 -0
- package/dist/src/react/hooks/useNavigationType.d.ts +12 -0
- package/dist/src/react/hooks/useNextMatch.d.ts +26 -0
- package/dist/src/react/hooks/useParams.d.ts +19 -0
- package/dist/src/react/hooks/usePathname.d.ts +23 -0
- package/dist/src/react/hooks/usePrefetch.d.ts +27 -0
- package/dist/src/react/hooks/usePrefetchEffect.d.ts +69 -0
- package/dist/src/react/hooks/useSearchParams.d.ts +58 -0
- package/dist/src/react/index.d.ts +31 -0
- package/dist/src/react/navigation/createMemoryNavigation.d.ts +52 -0
- package/dist/src/react/router.d.ts +139 -0
- package/dist/src/react/test-helpers.d.ts +50 -0
- package/dist/src/router/index.d.ts +1 -0
- package/dist/src/router/matcher.d.ts +127 -0
- package/package.json +107 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Handler } from '../router';
|
|
2
|
+
import { Matcher } from '../../router/matcher';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the `usePrefetch` hook.
|
|
5
|
+
*/
|
|
6
|
+
export interface PrefetchOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Optional matcher override. When omitted the hook reads
|
|
9
|
+
* from `MatcherContext`.
|
|
10
|
+
*/
|
|
11
|
+
matcher?: Matcher<Handler>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns a function that triggers the prefetch logic for a
|
|
15
|
+
* given URL by resolving it against the matcher and calling
|
|
16
|
+
* the route's prefetch function. Used by the Link component
|
|
17
|
+
* for hover and viewport prefetch strategies.
|
|
18
|
+
*
|
|
19
|
+
* Since prefetch triggered from Link happens outside of a
|
|
20
|
+
* navigation event, a stub NavigationPrecommitController is
|
|
21
|
+
* passed (the redirect capability is not meaningful here).
|
|
22
|
+
*
|
|
23
|
+
* @param options - Optional matcher override.
|
|
24
|
+
* @returns A function that accepts a URL string and invokes
|
|
25
|
+
* the matched route's prefetch handler, if any.
|
|
26
|
+
*/
|
|
27
|
+
export declare function usePrefetch(options?: PrefetchOptions): (url: string) => void | Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { Handler } from '../router';
|
|
3
|
+
import { Matcher } from '../../router/matcher';
|
|
4
|
+
/**
|
|
5
|
+
* Prefetch trigger strategy. Determines which DOM event
|
|
6
|
+
* activates the prefetch for the target element.
|
|
7
|
+
*
|
|
8
|
+
* - `viewport` — prefetch fires when the element enters
|
|
9
|
+
* the viewport via an IntersectionObserver.
|
|
10
|
+
* - `hover` — prefetch fires on `mouseenter`.
|
|
11
|
+
*/
|
|
12
|
+
export type PrefetchStrategy = 'viewport' | 'hover';
|
|
13
|
+
/**
|
|
14
|
+
* Options for the `usePrefetchEffect` hook.
|
|
15
|
+
*/
|
|
16
|
+
export interface PrefetchEffectOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The URL to prefetch. When undefined, the effect is
|
|
19
|
+
* a no-op (no observer or listener is attached).
|
|
20
|
+
*/
|
|
21
|
+
href: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Prefetch trigger strategy. When undefined, the effect
|
|
24
|
+
* is a no-op — no proactive prefetch is set up.
|
|
25
|
+
*/
|
|
26
|
+
on: PrefetchStrategy | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to only prefetch once. When true, hover
|
|
29
|
+
* listeners use the `{ once }` option and viewport
|
|
30
|
+
* observers disconnect after the first intersection.
|
|
31
|
+
* Defaults to `true`.
|
|
32
|
+
*/
|
|
33
|
+
once?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Optional matcher override. When omitted, reads from
|
|
36
|
+
* `MatcherContext` via `usePrefetch`.
|
|
37
|
+
*/
|
|
38
|
+
matcher?: Matcher<Handler>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Attaches prefetch behavior to a DOM element via a ref.
|
|
42
|
+
* Sets up an IntersectionObserver (for `viewport` strategy)
|
|
43
|
+
* or a mouseenter listener (for `hover` strategy) that
|
|
44
|
+
* triggers route prefetching when activated.
|
|
45
|
+
*
|
|
46
|
+
* When `on` is undefined or `href` is undefined, no
|
|
47
|
+
* observer or listener is attached — the hook is a no-op.
|
|
48
|
+
*
|
|
49
|
+
* This hook is used internally by the Link component and
|
|
50
|
+
* can also be used standalone to add prefetch behavior to
|
|
51
|
+
* any DOM element.
|
|
52
|
+
*
|
|
53
|
+
* @param ref - A ref to the DOM element to observe. The
|
|
54
|
+
* element must be mounted before the effect runs.
|
|
55
|
+
* @param options - Configuration for the prefetch behavior
|
|
56
|
+
* including the URL, trigger strategy, and matcher.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* function Card({ href }: { href: string }) {
|
|
61
|
+
* const ref = useRef<HTMLDivElement>(null)
|
|
62
|
+
*
|
|
63
|
+
* usePrefetchEffect(ref, { href, on: 'viewport' })
|
|
64
|
+
*
|
|
65
|
+
* return <div ref={ref}>...</div>
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function usePrefetchEffect(ref: RefObject<Element | null>, options: PrefetchEffectOptions): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Updater function signature for setting search params.
|
|
3
|
+
* Accepts either a new URLSearchParams instance, a plain
|
|
4
|
+
* record of string key-value pairs, or a function that
|
|
5
|
+
* receives the current params and returns updated params.
|
|
6
|
+
*/
|
|
7
|
+
export type SearchParamsUpdater = URLSearchParams | Record<string, string> | ((current: URLSearchParams) => URLSearchParams | Record<string, string>);
|
|
8
|
+
/**
|
|
9
|
+
* Options for the search params navigation performed by
|
|
10
|
+
* the setter function returned from `useSearchParams`.
|
|
11
|
+
*/
|
|
12
|
+
export interface SetSearchParamsOptions {
|
|
13
|
+
/**
|
|
14
|
+
* History behavior for the navigation. Defaults to
|
|
15
|
+
* `'replace'` since search param changes typically
|
|
16
|
+
* should not create new history entries.
|
|
17
|
+
*/
|
|
18
|
+
history?: NavigationHistoryBehavior;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the current URL's search parameters as a
|
|
22
|
+
* `URLSearchParams` instance and a setter function to
|
|
23
|
+
* update them via navigation.
|
|
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.
|
|
30
|
+
*
|
|
31
|
+
* The React Compiler handles memoization of the setter,
|
|
32
|
+
* so no manual `useCallback` is needed.
|
|
33
|
+
*
|
|
34
|
+
* Must be used inside a `<Router>` component tree.
|
|
35
|
+
*
|
|
36
|
+
* @returns A tuple of `[searchParams, setSearchParams]`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function SearchPage() {
|
|
41
|
+
* const [searchParams, setSearchParams] = useSearchParams()
|
|
42
|
+
* const query = searchParams.get('q') ?? ''
|
|
43
|
+
*
|
|
44
|
+
* return (
|
|
45
|
+
* <input
|
|
46
|
+
* value={query}
|
|
47
|
+
* onChange={function (event) {
|
|
48
|
+
* setSearchParams({ q: event.target.value })
|
|
49
|
+
* }}
|
|
50
|
+
* />
|
|
51
|
+
* )
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function useSearchParams(): [
|
|
56
|
+
URLSearchParams,
|
|
57
|
+
(updater: SearchParamsUpdater, options?: SetSearchParamsOptions) => NavigationResult
|
|
58
|
+
];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export * from './components/Middlewares';
|
|
2
|
+
export * from './components/Router';
|
|
3
|
+
export * from './components/NotFound';
|
|
4
|
+
export * from './components/Link';
|
|
5
|
+
export * from './context/MatcherContext';
|
|
6
|
+
export * from './context/TransitionContext';
|
|
7
|
+
export * from './context/PropsContext';
|
|
8
|
+
export * from './context/NavigationContext';
|
|
9
|
+
export * from './context/NavigationSignalContext';
|
|
10
|
+
export * from './context/NavigationTypeContext';
|
|
11
|
+
export * from './context/PathnameContext';
|
|
12
|
+
export * from './hooks/useNavigation';
|
|
13
|
+
export * from './hooks/useNavigate';
|
|
14
|
+
export * from './hooks/useNavigationSignal';
|
|
15
|
+
export * from './hooks/useNavigationType';
|
|
16
|
+
export * from './hooks/usePrefetch';
|
|
17
|
+
export * from './hooks/useNextMatch';
|
|
18
|
+
export * from './hooks/useNavigationHandlers';
|
|
19
|
+
export * from './hooks/useParams';
|
|
20
|
+
export * from './hooks/useIsPending';
|
|
21
|
+
export * from './hooks/usePathname';
|
|
22
|
+
export * from './hooks/useSearchParams';
|
|
23
|
+
export * from './hooks/useBack';
|
|
24
|
+
export * from './hooks/useForward';
|
|
25
|
+
export * from './hooks/useNavigationEvents';
|
|
26
|
+
export * from './hooks/useActiveLinkProps';
|
|
27
|
+
export * from './hooks/usePrefetchEffect';
|
|
28
|
+
export * from './navigation/createMemoryNavigation';
|
|
29
|
+
export * from './extractPathname';
|
|
30
|
+
export * from './createRouter';
|
|
31
|
+
export * from './router';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for creating a memory navigation instance.
|
|
3
|
+
*/
|
|
4
|
+
export interface MemoryNavigationOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The initial URL for the memory navigation. This is the
|
|
7
|
+
* URL that `currentEntry.url` will return. Typically the
|
|
8
|
+
* request URL from the server for SSR, or a test URL.
|
|
9
|
+
*
|
|
10
|
+
* @example `'https://example.com/user/42?tab=posts'`
|
|
11
|
+
*/
|
|
12
|
+
readonly url: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a minimal in-memory Navigation object suitable for
|
|
16
|
+
* server-side rendering and testing environments where the
|
|
17
|
+
* browser Navigation API is unavailable.
|
|
18
|
+
*
|
|
19
|
+
* The returned object satisfies the subset of the `Navigation`
|
|
20
|
+
* interface consumed by the Router component:
|
|
21
|
+
*
|
|
22
|
+
* - `currentEntry.url` — returns the initial URL
|
|
23
|
+
* - `addEventListener` / `removeEventListener` — no-ops
|
|
24
|
+
* (no events fire in a memory environment)
|
|
25
|
+
* - `navigate()` — no-op that returns a NavigationResult
|
|
26
|
+
* with immediately-resolved promises
|
|
27
|
+
* - `canGoBack` / `canGoForward` — always false
|
|
28
|
+
* - `entries()` — returns a single-entry array
|
|
29
|
+
*
|
|
30
|
+
* The object is cast to `Navigation` for compatibility with
|
|
31
|
+
* the Router's `navigation` prop and `NavigationContext`.
|
|
32
|
+
* Properties not listed above are not implemented and will
|
|
33
|
+
* throw if accessed by consumer code outside the Router.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Configuration including the initial URL.
|
|
36
|
+
* @returns A Navigation-compatible object for SSR or testing.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* // Server-side rendering
|
|
41
|
+
* const navigation = createMemoryNavigation({
|
|
42
|
+
* url: request.url,
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* const html = renderToString(
|
|
46
|
+
* <Router navigation={navigation} matcher={matcher}>
|
|
47
|
+
* <App />
|
|
48
|
+
* </Router>
|
|
49
|
+
* )
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function createMemoryNavigation(options: MemoryNavigationOptions): Navigation;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Defines the route handler configuration for a matched route.
|
|
4
|
+
* Contains the component to render, optional prefetch logic,
|
|
5
|
+
* middleware components, scroll/focus behavior, and form handling.
|
|
6
|
+
*/
|
|
7
|
+
export interface Handler {
|
|
8
|
+
/**
|
|
9
|
+
* The React component to render for this route.
|
|
10
|
+
* Can be a regular component or a `lazy()` component
|
|
11
|
+
* for code-split routes.
|
|
12
|
+
*/
|
|
13
|
+
readonly component: ComponentType;
|
|
14
|
+
/**
|
|
15
|
+
* Optional prefetch function invoked during the precommit phase.
|
|
16
|
+
* Receives a context with matched route params, destination URL,
|
|
17
|
+
* and the precommit controller for redirects.
|
|
18
|
+
*/
|
|
19
|
+
readonly prefetch?: PrefetchFunc;
|
|
20
|
+
/**
|
|
21
|
+
* Middleware components that wrap the route component.
|
|
22
|
+
* Applied via reduceRight so the first middleware in
|
|
23
|
+
* the array wraps outermost, and the last wraps innermost.
|
|
24
|
+
*/
|
|
25
|
+
readonly middlewares?: ComponentType<MiddlewareProps>[];
|
|
26
|
+
/**
|
|
27
|
+
* Controls whether the browser performs scroll restoration
|
|
28
|
+
* after the navigation handler completes.
|
|
29
|
+
* - `after-transition` (default): browser handles scrolling
|
|
30
|
+
* after the handler promise resolves.
|
|
31
|
+
* - `manual`: disables automatic scrolling so the route
|
|
32
|
+
* component can call `event.scroll()` manually.
|
|
33
|
+
*/
|
|
34
|
+
readonly scroll?: NavigationScrollBehavior;
|
|
35
|
+
/**
|
|
36
|
+
* Controls whether the browser resets focus after navigation.
|
|
37
|
+
* - `after-transition` (default): focuses the first autofocus
|
|
38
|
+
* element or the body after the handler resolves.
|
|
39
|
+
* - `manual`: disables automatic focus reset so the route
|
|
40
|
+
* component can manage focus manually.
|
|
41
|
+
*/
|
|
42
|
+
readonly focusReset?: NavigationFocusReset;
|
|
43
|
+
/**
|
|
44
|
+
* Optional handler for form submissions (POST navigations).
|
|
45
|
+
* When a navigation includes FormData and the matched route
|
|
46
|
+
* has a formHandler, it is called instead of the normal
|
|
47
|
+
* component render flow.
|
|
48
|
+
*/
|
|
49
|
+
readonly formHandler?: FormHandler;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Context passed to prefetch functions during the precommit
|
|
53
|
+
* phase of a navigation. Provides the matched route parameters,
|
|
54
|
+
* the destination URL, and the Navigation API's precommit
|
|
55
|
+
* controller for redirects.
|
|
56
|
+
*/
|
|
57
|
+
export interface PrefetchContext {
|
|
58
|
+
/**
|
|
59
|
+
* Dynamic route parameters extracted from the matched URL
|
|
60
|
+
* pattern. For example, a route registered as `/user/:id`
|
|
61
|
+
* matching `/user/42` produces `{ id: '42' }`.
|
|
62
|
+
*/
|
|
63
|
+
readonly params: Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* The full destination URL being navigated to. Includes
|
|
66
|
+
* the pathname, search parameters, and hash. Useful for
|
|
67
|
+
* constructing query keys or reading search params during
|
|
68
|
+
* prefetch.
|
|
69
|
+
*/
|
|
70
|
+
readonly url: URL;
|
|
71
|
+
/**
|
|
72
|
+
* The precommit controller from the Navigation API,
|
|
73
|
+
* providing `redirect()` to abort the current navigation
|
|
74
|
+
* and redirect elsewhere, and `addHandler()` to register
|
|
75
|
+
* additional post-commit handlers. When prefetch is
|
|
76
|
+
* triggered outside a real navigation (e.g. Link hover),
|
|
77
|
+
* this is a stub with no-op methods.
|
|
78
|
+
*/
|
|
79
|
+
readonly controller: NavigationPrecommitController;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Function invoked during the precommit phase of a navigation.
|
|
83
|
+
* Receives a context object with the matched route parameters,
|
|
84
|
+
* destination URL, and the Navigation API's precommit controller
|
|
85
|
+
* for redirects or additional handler registration.
|
|
86
|
+
*
|
|
87
|
+
* @param context - The prefetch context containing route params,
|
|
88
|
+
* destination URL, and the precommit controller.
|
|
89
|
+
* @returns Void or a promise that resolves when prefetching
|
|
90
|
+
* is complete.
|
|
91
|
+
*/
|
|
92
|
+
export type PrefetchFunc = {
|
|
93
|
+
(context: PrefetchContext): void | Promise<void>;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Target for a redirect route. Can be a static absolute path
|
|
97
|
+
* string, or a callback that receives the prefetch context and
|
|
98
|
+
* returns the path to redirect to. The callback form enables
|
|
99
|
+
* dynamic redirects that use matched route parameters or the
|
|
100
|
+
* destination URL to compute the target.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* // Static redirect
|
|
105
|
+
* route('/old').redirect('/new')
|
|
106
|
+
*
|
|
107
|
+
* // Dynamic redirect using route params
|
|
108
|
+
* route('/old-user/:id').redirect(({ params }) => `/user/${params.id}`)
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export type RedirectTarget = string | ((context: PrefetchContext) => string);
|
|
112
|
+
/**
|
|
113
|
+
* Handler invoked when a form submission navigation matches
|
|
114
|
+
* this route. Receives the submitted FormData and the original
|
|
115
|
+
* NavigateEvent for full access to the navigation context.
|
|
116
|
+
*
|
|
117
|
+
* @param formData - The FormData from the submitted form.
|
|
118
|
+
* @param event - The original NavigateEvent that triggered
|
|
119
|
+
* the form submission.
|
|
120
|
+
* @returns Void or a promise that resolves when the form
|
|
121
|
+
* submission handling is complete.
|
|
122
|
+
*/
|
|
123
|
+
export type FormHandler = {
|
|
124
|
+
(formData: FormData, event: NavigateEvent): void | Promise<void>;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Props passed to middleware wrapper components. Each middleware
|
|
128
|
+
* receives `children` and decides whether to render them,
|
|
129
|
+
* enabling patterns like authentication guards and layout
|
|
130
|
+
* wrappers.
|
|
131
|
+
*/
|
|
132
|
+
export interface MiddlewareProps {
|
|
133
|
+
/**
|
|
134
|
+
* The downstream content to render. A middleware can choose
|
|
135
|
+
* to render this directly, wrap it in additional providers,
|
|
136
|
+
* or omit it entirely (e.g. for auth redirects).
|
|
137
|
+
*/
|
|
138
|
+
children: ReactNode;
|
|
139
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Result returned by `renderHook`. Provides access to the
|
|
4
|
+
* current hook return value and a cleanup function.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - The return type of the hook under test.
|
|
7
|
+
*/
|
|
8
|
+
export interface RenderHookResult<T> {
|
|
9
|
+
/**
|
|
10
|
+
* The current return value of the hook. Updated after
|
|
11
|
+
* each render.
|
|
12
|
+
*/
|
|
13
|
+
readonly current: T;
|
|
14
|
+
/**
|
|
15
|
+
* Unmounts the test component and removes it from the
|
|
16
|
+
* DOM. Must be called after each test to avoid leaks.
|
|
17
|
+
*/
|
|
18
|
+
readonly unmount: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options for `renderHook`.
|
|
22
|
+
*
|
|
23
|
+
* @typeParam T - The return type of the hook under test.
|
|
24
|
+
*/
|
|
25
|
+
export interface RenderHookOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Optional wrapper component that provides context
|
|
28
|
+
* providers around the hook test component.
|
|
29
|
+
*/
|
|
30
|
+
wrapper?: (props: {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}) => ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Renders a React hook inside a minimal test component and
|
|
36
|
+
* returns the hook's current value. Mimics the API of
|
|
37
|
+
* `@testing-library/react`'s `renderHook` without requiring
|
|
38
|
+
* that dependency.
|
|
39
|
+
*
|
|
40
|
+
* The hook is rendered inside an `act()` boundary. The
|
|
41
|
+
* returned `current` property always reflects the latest
|
|
42
|
+
* hook return value.
|
|
43
|
+
*
|
|
44
|
+
* @typeParam T - The return type of the hook.
|
|
45
|
+
* @param hook - A function that calls the hook under test
|
|
46
|
+
* and returns its result.
|
|
47
|
+
* @param options - Optional wrapper for context providers.
|
|
48
|
+
* @returns The hook result and an unmount function.
|
|
49
|
+
*/
|
|
50
|
+
export declare function renderHook<T>(hook: () => T, options?: RenderHookOptions): RenderHookResult<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './matcher';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes a route matcher that maps URL path patterns to
|
|
3
|
+
* handlers. Supports static segments, dynamic `:param`
|
|
4
|
+
* segments, and wildcard `*param` segments with a trie-based
|
|
5
|
+
* lookup.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T - The handler type associated with each route.
|
|
8
|
+
*/
|
|
9
|
+
export interface Matcher<T> {
|
|
10
|
+
/**
|
|
11
|
+
* Registers a route pattern with a corresponding handler.
|
|
12
|
+
* Patterns use `/`-separated segments where `:name` denotes
|
|
13
|
+
* a dynamic parameter (e.g. `/user/:id/posts`) and `*name`
|
|
14
|
+
* denotes a wildcard that captures the rest of the path
|
|
15
|
+
* (e.g. `/files/*path`). A bare `*` captures into a param
|
|
16
|
+
* named `'*'`.
|
|
17
|
+
*
|
|
18
|
+
* @param pattern - The URL path pattern to register.
|
|
19
|
+
* @param handler - The handler to associate with this pattern.
|
|
20
|
+
*/
|
|
21
|
+
readonly register: (pattern: string, handler: T) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Attempts to match a URL path against registered routes.
|
|
24
|
+
* Static segments take priority over dynamic ones, which
|
|
25
|
+
* take priority over wildcard segments. Returns the matched
|
|
26
|
+
* handler and extracted parameters, or `null` if no route
|
|
27
|
+
* matches.
|
|
28
|
+
*
|
|
29
|
+
* @param path - The URL path to match (e.g. `/user/42`).
|
|
30
|
+
* @returns The resolved match with handler and params, or null.
|
|
31
|
+
*/
|
|
32
|
+
readonly match: (path: string) => Resolved<T> | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The result of a successful route match. Contains the handler
|
|
36
|
+
* registered for the matched pattern and a record of extracted
|
|
37
|
+
* dynamic parameters.
|
|
38
|
+
*
|
|
39
|
+
* @typeParam T - The handler type associated with the route.
|
|
40
|
+
*/
|
|
41
|
+
export interface Resolved<T> {
|
|
42
|
+
/**
|
|
43
|
+
* The handler that was registered for the matched route pattern.
|
|
44
|
+
*/
|
|
45
|
+
readonly handler: T;
|
|
46
|
+
/**
|
|
47
|
+
* Dynamic path parameters extracted from the URL. Keys are the
|
|
48
|
+
* parameter names from the pattern (without the `:` prefix),
|
|
49
|
+
* values are the corresponding URL segments.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* Pattern `/user/:id` matched against `/user/42`
|
|
53
|
+
* produces `{ id: "42" }`.
|
|
54
|
+
*/
|
|
55
|
+
readonly params: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Configuration options for creating a new matcher instance.
|
|
59
|
+
*
|
|
60
|
+
* @typeParam T - The handler type associated with each route.
|
|
61
|
+
*/
|
|
62
|
+
export interface Options<T> {
|
|
63
|
+
/**
|
|
64
|
+
* An existing trie root node to use instead of creating
|
|
65
|
+
* an empty one. Useful for pre-built or shared route trees.
|
|
66
|
+
*/
|
|
67
|
+
readonly root?: Node<T>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A node in the route-matching trie. Each node represents a
|
|
71
|
+
* single URL path segment and may hold a handler (indicating
|
|
72
|
+
* a complete route), static children (keyed by segment string),
|
|
73
|
+
* a single dynamic child (for `:param` segments), and/or a
|
|
74
|
+
* wildcard child (for `*param` segments that capture the rest
|
|
75
|
+
* of the path).
|
|
76
|
+
*
|
|
77
|
+
* @typeParam T - The handler type associated with each route.
|
|
78
|
+
*/
|
|
79
|
+
export interface Node<T> {
|
|
80
|
+
/**
|
|
81
|
+
* Map of static child segments. Each key is a literal path
|
|
82
|
+
* segment string (e.g. `"user"`, `"posts"`).
|
|
83
|
+
*/
|
|
84
|
+
readonly children: Map<string, Node<T>>;
|
|
85
|
+
/**
|
|
86
|
+
* The handler registered at this node, or `undefined` if
|
|
87
|
+
* this node is only an intermediate segment in a longer
|
|
88
|
+
* pattern.
|
|
89
|
+
*/
|
|
90
|
+
handler?: T;
|
|
91
|
+
/**
|
|
92
|
+
* The single dynamic child node for `:param` segments.
|
|
93
|
+
* Only one dynamic segment is allowed per trie level.
|
|
94
|
+
*/
|
|
95
|
+
child?: Node<T>;
|
|
96
|
+
/**
|
|
97
|
+
* The parameter name for this dynamic segment (without the
|
|
98
|
+
* `:` prefix). Only set on nodes created from `:param`
|
|
99
|
+
* patterns.
|
|
100
|
+
*/
|
|
101
|
+
readonly name?: string;
|
|
102
|
+
/**
|
|
103
|
+
* The wildcard child node for `*param` segments. Captures
|
|
104
|
+
* all remaining path segments into a single parameter.
|
|
105
|
+
* Only one wildcard is allowed per trie level and it must
|
|
106
|
+
* be the last segment in the pattern.
|
|
107
|
+
*/
|
|
108
|
+
wildcard?: Node<T>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a new trie-based route matcher. Routes are registered
|
|
112
|
+
* with patterns containing static segments, dynamic (`:param`)
|
|
113
|
+
* segments, and wildcard (`*param`) segments. Matching
|
|
114
|
+
* prioritises static segments over dynamic ones, and dynamic
|
|
115
|
+
* over wildcards, performing a depth-first search through the
|
|
116
|
+
* trie.
|
|
117
|
+
*
|
|
118
|
+
* Wildcard segments capture all remaining path segments into a
|
|
119
|
+
* single parameter joined by `/`. A bare `*` captures into
|
|
120
|
+
* a param named `'*'`. Wildcards must be the last segment
|
|
121
|
+
* in a pattern.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam T - The handler type associated with each route.
|
|
124
|
+
* @param options - Optional configuration with a pre-built root node.
|
|
125
|
+
* @returns A matcher instance with `register` and `match` methods.
|
|
126
|
+
*/
|
|
127
|
+
export declare function createMatcher<T>(options?: Options<T>): Matcher<T>;
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@studiolambda/router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"router",
|
|
7
|
+
"react-router",
|
|
8
|
+
"isomorphic",
|
|
9
|
+
"routing",
|
|
10
|
+
"url",
|
|
11
|
+
"management",
|
|
12
|
+
"history",
|
|
13
|
+
"location"
|
|
14
|
+
],
|
|
15
|
+
"description": "Lightweight, isomorphic and framework agnostic router for modern UIs",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Erik C. Forés",
|
|
18
|
+
"email": "soc@erik.cat",
|
|
19
|
+
"url": "https://erik.cat"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://erik.cat/blog/router-docs",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/StudioLambda/Router.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"types": "./dist/src/router/index.d.ts",
|
|
31
|
+
"main": "./dist/router.cjs",
|
|
32
|
+
"module": "./dist/router.js",
|
|
33
|
+
"exports": {
|
|
34
|
+
"./package.json": "./package.json",
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/src/router/index.d.ts",
|
|
37
|
+
"import": "./dist/router.js",
|
|
38
|
+
"require": "./dist/router.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./react": {
|
|
41
|
+
"types": "./dist/src/react/index.d.ts",
|
|
42
|
+
"import": "./dist/router_react.js",
|
|
43
|
+
"require": "./dist/router_react.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"package.json"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build:only": "vite build",
|
|
52
|
+
"build": "npm run build:only",
|
|
53
|
+
"prebuild": "npm run format:check && npm run lint",
|
|
54
|
+
"lint": "oxlint",
|
|
55
|
+
"format": "oxfmt --write .",
|
|
56
|
+
"format:check": "oxfmt --check .",
|
|
57
|
+
"dev": "vite .",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:ui": "vitest --ui --coverage",
|
|
60
|
+
"test:cover": "vitest run --coverage",
|
|
61
|
+
"prepack": "npm run build"
|
|
62
|
+
},
|
|
63
|
+
"devEngines": {
|
|
64
|
+
"packageManager": {
|
|
65
|
+
"name": "npm",
|
|
66
|
+
"version": ">=11.0.0"
|
|
67
|
+
},
|
|
68
|
+
"runtime": {
|
|
69
|
+
"name": "node",
|
|
70
|
+
"version": ">=24.0.0"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"react": "^19.2.0",
|
|
75
|
+
"react-dom": "^19.2.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"react": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"react-dom": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"access": "public"
|
|
87
|
+
},
|
|
88
|
+
"sideEffects": false,
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@rolldown/plugin-babel": "^0.2.2",
|
|
91
|
+
"@types/node": "^25.5.0",
|
|
92
|
+
"@types/react": "^19.2.2",
|
|
93
|
+
"@types/react-dom": "^19.2.3",
|
|
94
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
95
|
+
"@vitest/coverage-v8": "^4.0.7",
|
|
96
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
97
|
+
"happy-dom": "^20.0.10",
|
|
98
|
+
"oxfmt": "^0.42.0",
|
|
99
|
+
"oxlint": "^1.57.0",
|
|
100
|
+
"react": "^19.2.0",
|
|
101
|
+
"react-dom": "^19.2.0",
|
|
102
|
+
"typescript": "^6.0.2",
|
|
103
|
+
"vite": "^8.0.3",
|
|
104
|
+
"vite-plugin-dts": "^4.5.4",
|
|
105
|
+
"vitest": "^4.0.7"
|
|
106
|
+
}
|
|
107
|
+
}
|