jotai-iten 0.0.1 → 0.4.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 (66) hide show
  1. package/README.md +600 -0
  2. package/dist/components/Link.cjs +21 -0
  3. package/dist/components/Link.d.cts +13 -0
  4. package/dist/components/Link.d.ts +13 -0
  5. package/dist/components/Link.mjs +21 -0
  6. package/dist/components/Navigate.cjs +15 -0
  7. package/dist/components/Navigate.d.cts +10 -0
  8. package/dist/components/Navigate.d.ts +10 -0
  9. package/dist/components/Navigate.mjs +15 -0
  10. package/dist/components/Route.cjs +22 -0
  11. package/dist/components/Route.d.cts +17 -0
  12. package/dist/components/Route.d.ts +17 -0
  13. package/dist/components/Route.mjs +22 -0
  14. package/dist/components/Switch.cjs +21 -0
  15. package/dist/components/Switch.d.cts +9 -0
  16. package/dist/components/Switch.d.ts +9 -0
  17. package/dist/components/Switch.mjs +21 -0
  18. package/dist/create-router.cjs +22 -0
  19. package/dist/create-router.d.cts +18 -0
  20. package/dist/create-router.d.ts +18 -0
  21. package/dist/create-router.mjs +22 -0
  22. package/dist/headless-core.cjs +54 -0
  23. package/dist/headless-core.d.cts +17 -0
  24. package/dist/headless-core.d.ts +17 -0
  25. package/dist/headless-core.mjs +54 -0
  26. package/dist/headless.cjs +46 -0
  27. package/dist/headless.d.cts +4 -0
  28. package/dist/headless.d.ts +4 -0
  29. package/dist/headless.mjs +3 -0
  30. package/dist/hooks.cjs +48 -0
  31. package/dist/hooks.d.cts +20 -0
  32. package/dist/hooks.d.ts +20 -0
  33. package/dist/hooks.mjs +48 -0
  34. package/dist/index.cjs +48 -0
  35. package/dist/index.d.cts +8 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.mjs +4 -0
  38. package/dist/types.d.cts +30 -0
  39. package/dist/types.d.ts +30 -0
  40. package/dist/url.cjs +23 -0
  41. package/dist/url.d.cts +18 -0
  42. package/dist/url.d.ts +18 -0
  43. package/dist/url.mjs +22 -0
  44. package/dist/utils.cjs +44 -0
  45. package/dist/utils.d.cts +3 -0
  46. package/dist/utils.d.ts +3 -0
  47. package/dist/utils.mjs +2 -0
  48. package/dist/zod.cjs +77 -0
  49. package/dist/zod.d.cts +48 -0
  50. package/dist/zod.d.ts +48 -0
  51. package/dist/zod.mjs +73 -0
  52. package/package.json +122 -5
  53. package/src/components/Link.tsx +36 -0
  54. package/src/components/Navigate.tsx +26 -0
  55. package/src/components/Route.tsx +53 -0
  56. package/src/components/Switch.tsx +32 -0
  57. package/src/create-router.ts +37 -0
  58. package/src/headless-core.ts +126 -0
  59. package/src/headless.ts +31 -0
  60. package/src/hooks.ts +64 -0
  61. package/src/index.ts +36 -0
  62. package/src/types.ts +62 -0
  63. package/src/url.ts +54 -0
  64. package/src/utils.ts +23 -0
  65. package/src/zod.ts +150 -0
  66. package/index.js +0 -1
package/README.md ADDED
@@ -0,0 +1,600 @@
1
+ # jotai-iten
2
+
3
+ Typed in-memory routing for React apps that already use Jotai.
4
+
5
+ `jotai-iten` is built for embedded UI surfaces: Figma plugins, VS Code webviews, browser extension panels, Electron sidebars, iframe widgets, modal stacks, and internal tools where a browser URL is absent or secondary. It gives you typed route state, loader orchestration, guards, pending state, history, and scoped atoms without adopting a URL-first router.
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install jotai-iten jotai react
13
+ ```
14
+
15
+ Peer dependencies:
16
+
17
+ - `jotai@^2`
18
+ - `react@^18.3 || ^19`
19
+
20
+ Optional:
21
+
22
+ - `@tanstack/react-query@^5` if your loaders call `ensureQueryData`
23
+ - `zod@^4` if you import `jotai-iten/zod`
24
+
25
+ `iten-core` is installed transitively by `jotai-iten`. Install it directly only if you are using the framework-agnostic core without the Jotai adapter.
26
+
27
+ Entry points:
28
+
29
+ - `jotai-iten` for the full router with components, hooks, atoms, and utilities
30
+ - `jotai-iten/headless` for hooks and atoms without component helpers
31
+ - `jotai-iten/zod` for optional schema-backed route factories and unknown-input parsing
32
+ - `jotai-iten/url` for optional URL synchronization around explicit parse/format hooks
33
+ - `jotai-iten/utils` for route factories, guards, and exhaustive matching
34
+
35
+ ---
36
+
37
+ ## Mental Model
38
+
39
+ A route is a discriminated union:
40
+
41
+ ```ts
42
+ const routes = defineRoutes({
43
+ home: route(),
44
+ detail: route<{ id: string }>(),
45
+ settings: route(),
46
+ })
47
+
48
+ type Routes = InferRoutes<typeof routes>
49
+ ```
50
+
51
+ Navigation is an async pipeline:
52
+
53
+ ```text
54
+ navigate({ target })
55
+ -> beforeLoad guard
56
+ -> optional loader
57
+ -> commit target route
58
+ ```
59
+
60
+ The current route stays mounted until the target route is ready. Loader failures keep the previous route and expose a retryable error.
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ ```ts
67
+ // router.ts
68
+ import {
69
+ createRouter,
70
+ createRoute,
71
+ defineRoutes,
72
+ type InferRoutes,
73
+ route,
74
+ } from 'jotai-iten'
75
+
76
+ const routes = defineRoutes({
77
+ home: route(),
78
+ detail: route<{ id: string }>(),
79
+ settings: route(),
80
+ })
81
+
82
+ type Routes = InferRoutes<typeof routes>
83
+
84
+ export const toRoute = createRoute(routes)
85
+
86
+ export const router = createRouter<Routes, unknown>({
87
+ initial: toRoute({ name: 'home' }),
88
+ })
89
+
90
+ export const {
91
+ Route,
92
+ Switch,
93
+ Link,
94
+ Navigate,
95
+ useNavigate,
96
+ useRoute,
97
+ useCurrentRoute,
98
+ } = router
99
+ ```
100
+
101
+ ```tsx
102
+ // App.tsx
103
+ import { Navigate, Route, Switch, toRoute } from './router'
104
+
105
+ export function App() {
106
+ return (
107
+ <Switch>
108
+ <Route name="home">{() => <HomeView />}</Route>
109
+ <Route name="detail">{({ id }) => <DetailView id={id} />}</Route>
110
+ <Route name="settings">{() => <SettingsView />}</Route>
111
+ <Navigate to={toRoute({ name: 'home' })} />
112
+ </Switch>
113
+ )
114
+ }
115
+ ```
116
+
117
+ No router provider is required. Use the normal Jotai provider only if your app already uses a custom store.
118
+
119
+ ---
120
+
121
+ ## Headless Router
122
+
123
+ Use `jotai-iten/headless` when you want atoms and hooks but do not need `Route`, `Switch`, `Link`, or `Navigate`. This keeps component helpers out of hook-only bundles.
124
+
125
+ ```ts
126
+ import {
127
+ createHeadlessRouter,
128
+ createRoute,
129
+ defineRoutes,
130
+ type InferRoutes,
131
+ route,
132
+ } from 'jotai-iten/headless'
133
+
134
+ const routes = defineRoutes({
135
+ home: route(),
136
+ detail: route<{ id: string }>(),
137
+ })
138
+
139
+ type Routes = InferRoutes<typeof routes>
140
+
141
+ export const toRoute = createRoute(routes)
142
+
143
+ export const router = createHeadlessRouter<Routes, unknown>({
144
+ initial: toRoute({ name: 'home' }),
145
+ })
146
+
147
+ export const { atoms, useNavigate, useRoute, useCurrentRoute } = router
148
+ ```
149
+
150
+ `createRouter` is built on the same headless layer, so behavior and types stay consistent across both entry points.
151
+
152
+ ---
153
+
154
+ ## Creating Routes
155
+
156
+ Use `defineRoutes` plus `createRoute` instead of hand-written object literals. The `routes` object is the runtime source of truth for names, and `InferRoutes` derives the router type.
157
+
158
+ ```ts
159
+ const toRoute = createRoute(routes)
160
+
161
+ toRoute({ name: 'home' })
162
+ toRoute({ name: 'detail', params: { id: '42' } })
163
+ ```
164
+
165
+ For no-param routes, call `route()` without a type argument. Avoid `route<Record<string, never>>()` because `Record<string, never>` conflicts with the `name` discriminant in intersection types.
166
+
167
+ ---
168
+
169
+ ## Loaders
170
+
171
+ Loaders run before the target route commits.
172
+
173
+ ```ts
174
+ export const router = createRouter<Routes, unknown>({
175
+ initial: toRoute({ name: 'home' }),
176
+ queryClient,
177
+ routeConfig: {
178
+ detail: {
179
+ loader: async ({ params, queryClient }) => {
180
+ await queryClient.ensureQueryData(detailQuery(params.id))
181
+ },
182
+ loaderDeps: ({ params }) => params.id,
183
+ staleTime: 30_000,
184
+ },
185
+ },
186
+ })
187
+ ```
188
+
189
+ Behavior:
190
+
191
+ - The current route remains active while the loader runs.
192
+ - `pendingRoute` tracks the target route immediately.
193
+ - `useRouteLoading({ name: 'detail' })` is true while that route is pending.
194
+ - If the loader throws, the previous route stays mounted and `useRouterError()` returns `{ error, retry }`.
195
+ - `retry()` reruns the full pipeline, including guards.
196
+
197
+ The `queryClient` only needs an `ensureQueryData` method. TanStack Query works, but it is not required by the router.
198
+
199
+ ---
200
+
201
+ ## Guards
202
+
203
+ `beforeLoad` can redirect before a loader runs. In `jotai-iten`, guards receive a Jotai `Getter`, so they can read atoms.
204
+
205
+ ```ts
206
+ import { authAtom } from './atoms'
207
+
208
+ const router = createRouter<Routes, unknown>({
209
+ initial: toRoute({ name: 'home' }),
210
+ routeConfig: {
211
+ detail: {
212
+ beforeLoad: ({ get }) => {
213
+ if (!get(authAtom).userId) return toRoute({ name: 'home' })
214
+ },
215
+ loader: async ({ params, queryClient }) => {
216
+ await queryClient.ensureQueryData(detailQuery(params.id))
217
+ },
218
+ },
219
+ },
220
+ })
221
+ ```
222
+
223
+ Redirect loops are capped by `iten-core` and become retryable errors instead of infinite recursion.
224
+
225
+ ---
226
+
227
+ ## Context
228
+
229
+ Use `context` to compute shared values once per navigation. It can be a static object or a function that reads atoms.
230
+
231
+ ```ts
232
+ const router = createRouter<Routes, { userId: string | null }>({
233
+ initial: toRoute({ name: 'home' }),
234
+ context: ({ get }) => ({ userId: get(authAtom).userId }),
235
+ routeConfig: {
236
+ detail: {
237
+ loader: async ({ params, queryClient, context }) => {
238
+ await queryClient.ensureQueryData(detailQuery(params.id, context.userId))
239
+ },
240
+ },
241
+ },
242
+ })
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Components
248
+
249
+ ### `<Route>`
250
+
251
+ Renders when its route is active.
252
+
253
+ ```tsx
254
+ <Route name="detail">
255
+ {({ id }) => <DetailView id={id} />}
256
+ </Route>
257
+ ```
258
+
259
+ You can pass a component instead of a render prop:
260
+
261
+ ```tsx
262
+ <Route name="detail" component={DetailView} />
263
+ ```
264
+
265
+ Pending and error states are route-local:
266
+
267
+ ```tsx
268
+ <Route
269
+ name="detail"
270
+ pendingComponent={DetailSkeleton}
271
+ errorComponent={({ error, retry }) => (
272
+ <ErrorBanner error={error} onRetry={retry} />
273
+ )}
274
+ >
275
+ {({ id }) => <DetailView id={id} />}
276
+ </Route>
277
+ ```
278
+
279
+ ### `<Switch>`
280
+
281
+ Renders the first matching child. Use `<Navigate>` as a fallback.
282
+
283
+ ```tsx
284
+ <Switch>
285
+ <Route name="home">{() => <HomeView />}</Route>
286
+ <Route name="detail">{({ id }) => <DetailView id={id} />}</Route>
287
+ <Navigate to={toRoute({ name: 'home' })} />
288
+ </Switch>
289
+ ```
290
+
291
+ ### `<Link>`
292
+
293
+ Typed navigation with route-specific loading state.
294
+
295
+ ```tsx
296
+ <Link to={toRoute({ name: 'detail', params: { id: item.id } })}>
297
+ {({ isLoading }) => (isLoading ? 'Loading...' : 'Open')}
298
+ </Link>
299
+ ```
300
+
301
+ ### `<Navigate>`
302
+
303
+ Redirects on mount.
304
+
305
+ ```tsx
306
+ {!isAuthenticated && <Navigate to={toRoute({ name: 'home' })} />}
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Hooks
312
+
313
+ | Hook | Returns | Use for |
314
+ |---|---|---|
315
+ | `useCurrentRoute()` | `RouteUnion<M> \| null` | Current committed route |
316
+ | `useRoute({ name })` | `{ isActive, params }` | Active checks with narrowed params |
317
+ | `useNavigate()` | `({ target, options }) => Promise<void>` | Programmatic navigation |
318
+ | `useGoBack()` | `() => Promise<void>` | Back navigation without rerunning loaders |
319
+ | `useIsNavigating()` | `boolean` | Global loader/pending indicator |
320
+ | `useCanGoBack()` | `boolean` | History availability |
321
+ | `useRouteLoading({ name })` | `boolean` | Route-specific pending indicator |
322
+ | `useRouterError()` | `RouterError \| null` | Last retryable navigation error |
323
+
324
+ Example:
325
+
326
+ ```tsx
327
+ function Header() {
328
+ const navigate = useNavigate()
329
+ const { isActive } = useRoute({ name: 'settings' })
330
+ const isLoading = useRouteLoading({ name: 'settings' })
331
+
332
+ return (
333
+ <button
334
+ type="button"
335
+ aria-current={isActive ? 'page' : undefined}
336
+ onClick={() => void navigate({ target: toRoute({ name: 'settings' }) })}
337
+ >
338
+ {isLoading ? 'Loading...' : 'Settings'}
339
+ </button>
340
+ )
341
+ }
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Type Utilities and Guards
347
+
348
+ These are also available from `jotai-iten/utils` for utility-only imports.
349
+
350
+ ```ts
351
+ import {
352
+ createRoute,
353
+ defineRoutes,
354
+ isRoute,
355
+ isRouteName,
356
+ matchRoute,
357
+ type InferRoutes,
358
+ route,
359
+ } from 'jotai-iten/utils'
360
+ ```
361
+
362
+ ### `defineRoutes`
363
+
364
+ Defines the runtime route-name object and derives the compile-time route map.
365
+
366
+ ```ts
367
+ const routes = defineRoutes({
368
+ list: route(),
369
+ detail: route<{ id: string }>(),
370
+ })
371
+
372
+ type Routes = InferRoutes<typeof routes>
373
+
374
+ const toRoute = createRoute(routes)
375
+ ```
376
+
377
+ ### `isRoute`
378
+
379
+ Narrows unknown values by discriminant.
380
+
381
+ ```ts
382
+ function readDetailId(value: unknown) {
383
+ const candidate = { value, name: 'detail' as const }
384
+ if (isRoute<Routes, 'detail'>(candidate)) {
385
+ return candidate.value.id
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### `isRouteName`
391
+
392
+ Useful when decoding host messages or URL-like state.
393
+
394
+ ```ts
395
+ const names = ['home', 'detail', 'settings'] as const
396
+
397
+ const candidateName = { names, value: maybeName }
398
+
399
+ if (isRouteName(candidateName)) {
400
+ candidateName.value // 'home' | 'detail' | 'settings'
401
+ }
402
+ ```
403
+
404
+ ### `matchRoute`
405
+
406
+ Exhaustive branching over the current route.
407
+
408
+ ```ts
409
+ const label = matchRoute<Routes, string>({
410
+ route: currentRoute,
411
+ matcher: {
412
+ home: () => 'Home',
413
+ detail: ({ params }) => `Detail ${params.id}`,
414
+ settings: () => 'Settings',
415
+ },
416
+ })
417
+ ```
418
+
419
+ ---
420
+
421
+ ## Multiple Routers
422
+
423
+ Each `createRouter` call creates isolated atoms and components. This is useful for modal stacks or embedded subpanels.
424
+
425
+ ```ts
426
+ const modals = defineRoutes({
427
+ confirm: route<{ message: string; onConfirm: () => void }>(),
428
+ imagePicker: route<{ onSelect: (uri: string) => void }>(),
429
+ })
430
+
431
+ type Modals = InferRoutes<typeof modals>
432
+
433
+ export const modalRoute = createRoute(modals)
434
+
435
+ export const modalRouter = createRouter<Modals, unknown>({
436
+ initial: null,
437
+ })
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Timing
443
+
444
+ Use `pendingMs` and `pendingMinMs` to avoid flicker.
445
+
446
+ ```ts
447
+ routeConfig: {
448
+ detail: {
449
+ pendingMs: 200,
450
+ pendingMinMs: 100,
451
+ loader: async ({ params, queryClient }) => {
452
+ await queryClient.ensureQueryData(detailQuery(params.id))
453
+ },
454
+ },
455
+ }
456
+ ```
457
+
458
+ `pendingMs` delays the visible loading state. `pendingMinMs` keeps it visible long enough to avoid a flash once shown.
459
+
460
+ ---
461
+
462
+ ## Advanced Atoms
463
+
464
+ The router exposes raw atoms for advanced Jotai composition.
465
+
466
+ ```ts
467
+ const { atoms } = router
468
+
469
+ // atoms.state
470
+ // atoms.navigate
471
+ // atoms.goBack
472
+ ```
473
+
474
+ Most apps should prefer hooks/components. Atoms are useful when composing with existing Jotai state modules.
475
+
476
+ ---
477
+
478
+ ## URL Sync
479
+
480
+ `jotai-iten` is in-memory by default. Import `jotai-iten/url` only when the host surface needs URL synchronization. The adapter uses explicit `parse` and `format` functions instead of path-pattern route definitions, so the default router entry stays small.
481
+
482
+ ```ts
483
+ import { createUrlSync } from 'jotai-iten/url'
484
+ ```
485
+
486
+ Hydrate from the URL when your app starts, then subscribe if the URL should keep following router state:
487
+
488
+ ```ts
489
+ const urlSync = createUrlSync<Routes>({
490
+ router,
491
+ parse: ({ url }) => {
492
+ const id = url.searchParams.get('id')
493
+ return id ? toRoute({ name: 'detail', params: { id } }) : toRoute({ name: 'home' })
494
+ },
495
+ format: ({ route, url }) => {
496
+ const next = new URL(url)
497
+ next.searchParams.set('route', String(route.name))
498
+ if (route.name === 'detail') {
499
+ next.searchParams.set('id', route.id)
500
+ } else {
501
+ next.searchParams.delete('id')
502
+ }
503
+ return next
504
+ },
505
+ })
506
+
507
+ await urlSync.hydrate()
508
+ const stopUrlSync = urlSync.start({ mode: 'replace' })
509
+ ```
510
+
511
+ Pass custom `getUrl`, `writeUrl`, and `subscribeUrl` functions for tests, embedded hosts, iframe bridges, extension panels, or any environment where the browser History API is not the source of truth.
512
+
513
+ ---
514
+
515
+ ## Zod Runtime Validation
516
+
517
+ Zod is a good fit for validating external input: host messages, deep links, persisted state, or URL sync. It is an optional peer used only by `jotai-iten/zod`, so the default router entry stays small.
518
+
519
+ Define schemas once, derive the route union from them, and use a validated route factory in app code:
520
+
521
+ ```ts
522
+ import { createRouter } from 'jotai-iten'
523
+ import {
524
+ createZodRoute,
525
+ defineZodRoutes,
526
+ parseZodRoute,
527
+ zodNoParams,
528
+ type ZodRouteMap,
529
+ } from 'jotai-iten/zod'
530
+ import { z } from 'zod'
531
+
532
+ const schemas = defineZodRoutes({
533
+ home: zodNoParams(),
534
+ detail: z.object({
535
+ id: z.string().min(1),
536
+ tab: z.enum(['summary', 'activity']).default('summary'),
537
+ }),
538
+ })
539
+
540
+ type Routes = ZodRouteMap<typeof schemas>
541
+
542
+ const zodRoute = createZodRoute(schemas)
543
+
544
+ const router = createRouter<Routes, unknown>({
545
+ initial: zodRoute({ name: 'home' }),
546
+ })
547
+
548
+ zodRoute({ name: 'detail', input: { id: '42' } })
549
+ zodRoute({ name: 'detail', input: { id: '42', tab: 'activity' } })
550
+ ```
551
+
552
+ Use `parseZodRoute` when the input is unknown:
553
+
554
+ ```ts
555
+ const parsed = parseZodRoute({ schemas, value: hostMessage })
556
+
557
+ if (parsed.success) {
558
+ await navigate({ target: parsed.route })
559
+ }
560
+ ```
561
+
562
+ The parser rejects unknown route names, invalid params, and params that try to define their own `name` field.
563
+
564
+ For an end-to-end example, see [`examples/zod`](../../examples/zod).
565
+
566
+ ---
567
+
568
+ ## Troubleshooting
569
+
570
+ ### My no-param route type does not work
571
+
572
+ Use `route()` for no-param routes, not `route<Record<string, never>>()`.
573
+
574
+ ```ts
575
+ const routes = defineRoutes({
576
+ home: route(),
577
+ })
578
+ ```
579
+
580
+ ### My loader does not run again
581
+
582
+ Check `loaderDeps` and `staleTime`. If `staleTime` has not expired for the same dependency key, the loader is skipped.
583
+
584
+ ### I need direct imports without components
585
+
586
+ Use:
587
+
588
+ ```ts
589
+ import { createRoute, matchRoute } from 'jotai-iten/utils'
590
+ ```
591
+
592
+ ### Should I use React Router or TanStack Router instead?
593
+
594
+ Use a URL router when URLs, nested route trees, SSR, route files, or search-param state are central to the app. Use `jotai-iten` when routing is local state and you want small typed primitives.
595
+
596
+ ---
597
+
598
+ ## License
599
+
600
+ MIT
@@ -0,0 +1,21 @@
1
+ let react = require("react");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ //#region src/components/Link.tsx
4
+ function makeLink(hooks) {
5
+ const { useNavigate, useRouteLoading } = hooks;
6
+ function Link({ to, children }) {
7
+ const navigate = useNavigate();
8
+ const isLoading = useRouteLoading({ name: to.name });
9
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
10
+ href: "#",
11
+ onClick: (0, react.useCallback)((e) => {
12
+ e.preventDefault();
13
+ navigate({ target: to });
14
+ }, [navigate, to]),
15
+ children: children({ isLoading })
16
+ });
17
+ }
18
+ return Link;
19
+ }
20
+ //#endregion
21
+ exports.makeLink = makeLink;
@@ -0,0 +1,13 @@
1
+ import { RouteMapDef, RouteUnion } from "iten-core";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/Link.d.ts
5
+ type LinkProps<M extends RouteMapDef> = {
6
+ to: RouteUnion<M>;
7
+ children: (state: {
8
+ isLoading: boolean;
9
+ }) => ReactNode;
10
+ };
11
+ type LinkComponent<M extends RouteMapDef> = (props: LinkProps<M>) => ReactNode;
12
+ //#endregion
13
+ export { LinkComponent, LinkProps };
@@ -0,0 +1,13 @@
1
+ import { RouteMapDef, RouteUnion } from "iten-core";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/Link.d.ts
5
+ type LinkProps<M extends RouteMapDef> = {
6
+ to: RouteUnion<M>;
7
+ children: (state: {
8
+ isLoading: boolean;
9
+ }) => ReactNode;
10
+ };
11
+ type LinkComponent<M extends RouteMapDef> = (props: LinkProps<M>) => ReactNode;
12
+ //#endregion
13
+ export { LinkComponent, LinkProps };
@@ -0,0 +1,21 @@
1
+ import { useCallback } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/Link.tsx
4
+ function makeLink(hooks) {
5
+ const { useNavigate, useRouteLoading } = hooks;
6
+ function Link({ to, children }) {
7
+ const navigate = useNavigate();
8
+ const isLoading = useRouteLoading({ name: to.name });
9
+ return /* @__PURE__ */ jsx("a", {
10
+ href: "#",
11
+ onClick: useCallback((e) => {
12
+ e.preventDefault();
13
+ navigate({ target: to });
14
+ }, [navigate, to]),
15
+ children: children({ isLoading })
16
+ });
17
+ }
18
+ return Link;
19
+ }
20
+ //#endregion
21
+ export { makeLink };
@@ -0,0 +1,15 @@
1
+ let react = require("react");
2
+ //#region src/components/Navigate.tsx
3
+ function makeNavigate(hooks) {
4
+ const { useNavigate } = hooks;
5
+ function Navigate({ to }) {
6
+ const navigate = useNavigate();
7
+ (0, react.useEffect)(() => {
8
+ navigate({ target: to });
9
+ }, []);
10
+ return null;
11
+ }
12
+ return Navigate;
13
+ }
14
+ //#endregion
15
+ exports.makeNavigate = makeNavigate;
@@ -0,0 +1,10 @@
1
+ import { RouteMapDef, RouteUnion } from "iten-core";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/Navigate.d.ts
5
+ type NavigateProps<M extends RouteMapDef> = {
6
+ to: RouteUnion<M>;
7
+ };
8
+ type NavigateComponent<M extends RouteMapDef> = (props: NavigateProps<M>) => ReactNode;
9
+ //#endregion
10
+ export { NavigateComponent, NavigateProps };
@@ -0,0 +1,10 @@
1
+ import { RouteMapDef, RouteUnion } from "iten-core";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/Navigate.d.ts
5
+ type NavigateProps<M extends RouteMapDef> = {
6
+ to: RouteUnion<M>;
7
+ };
8
+ type NavigateComponent<M extends RouteMapDef> = (props: NavigateProps<M>) => ReactNode;
9
+ //#endregion
10
+ export { NavigateComponent, NavigateProps };