@fluixi/core 1.0.0-alpha.53

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +611 -0
  3. package/dist/cdn/core.global.js +3 -0
  4. package/dist/cli/generate.cjs +1814 -0
  5. package/dist/cli/generate.d.ts +7 -0
  6. package/dist/cli/generate.d.ts.map +1 -0
  7. package/dist/cli/generate.js +2580 -0
  8. package/dist/cli/generate.mjs +1814 -0
  9. package/dist/cli/index.cjs +1814 -0
  10. package/dist/cli/index.d.ts +7 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +2580 -0
  13. package/dist/cli/index.mjs +1814 -0
  14. package/dist/cli/run.cjs +2 -0
  15. package/dist/cli/run.d.ts +3 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +62 -0
  18. package/dist/cli/run.mjs +2 -0
  19. package/dist/fluixi.d.ts +5 -0
  20. package/dist/src/cdn/chunk-PFM5O5UE.mjs +1 -0
  21. package/dist/src/cdn/core.cjs +1 -0
  22. package/dist/src/cdn/core.d.ts +16 -0
  23. package/dist/src/cdn/core.d.ts.map +1 -0
  24. package/dist/src/cdn/core.js +15 -0
  25. package/dist/src/cdn/core.mjs +1 -0
  26. package/dist/src/cdn/router-next-WNWCFVC2.mjs +1 -0
  27. package/dist/src/index.cjs +1 -0
  28. package/dist/src/index.d.ts +7 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +26 -0
  31. package/dist/src/index.mjs +1 -0
  32. package/dist/src/jsx-dev-runtime.cjs +1 -0
  33. package/dist/src/jsx-dev-runtime.d.ts +2 -0
  34. package/dist/src/jsx-dev-runtime.d.ts.map +1 -0
  35. package/dist/src/jsx-dev-runtime.js +1 -0
  36. package/dist/src/jsx-dev-runtime.mjs +1 -0
  37. package/dist/src/jsx-runtime.cjs +1 -0
  38. package/dist/src/jsx-runtime.d.ts +7 -0
  39. package/dist/src/jsx-runtime.d.ts.map +1 -0
  40. package/dist/src/jsx-runtime.js +17 -0
  41. package/dist/src/jsx-runtime.mjs +1 -0
  42. package/dist/src/lib/client/chunk-D3VTWVNJ.mjs +1 -0
  43. package/dist/src/lib/client/index.cjs +1 -0
  44. package/dist/src/lib/client/index.d.ts +54 -0
  45. package/dist/src/lib/client/index.d.ts.map +1 -0
  46. package/dist/src/lib/client/index.js +142 -0
  47. package/dist/src/lib/client/index.mjs +1 -0
  48. package/dist/src/lib/client/router-next-UYIYMRJL.mjs +1 -0
  49. package/dist/src/lib/context/context.cjs +1 -0
  50. package/dist/src/lib/context/context.d.ts +337 -0
  51. package/dist/src/lib/context/context.d.ts.map +1 -0
  52. package/dist/src/lib/context/context.js +462 -0
  53. package/dist/src/lib/context/context.mjs +1 -0
  54. package/dist/src/lib/context/index.cjs +1 -0
  55. package/dist/src/lib/context/index.d.ts +3 -0
  56. package/dist/src/lib/context/index.d.ts.map +1 -0
  57. package/dist/src/lib/context/index.js +5 -0
  58. package/dist/src/lib/context/index.mjs +1 -0
  59. package/dist/src/lib/control-flow.cjs +1 -0
  60. package/dist/src/lib/control-flow.d.ts +4 -0
  61. package/dist/src/lib/control-flow.d.ts.map +1 -0
  62. package/dist/src/lib/control-flow.js +1 -0
  63. package/dist/src/lib/control-flow.mjs +1 -0
  64. package/dist/src/lib/core.cjs +1 -0
  65. package/dist/src/lib/core.d.ts +90 -0
  66. package/dist/src/lib/core.d.ts.map +1 -0
  67. package/dist/src/lib/core.js +181 -0
  68. package/dist/src/lib/core.mjs +1 -0
  69. package/dist/src/lib/directives/lit.cjs +1 -0
  70. package/dist/src/lib/directives/lit.d.ts +2 -0
  71. package/dist/src/lib/directives/lit.d.ts.map +1 -0
  72. package/dist/src/lib/directives/lit.js +31 -0
  73. package/dist/src/lib/directives/lit.mjs +0 -0
  74. package/dist/src/lib/env/index.cjs +1 -0
  75. package/dist/src/lib/env/index.d.ts +39 -0
  76. package/dist/src/lib/env/index.d.ts.map +1 -0
  77. package/dist/src/lib/env/index.js +50 -0
  78. package/dist/src/lib/env/index.mjs +1 -0
  79. package/dist/src/lib/i18n/index.cjs +1 -0
  80. package/dist/src/lib/i18n/index.d.ts +79 -0
  81. package/dist/src/lib/i18n/index.d.ts.map +1 -0
  82. package/dist/src/lib/i18n/index.js +127 -0
  83. package/dist/src/lib/i18n/index.mjs +1 -0
  84. package/dist/src/lib/index.cjs +1 -0
  85. package/dist/src/lib/index.d.ts +83 -0
  86. package/dist/src/lib/index.d.ts.map +1 -0
  87. package/dist/src/lib/index.js +147 -0
  88. package/dist/src/lib/index.mjs +1 -0
  89. package/dist/src/lib/isomorphic.cjs +1 -0
  90. package/dist/src/lib/isomorphic.d.ts +69 -0
  91. package/dist/src/lib/isomorphic.d.ts.map +1 -0
  92. package/dist/src/lib/isomorphic.js +92 -0
  93. package/dist/src/lib/isomorphic.mjs +1 -0
  94. package/dist/src/lib/jsx-runtime/index.cjs +1 -0
  95. package/dist/src/lib/jsx-runtime/index.d.ts +13 -0
  96. package/dist/src/lib/jsx-runtime/index.d.ts.map +1 -0
  97. package/dist/src/lib/jsx-runtime/index.js +23 -0
  98. package/dist/src/lib/jsx-runtime/index.mjs +1 -0
  99. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -0
  100. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.d.ts +2 -0
  101. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.d.ts.map +1 -0
  102. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.js +1 -0
  103. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -0
  104. package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +13 -0
  105. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts +21 -0
  106. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts.map +1 -0
  107. package/dist/src/lib/plugins/fluixi-routes-plugin.js +102 -0
  108. package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +13 -0
  109. package/dist/src/lib/plugins/index.cjs +13 -0
  110. package/dist/src/lib/plugins/index.d.ts +4 -0
  111. package/dist/src/lib/plugins/index.d.ts.map +1 -0
  112. package/dist/src/lib/plugins/index.js +3 -0
  113. package/dist/src/lib/plugins/index.mjs +13 -0
  114. package/dist/src/lib/plugins/route-codegen.cjs +8 -0
  115. package/dist/src/lib/plugins/route-codegen.d.ts +13 -0
  116. package/dist/src/lib/plugins/route-codegen.d.ts.map +1 -0
  117. package/dist/src/lib/plugins/route-codegen.js +81 -0
  118. package/dist/src/lib/plugins/route-codegen.mjs +8 -0
  119. package/dist/src/lib/plugins/route-scanner.cjs +1 -0
  120. package/dist/src/lib/plugins/route-scanner.d.ts +39 -0
  121. package/dist/src/lib/plugins/route-scanner.d.ts.map +1 -0
  122. package/dist/src/lib/plugins/route-scanner.js +216 -0
  123. package/dist/src/lib/plugins/route-scanner.mjs +1 -0
  124. package/dist/src/lib/plugins/vite.cjs +1 -0
  125. package/dist/src/lib/plugins/vite.d.ts +3 -0
  126. package/dist/src/lib/plugins/vite.d.ts.map +1 -0
  127. package/dist/src/lib/plugins/vite.js +1 -0
  128. package/dist/src/lib/plugins/vite.mjs +1 -0
  129. package/dist/src/lib/render/component.cjs +1 -0
  130. package/dist/src/lib/render/component.d.ts +328 -0
  131. package/dist/src/lib/render/component.d.ts.map +1 -0
  132. package/dist/src/lib/render/component.js +520 -0
  133. package/dist/src/lib/render/component.mjs +1 -0
  134. package/dist/src/lib/render/index.cjs +1 -0
  135. package/dist/src/lib/render/index.d.ts +113 -0
  136. package/dist/src/lib/render/index.d.ts.map +1 -0
  137. package/dist/src/lib/render/index.js +201 -0
  138. package/dist/src/lib/render/index.mjs +1 -0
  139. package/dist/src/lib/render/lazy.cjs +1 -0
  140. package/dist/src/lib/render/lazy.d.ts +28 -0
  141. package/dist/src/lib/render/lazy.d.ts.map +1 -0
  142. package/dist/src/lib/render/lazy.js +62 -0
  143. package/dist/src/lib/render/lazy.mjs +1 -0
  144. package/dist/src/lib/render/suspense.cjs +1 -0
  145. package/dist/src/lib/render/suspense.d.ts +25 -0
  146. package/dist/src/lib/render/suspense.d.ts.map +1 -0
  147. package/dist/src/lib/render/suspense.js +139 -0
  148. package/dist/src/lib/render/suspense.mjs +1 -0
  149. package/dist/src/lib/router-next/data.cjs +1 -0
  150. package/dist/src/lib/router-next/data.d.ts +101 -0
  151. package/dist/src/lib/router-next/data.d.ts.map +1 -0
  152. package/dist/src/lib/router-next/data.js +179 -0
  153. package/dist/src/lib/router-next/data.mjs +1 -0
  154. package/dist/src/lib/router-next/index.cjs +1 -0
  155. package/dist/src/lib/router-next/index.d.ts +156 -0
  156. package/dist/src/lib/router-next/index.d.ts.map +1 -0
  157. package/dist/src/lib/router-next/index.js +464 -0
  158. package/dist/src/lib/router-next/index.mjs +1 -0
  159. package/dist/src/lib/server/hydration.cjs +1 -0
  160. package/dist/src/lib/server/hydration.d.ts +238 -0
  161. package/dist/src/lib/server/hydration.d.ts.map +1 -0
  162. package/dist/src/lib/server/hydration.js +384 -0
  163. package/dist/src/lib/server/hydration.mjs +1 -0
  164. package/dist/src/lib/server/index.cjs +16 -0
  165. package/dist/src/lib/server/index.d.ts +91 -0
  166. package/dist/src/lib/server/index.d.ts.map +1 -0
  167. package/dist/src/lib/server/index.js +141 -0
  168. package/dist/src/lib/server/index.mjs +16 -0
  169. package/dist/src/lib/server/reactive.cjs +1 -0
  170. package/dist/src/lib/server/reactive.d.ts +298 -0
  171. package/dist/src/lib/server/reactive.d.ts.map +1 -0
  172. package/dist/src/lib/server/reactive.js +472 -0
  173. package/dist/src/lib/server/reactive.mjs +1 -0
  174. package/dist/src/lib/theme/index.cjs +1 -0
  175. package/dist/src/lib/theme/index.d.ts +51 -0
  176. package/dist/src/lib/theme/index.d.ts.map +1 -0
  177. package/dist/src/lib/theme/index.js +79 -0
  178. package/dist/src/lib/theme/index.mjs +1 -0
  179. package/dist/src/lib/utils/index.cjs +1 -0
  180. package/dist/src/lib/utils/index.d.ts +379 -0
  181. package/dist/src/lib/utils/index.d.ts.map +1 -0
  182. package/dist/src/lib/utils/index.js +527 -0
  183. package/dist/src/lib/utils/index.mjs +1 -0
  184. package/dist/src/rx.cjs +1 -0
  185. package/dist/src/rx.d.ts +2 -0
  186. package/dist/src/rx.d.ts.map +1 -0
  187. package/dist/src/rx.js +101 -0
  188. package/dist/src/rx.mjs +0 -0
  189. package/dist/src/server.cjs +16 -0
  190. package/dist/src/server.d.ts +64 -0
  191. package/dist/src/server.d.ts.map +1 -0
  192. package/dist/src/server.js +63 -0
  193. package/dist/src/server.mjs +16 -0
  194. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  195. package/package.json +128 -0
  196. package/src/index.ts +31 -0
  197. package/types/fluixi.d.ts +5 -0
  198. package/types/vinxi.d.ts +50 -0
@@ -0,0 +1,464 @@
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
+ */
15
+ import { createSignal, createMemo, createResource, createEffect, getOwner, onCleanup, untrack, } from '@fluixi/reactive/signal';
16
+ import { insert, createComponent, createNativeElement, setAttribute, } from '@fluixi/dom';
17
+ // Reactive context (not fluixi createProvider, which destructures children
18
+ // eagerly) so a `get children()` getter renders AFTER the context is stamped.
19
+ import { createContext, useContext } from '@fluixi/reactive/signal';
20
+ import { get } from '@fluixi/utils/object';
21
+ import { deepEqual } from '@fluixi/utils/compare';
22
+ import { createRouter as createCoreRouter, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
23
+ const RouterContext = createContext();
24
+ const OutletContext = createContext();
25
+ const LevelParamsContext = createContext();
26
+ const RouteDataContext = createContext();
27
+ // ─── Rendering ────────────────────────────────────────────────────────────────
28
+ /** Render one matched layer: load its data, stamp its contexts, run its component. */
29
+ function renderLayer(m, depth, base) {
30
+ // Load this layer's data once. createResource integrates with the nearest
31
+ // Suspense; it is owned by the enclosing memo, so it is recreated only when this
32
+ // layer's route changes (not on deeper navigation).
33
+ let data;
34
+ const loader = m.route.data;
35
+ if (typeof loader === 'function') {
36
+ let cancelled = false;
37
+ onCleanup(() => {
38
+ cancelled = true;
39
+ });
40
+ const owner = getOwner();
41
+ const [resource] = createResource(() => loader({ params: base.params, location: base.location }, owner, () => cancelled));
42
+ data = resource;
43
+ }
44
+ const buildPage = () => RouteDataContext.Provider({
45
+ value: data,
46
+ get children() {
47
+ return LevelParamsContext.Provider({
48
+ value: m.params,
49
+ get children() {
50
+ return OutletContext.Provider({
51
+ value: { depth, ...base },
52
+ get children() {
53
+ return createComponent(m.component, {});
54
+ },
55
+ });
56
+ },
57
+ });
58
+ },
59
+ });
60
+ if (!data)
61
+ return buildPage();
62
+ // Gate on the data being resolved — like the old router — so the page reads
63
+ // ready data (rd() returns the value, not a thrown promise) and works without a
64
+ // Suspense boundary. Signal-gated (`ready` only flips when loading actually
65
+ // changes) so navigation re-renders don't re-run this and rebuild the page.
66
+ const res = data;
67
+ const isLoading = () => res.loading;
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).
70
+ const [ready, setReady] = createSignal(untrack(() => !isLoading()));
71
+ createEffect(() => setReady(!isLoading()));
72
+ return createMemo(() => (ready() ? buildPage() : null));
73
+ }
74
+ /**
75
+ * Renders the next matched layer, reactively but keyed on its component ref so the
76
+ * child is only rebuilt when the route at this depth changes. Used by both <Outlet/>
77
+ * and the Router root.
78
+ */
79
+ function renderChild(ctx) {
80
+ const childIdx = ctx.depth + 1;
81
+ // Gate rendering on the child component reference through a SIGNAL, not a direct
82
+ // memo read. This reactive core marks the whole downstream memo chain stale on any
83
+ // signal write (no value check), so a memo that read `matched` would re-run on
84
+ // every navigation and re-render the child. A signal only notifies when its value
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
87
+ // this level untouched. The child is rendered INLINE (no detached createRoot), so
88
+ // it stays connected to the Suspense / context owner chain.
89
+ const [childComp, setChildComp] = createSignal(untrack(() => ctx.matched()[childIdx]?.component));
90
+ createEffect(() => {
91
+ const c = ctx.matched()[childIdx]?.component;
92
+ setChildComp(() => c);
93
+ });
94
+ return createMemo(() => {
95
+ const component = childComp();
96
+ 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
99
+ // Outlets have no `notFound`, so they stay null (a missing child is just an empty Outlet).
100
+ return ctx.notFound ? ctx.notFound() : null;
101
+ }
102
+ const m = untrack(() => ctx.matched())[childIdx];
103
+ return renderLayer(m, childIdx, {
104
+ matched: ctx.matched,
105
+ params: ctx.params,
106
+ location: ctx.location,
107
+ });
108
+ });
109
+ }
110
+ /**
111
+ * Resolve the Router's `notFound` prop to a renderable view. A component is instantiated; an
112
+ * 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.
115
+ */
116
+ function renderNotFound(notFound) {
117
+ if (notFound == null)
118
+ return buildDefaultNotFound();
119
+ return typeof notFound === 'function' ? createComponent(notFound, {}) : notFound;
120
+ }
121
+ /** Minimal, unstyled-enough default 404 (single element so it hydrates cleanly). */
122
+ function buildDefaultNotFound() {
123
+ const el = createNativeElement('div');
124
+ setAttribute(el, 'class', 'fx-not-found');
125
+ setAttribute(el, 'role', 'main');
126
+ setAttribute(el, 'style', 'min-height:60vh;display:grid;place-content:center;gap:.25rem;text-align:center;' +
127
+ 'font-family:system-ui,-apple-system,sans-serif');
128
+ const h1 = createNativeElement('h1');
129
+ setAttribute(h1, 'style', 'margin:0;font-size:2.5rem;font-weight:700');
130
+ insert(h1, '404');
131
+ const p = createNativeElement('p');
132
+ setAttribute(p, 'style', 'margin:0;opacity:.7');
133
+ insert(p, 'This page could not be found.');
134
+ el.appendChild(h1);
135
+ el.appendChild(p);
136
+ return el;
137
+ }
138
+ export function Router(props) {
139
+ const history = props.history ?? createBrowserHistory();
140
+ const router = createCoreRouter({
141
+ routes: props.routes,
142
+ base: props.base,
143
+ history,
144
+ });
145
+ const [state, setState] = createSignal(router.state());
146
+ const unsub = router.subscribe(setState);
147
+ onCleanup(() => {
148
+ unsub();
149
+ router.destroy();
150
+ });
151
+ // Drop pathless layers (no component) — their params still appear in the merged
152
+ // params exposed by RouterContext.
153
+ const matched = createMemo(() => (state().match?.matched ?? []).filter((m) => m.component));
154
+ const ctx = {
155
+ router: router,
156
+ location: createMemo(() => state().location),
157
+ params: createMemo(() => state().match?.params ?? {}),
158
+ };
159
+ // Render depth 0 (a virtual parent at depth -1 so the root reuses the same path).
160
+ const root = {
161
+ depth: -1,
162
+ matched,
163
+ params: ctx.params,
164
+ location: ctx.location,
165
+ // Only the root carries this — renderChild shows it when the URL matched nothing.
166
+ notFound: () => renderNotFound(props.notFound),
167
+ };
168
+ // Build the root view once, lazily, inside the provider's children getter: the
169
+ // 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
171
+ // root chain from being rebuilt on every navigation.
172
+ let rootView;
173
+ return RouterContext.Provider({
174
+ value: ctx,
175
+ get children() {
176
+ return (rootView ??= renderChild(root));
177
+ },
178
+ });
179
+ }
180
+ // ─── Outlet ───────────────────────────────────────────────────────────────────
181
+ /** Renders this layout's child route, inside this layout's context. */
182
+ export function Outlet() {
183
+ const ctx = useContext(OutletContext);
184
+ if (!ctx)
185
+ return null;
186
+ return renderChild(ctx);
187
+ }
188
+ /** Navigate to `to` on mount (replace by default). Renders nothing. */
189
+ export function Redirect(props) {
190
+ const ctx = useContext(RouterContext);
191
+ if (!ctx || typeof window === 'undefined')
192
+ return null;
193
+ // Navigate AFTER the current render commits. Navigating during render (an effect
194
+ // running mid-render) updated the location but the route never re-matched, so the
195
+ // target never showed. Guard against firing if this Redirect is torn down first.
196
+ let cancelled = false;
197
+ onCleanup(() => (cancelled = true));
198
+ queueMicrotask(() => {
199
+ if (!cancelled)
200
+ ctx.router.navigate(props.to, { replace: props.replace !== false });
201
+ });
202
+ return null;
203
+ }
204
+ // ─── Hooks ──────────────────────────────────────────────────────────────────
205
+ // Safe stub returned by useRouter on the SERVER when no RouterContext is on the owner
206
+ // chain. That happens during a late/detached re-render — e.g. a slow data resource
207
+ // settling AFTER renderToStringAsync already serialized the response and disposed the
208
+ // root — whose output is discarded. Returning this (instead of throwing) prevents a
209
+ // floating rejection that crashes the dev server / shows an error overlay seconds
210
+ // after the page already rendered.
211
+ const _ssrRouterStub = {
212
+ router: { navigate: () => { } },
213
+ location: () => ({ pathname: '', search: '', hash: '', query: {} }),
214
+ params: () => ({}),
215
+ };
216
+ export function useRouter() {
217
+ const ctx = useContext(RouterContext);
218
+ if (ctx)
219
+ return ctx;
220
+ // On the server, degrade to a no-op stub (see above). On the client, a missing
221
+ // router is genuine misuse — throw.
222
+ if (typeof window === 'undefined')
223
+ return _ssrRouterStub;
224
+ throw new Error('[router-next] useRouter must be used within <Router>');
225
+ }
226
+ /**
227
+ * Reactive merged params across all matched layers, as an accessor. Deep-equal
228
+ * memoized so it only notifies when a value actually changes (the underlying
229
+ * params object is rebuilt on every transition).
230
+ */
231
+ export function useParams() {
232
+ const params = useRouter().params;
233
+ return createMemo(() => params(), {}, {
234
+ equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
235
+ });
236
+ }
237
+ /** Params introduced at the nearest matched layer. */
238
+ export function useLevelParams() {
239
+ return useContext(LevelParamsContext) ?? {};
240
+ }
241
+ /**
242
+ * Reactive accessor for a single route param. Default `===` equality on the
243
+ * string value, so it only re-triggers when THIS key changes — not when other
244
+ * params at the same level change.
245
+ */
246
+ export function useParam(key) {
247
+ const params = useParams();
248
+ return createMemo(() => params()[key]);
249
+ }
250
+ /**
251
+ * Derive a stable value from route params. Only re-triggers when the selector's
252
+ * return value changes. For object returns, pass a custom `equals`.
253
+ *
254
+ * @example
255
+ * const ids = useParamSelector((p) => ({ wsId: p.workspaceId, chId: p.channelId }), deepEqual);
256
+ */
257
+ export function useParamSelector(selector, equals) {
258
+ const params = useParams();
259
+ return createMemo(() => selector(params()), undefined, equals ? { equals } : undefined);
260
+ }
261
+ /**
262
+ * Match a path pattern against the current pathname, reactively. Returns the
263
+ * matched `{ params, path }` or null.
264
+ */
265
+ export function useMatch(path) {
266
+ const location = useLocation();
267
+ return createMemo(() => matchPath(typeof path === 'function' ? path() : path, location.pathname));
268
+ }
269
+ /**
270
+ * Read/write the current query string. Returns `[params, setParams]`: `params`
271
+ * is a reactive accessor of the parsed query; `setParams` merges the given keys
272
+ * (null deletes) and navigates. Matches the old router.
273
+ */
274
+ export function useSearchParams() {
275
+ const location = useLocation();
276
+ const navigate = useNavigate();
277
+ const params = createMemo(() => location.query);
278
+ const setParams = (next, options) => {
279
+ const current = new URLSearchParams(location.search);
280
+ for (const [k, v] of Object.entries(next)) {
281
+ if (v === null)
282
+ current.delete(k);
283
+ else
284
+ current.set(k, v);
285
+ }
286
+ const search = current.toString();
287
+ navigate(location.pathname + (search ? `?${search}` : ''), options);
288
+ };
289
+ return [params, setParams];
290
+ }
291
+ /**
292
+ * Stable accessor from any reactive source with deep-equal memoization. Pass a
293
+ * `key` to select a nested property. Only re-triggers downstream when the value
294
+ * (or selected field) actually changes.
295
+ *
296
+ * @example
297
+ * const ws = select(store.activeWorkspace); // whole object
298
+ * const id = select(store.activeWorkspace, 'id'); // single field
299
+ */
300
+ export function select(source, key) {
301
+ const equals = (a, b) => deepEqual(a, b);
302
+ if (key !== undefined) {
303
+ return createMemo(() => get(source(), key), undefined, { equals });
304
+ }
305
+ return createMemo(source, undefined, { equals });
306
+ }
307
+ /**
308
+ * The current location as a reactive object: reading a property (pathname, etc.)
309
+ * subscribes to location changes. Matches the old router's useLocation().
310
+ */
311
+ export function useLocation() {
312
+ const { location } = useRouter();
313
+ return new Proxy({}, {
314
+ get: (_t, key) => location()[key],
315
+ has: (_t, key) => key in location(),
316
+ ownKeys: () => Reflect.ownKeys(location()),
317
+ getOwnPropertyDescriptor: (_t, key) => Object.getOwnPropertyDescriptor(location(), key),
318
+ });
319
+ }
320
+ export function useNavigate() {
321
+ // useRouter is SSR-safe (returns a no-op stub on the server when the context is
322
+ // detached), so navigation degrades to a no-op during SSR and only throws on
323
+ // genuine client-side misuse outside <Router>.
324
+ const { router } = useRouter();
325
+ return (to, options) => router.navigate(to, options);
326
+ }
327
+ /**
328
+ * The resolved data for the nearest route that declared a `data` loader. Always
329
+ * returns a callable accessor (matching the old router): it suspends under
330
+ * <Suspense> while loading and yields undefined when no ancestor declared data.
331
+ */
332
+ export function useRouteData() {
333
+ const acc = useContext(RouteDataContext);
334
+ return acc ?? (() => undefined);
335
+ }
336
+ const read = (v) => typeof v === 'function' ? v() : v;
337
+ export function Link(props) {
338
+ const ctx = useContext(RouterContext);
339
+ const isServer = typeof document === 'undefined';
340
+ const navigate = () => ctx?.router.navigate(read(props.href) ?? '', { replace: read(props.replace) });
341
+ // createNativeElement branches on isServer → a real ServerNode <a> (serialized as a true
342
+ // element, not an escaped string) on the server, a real DOM <a> on the client.
343
+ const a = createNativeElement('a');
344
+ setAttribute(a, 'href', read(props.href) ?? '');
345
+ if (props.slot)
346
+ setAttribute(a, 'slot', read(props.slot));
347
+ // Client only (ServerNode has no addEventListener). Direct listener (not delegated) so
348
+ // preventDefault reliably blocks navigation.
349
+ if (typeof a.addEventListener === 'function') {
350
+ a.addEventListener('click', (e) => {
351
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
352
+ return;
353
+ e.preventDefault();
354
+ navigate();
355
+ });
356
+ }
357
+ else {
358
+ // Server: set the static class so the markup matches the client's first render.
359
+ const base = (read(props.className ?? props.class) ?? '');
360
+ if (base)
361
+ setAttribute(a, 'class', base);
362
+ }
363
+ // Client only: keep href + active class in sync with reactive props / location.
364
+ // (Skipped on the server — `a` is a ServerNode without these DOM setters; the static
365
+ // href/class were set above.)
366
+ if (typeof a.addEventListener === 'function') {
367
+ createEffect(() => {
368
+ const href = read(props.href) ?? '';
369
+ a.setAttribute('href', href);
370
+ const base = read(props.className ?? props.class) ?? '';
371
+ const activeClass = read(props.activeClass) ?? '';
372
+ const active = read(props.active) ?? ctx?.location().pathname === href;
373
+ a.className = [base, active ? activeClass : ''].filter(Boolean).join(' ');
374
+ });
375
+ }
376
+ const linkChildren = props.children; // read once (getter builds fresh nodes per access)
377
+ if (linkChildren != null)
378
+ insert(a, linkChildren);
379
+ return a;
380
+ }
381
+ /** Alias for {@link Link}. */
382
+ export const A = Link;
383
+ /**
384
+ * A `<form>` wired to an action: on submit it builds `FormData` from the form and calls
385
+ * `action(formData)` (which tracks submission state via `useSubmission`), without a full
386
+ * page navigation. The markup carries `method="post"` + `action={action.url}` so it's
387
+ * progressive-enhancement-ready.
388
+ *
389
+ * <Form action={save}>
390
+ * <input name="title" />
391
+ * <button>Save</button>
392
+ * </Form>
393
+ */
394
+ export function Form(props) {
395
+ const action = props.action;
396
+ const cls = (props.className ?? props.class); // compiler passes class→className
397
+ const url = action.url ?? '';
398
+ // createNativeElement/setAttribute branch on isServer, so this builds a real ServerNode
399
+ // <form> on the server (serialized as a true element, NOT an escaped string) and a real
400
+ // DOM <form> on the client — hydration reuses the same element.
401
+ const form = createNativeElement('form');
402
+ setAttribute(form, 'method', 'post');
403
+ if (url)
404
+ setAttribute(form, 'action', url);
405
+ if (cls)
406
+ setAttribute(form, 'class', cls);
407
+ for (const [k, v] of Object.entries(props)) {
408
+ if (k === 'action' || k === 'children' || k === 'class' || k === 'className')
409
+ continue;
410
+ if (v != null && typeof v !== 'function')
411
+ setAttribute(form, k, String(v));
412
+ }
413
+ // Client only (ServerNode has no addEventListener). Direct listener so preventDefault
414
+ // reliably blocks the native submit/navigation. Fields are passed as a plain object so
415
+ // a `"use server"` action (JSON RPC) gets them unchanged. (File uploads are a follow-up.)
416
+ if (typeof form.addEventListener === 'function') {
417
+ form.addEventListener('submit', (e) => {
418
+ e.preventDefault();
419
+ const data = {};
420
+ new FormData(form).forEach((v, k) => {
421
+ data[k] = v;
422
+ });
423
+ Promise.resolve(action(data)).catch(() => {
424
+ /* error is captured in the submission state via useSubmission */
425
+ });
426
+ });
427
+ }
428
+ // Read children ONCE: the compiler emits `get children()` as a getter that builds fresh
429
+ // nodes on every access, so reading it twice (here + the null check) created a duplicate
430
+ // set — under hydration that meant two <input>s in the form.
431
+ const children = props.children;
432
+ if (children != null)
433
+ insert(form, children);
434
+ return form;
435
+ }
436
+ // ─── Hydration preload ───────────────────────────────────────────────────────────
437
+ /**
438
+ * Download the lazy component chunks for the route(s) matching `pathname` and resolve when
439
+ * they're all loaded. Await this BEFORE hydrating: once each matched lazy file-route is
440
+ * resolved it renders synchronously on the first pass (no Suspense fallback, no late
441
+ * re-render), so hydration adopts the server DOM in place instead of building a duplicate
442
+ * subtree next to it. No-op for routes with no lazy components. Safe to call when the URL
443
+ * matches nothing (resolves immediately).
444
+ */
445
+ export async function preloadRoutes(routes, pathname) {
446
+ const match = matchRoutes(routes, pathname);
447
+ if (!match)
448
+ return;
449
+ const loads = [];
450
+ for (const m of match.matched) {
451
+ const c = m.component;
452
+ if (c && typeof c.preload === 'function')
453
+ loads.push(c.preload());
454
+ }
455
+ await Promise.all(loads);
456
+ }
457
+ // ─── Re-exports ────────────────────────────────────────────────────────────────
458
+ export { createMemoryHistory, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
459
+ // Action/cache/lazy layer is orthogonal to routing — re-export so a route file can
460
+ // swap its import path wholesale. (Routing hooks above are the router-next ones.)
461
+ export { cache, action, revalidate, createAsync, useSubmission, useAction, } from './data.js';
462
+ export { lazy } from '../render/lazy.js';
463
+ // Parity with the retired '@fluixi/core/router' barrel (route codegen used it).
464
+ export { runWithOwner } from '@fluixi/reactive/signal';
@@ -0,0 +1 @@
1
+ import{createSignal as P,createMemo as d,createResource as ge,createEffect as w,getOwner as xe,onCleanup as A,untrack as R}from"@fluixi/reactive/signal";import{insert as h,createComponent as F,createNativeElement as g,setAttribute as u}from"@fluixi/dom";import{createContext as v,useContext as p}from"@fluixi/reactive/signal";import{get as he}from"@fluixi/utils/object";import{deepEqual as ve}from"@fluixi/utils/compare";import{createRouter as Te,createBrowserHistory as Ce,matchRoutes as be,matchPath as Re}from"@fluixi/router";import{createMemoryHistory as Vt,createBrowserHistory as Ft,matchRoutes as jt,matchPath as Ut}from"@fluixi/router";import{createResource as oe,createSignal as N}from"@fluixi/reactive/signal";var B=!1,C=new Map;function O(){return B}function E(e,n){C.set(e,n)}function L(e){return C.get(e)}function J(){C.clear()}function X(){if(typeof window>"u")return;let e=document.getElementById("__FLUIXI_HYDRATION__");e&&e.remove();let n=document.createTreeWalker(document.body,NodeFilter.SHOW_COMMENT,null),t=[],o;for(;o=n.nextNode();){let r=o,a=r.nodeValue||"";(a.startsWith("#")||a.startsWith("/"))&&t.push(r)}t.forEach(r=>r.remove()),J()}typeof window<"u"&&window.addEventListener("load",()=>{setTimeout(X,100)});function D(){return typeof window>"u"}import{asProvider as ne}from"@fluixi/dom";import{createContext as re,useContext as Qe}from"@fluixi/reactive/signal";import{readChildren as nt}from"@fluixi/reactive/signal";import{registerCreateComponent as Y,asComponent as G}from"@fluixi/dom";import{untrack as Q,onCleanup as Ne,getOwner as _e,createEffect as He,createMemo as Ve,createSignal as Fe,isSignal as Z,batch as je,runWithOwner as Ie}from"@fluixi/reactive/signal";function k(e){Y(ee)}function ee(e,n){return te(e,n)}function te(e,n){let t=typeof e=="function"?Q(()=>e(n)):e;if(t!=null&&(typeof t=="function"||typeof t=="object")&&!Z(t)){try{t.$name=e.name}catch{}return G(t)}return t}k("index");function M(e){let n=re(e),t=o=>(o.debug,n.Provider({value:o.value,get children(){return o.children},debug:o.debug}));return typeof Symbol<"u"&&Symbol.for&&(t.$$typeof=Symbol.for("react.provider")),t.$$typeof=Symbol.for("react.provider"),{id:n.id,Provider:ne(t),defaultValue:e}}var m=new Map,y=new Map;function ae(e,n){return async(...t)=>{let o=n+":"+JSON.stringify(t);if(O()||typeof window<"u"){let a=L(o);if(a!==void 0)return a}if(m.has(o))return m.get(o);if(y.has(o))return y.get(o);let r=Promise.resolve(e(...t)).then(a=>(m.set(o,a),y.delete(o),D()&&E(o,a),a),a=>{throw y.delete(o),a});return y.set(o,r),r}}var[dt,ie]=N(0),se=new Set;function ce(e){let n=typeof e=="string"?t=>t.startsWith(e):e;for(let t of m.keys())n(t)&&(m.delete(t),se.add(t));ie(t=>t+1)}function ue(e,n){let[t]=oe(n||(()=>!0),e);return t}var b=new Map;function _(e){return b.has(e)||b.set(e,N(void 0)),b.get(e)}function H(e,n){let t=n||e.name||"action",o=async r=>{let[,a]=_(t),i=()=>a(void 0),s=()=>o(r);a({input:r,pending:!0,clear:i,retry:s});try{let c=await e(r);return a({input:r,result:c,pending:!1,clear:i,retry:s}),c}catch(c){throw a({input:r,error:c,pending:!1,clear:i,retry:s}),c}};return o.url=t,o.with=r=>H((...a)=>e(r,...a),n),o}function de(e){let[n]=_(e.url);return n}function fe(e){return e}var le=M(),ft=le.Provider;import{untrack as V,createMemo as pe,createResource as ye}from"@fluixi/reactive/signal";function me(e,n){let t,o,r;function a(){return o||(o=e().then(s=>t=s.default)),o}n?.preload&&a();let i=(s=>{if(t){let c=t;return V(()=>c(s))}if(!r){let[c]=ye(()=>a());if(r=c,c.error)throw c.error}return pe(()=>{let c=r();return c?V(()=>c(s)):void 0},void 0,{name:"lazy"})});return i.preload=()=>a().then(()=>{}),i.$lazy=!0,i.loader=e,i}import{runWithOwner as Wt}from"@fluixi/reactive/signal";var T=v(),j=v(),U=v(),I=v();function Pe(e,n,t){let o,r=e.route.data;if(typeof r=="function"){let x=!1;A(()=>{x=!0});let $=xe(),[q]=ge(()=>r({params:t.params,location:t.location},$,()=>x));o=q}let a=()=>I.Provider({value:o,get children(){return U.Provider({value:e.params,get children(){return j.Provider({value:{depth:n,...t},get children(){return F(e.component,{})}})}})}});if(!o)return a();let i=o,s=()=>i.loading,[c,f]=P(R(()=>!s()));return w(()=>f(!s())),d(()=>c()?a():null)}function z(e){let n=e.depth+1,[t,o]=P(R(()=>e.matched()[n]?.component));return w(()=>{let r=e.matched()[n]?.component;o(()=>r)}),d(()=>{if(!t())return e.notFound?e.notFound():null;let a=R(()=>e.matched())[n];return Pe(a,n,{matched:e.matched,params:e.params,location:e.location})})}function we(e){return e==null?Ae():typeof e=="function"?F(e,{}):e}function Ae(){let e=g("div");u(e,"class","fx-not-found"),u(e,"role","main"),u(e,"style","min-height:60vh;display:grid;place-content:center;gap:.25rem;text-align:center;font-family:system-ui,-apple-system,sans-serif");let n=g("h1");u(n,"style","margin:0;font-size:2.5rem;font-weight:700"),h(n,"404");let t=g("p");return u(t,"style","margin:0;opacity:.7"),h(t,"This page could not be found."),e.appendChild(n),e.appendChild(t),e}function bt(e){let n=e.history??Ce(),t=Te({routes:e.routes,base:e.base,history:n}),[o,r]=P(t.state()),a=t.subscribe(r);A(()=>{a(),t.destroy()});let i=d(()=>(o().match?.matched??[]).filter(x=>x.component)),s={router:t,location:d(()=>o().location),params:d(()=>o().match?.params??{})},c={depth:-1,matched:i,params:s.params,location:s.location,notFound:()=>we(e.notFound)},f;return T.Provider({value:s,get children(){return f??=z(c)}})}function Rt(){let e=p(j);return e?z(e):null}function Pt(e){let n=p(T);if(!n||typeof window>"u")return null;let t=!1;return A(()=>t=!0),queueMicrotask(()=>{t||n.router.navigate(e.to,{replace:e.replace!==!1})}),null}var Se={router:{navigate:()=>{}},location:()=>({pathname:"",search:"",hash:"",query:{}}),params:()=>({})};function S(){let e=p(T);if(e)return e;if(typeof window>"u")return Se;throw new Error("[router-next] useRouter must be used within <Router>")}function K(){let e=S().params;return d(()=>e(),{},{equals:(n,t)=>JSON.stringify(n)===JSON.stringify(t)})}function wt(){return p(U)??{}}function At(e){let n=K();return d(()=>n()[e])}function St(e,n){let t=K();return d(()=>e(t()),void 0,n?{equals:n}:void 0)}function Ot(e){let n=W();return d(()=>Re(typeof e=="function"?e():e,n.pathname))}function Et(){let e=W(),n=Oe();return[d(()=>e.query),(r,a)=>{let i=new URLSearchParams(e.search);for(let[c,f]of Object.entries(r))f===null?i.delete(c):i.set(c,f);let s=i.toString();n(e.pathname+(s?`?${s}`:""),a)}]}function Lt(e,n){let t=(o,r)=>ve(o,r);return n!==void 0?d(()=>he(e(),n),void 0,{equals:t}):d(e,void 0,{equals:t})}function W(){let{location:e}=S();return new Proxy({},{get:(n,t)=>e()[t],has:(n,t)=>t in e(),ownKeys:()=>Reflect.ownKeys(e()),getOwnPropertyDescriptor:(n,t)=>Object.getOwnPropertyDescriptor(e(),t)})}function Oe(){let{router:e}=S();return(n,t)=>e.navigate(n,t)}function Dt(){return p(I)??(()=>{})}var l=e=>typeof e=="function"?e():e;function Ee(e){let n=p(T),t=typeof document>"u",o=()=>n?.router.navigate(l(e.href)??"",{replace:l(e.replace)}),r=g("a");if(u(r,"href",l(e.href)??""),e.slot&&u(r,"slot",l(e.slot)),typeof r.addEventListener=="function")r.addEventListener("click",i=>{i.metaKey||i.ctrlKey||i.shiftKey||i.altKey||(i.preventDefault(),o())});else{let i=l(e.className??e.class)??"";i&&u(r,"class",i)}typeof r.addEventListener=="function"&&w(()=>{let i=l(e.href)??"";r.setAttribute("href",i);let s=l(e.className??e.class)??"",c=l(e.activeClass)??"",f=l(e.active)??n?.location().pathname===i;r.className=[s,f?c:""].filter(Boolean).join(" ")});let a=e.children;return a!=null&&h(r,a),r}var kt=Ee;function Mt(e){let n=e.action,t=e.className??e.class,o=n.url??"",r=g("form");u(r,"method","post"),o&&u(r,"action",o),t&&u(r,"class",t);for(let[i,s]of Object.entries(e))i==="action"||i==="children"||i==="class"||i==="className"||s!=null&&typeof s!="function"&&u(r,i,String(s));typeof r.addEventListener=="function"&&r.addEventListener("submit",i=>{i.preventDefault();let s={};new FormData(r).forEach((c,f)=>{s[f]=c}),Promise.resolve(n(s)).catch(()=>{})});let a=e.children;return a!=null&&h(r,a),r}async function Nt(e,n){let t=be(e,n);if(!t)return;let o=[];for(let r of t.matched){let a=r.component;a&&typeof a.preload=="function"&&o.push(a.preload())}await Promise.all(o)}export{kt as A,Mt as Form,Ee as Link,Rt as Outlet,Pt as Redirect,bt as Router,H as action,ae as cache,ue as createAsync,Ft as createBrowserHistory,Vt as createMemoryHistory,me as lazy,Ut as matchPath,jt as matchRoutes,Nt as preloadRoutes,ce as revalidate,Wt as runWithOwner,Lt as select,fe as useAction,wt as useLevelParams,W as useLocation,Ot as useMatch,Oe as useNavigate,At as useParam,St as useParamSelector,K as useParams,Dt as useRouteData,S as useRouter,Et as useSearchParams,de as useSubmission};
@@ -0,0 +1 @@
1
+ "use strict";var a=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var w=(t,n)=>{for(var e in n)a(t,e,{get:n[e],enumerable:!0})},x=(t,n,e,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of g(n))!m.call(t,o)&&o!==e&&a(t,o,{get:()=>n[o],enumerable:!(r=l(n,o))||r.enumerable});return t};var h=t=>x(a({},"__esModule",{value:!0}),t);var F={};w(F,{cleanupHydration:()=>y,clearHydrationData:()=>p,clearHydrationDataKey:()=>D,createHydratableResource:()=>M,deserializeHydrationData:()=>b,generateMarkerID:()=>v,getHydrationData:()=>s,hasHydrationData:()=>_,hydrationMarker:()=>E,hydrationMarkerEnd:()=>O,hydrationMarkerStart:()=>N,isClientEnvironment:()=>C,isHydrating:()=>f,isServerEnvironment:()=>k,serializeHydrationData:()=>I,setHydrating:()=>T,setHydrationData:()=>d,useHydrationData:()=>j});module.exports=h(F);var c=!1,i=new Map,H=0;function f(){return c}function T(t){c=t}function d(t,n){i.set(t,n)}function s(t){return i.get(t)}function _(t){return i.has(t)}function p(){i.clear()}function D(t){i.delete(t)}function I(){if(i.size===0)return"";let t=Object.fromEntries(i);return`<script type="application/json" id="__FLUIXI_HYDRATION__">${JSON.stringify(t)}<\/script>`}function b(){if(typeof window>"u")return!1;let t=document.getElementById("__FLUIXI_HYDRATION__");if(!t||t.getAttribute("type")!=="application/json")return!1;try{let n=t.textContent||"{}",e=JSON.parse(n);for(let[r,o]of Object.entries(e))i.set(r,o);return!0}catch(n){return console.error("Failed to deserialize hydration data:",n),!1}}function v(){return`h${H++}`}function E(t){return`<!--#${t}-->`}function N(t){return`<!--#${t}-->`}function O(t){return`<!--/${t}-->`}function M(t,n){return async()=>{if(f()||typeof window<"u"){let r=s(t);if(r!==void 0)return r}let e=await n();return typeof window>"u"&&d(t,e),e}}function j(t,n){let e=s(t);if(e!==void 0)return e;let r=typeof n=="function"?n():n;return typeof window>"u"&&d(t,r),r}function y(){if(typeof window>"u")return;let t=document.getElementById("__FLUIXI_HYDRATION__");t&&t.remove();let n=document.createTreeWalker(document.body,NodeFilter.SHOW_COMMENT,null),e=[],r;for(;r=n.nextNode();){let o=r,u=o.nodeValue||"";(u.startsWith("#")||u.startsWith("/"))&&e.push(o)}e.forEach(o=>o.remove()),p()}typeof window<"u"&&window.addEventListener("load",()=>{setTimeout(y,100)});function k(){return typeof window>"u"}function C(){return typeof window<"u"}