@fluixi/core 1.0.0-alpha.83 → 1.0.0-alpha.85

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 (182) hide show
  1. package/dist/cdn/core.global.js +2 -1
  2. package/dist/cli/generate.cjs +4117 -292
  3. package/dist/cli/generate.mjs +4122 -292
  4. package/dist/cli/index.cjs +4117 -292
  5. package/dist/cli/index.mjs +4122 -292
  6. package/dist/cli/run.cjs +63 -1
  7. package/dist/cli/run.mjs +39 -1
  8. package/dist/src/cdn/chunk-MDJLC3PE.mjs +830 -0
  9. package/dist/src/cdn/core.cjs +1365 -1
  10. package/dist/src/cdn/core.d.ts +3 -3
  11. package/dist/src/cdn/core.js +3 -3
  12. package/dist/src/cdn/core.mjs +642 -1
  13. package/dist/src/cdn/router-JYFZP3EZ.mjs +70 -0
  14. package/dist/src/index.cjs +1280 -1
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +1 -11
  17. package/dist/src/index.mjs +1393 -1
  18. package/dist/src/jsx-dev-runtime.cjs +26 -1
  19. package/dist/src/jsx-dev-runtime.mjs +24 -1
  20. package/dist/src/jsx-runtime.cjs +26 -1
  21. package/dist/src/jsx-runtime.js +0 -9
  22. package/dist/src/jsx-runtime.mjs +24 -1
  23. package/dist/src/lib/client/chunk-7IUN2FLI.mjs +133 -0
  24. package/dist/src/lib/client/index.cjs +956 -1
  25. package/dist/src/lib/client/index.d.ts +17 -6
  26. package/dist/src/lib/client/index.d.ts.map +1 -1
  27. package/dist/src/lib/client/index.js +26 -19
  28. package/dist/src/lib/client/index.mjs +222 -1
  29. package/dist/src/lib/client/router-NOFEMOEH.mjs +631 -0
  30. package/dist/src/lib/context/context.cjs +118 -1
  31. package/dist/src/lib/context/context.d.ts +19 -37
  32. package/dist/src/lib/context/context.d.ts.map +1 -1
  33. package/dist/src/lib/context/context.js +8 -45
  34. package/dist/src/lib/context/context.mjs +99 -1
  35. package/dist/src/lib/context/index.cjs +120 -1
  36. package/dist/src/lib/context/index.mjs +99 -1
  37. package/dist/src/lib/control-flow.cjs +35 -1
  38. package/dist/src/lib/control-flow.mjs +25 -1
  39. package/dist/src/lib/core.cjs +1279 -1
  40. package/dist/src/lib/core.d.ts +0 -2
  41. package/dist/src/lib/core.d.ts.map +1 -1
  42. package/dist/src/lib/core.js +14 -48
  43. package/dist/src/lib/core.mjs +1393 -1
  44. package/dist/src/lib/env/index.cjs +60 -1
  45. package/dist/src/lib/env/index.d.ts +5 -5
  46. package/dist/src/lib/env/index.js +5 -5
  47. package/dist/src/lib/env/index.mjs +39 -1
  48. package/dist/src/lib/i18n/index.cjs +108 -1
  49. package/dist/src/lib/i18n/index.d.ts +31 -10
  50. package/dist/src/lib/i18n/index.d.ts.map +1 -1
  51. package/dist/src/lib/i18n/index.js +31 -10
  52. package/dist/src/lib/i18n/index.mjs +88 -1
  53. package/dist/src/lib/index.cjs +633 -1
  54. package/dist/src/lib/index.d.ts +0 -2
  55. package/dist/src/lib/index.d.ts.map +1 -1
  56. package/dist/src/lib/index.js +5 -11
  57. package/dist/src/lib/index.mjs +721 -1
  58. package/dist/src/lib/isomorphic.cjs +85 -1
  59. package/dist/src/lib/isomorphic.d.ts +41 -8
  60. package/dist/src/lib/isomorphic.d.ts.map +1 -1
  61. package/dist/src/lib/isomorphic.js +38 -5
  62. package/dist/src/lib/isomorphic.mjs +64 -1
  63. package/dist/src/lib/jsx-runtime/index.cjs +21 -1
  64. package/dist/src/lib/jsx-runtime/index.d.ts +1 -1
  65. package/dist/src/lib/jsx-runtime/index.js +1 -11
  66. package/dist/src/lib/jsx-runtime/index.mjs +4 -1
  67. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +21 -1
  68. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +4 -1
  69. package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +302 -8
  70. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts +15 -1
  71. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts.map +1 -1
  72. package/dist/src/lib/plugins/fluixi-routes-plugin.js +19 -5
  73. package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +271 -8
  74. package/dist/src/lib/plugins/index.cjs +311 -8
  75. package/dist/src/lib/plugins/index.mjs +279 -8
  76. package/dist/src/lib/plugins/route-codegen.cjs +73 -8
  77. package/dist/src/lib/plugins/route-codegen.d.ts +0 -1
  78. package/dist/src/lib/plugins/route-codegen.d.ts.map +1 -1
  79. package/dist/src/lib/plugins/route-codegen.js +4 -5
  80. package/dist/src/lib/plugins/route-codegen.mjs +52 -8
  81. package/dist/src/lib/plugins/route-scanner.cjs +189 -1
  82. package/dist/src/lib/plugins/route-scanner.d.ts +1 -2
  83. package/dist/src/lib/plugins/route-scanner.d.ts.map +1 -1
  84. package/dist/src/lib/plugins/route-scanner.js +7 -8
  85. package/dist/src/lib/plugins/route-scanner.mjs +158 -1
  86. package/dist/src/lib/plugins/vite.cjs +27 -1
  87. package/dist/src/lib/plugins/vite.mjs +6 -1
  88. package/dist/src/lib/render/await.cjs +38 -1
  89. package/dist/src/lib/render/await.d.ts +18 -0
  90. package/dist/src/lib/render/await.d.ts.map +1 -1
  91. package/dist/src/lib/render/await.js +20 -2
  92. package/dist/src/lib/render/await.mjs +17 -1
  93. package/dist/src/lib/render/component.cjs +221 -1
  94. package/dist/src/lib/render/component.d.ts +3 -44
  95. package/dist/src/lib/render/component.d.ts.map +1 -1
  96. package/dist/src/lib/render/component.js +23 -55
  97. package/dist/src/lib/render/component.mjs +214 -1
  98. package/dist/src/lib/render/deferred.cjs +175 -1
  99. package/dist/src/lib/render/deferred.js +5 -5
  100. package/dist/src/lib/render/deferred.mjs +158 -1
  101. package/dist/src/lib/render/index.cjs +573 -1
  102. package/dist/src/lib/render/index.d.ts +3 -17
  103. package/dist/src/lib/render/index.d.ts.map +1 -1
  104. package/dist/src/lib/render/index.js +8 -22
  105. package/dist/src/lib/render/index.mjs +590 -1
  106. package/dist/src/lib/render/lazy.cjs +67 -1
  107. package/dist/src/lib/render/lazy.d.ts +22 -1
  108. package/dist/src/lib/render/lazy.d.ts.map +1 -1
  109. package/dist/src/lib/render/lazy.js +26 -5
  110. package/dist/src/lib/render/lazy.mjs +50 -1
  111. package/dist/src/lib/render/suspense.cjs +141 -1
  112. package/dist/src/lib/render/suspense.d.ts +40 -0
  113. package/dist/src/lib/render/suspense.d.ts.map +1 -1
  114. package/dist/src/lib/render/suspense.js +46 -6
  115. package/dist/src/lib/render/suspense.mjs +136 -1
  116. package/dist/src/lib/render/versions.cjs +35 -1
  117. package/dist/src/lib/render/versions.js +6 -6
  118. package/dist/src/lib/render/versions.mjs +14 -1
  119. package/dist/src/lib/router/data.cjs +230 -1
  120. package/dist/src/lib/router/data.d.ts +1 -13
  121. package/dist/src/lib/router/data.d.ts.map +1 -1
  122. package/dist/src/lib/router/data.js +0 -12
  123. package/dist/src/lib/router/data.mjs +214 -1
  124. package/dist/src/lib/router/index.cjs +709 -1
  125. package/dist/src/lib/router/index.d.ts +78 -23
  126. package/dist/src/lib/router/index.d.ts.map +1 -1
  127. package/dist/src/lib/router/index.js +161 -37
  128. package/dist/src/lib/router/index.mjs +722 -1
  129. package/dist/src/lib/router/transition.cjs +75 -0
  130. package/dist/src/lib/router/transition.d.ts +81 -0
  131. package/dist/src/lib/router/transition.d.ts.map +1 -0
  132. package/dist/src/lib/router/transition.js +136 -0
  133. package/dist/src/lib/router/transition.mjs +54 -0
  134. package/dist/src/lib/server/hydration.cjs +169 -1
  135. package/dist/src/lib/server/hydration.d.ts +15 -55
  136. package/dist/src/lib/server/hydration.d.ts.map +1 -1
  137. package/dist/src/lib/server/hydration.js +23 -62
  138. package/dist/src/lib/server/hydration.mjs +148 -1
  139. package/dist/src/lib/server/index.cjs +401 -11
  140. package/dist/src/lib/server/index.d.ts +30 -6
  141. package/dist/src/lib/server/index.d.ts.map +1 -1
  142. package/dist/src/lib/server/index.js +34 -11
  143. package/dist/src/lib/server/index.mjs +379 -11
  144. package/dist/src/lib/server/reactive.cjs +197 -1
  145. package/dist/src/lib/server/reactive.d.ts +18 -77
  146. package/dist/src/lib/server/reactive.d.ts.map +1 -1
  147. package/dist/src/lib/server/reactive.js +19 -78
  148. package/dist/src/lib/server/reactive.mjs +176 -1
  149. package/dist/src/lib/theme/index.cjs +110 -1
  150. package/dist/src/lib/theme/index.d.ts +19 -4
  151. package/dist/src/lib/theme/index.d.ts.map +1 -1
  152. package/dist/src/lib/theme/index.js +17 -2
  153. package/dist/src/lib/theme/index.mjs +89 -1
  154. package/dist/src/lib/utils/index.cjs +209 -1
  155. package/dist/src/lib/utils/index.d.ts +0 -93
  156. package/dist/src/lib/utils/index.d.ts.map +1 -1
  157. package/dist/src/lib/utils/index.js +0 -93
  158. package/dist/src/lib/utils/index.mjs +188 -1
  159. package/dist/src/routes.cjs +37 -1
  160. package/dist/src/routes.d.ts +1 -1
  161. package/dist/src/routes.js +2 -2
  162. package/dist/src/routes.mjs +17 -1
  163. package/dist/src/server.cjs +400 -11
  164. package/dist/src/server.d.ts +0 -1
  165. package/dist/src/server.d.ts.map +1 -1
  166. package/dist/src/server.js +0 -1
  167. package/dist/src/server.mjs +377 -11
  168. package/dist/src/utils.cjs +21 -1
  169. package/dist/src/utils.d.ts +1 -1
  170. package/dist/src/utils.js +1 -1
  171. package/dist/src/utils.mjs +4 -1
  172. package/dist/src/version.generated.cjs +26 -1
  173. package/dist/src/version.generated.d.ts +1 -1
  174. package/dist/src/version.generated.js +2 -2
  175. package/dist/src/version.generated.mjs +5 -1
  176. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  177. package/package.json +17 -16
  178. package/src/index.ts +2 -14
  179. package/dist/src/cdn/chunk-55BT3H6E.mjs +0 -1
  180. package/dist/src/cdn/router-5JJZBK4E.mjs +0 -1
  181. package/dist/src/lib/client/chunk-SM4WHBOP.mjs +0 -1
  182. package/dist/src/lib/client/router-IBD4WPQT.mjs +0 -1
@@ -1,17 +1,4 @@
1
- /**
2
- * @fileoverview Fluixi adapter for the framework-agnostic @fluixi/router core.
3
- *
4
- * Renders the resolved match tree TOP-DOWN. Each layer's component is rendered
5
- * inside the PARENT layer's <Outlet/> scope, so a child route inherits whatever
6
- * context a parent layout provides. <Outlet/> is reactive but keyed on the child's
7
- * component reference, so it only re-renders when the child route at that depth
8
- * actually changes — sibling navigation reuses the layout, deeper navigation
9
- * leaves shallower layouts untouched.
10
- *
11
- * No parentGate, no deferred content memo: a child is rendered straight from the
12
- * matched array, never by re-entering a still-building parent, so deeply nested
13
- * <Outlet/> works at any depth.
14
- */
1
+ import { type TransitionMode } from './transition.js';
15
2
  import { type GetAtPath } from '@fluixi/utils/object';
16
3
  import type { IsUndefined } from '@fluixi/utils/primitive';
17
4
  import { type RouteDefinition, type Router as CoreRouter, type HistoryAdapter, type RouterLocation, type NavigateOptions } from '@fluixi/router';
@@ -25,12 +12,36 @@ export interface RouterProps<C = any> {
25
12
  base?: string;
26
13
  history?: HistoryAdapter;
27
14
  /**
28
- * Rendered when the current URL matches no route a component, an element, or any renderable.
15
+ * Rendered when the current URL matches no route, a component, an element, or any renderable.
29
16
  * Defaults to a minimal built-in 404 so an unmatched URL never renders a blank page. For a 404
30
- * that keeps your layout/chrome, add a catch-all file route instead (`[...slug].tsx` `*`).
17
+ * that keeps your layout/chrome, add a catch-all file route instead (`[...slug].tsx` -> `*`).
31
18
  */
32
19
  notFound?: any;
20
+ /**
21
+ * How route changes are presented. `'auto'` (the default) hands the DOM swap to the
22
+ * browser's View Transition API where there is one, and navigates directly where
23
+ * there is not; `'none'` always navigates directly.
24
+ *
25
+ * A reader who asked for reduced motion never gets an animation either way. Styling
26
+ * is the native mechanism: `::view-transition-old(root)` and
27
+ * `::view-transition-new(root)`: so the router imposes no visual style of its own.
28
+ */
29
+ transition?: TransitionMode;
33
30
  }
31
+ /**
32
+ * The router. Matches the current URL against `routes` and renders what matches.
33
+ *
34
+ * Supplies the router context everything below reaches through `useRouter`,
35
+ * `useNavigate` and `Link`, so those only work inside it. `history` defaults to a
36
+ * browser history; pass a memory history for tests or a server render. An unmatched URL
37
+ * renders `notFound`, which defaults to a minimal built-in page so a bad URL is never
38
+ * blank. For a 404 that keeps your layout, add a catch-all file route instead.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * html`<${Router} routes=${routes} base="/app" />`;
43
+ * ```
44
+ */
34
45
  export declare function Router<C = any>(props: RouterProps<C>): any;
35
46
  /** Renders this layout's child route, inside this layout's context. */
36
47
  export declare function Outlet(): any;
@@ -39,6 +50,14 @@ export declare function Redirect(props: {
39
50
  to: string;
40
51
  replace?: boolean;
41
52
  }): null;
53
+ /**
54
+ * The router context: the router itself, the current match, and the location.
55
+ *
56
+ * Safe to call during a server render, where a detached context yields a no-op stub
57
+ * rather than throwing, since an SSR pass has nowhere to navigate to. On the client a
58
+ * missing router is genuine misuse and throws, because the alternative is a component
59
+ * that silently never navigates.
60
+ */
42
61
  export declare function useRouter(): RouterCtxValue;
43
62
  /**
44
63
  * Reactive merged params across all matched layers, as an accessor. Deep-equal
@@ -50,7 +69,7 @@ export declare function useParams<T extends Record<string, string> = Record<stri
50
69
  export declare function useLevelParams(): Record<string, string>;
51
70
  /**
52
71
  * Reactive accessor for a single route param. Default `===` equality on the
53
- * string value, so it only re-triggers when THIS key changes not when other
72
+ * string value, so it only re-triggers when this key changes, not when other
54
73
  * params at the same level change.
55
74
  */
56
75
  export declare function useParam(key: string): () => string | undefined;
@@ -97,9 +116,11 @@ export declare function useLocation(): RouterLocation;
97
116
  export interface LinkNavigationOptions {
98
117
  /** Replace the current history entry instead of pushing a new one. */
99
118
  replace?: boolean;
119
+ /** How this navigation is presented. Defaults to the Router's own setting. */
120
+ transition?: TransitionMode;
100
121
  /**
101
122
  * Called with the resolved URL before navigating. Return false to leave the click
102
- * alone for a section of the app that must load as a document (a different
123
+ * alone, for a section of the app that must load as a document (a different
103
124
  * bundle, a server-rendered admin area, a file route).
104
125
  */
105
126
  shouldNavigate?: (url: URL, anchor: HTMLAnchorElement) => boolean;
@@ -117,17 +138,35 @@ export interface LinkNavigationOptions {
117
138
  * <article onClick={onClick} innerHTML={doc.html} />
118
139
  * ```
119
140
  *
120
- * The anchors stay real anchors middle-click, right-click open in new tab, "copy
141
+ * The anchors stay real anchors: middle-click, right-click -> open in new tab, "copy
121
142
  * link address" and crawlers all keep working, because the element is untouched and only
122
143
  * a plain left-click is intercepted.
123
144
  *
124
145
  * Left alone: modified clicks, anything but the primary button, an already-prevented
125
146
  * event, `target`, `download`, `rel="external"`, a non-http scheme (`mailto:`, `tel:`),
126
- * another origin, and a hash pointing inside the current page that last one is the
147
+ * another origin, and a hash pointing inside the current page, that last one is the
127
148
  * browser's scroll, and hijacking it breaks in-page anchors.
128
149
  */
129
150
  export declare function useLinkNavigation(options?: LinkNavigationOptions): (event: MouseEvent) => void;
130
- export declare function useNavigate(): (to: string, options?: NavigateOptions) => void;
151
+ /**
152
+ * A function that navigates, for use outside a `Link`.
153
+ *
154
+ * Reach for it after an action rather than for a plain link: a form that redirects on
155
+ * success, a guard that bounces an unauthenticated visitor. Prefer `Link` for anything a
156
+ * visitor clicks, since an anchor is what middle-click, copy-link and a crawler expect.
157
+ *
158
+ * Navigation is a no-op during a server render, for the reason `useRouter` explains.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const navigate = useNavigate();
163
+ * await save(form);
164
+ * navigate(`/posts/${id}`, { replace: true });
165
+ * ```
166
+ */
167
+ export declare function useNavigate(): (to: string, options?: NavigateOptions & {
168
+ transition?: TransitionMode;
169
+ }) => void;
131
170
  /**
132
171
  * The resolved data for the nearest route that declared a `data` loader. Always
133
172
  * returns a callable accessor (matching the old router): it suspends under
@@ -147,13 +186,28 @@ export interface LinkProps {
147
186
  slot?: string;
148
187
  children?: any;
149
188
  /**
150
- * Everything else is forwarded onto the `<a>` id, style, title, aria-*,
189
+ * Everything else is forwarded onto the `<a>`: id, style, title, aria-*,
151
190
  * data-*, event handlers. Typed openly because the forwarding is
152
191
  * allow-by-default: a prop this interface forgot still reaches the DOM, and
153
192
  * refusing it here would only mean the type disagreeing with the runtime.
154
193
  */
155
194
  [key: string]: unknown;
156
195
  }
196
+ /**
197
+ * An anchor that navigates without a full page load.
198
+ *
199
+ * Renders a real `<a href>`, so middle-click, copy-link, and a crawler all behave. A
200
+ * plain left click is intercepted and routed instead.
201
+ *
202
+ * Active state defaults to the current pathname matching `href`, and `activeClass` is
203
+ * added while it holds. Override `active` when a section link should stay lit for its
204
+ * children.
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * html`<${Link} href="/about" activeClass="current">About</${Link}>`;
209
+ * ```
210
+ */
157
211
  export declare function Link(props: LinkProps): any;
158
212
  /** Alias for {@link Link}. */
159
213
  export declare const A: typeof Link;
@@ -180,7 +234,7 @@ export interface FormProps {
180
234
  export declare function Form(props: FormProps): any;
181
235
  /**
182
236
  * Download the lazy component chunks for the route(s) matching `pathname` and resolve when
183
- * they're all loaded. Await this BEFORE hydrating: once each matched lazy file-route is
237
+ * they're all loaded. Await this before hydrating: once each matched lazy file-route is
184
238
  * resolved it renders synchronously on the first pass (no Suspense fallback, no late
185
239
  * re-render), so hydration adopts the server DOM in place instead of building a duplicate
186
240
  * subtree next to it. No-op for routes with no lazy components. Safe to call when the URL
@@ -192,5 +246,6 @@ export type { RouteDefinition, RouteMatch, MatchedRoute, RouterLocation, Navigat
192
246
  export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
193
247
  export type { RouteDataArgs, RouteLoadFunc, Action, Submission, CacheFn, } from './data.js';
194
248
  export { lazy } from '../render/lazy.js';
249
+ export { supportsViewTransitions, prefersReducedMotion, type TransitionMode, } from './transition.js';
195
250
  export { runWithOwner } from '@fluixi/reactive/signal';
196
251
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAqBH,OAAO,EAAO,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EAKL,KAAK,eAAe,EAEpB,KAAK,MAAM,IAAI,UAAU,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAIxB,UAAU,cAAc;IACtB,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,QAAQ,EAAE,MAAM,cAAc,CAAC;IAC/B,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AA8JD,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG;IAClC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,wBAAgB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,OAgDpD;AAID,uEAAuE;AACvE,wBAAgB,MAAM,QAIrB;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,QAchE;AAkBD,wBAAgB,SAAS,IAAI,cAAc,CAO1C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACtD,MAAM,CAAC,CAKX;AAED,sDAAsD;AACtD,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,MAAM,GAAG,SAAS,CAG9D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAC/C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,GAC/B,MAAM,CAAC,CAOT;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAC5B,MAAM;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAK/D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,SAAS;IAC1C,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAC5B,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,IAAI;CACzE,CAmBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,EAChD,MAAM,EAAE,MAAM,CAAC,EACf,GAAG,CAAC,EAAE,CAAC,GACN,MAAM,WAAW,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAUzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,cAAc,CAS5C;AAED,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,qBAA0B,GAClC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAyB7B;AAED,wBAAgB,WAAW,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,IAAI,CAM7E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,GAAG,KAAK,MAAM,CAAC,GAAG,SAAS,CAG3D;AAID,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAItC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACjC,oEAAoE;IACpE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAClC,4CAA4C;IAC5C,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf;;;;;OAKG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAcD,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,OA6DpC;AAED,8BAA8B;AAC9B,eAAO,MAAM,CAAC,aAAO,CAAC;AAEtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACzB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,OA0CpC;AAID;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,GAAG,EACzC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,EAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CASf;AAID,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,WAAW,EACX,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,eAAe,EACf,UAAU,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,cAAc,GACf,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EACL,KAAK,EACL,MAAM,EACN,UAAU,EACV,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,aAAa,EACb,aAAa,EACb,MAAM,EACN,UAAU,EACV,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,21 @@
1
1
  /**
2
- * @fileoverview Fluixi adapter for the framework-agnostic @fluixi/router core.
3
2
  *
4
3
  * Renders the resolved match tree TOP-DOWN. Each layer's component is rendered
5
4
  * inside the PARENT layer's <Outlet/> scope, so a child route inherits whatever
6
5
  * context a parent layout provides. <Outlet/> is reactive but keyed on the child's
7
6
  * component reference, so it only re-renders when the child route at that depth
8
- * actually changes sibling navigation reuses the layout, deeper navigation
7
+ * actually changes: sibling navigation reuses the layout, deeper navigation
9
8
  * leaves shallower layouts untouched.
10
9
  *
11
10
  * No parentGate, no deferred content memo: a child is rendered straight from the
12
11
  * matched array, never by re-entering a still-building parent, so deeply nested
13
12
  * <Outlet/> works at any depth.
14
13
  */
15
- import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, } from '@fluixi/reactive/signal';
14
+ import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, flush, } from '@fluixi/reactive/signal';
15
+ import { prepareComponents, runTransition, } from './transition.js';
16
16
  import { insert, createComponent, createNativeElement, setAttribute, } from '@fluixi/dom';
17
17
  // Reactive context (not fluixi createProvider, which destructures children
18
- // eagerly) so a `get children()` getter renders AFTER the context is stamped.
18
+ // eagerly) so a `get children()` getter renders after the context is stamped.
19
19
  import { createContext, useContext } from '@fluixi/reactive/signal';
20
20
  import { get } from '@fluixi/utils/object';
21
21
  import { deepEqual } from '@fluixi/utils/compare';
@@ -59,14 +59,14 @@ function renderLayer(m, depth, base) {
59
59
  });
60
60
  if (!data)
61
61
  return buildPage();
62
- // Gate on the data being resolved like the old router so the page reads
62
+ // Gate on the data being resolved, like the old router, so the page reads
63
63
  // ready data (rd() returns the value, not a thrown promise) and works without a
64
64
  // Suspense boundary. Signal-gated (`ready` only flips when loading actually
65
65
  // changes) so navigation re-renders don't re-run this and rebuild the page.
66
66
  const res = data;
67
67
  const isLoading = () => res.loading;
68
68
  // untrack the initial read so the enclosing render memo doesn't subscribe to the
69
- // resource state (that would re-run renderLayer and recreate the resource a loop).
69
+ // resource state (that would re-run renderLayer and recreate the resource, a loop).
70
70
  const [ready, setReady] = createSignal(untrack(() => !isLoading()));
71
71
  createEffect(() => setReady(!isLoading()));
72
72
  return createMemo(() => (ready() ? buildPage() : null));
@@ -83,19 +83,23 @@ function renderChild(ctx) {
83
83
  // signal write (no value check), so a memo that read `matched` would re-run on
84
84
  // every navigation and re-render the child. A signal only notifies when its value
85
85
  // actually changes, so the render memo below re-runs only when the route at this
86
- // depth changes sibling navigation reuses the layout, deeper navigation leaves
86
+ // depth changes: sibling navigation reuses the layout, deeper navigation leaves
87
87
  // this level untouched. The child is rendered INLINE (no detached createRoot), so
88
88
  // it stays connected to the Suspense / context owner chain.
89
89
  const [childComp, setChildComp] = createSignal(untrack(() => ctx.matched()[childIdx]?.component));
90
90
  createEffect(() => {
91
91
  const c = ctx.matched()[childIdx]?.component;
92
- setChildComp(() => c);
92
+ // `matched` is invalidated more than once per navigation, so this effect runs more
93
+ // than once. Writing only on a real change keeps the redundant runs from touching
94
+ // the graph at all.
95
+ if (untrack(childComp) !== c)
96
+ setChildComp(() => c);
93
97
  });
94
98
  return createMemo(() => {
95
99
  const component = childComp();
96
100
  if (!component) {
97
- // No route at this depth. At the ROOT (the only ctx carrying `notFound`) that means the
98
- // URL matched nothing render the not-found fallback instead of a blank page. Nested
101
+ // No route at this depth. At the root (the only ctx carrying `notFound`) that means the
102
+ // URL matched nothing: render the not-found fallback instead of a blank page. Nested
99
103
  // Outlets have no `notFound`, so they stay null (a missing child is just an empty Outlet).
100
104
  return ctx.notFound ? ctx.notFound() : null;
101
105
  }
@@ -110,8 +114,8 @@ function renderChild(ctx) {
110
114
  /**
111
115
  * Resolve the Router's `notFound` prop to a renderable view. A component is instantiated; an
112
116
  * element/value is returned as-is; when omitted, a minimal built-in 404 keeps the page from
113
- * rendering blank. Prefer a catch-all file route (`[...slug].tsx` `*`) when the 404 should sit
114
- * inside your layout this prop is the app-level fallback for an otherwise unmatched URL.
117
+ * rendering blank. Prefer a catch-all file route (`[...slug].tsx` -> `*`) when the 404 should sit
118
+ * inside your layout, this prop is the app-level fallback for an otherwise unmatched URL.
115
119
  */
116
120
  function renderNotFound(notFound) {
117
121
  if (notFound == null)
@@ -135,6 +139,25 @@ function buildDefaultNotFound() {
135
139
  el.appendChild(p);
136
140
  return el;
137
141
  }
142
+ /**
143
+ * The presentation for the navigation about to happen, handed from `useNavigate` to the
144
+ * subscriber in `Router`. Read and cleared within the same synchronous call.
145
+ */
146
+ let pendingTransitionMode;
147
+ /**
148
+ * The router. Matches the current URL against `routes` and renders what matches.
149
+ *
150
+ * Supplies the router context everything below reaches through `useRouter`,
151
+ * `useNavigate` and `Link`, so those only work inside it. `history` defaults to a
152
+ * browser history; pass a memory history for tests or a server render. An unmatched URL
153
+ * renders `notFound`, which defaults to a minimal built-in page so a bad URL is never
154
+ * blank. For a 404 that keeps your layout, add a catch-all file route instead.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * html`<${Router} routes=${routes} base="/app" />`;
159
+ * ```
160
+ */
138
161
  export function Router(props) {
139
162
  const history = props.history ?? createBrowserHistory();
140
163
  const router = createCoreRouter({
@@ -143,12 +166,64 @@ export function Router(props) {
143
166
  history,
144
167
  });
145
168
  const [state, setState] = createSignal(router.state());
146
- const unsub = router.subscribe(setState);
169
+ /**
170
+ * Which navigation is allowed to commit.
171
+ *
172
+ * Preparation is async, so two navigations can be in flight at once: A→B starts
173
+ * loading B's chunk, the reader clicks through to C before it lands. Each navigation
174
+ * takes the next number and checks it is still the current one before writing the
175
+ * match signal, so B resolving late finds itself superseded and returns without
176
+ * touching anything. Without this, B would commit over C, the URL would say /c and
177
+ * the page would show B.
178
+ */
179
+ let navigationId = 0;
180
+ /**
181
+ * How to present the next commit, set by `useNavigate` immediately before it calls
182
+ * into the core router.
183
+ *
184
+ * A module-level hand-off rather than a parameter because the core router's
185
+ * `navigate` does not carry options through to its listeners, and it notifies
186
+ * synchronously: the value is written and read within the same call, never left
187
+ * lying around for an unrelated navigation to pick up.
188
+ */
189
+ const takeMode = () => {
190
+ const mode = pendingTransitionMode ?? (props.transition ?? 'auto');
191
+ pendingTransitionMode = undefined;
192
+ return mode;
193
+ };
194
+ const unsub = router.subscribe((next) => {
195
+ const id = ++navigationId;
196
+ const mode = takeMode();
197
+ // Load the modules the next route needs before anything is torn down, so a lazy
198
+ // route renders straight through instead of suspending into a fallback with the
199
+ // old page already gone.
200
+ const prepared = prepareComponents((next.match?.matched ?? []).map((m) => m.component).filter(Boolean));
201
+ const commit = () => {
202
+ setState(next);
203
+ // The write only queues; effects are scheduled. Flushing here is what lets the
204
+ // browser snapshot a settled DOM rather than a half-built one.
205
+ return flush();
206
+ };
207
+ if (!prepared) {
208
+ // Nothing to wait for: stay on the tick this navigation was already on.
209
+ void runTransition(mode, commit);
210
+ return;
211
+ }
212
+ void prepared.then(() => {
213
+ // Superseded while the module loaded. The newer navigation owns the DOM now.
214
+ if (id !== navigationId)
215
+ return;
216
+ return runTransition(mode, commit);
217
+ });
218
+ });
147
219
  onCleanup(() => {
148
220
  unsub();
149
221
  router.destroy();
222
+ // Anything still preparing is now stale, so nothing can commit into a torn-down
223
+ // router.
224
+ navigationId++;
150
225
  });
151
- // Drop pathless layers (no component) their params still appear in the merged
226
+ // Drop pathless layers (no component), their params still appear in the merged
152
227
  // params exposed by RouterContext.
153
228
  const matched = createMemo(() => (state().match?.matched ?? []).filter((m) => m.component));
154
229
  const ctx = {
@@ -162,12 +237,12 @@ export function Router(props) {
162
237
  matched,
163
238
  params: ctx.params,
164
239
  location: ctx.location,
165
- // Only the root carries this renderChild shows it when the URL matched nothing.
240
+ // Only the root carries this: renderChild shows it when the URL matched nothing.
166
241
  notFound: () => renderNotFound(props.notFound),
167
242
  };
168
243
  // Build the root view once, lazily, inside the provider's children getter: the
169
244
  // getter must run in the provider scope (so components can useRouter), but the
170
- // reactive provider re-invokes it whenever content changes caching keeps the
245
+ // reactive provider re-invokes it whenever content changes: caching keeps the
171
246
  // root chain from being rebuilt on every navigation.
172
247
  let rootView;
173
248
  return RouterContext.Provider({
@@ -190,7 +265,7 @@ export function Redirect(props) {
190
265
  const ctx = useContext(RouterContext);
191
266
  // Nothing to do without a router, and nothing to navigate on the server.
192
267
  if (ctx && typeof window !== 'undefined') {
193
- // Navigate AFTER the current render commits. Navigating during render (an effect
268
+ // Navigate after the current render commits. Navigating during render (an effect
194
269
  // running mid-render) updated the location but the route never re-matched, so the
195
270
  // target never showed. Guard against firing if this Redirect is torn down first.
196
271
  let cancelled = false;
@@ -204,9 +279,9 @@ export function Redirect(props) {
204
279
  }
205
280
  // ─── Hooks ──────────────────────────────────────────────────────────────────
206
281
  // Safe stub returned by useRouter on the SERVER when no RouterContext is on the owner
207
- // chain. That happens during a late/detached re-render e.g. a slow data resource
208
- // settling AFTER renderToStringAsync already serialized the response and disposed the
209
- // root whose output is discarded. Returning this (instead of throwing) prevents a
282
+ // chain. That happens during a late/detached re-render: e.g. a slow data resource
283
+ // settling after renderToStringAsync already serialized the response and disposed the
284
+ // root: whose output is discarded. Returning this (instead of throwing) prevents a
210
285
  // floating rejection that crashes the dev server / shows an error overlay seconds
211
286
  // after the page already rendered.
212
287
  const _ssrRouterStub = {
@@ -214,12 +289,20 @@ const _ssrRouterStub = {
214
289
  location: () => ({ pathname: '', search: '', hash: '', query: {} }),
215
290
  params: () => ({}),
216
291
  };
292
+ /**
293
+ * The router context: the router itself, the current match, and the location.
294
+ *
295
+ * Safe to call during a server render, where a detached context yields a no-op stub
296
+ * rather than throwing, since an SSR pass has nowhere to navigate to. On the client a
297
+ * missing router is genuine misuse and throws, because the alternative is a component
298
+ * that silently never navigates.
299
+ */
217
300
  export function useRouter() {
218
301
  const ctx = useContext(RouterContext);
219
302
  if (ctx)
220
303
  return ctx;
221
304
  // On the server, degrade to a no-op stub (see above). On the client, a missing
222
- // router is genuine misuse throw.
305
+ // router is genuine misuse: throw.
223
306
  if (typeof window === 'undefined')
224
307
  return _ssrRouterStub;
225
308
  throw new Error('[router] useRouter must be used within <Router>');
@@ -241,7 +324,7 @@ export function useLevelParams() {
241
324
  }
242
325
  /**
243
326
  * Reactive accessor for a single route param. Default `===` equality on the
244
- * string value, so it only re-triggers when THIS key changes not when other
327
+ * string value, so it only re-triggers when this key changes, not when other
245
328
  * params at the same level change.
246
329
  */
247
330
  export function useParam(key) {
@@ -331,13 +414,13 @@ export function useLocation() {
331
414
  * <article onClick={onClick} innerHTML={doc.html} />
332
415
  * ```
333
416
  *
334
- * The anchors stay real anchors middle-click, right-click open in new tab, "copy
417
+ * The anchors stay real anchors: middle-click, right-click -> open in new tab, "copy
335
418
  * link address" and crawlers all keep working, because the element is untouched and only
336
419
  * a plain left-click is intercepted.
337
420
  *
338
421
  * Left alone: modified clicks, anything but the primary button, an already-prevented
339
422
  * event, `target`, `download`, `rel="external"`, a non-http scheme (`mailto:`, `tel:`),
340
- * another origin, and a hash pointing inside the current page that last one is the
423
+ * another origin, and a hash pointing inside the current page, that last one is the
341
424
  * browser's scroll, and hijacking it breaks in-page anchors.
342
425
  */
343
426
  export function useLinkNavigation(options = {}) {
@@ -369,15 +452,38 @@ export function useLinkNavigation(options = {}) {
369
452
  if (options.shouldNavigate && !options.shouldNavigate(url, anchor))
370
453
  return;
371
454
  event.preventDefault();
455
+ pendingTransitionMode = options.transition;
372
456
  ctx.router.navigate(url.pathname + url.search + url.hash, { replace: options.replace });
373
457
  };
374
458
  }
459
+ /**
460
+ * A function that navigates, for use outside a `Link`.
461
+ *
462
+ * Reach for it after an action rather than for a plain link: a form that redirects on
463
+ * success, a guard that bounces an unauthenticated visitor. Prefer `Link` for anything a
464
+ * visitor clicks, since an anchor is what middle-click, copy-link and a crawler expect.
465
+ *
466
+ * Navigation is a no-op during a server render, for the reason `useRouter` explains.
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * const navigate = useNavigate();
471
+ * await save(form);
472
+ * navigate(`/posts/${id}`, { replace: true });
473
+ * ```
474
+ */
375
475
  export function useNavigate() {
376
476
  // useRouter is SSR-safe (returns a no-op stub on the server when the context is
377
477
  // detached), so navigation degrades to a no-op during SSR and only throws on
378
478
  // genuine client-side misuse outside <Router>.
379
479
  const { router } = useRouter();
380
- return (to, options) => router.navigate(to, options);
480
+ return (to, options) => {
481
+ // Handed to the Router's subscriber, which runs synchronously inside the call
482
+ // below and clears it. `transition: 'none'` on one navigation leaves the router's
483
+ // own setting alone for the next.
484
+ pendingTransitionMode = options?.transition;
485
+ router.navigate(to, options);
486
+ };
381
487
  }
382
488
  /**
383
489
  * The resolved data for the nearest route that declared a `data` loader. Always
@@ -400,18 +506,33 @@ const LINK_OWN_PROPS = new Set([
400
506
  'replace',
401
507
  'children',
402
508
  ]);
509
+ /**
510
+ * An anchor that navigates without a full page load.
511
+ *
512
+ * Renders a real `<a href>`, so middle-click, copy-link, and a crawler all behave. A
513
+ * plain left click is intercepted and routed instead.
514
+ *
515
+ * Active state defaults to the current pathname matching `href`, and `activeClass` is
516
+ * added while it holds. Override `active` when a section link should stay lit for its
517
+ * children.
518
+ *
519
+ * @example
520
+ * ```ts
521
+ * html`<${Link} href="/about" activeClass="current">About</${Link}>`;
522
+ * ```
523
+ */
403
524
  export function Link(props) {
404
525
  const ctx = useContext(RouterContext);
405
526
  const navigate = () => ctx?.router.navigate(read(props.href) ?? '', { replace: read(props.replace) });
406
- // createNativeElement branches on isServer() a real ServerNode <a> (serialized as a true
527
+ // createNativeElement branches on isServer() -> a real ServerNode <a> (serialized as a true
407
528
  // element, not an escaped string) on the server, a real DOM <a> on the client.
408
529
  const a = createNativeElement('a');
409
530
  setAttribute(a, 'href', read(props.href) ?? '');
410
531
  if (props.slot)
411
532
  setAttribute(a, 'slot', read(props.slot));
412
- // Forward any other author attributes (data-*, aria-*, style, id, title, role, )
533
+ // Forward any other author attributes (data-*, aria-*, style, id, title, role, ...)
413
534
  // onto the <a>, the way <Form> does. Without this a component composed via
414
- // `as={Link}` (polymorphic) silently loses its styling hooks e.g. @fluixi-ui
535
+ // `as={Link}` (polymorphic) silently loses its styling hooks: e.g. @fluixi-ui
415
536
  // parts carry `data-*` slots + inline styles that would otherwise be dropped, so
416
537
  // the element renders unstyled. Own-props handled elsewhere are skipped:
417
538
  // href/slot above, class/className/active/activeClass in the effect, and
@@ -419,7 +540,7 @@ export function Link(props) {
419
540
  // Keys, not entries: `Object.entries` reads every prop, and `children` is a getter
420
541
  // that builds fresh nodes per access. Under hydration that discarded first build
421
542
  // consumes the server's nodes, so the real read below produced a second copy of
422
- // them a duplicated logo and pager on every prerendered page.
543
+ // them, a duplicated logo and pager on every prerendered page.
423
544
  for (const k of Object.keys(props)) {
424
545
  if (LINK_OWN_PROPS.has(k))
425
546
  continue;
@@ -444,7 +565,7 @@ export function Link(props) {
444
565
  setAttribute(a, 'class', base);
445
566
  }
446
567
  // Client only: keep href + active class in sync with reactive props / location.
447
- // (Skipped on the server `a` is a ServerNode without these DOM setters; the static
568
+ // (Skipped on the server: `a` is a ServerNode without these DOM setters; the static
448
569
  // href/class were set above.)
449
570
  if (typeof a.addEventListener === 'function') {
450
571
  createEffect(() => {
@@ -476,18 +597,18 @@ export const A = Link;
476
597
  */
477
598
  export function Form(props) {
478
599
  const action = props.action;
479
- const cls = (props.className ?? props.class); // compiler passes classclassName
600
+ const cls = (props.className ?? props.class); // compiler passes class -> className
480
601
  const url = action.url ?? '';
481
602
  // createNativeElement/setAttribute branch on isServer(), so this builds a real ServerNode
482
- // <form> on the server (serialized as a true element, NOT an escaped string) and a real
483
- // DOM <form> on the client hydration reuses the same element.
603
+ // <form> on the server (serialized as a true element, not an escaped string) and a real
604
+ // DOM <form> on the client: hydration reuses the same element.
484
605
  const form = createNativeElement('form');
485
606
  setAttribute(form, 'method', 'post');
486
607
  if (url)
487
608
  setAttribute(form, 'action', url);
488
609
  if (cls)
489
610
  setAttribute(form, 'class', cls);
490
- // Keys, not entries same reason as Link: reading `children` here would build its
611
+ // Keys, not entries, same reason as Link: reading `children` here would build its
491
612
  // nodes once and throw them away.
492
613
  for (const k of Object.keys(props)) {
493
614
  if (k === 'action' || k === 'children' || k === 'class' || k === 'className')
@@ -513,7 +634,7 @@ export function Form(props) {
513
634
  }
514
635
  // Read children ONCE: the compiler emits `get children()` as a getter that builds fresh
515
636
  // nodes on every access, so reading it twice (here + the null check) created a duplicate
516
- // set under hydration that meant two <input>s in the form.
637
+ // set: under hydration that meant two <input>s in the form.
517
638
  const children = props.children;
518
639
  if (children != null)
519
640
  insert(form, children);
@@ -522,7 +643,7 @@ export function Form(props) {
522
643
  // ─── Hydration preload ───────────────────────────────────────────────────────────
523
644
  /**
524
645
  * Download the lazy component chunks for the route(s) matching `pathname` and resolve when
525
- * they're all loaded. Await this BEFORE hydrating: once each matched lazy file-route is
646
+ * they're all loaded. Await this before hydrating: once each matched lazy file-route is
526
647
  * resolved it renders synchronously on the first pass (no Suspense fallback, no late
527
648
  * re-render), so hydration adopts the server DOM in place instead of building a duplicate
528
649
  * subtree next to it. No-op for routes with no lazy components. Safe to call when the URL
@@ -542,9 +663,12 @@ export async function preloadRoutes(routes, pathname) {
542
663
  }
543
664
  // ─── Re-exports ────────────────────────────────────────────────────────────────
544
665
  export { createMemoryHistory, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
545
- // Action/cache/lazy layer is orthogonal to routing re-export so a route file can
666
+ // Action/cache/lazy layer is orthogonal to routing: re-export so a route file can
546
667
  // swap its import path wholesale. (Routing hooks above are the router ones.)
547
668
  export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
548
669
  export { lazy } from '../render/lazy.js';
670
+ // Navigation presentation. The router owns the integration; these are exported for
671
+ // apps that need to ask what the environment supports.
672
+ export { supportsViewTransitions, prefersReducedMotion, } from './transition.js';
549
673
  // Carried over from the barrel this replaced; route codegen imports it from here.
550
674
  export { runWithOwner } from '@fluixi/reactive/signal';