@real-router/solid 0.0.1

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 (73) hide show
  1. package/README.md +313 -0
  2. package/dist/cjs/index.d.ts +103 -0
  3. package/dist/cjs/index.js +351 -0
  4. package/dist/esm/index.d.mts +103 -0
  5. package/dist/esm/index.mjs +334 -0
  6. package/dist/types/RouterProvider.d.ts +8 -0
  7. package/dist/types/RouterProvider.d.ts.map +1 -0
  8. package/dist/types/components/Link.d.ts +5 -0
  9. package/dist/types/components/Link.d.ts.map +1 -0
  10. package/dist/types/components/RouteView/RouteView.d.ts +13 -0
  11. package/dist/types/components/RouteView/RouteView.d.ts.map +1 -0
  12. package/dist/types/components/RouteView/components.d.ts +25 -0
  13. package/dist/types/components/RouteView/components.d.ts.map +1 -0
  14. package/dist/types/components/RouteView/helpers.d.ts +8 -0
  15. package/dist/types/components/RouteView/helpers.d.ts.map +1 -0
  16. package/dist/types/components/RouteView/index.d.ts +3 -0
  17. package/dist/types/components/RouteView/index.d.ts.map +1 -0
  18. package/dist/types/components/RouteView/types.d.ts +15 -0
  19. package/dist/types/components/RouteView/types.d.ts.map +1 -0
  20. package/dist/types/constants.d.ts +9 -0
  21. package/dist/types/constants.d.ts.map +1 -0
  22. package/dist/types/context.d.ts +11 -0
  23. package/dist/types/context.d.ts.map +1 -0
  24. package/dist/types/createSignalFromSource.d.ts +4 -0
  25. package/dist/types/createSignalFromSource.d.ts.map +1 -0
  26. package/dist/types/createStoreFromSource.d.ts +3 -0
  27. package/dist/types/createStoreFromSource.d.ts.map +1 -0
  28. package/dist/types/directives/link.d.ts +11 -0
  29. package/dist/types/directives/link.d.ts.map +1 -0
  30. package/dist/types/hooks/useNavigator.d.ts +3 -0
  31. package/dist/types/hooks/useNavigator.d.ts.map +1 -0
  32. package/dist/types/hooks/useRoute.d.ts +4 -0
  33. package/dist/types/hooks/useRoute.d.ts.map +1 -0
  34. package/dist/types/hooks/useRouteNode.d.ts +4 -0
  35. package/dist/types/hooks/useRouteNode.d.ts.map +1 -0
  36. package/dist/types/hooks/useRouteNodeStore.d.ts +3 -0
  37. package/dist/types/hooks/useRouteNodeStore.d.ts.map +1 -0
  38. package/dist/types/hooks/useRouteStore.d.ts +3 -0
  39. package/dist/types/hooks/useRouteStore.d.ts.map +1 -0
  40. package/dist/types/hooks/useRouteUtils.d.ts +3 -0
  41. package/dist/types/hooks/useRouteUtils.d.ts.map +1 -0
  42. package/dist/types/hooks/useRouter.d.ts +3 -0
  43. package/dist/types/hooks/useRouter.d.ts.map +1 -0
  44. package/dist/types/hooks/useRouterTransition.d.ts +4 -0
  45. package/dist/types/hooks/useRouterTransition.d.ts.map +1 -0
  46. package/dist/types/index.d.ts +21 -0
  47. package/dist/types/index.d.ts.map +1 -0
  48. package/dist/types/types.d.ts +17 -0
  49. package/dist/types/types.d.ts.map +1 -0
  50. package/package.json +89 -0
  51. package/src/RouterProvider.tsx +60 -0
  52. package/src/components/Link.tsx +91 -0
  53. package/src/components/RouteView/RouteView.tsx +53 -0
  54. package/src/components/RouteView/components.tsx +50 -0
  55. package/src/components/RouteView/helpers.tsx +108 -0
  56. package/src/components/RouteView/index.ts +7 -0
  57. package/src/components/RouteView/types.ts +17 -0
  58. package/src/constants.ts +9 -0
  59. package/src/context.ts +15 -0
  60. package/src/createSignalFromSource.ts +20 -0
  61. package/src/createStoreFromSource.ts +20 -0
  62. package/src/directives/link.tsx +79 -0
  63. package/src/directives.d.ts +10 -0
  64. package/src/hooks/useNavigator.tsx +15 -0
  65. package/src/hooks/useRoute.tsx +16 -0
  66. package/src/hooks/useRouteNode.tsx +14 -0
  67. package/src/hooks/useRouteNodeStore.tsx +12 -0
  68. package/src/hooks/useRouteStore.tsx +20 -0
  69. package/src/hooks/useRouteUtils.tsx +12 -0
  70. package/src/hooks/useRouter.tsx +15 -0
  71. package/src/hooks/useRouterTransition.tsx +14 -0
  72. package/src/index.tsx +43 -0
  73. package/src/types.ts +24 -0
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # @real-router/solid
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE)
4
+
5
+ > Solid.js integration for [Real-Router](https://github.com/greydragon888/real-router) — hooks, components, and context providers.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @real-router/solid @real-router/core @real-router/browser-plugin
11
+ ```
12
+
13
+ **Peer dependency:** `solid-js` >= 1.7.0
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { createRouter } from "@real-router/core";
19
+ import { browserPluginFactory } from "@real-router/browser-plugin";
20
+ import { RouterProvider, RouteView, Link } from "@real-router/solid";
21
+
22
+ const router = createRouter([
23
+ { name: "home", path: "/" },
24
+ {
25
+ name: "users",
26
+ path: "/users",
27
+ children: [{ name: "profile", path: "/:id" }],
28
+ },
29
+ ]);
30
+
31
+ router.usePlugin(browserPluginFactory());
32
+ router.start();
33
+
34
+ function App() {
35
+ return (
36
+ <RouterProvider router={router}>
37
+ <nav>
38
+ <Link routeName="home">Home</Link>
39
+ <Link routeName="users">Users</Link>
40
+ </nav>
41
+ <RouteView nodeName="">
42
+ <RouteView.Match segment="home">
43
+ <HomePage />
44
+ </RouteView.Match>
45
+ <RouteView.Match segment="users">
46
+ <UsersPage />
47
+ </RouteView.Match>
48
+ <RouteView.NotFound>
49
+ <NotFoundPage />
50
+ </RouteView.NotFound>
51
+ </RouteView>
52
+ </RouterProvider>
53
+ );
54
+ }
55
+ ```
56
+
57
+ ## Hooks
58
+
59
+ All hooks that subscribe to route state return `Accessor<T>` — call the accessor inside a reactive context to read the current value.
60
+
61
+ | Hook | Returns | Reactive? |
62
+ | ------------------------- | ------------------------------------ | ------------------------------------ |
63
+ | `useRouter()` | `Router` | Never |
64
+ | `useNavigator()` | `Navigator` | Never |
65
+ | `useRoute()` | `Accessor<RouteState>` | Every navigation |
66
+ | `useRouteNode(name)` | `Accessor<RouteState>` | Only when node activates/deactivates |
67
+ | `useRouteUtils()` | `RouteUtils` | Never |
68
+ | `useRouterTransition()` | `Accessor<RouterTransitionSnapshot>` | On transition start/end |
69
+ | `useRouteStore()` | `RouteState` (store) | Granular — per-property |
70
+ | `useRouteNodeStore(name)` | `RouteState` (store) | Granular — per-property, node-scoped |
71
+
72
+ ### Store-Based Hooks (Granular Reactivity)
73
+
74
+ `useRouteStore()` and `useRouteNodeStore()` use `createStore` + `reconcile` for property-level reactivity. A component reading `state.route?.params.id` won't re-run when `state.route?.params.page` changes:
75
+
76
+ ```tsx
77
+ import { useRouteStore } from "@real-router/solid";
78
+ import { createEffect } from "solid-js";
79
+
80
+ function UserProfile() {
81
+ const state = useRouteStore();
82
+
83
+ createEffect(() => {
84
+ console.log(state.route?.params.id);
85
+ });
86
+
87
+ return <h1>User: {state.route?.params.id}</h1>;
88
+ }
89
+ ```
90
+
91
+ Signal-based hooks (`useRoute`, `useRouteNode`) remain available for simpler use cases.
92
+
93
+ ```tsx
94
+ // useRouteNode — updates only when "users.*" changes
95
+ function UsersLayout() {
96
+ const routeState = useRouteNode("users");
97
+
98
+ return (
99
+ <Show when={routeState().route}>
100
+ {(route) => {
101
+ switch (route().name) {
102
+ case "users":
103
+ return <UsersList />;
104
+ case "users.profile":
105
+ return <UserProfile id={route().params.id} />;
106
+ default:
107
+ return null;
108
+ }
109
+ }}
110
+ </Show>
111
+ );
112
+ }
113
+
114
+ // useNavigator — stable reference, never reactive
115
+ function BackButton() {
116
+ const navigator = useNavigator();
117
+ return <button onClick={() => navigator.navigate("home")}>Back</button>;
118
+ }
119
+
120
+ // useRouterTransition — progress bars, loading states
121
+ function GlobalProgress() {
122
+ const transition = useRouterTransition();
123
+ return (
124
+ <Show when={transition().isTransitioning}>
125
+ <div class="progress-bar" />
126
+ </Show>
127
+ );
128
+ }
129
+ ```
130
+
131
+ ## Components
132
+
133
+ ### `<Link>`
134
+
135
+ Navigation link with automatic active state detection. Uses `classList` for active class toggling — only the DOM attribute updates, not the whole component.
136
+
137
+ ```tsx
138
+ <Link
139
+ routeName="users.profile"
140
+ routeParams={{ id: "123" }}
141
+ activeClassName="active" // default: "active"
142
+ activeStrict={false} // default: false (ancestor match)
143
+ ignoreQueryParams={true} // default: true
144
+ routeOptions={{ replace: true }}
145
+ >
146
+ View Profile
147
+ </Link>
148
+ ```
149
+
150
+ ### `<RouteView>`
151
+
152
+ Declarative route matching. Renders the first matching `<RouteView.Match>` child.
153
+
154
+ ```tsx
155
+ <RouteView nodeName="">
156
+ <RouteView.Match segment="users">
157
+ <UsersPage />
158
+ </RouteView.Match>
159
+ <RouteView.Match segment="settings">
160
+ <SettingsPage />
161
+ </RouteView.Match>
162
+ <RouteView.NotFound>
163
+ <NotFoundPage />
164
+ </RouteView.NotFound>
165
+ </RouteView>
166
+ ```
167
+
168
+ `RouteView.Match` accepts an optional `exact` prop for strict segment matching:
169
+
170
+ ```tsx
171
+ <RouteView.Match segment="users" exact>
172
+ {/* Only matches "users" exactly, not "users.profile" */}
173
+ <UsersIndex />
174
+ </RouteView.Match>
175
+ ```
176
+
177
+ **Lazy loading with `fallback`:** Pass a `fallback` prop (`JSX.Element`) to wrap the matched content in Solid's `<Suspense>`. This lets you show a loading state while a `lazy()` component's chunk is fetching.
178
+
179
+ ```tsx
180
+ import { lazy } from "solid-js";
181
+
182
+ const LazyDashboard = lazy(() => import("./Dashboard"));
183
+
184
+ <RouteView nodeName="">
185
+ <RouteView.Match segment="dashboard" fallback={<Spinner />}>
186
+ <LazyDashboard />
187
+ </RouteView.Match>
188
+ </RouteView>;
189
+ ```
190
+
191
+ Without `fallback`, no `<Suspense>` boundary is added. The prop is optional.
192
+
193
+ > **Note:** `keepAlive` is not supported. Solid has no equivalent of React's `<Activity>` API. Components dispose completely when navigating away.
194
+
195
+ ## Directives
196
+
197
+ ### `use:link`
198
+
199
+ Low-level directive for adding navigation to any element. Automatically handles click events, keyboard navigation (Enter key), and active state styling.
200
+
201
+ ```tsx
202
+ import { link } from "@real-router/solid";
203
+
204
+ <a use:link={() => ({ routeName: "users.profile", routeParams: { id: "123" }, activeClassName: "active" })}>
205
+ User Profile
206
+ </a>
207
+
208
+ <button use:link={() => ({ routeName: "home" })}>
209
+ Go Home
210
+ </button>
211
+
212
+ <div
213
+ use:link={() => ({
214
+ routeName: "settings",
215
+ activeClassName: "active",
216
+ activeStrict: false,
217
+ ignoreQueryParams: true,
218
+ })}
219
+ role="link"
220
+ tabindex="0"
221
+ >
222
+ Settings
223
+ </div>
224
+ ```
225
+
226
+ **Options:**
227
+
228
+ | Option | Type | Default | Description |
229
+ | ------------------- | --------- | ------- | --------------------------------------- |
230
+ | `routeName` | `string` | — | Target route name |
231
+ | `routeParams` | `Params` | `{}` | Route parameters |
232
+ | `routeOptions` | `object` | `{}` | Navigation options (replace, etc.) |
233
+ | `activeClassName` | `string` | — | Class added when route is active |
234
+ | `activeStrict` | `boolean` | `false` | Exact match only (no ancestor matching) |
235
+ | `ignoreQueryParams` | `boolean` | `true` | Query params don't affect active state |
236
+
237
+ The directive automatically sets `href` on `<a>` elements and adds `role="link"` + `tabindex="0"` to non-interactive elements for accessibility.
238
+
239
+ ## Solid-Specific Patterns
240
+
241
+ ### Accessors, Not Values
242
+
243
+ Unlike the React and Preact adapters, hooks that subscribe to route state return `Accessor<T>`. Read the value by calling the accessor:
244
+
245
+ ```tsx
246
+ // React/Preact
247
+ const { route } = useRoute();
248
+
249
+ // Solid
250
+ const routeState = useRoute();
251
+ const { route } = routeState(); // call it
252
+ ```
253
+
254
+ Inside JSX, call the accessor directly in reactive positions:
255
+
256
+ ```tsx
257
+ function CurrentRoute() {
258
+ const routeState = useRoute();
259
+ return <div>{routeState().route?.name}</div>;
260
+ }
261
+ ```
262
+
263
+ ### Never Destructure Props
264
+
265
+ Solid props are reactive getters. Destructuring them breaks the reactive graph:
266
+
267
+ ```tsx
268
+ // WRONG — loses reactivity
269
+ function MyLink({ routeName, routeParams }) {
270
+ return <Link routeName={routeName} routeParams={routeParams} />;
271
+ }
272
+
273
+ // CORRECT — pass props through
274
+ function MyLink(props) {
275
+ return <Link routeName={props.routeName} routeParams={props.routeParams} />;
276
+ }
277
+ ```
278
+
279
+ ## Accessibility
280
+
281
+ Enable screen reader announcements for route changes:
282
+
283
+ ```tsx
284
+ <RouterProvider router={router} announceNavigation>
285
+ {/* Your app */}
286
+ </RouterProvider>
287
+ ```
288
+
289
+ When enabled, a visually hidden `aria-live` region announces each navigation. Focus moves to the first `<h1>` on the new page. See [Accessibility guide](https://github.com/greydragon888/real-router/wiki/Accessibility) for details.
290
+
291
+ ## Documentation
292
+
293
+ Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
294
+
295
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [Link](https://github.com/greydragon888/real-router/wiki/Link)
296
+ - [useRouter](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition](https://github.com/greydragon888/real-router/wiki/useRouterTransition)
297
+
298
+ ## Related Packages
299
+
300
+ | Package | Description |
301
+ | ---------------------------------------------------------------------------------------- | ------------------------------------ |
302
+ | [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required dependency) |
303
+ | [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | Browser History API integration |
304
+ | [@real-router/sources](https://www.npmjs.com/package/@real-router/sources) | Subscription layer (used internally) |
305
+ | [@real-router/route-utils](https://www.npmjs.com/package/@real-router/route-utils) | Route tree queries (`useRouteUtils`) |
306
+
307
+ ## Contributing
308
+
309
+ See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.
310
+
311
+ ## License
312
+
313
+ [MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
@@ -0,0 +1,103 @@
1
+ import * as solid_js from 'solid-js';
2
+ import { JSX, Accessor, ParentProps } from 'solid-js';
3
+ import * as _real_router_core from '@real-router/core';
4
+ import { Params, NavigationOptions, State, Router, Navigator } from '@real-router/core';
5
+ export { Navigator } from '@real-router/core';
6
+ import { RouteUtils } from '@real-router/route-utils';
7
+ import { RouterTransitionSnapshot, RouterSource } from '@real-router/sources';
8
+ export { RouterTransitionSnapshot } from '@real-router/sources';
9
+
10
+ interface RouteViewProps {
11
+ readonly nodeName: string;
12
+ readonly children: JSX.Element;
13
+ }
14
+ interface MatchProps {
15
+ readonly segment: string;
16
+ readonly exact?: boolean;
17
+ readonly fallback?: JSX.Element;
18
+ readonly children: JSX.Element;
19
+ }
20
+ interface NotFoundProps {
21
+ readonly children: JSX.Element;
22
+ }
23
+
24
+ declare function Match(props: MatchProps): JSX.Element;
25
+ declare namespace Match {
26
+ var displayName: string;
27
+ }
28
+ declare function NotFound(props: NotFoundProps): JSX.Element;
29
+ declare namespace NotFound {
30
+ var displayName: string;
31
+ }
32
+
33
+ declare function RouteViewRoot(props: Readonly<RouteViewProps>): JSX.Element;
34
+ declare namespace RouteViewRoot {
35
+ var displayName: string;
36
+ }
37
+ declare const RouteView: typeof RouteViewRoot & {
38
+ Match: typeof Match;
39
+ NotFound: typeof NotFound;
40
+ };
41
+
42
+ interface RouteState<P extends Params = Params, MP extends Params = Params> {
43
+ route: State<P, MP> | undefined;
44
+ previousRoute?: State | undefined;
45
+ }
46
+ interface LinkProps<P extends Params = Params> extends Omit<JSX.HTMLAttributes<HTMLAnchorElement>, "onClick"> {
47
+ routeName: string;
48
+ routeParams?: P;
49
+ routeOptions?: NavigationOptions;
50
+ activeClassName?: string;
51
+ activeStrict?: boolean;
52
+ ignoreQueryParams?: boolean;
53
+ target?: string;
54
+ onClick?: (evt: MouseEvent) => void;
55
+ }
56
+
57
+ declare function Link<P extends Params = Params>(props: Readonly<LinkProps<P>>): JSX.Element;
58
+
59
+ interface LinkDirectiveOptions<P extends Params = Params> {
60
+ routeName: string;
61
+ routeParams?: P;
62
+ routeOptions?: Record<string, unknown>;
63
+ activeClassName?: string;
64
+ activeStrict?: boolean;
65
+ ignoreQueryParams?: boolean;
66
+ }
67
+ declare function link<P extends Params = Params>(element: HTMLElement, accessor: () => LinkDirectiveOptions<P>): void;
68
+
69
+ declare const useRouter: () => Router;
70
+
71
+ declare const useNavigator: () => Navigator;
72
+
73
+ declare const useRouteUtils: () => RouteUtils;
74
+
75
+ declare const useRoute: () => Accessor<RouteState>;
76
+
77
+ declare function useRouteNode(nodeName: string): Accessor<RouteState>;
78
+
79
+ declare function useRouteStore(): RouteState;
80
+
81
+ declare function useRouteNodeStore(nodeName: string): RouteState;
82
+
83
+ declare function useRouterTransition(): Accessor<RouterTransitionSnapshot>;
84
+
85
+ interface RouteProviderProps {
86
+ router: Router;
87
+ announceNavigation?: boolean;
88
+ }
89
+ declare function RouterProvider(props: ParentProps<RouteProviderProps>): JSX.Element;
90
+
91
+ interface RouterContextValue {
92
+ router: Router;
93
+ navigator: Navigator;
94
+ routeSelector: (routeName: string) => boolean;
95
+ }
96
+ declare const RouterContext: solid_js.Context<RouterContextValue | null>;
97
+ declare const RouteContext: solid_js.Context<Accessor<RouteState<_real_router_core.Params, _real_router_core.Params>> | null>;
98
+
99
+ declare function createSignalFromSource<T>(source: RouterSource<T>): Accessor<T>;
100
+
101
+ declare function createStoreFromSource<T extends object>(source: RouterSource<T>): T;
102
+
103
+ export { Link, type LinkDirectiveOptions, type LinkProps, RouteContext, type RouteState, RouteView, type MatchProps as RouteViewMatchProps, type NotFoundProps as RouteViewNotFoundProps, type RouteViewProps, RouterContext, RouterProvider, createSignalFromSource, createStoreFromSource, link, useNavigator, useRoute, useRouteNode, useRouteNodeStore, useRouteStore, useRouteUtils, useRouter, useRouterTransition };