@fluixi/core 1.0.0-alpha.92 → 1.0.0-alpha.94

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 (113) hide show
  1. package/dist/cdn/core.global.js +2 -2
  2. package/dist/src/cdn/{chunk-MDJLC3PE.mjs → chunk-7XM7Q5VR.mjs} +271 -229
  3. package/dist/src/cdn/core.cjs +360 -253
  4. package/dist/src/cdn/core.mjs +92 -20
  5. package/dist/src/cdn/{router-JYFZP3EZ.mjs → router-VOY43YNM.mjs} +7 -3
  6. package/dist/src/index.cjs +548 -232
  7. package/dist/src/index.mjs +549 -224
  8. package/dist/src/lib/client/chunk-TSXM2PNY.mjs +53 -0
  9. package/dist/src/lib/client/index.cjs +310 -176
  10. package/dist/src/lib/client/index.d.ts.map +1 -1
  11. package/dist/src/lib/client/index.js +33 -18
  12. package/dist/src/lib/client/index.mjs +86 -15
  13. package/dist/src/lib/client/{router-PHLUYVQU.mjs → router-BDVPLLYX.mjs} +244 -99
  14. package/dist/src/lib/context/context.cjs +35 -72
  15. package/dist/src/lib/context/context.d.ts +45 -291
  16. package/dist/src/lib/context/context.d.ts.map +1 -1
  17. package/dist/src/lib/context/context.js +65 -397
  18. package/dist/src/lib/context/context.mjs +38 -73
  19. package/dist/src/lib/context/index.cjs +35 -72
  20. package/dist/src/lib/context/index.d.ts +2 -2
  21. package/dist/src/lib/context/index.d.ts.map +1 -1
  22. package/dist/src/lib/context/index.js +1 -5
  23. package/dist/src/lib/context/index.mjs +38 -73
  24. package/dist/src/lib/core.cjs +547 -231
  25. package/dist/src/lib/core.d.ts +5 -3
  26. package/dist/src/lib/core.d.ts.map +1 -1
  27. package/dist/src/lib/core.js +6 -4
  28. package/dist/src/lib/core.mjs +548 -223
  29. package/dist/src/lib/env/index.cjs +1 -17
  30. package/dist/src/lib/env/index.d.ts +2 -14
  31. package/dist/src/lib/env/index.d.ts.map +1 -1
  32. package/dist/src/lib/env/index.js +2 -26
  33. package/dist/src/lib/env/index.mjs +1 -17
  34. package/dist/src/lib/i18n/index.cjs +17 -2
  35. package/dist/src/lib/i18n/index.d.ts +10 -0
  36. package/dist/src/lib/i18n/index.d.ts.map +1 -1
  37. package/dist/src/lib/i18n/index.js +17 -1
  38. package/dist/src/lib/i18n/index.mjs +17 -2
  39. package/dist/src/lib/index.cjs +64 -99
  40. package/dist/src/lib/index.mjs +43 -76
  41. package/dist/src/lib/isomorphic.cjs +9 -4
  42. package/dist/src/lib/isomorphic.d.ts +9 -2
  43. package/dist/src/lib/isomorphic.d.ts.map +1 -1
  44. package/dist/src/lib/isomorphic.js +11 -4
  45. package/dist/src/lib/isomorphic.mjs +9 -4
  46. package/dist/src/lib/jsx-runtime/index.cjs +1 -1
  47. package/dist/src/lib/jsx-runtime/index.mjs +1 -1
  48. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
  49. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
  50. package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +2 -2
  51. package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +2 -2
  52. package/dist/src/lib/plugins/index.cjs +3 -3
  53. package/dist/src/lib/plugins/index.mjs +3 -3
  54. package/dist/src/lib/plugins/route-codegen.cjs +2 -2
  55. package/dist/src/lib/plugins/route-codegen.js +1 -1
  56. package/dist/src/lib/plugins/route-codegen.mjs +2 -2
  57. package/dist/src/lib/render/index.cjs +1 -1
  58. package/dist/src/lib/render/index.mjs +1 -1
  59. package/dist/src/lib/render/versions.cjs +1 -1
  60. package/dist/src/lib/render/versions.mjs +1 -1
  61. package/dist/src/lib/router/data.cjs +113 -115
  62. package/dist/src/lib/router/data.d.ts +63 -10
  63. package/dist/src/lib/router/data.d.ts.map +1 -1
  64. package/dist/src/lib/router/data.js +158 -68
  65. package/dist/src/lib/router/data.mjs +112 -112
  66. package/dist/src/lib/router/index.cjs +260 -163
  67. package/dist/src/lib/router/index.d.ts +22 -2
  68. package/dist/src/lib/router/index.d.ts.map +1 -1
  69. package/dist/src/lib/router/index.js +165 -22
  70. package/dist/src/lib/router/index.mjs +244 -143
  71. package/dist/src/lib/server/index.cjs +1 -1
  72. package/dist/src/lib/server/index.mjs +1 -1
  73. package/dist/src/lib/storage/cache.cjs +51 -0
  74. package/dist/src/lib/storage/cache.d.ts +18 -0
  75. package/dist/src/lib/storage/cache.d.ts.map +1 -0
  76. package/dist/src/lib/storage/cache.js +42 -0
  77. package/dist/src/lib/storage/cache.mjs +30 -0
  78. package/dist/src/lib/storage/index.cjs +246 -0
  79. package/dist/src/lib/storage/index.d.ts +83 -0
  80. package/dist/src/lib/storage/index.d.ts.map +1 -0
  81. package/dist/src/lib/storage/index.js +179 -0
  82. package/dist/src/lib/storage/index.mjs +231 -0
  83. package/dist/src/lib/storage/persisted.cjs +91 -0
  84. package/dist/src/lib/storage/persisted.d.ts +53 -0
  85. package/dist/src/lib/storage/persisted.d.ts.map +1 -0
  86. package/dist/src/lib/storage/persisted.js +75 -0
  87. package/dist/src/lib/storage/persisted.mjs +70 -0
  88. package/dist/src/lib/theme/color-scheme.cjs +281 -0
  89. package/dist/src/lib/theme/color-scheme.d.ts +61 -0
  90. package/dist/src/lib/theme/color-scheme.d.ts.map +1 -0
  91. package/dist/src/lib/theme/color-scheme.js +120 -0
  92. package/dist/src/lib/theme/color-scheme.mjs +265 -0
  93. package/dist/src/lib/theme/index.cjs +194 -37
  94. package/dist/src/lib/theme/index.d.ts +23 -11
  95. package/dist/src/lib/theme/index.d.ts.map +1 -1
  96. package/dist/src/lib/theme/index.js +73 -32
  97. package/dist/src/lib/theme/index.mjs +197 -35
  98. package/dist/src/lib/theme/provider.cjs +346 -0
  99. package/dist/src/lib/theme/provider.d.ts +34 -0
  100. package/dist/src/lib/theme/provider.d.ts.map +1 -0
  101. package/dist/src/lib/theme/provider.js +60 -0
  102. package/dist/src/lib/theme/provider.mjs +332 -0
  103. package/dist/src/routes.cjs +1 -1
  104. package/dist/src/routes.mjs +1 -1
  105. package/dist/src/utils.cjs +1 -1
  106. package/dist/src/utils.mjs +1 -1
  107. package/dist/src/version.generated.cjs +1 -1
  108. package/dist/src/version.generated.d.ts +1 -1
  109. package/dist/src/version.generated.js +1 -1
  110. package/dist/src/version.generated.mjs +1 -1
  111. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  112. package/package.json +18 -11
  113. package/dist/src/lib/client/chunk-VB567ABU.mjs +0 -133
@@ -1,11 +1,7 @@
1
- /*! @fluixi/core v1.0.0-alpha.92 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.94 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  import {
3
- getHydrationData,
4
- isHydrating,
5
- isServerEnvironment,
6
- lazy,
7
- setHydrationData
8
- } from "./chunk-VB567ABU.mjs";
3
+ lazy
4
+ } from "./chunk-TSXM2PNY.mjs";
9
5
 
10
6
  // src/lib/router/index.ts
11
7
  import {
@@ -13,8 +9,10 @@ import {
13
9
  createMemo,
14
10
  createResource as createResource2,
15
11
  createEffect,
16
- getOwner,
12
+ createRoot,
13
+ getOwner as getOwner2,
17
14
  onCleanup,
15
+ runWithOwner,
18
16
  untrack,
19
17
  flush
20
18
  } from "@fluixi/reactive/signal";
@@ -83,42 +81,35 @@ import {
83
81
  matchRoutes,
84
82
  matchPath
85
83
  } from "@fluixi/router";
86
- import {
87
- createMemoryHistory,
88
- createBrowserHistory as createBrowserHistory2,
89
- matchRoutes as matchRoutes2,
90
- matchPath as matchPath2
91
- } from "@fluixi/router";
92
84
 
93
85
  // src/lib/router/data.ts
94
86
  import {
95
87
  createResource,
96
88
  createSignal
97
89
  } from "@fluixi/reactive/signal";
90
+ import { currentResourceCache } from "@fluixi/reactive/signal";
98
91
 
99
92
  // src/lib/context/context.ts
100
93
  import { asProvider } from "@fluixi/dom";
101
- import { createContext as createReactiveContext, useContext as useReactiveContext } from "@fluixi/reactive/signal";
102
94
  import {
103
- readChildren
95
+ createContext as createReactiveContext,
96
+ getOwner,
97
+ useContext as useReactiveContext
104
98
  } from "@fluixi/reactive/signal";
105
99
  function createContext(defaultValue, name) {
106
100
  const context = createReactiveContext(defaultValue, name);
107
- const Provider = (props) => {
108
- if (props.debug === true) {
109
- }
110
- return context.Provider({
101
+ const Provider = (props) => (
102
+ // Children stay a getter: the reactive Provider reads them after stamping the value on
103
+ // its owner. Reading `props.children` here would render them, and any <Outlet/> in them,
104
+ // before the context exists.
105
+ context.Provider({
111
106
  value: props.value,
112
107
  get children() {
113
108
  return props.children;
114
109
  },
115
110
  debug: props.debug
116
- });
117
- };
118
- if (typeof Symbol !== "undefined" && Symbol.for) {
119
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
120
- }
121
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
111
+ })
112
+ );
122
113
  return {
123
114
  id: context.id,
124
115
  Provider: asProvider(Provider),
@@ -127,53 +118,73 @@ function createContext(defaultValue, name) {
127
118
  }
128
119
 
129
120
  // src/lib/router/data.ts
130
- var globalCache = /* @__PURE__ */ new Map();
131
- var inflightRequests = /* @__PURE__ */ new Map();
132
- function cache(fn, name) {
133
- return async (...args) => {
121
+ var revalidationListeners = /* @__PURE__ */ new Set();
122
+ function onRevalidate(listener) {
123
+ revalidationListeners.add(listener);
124
+ return () => void revalidationListeners.delete(listener);
125
+ }
126
+ var reloadMounted = () => {
127
+ for (const listener of [...revalidationListeners]) listener();
128
+ };
129
+ function cache(fn, name, options = {}) {
130
+ const { ttl = Infinity, staleWhileRevalidate = true } = options;
131
+ return (...args) => {
132
+ const store = options.cache ?? currentResourceCache();
134
133
  const key = name + ":" + JSON.stringify(args);
135
- if (isHydrating() || typeof window !== "undefined") {
136
- const hydrated = getHydrationData(key);
137
- if (hydrated !== void 0) {
138
- return hydrated;
139
- }
140
- }
141
- if (globalCache.has(key)) {
142
- return globalCache.get(key);
143
- }
144
- if (inflightRequests.has(key)) {
145
- return inflightRequests.get(key);
146
- }
147
- const promise = Promise.resolve(fn(...args)).then(
148
- (val) => {
149
- globalCache.set(key, val);
150
- inflightRequests.delete(key);
151
- if (isServerEnvironment()) {
152
- setHydrationData(key, val);
134
+ const fetchFresh = () => {
135
+ const running = store.inflight.get(key);
136
+ if (running) return running;
137
+ const promise = Promise.resolve(fn(...args)).then(
138
+ (value) => {
139
+ store.set(key, { value, timestamp: Date.now() });
140
+ store.inflight.delete(key);
141
+ return value;
142
+ },
143
+ (error) => {
144
+ store.inflight.delete(key);
145
+ throw error;
153
146
  }
154
- return val;
155
- },
156
- (err) => {
157
- inflightRequests.delete(key);
158
- throw err;
159
- }
160
- );
161
- inflightRequests.set(key, promise);
162
- return promise;
147
+ );
148
+ store.inflight.set(key, promise);
149
+ return promise;
150
+ };
151
+ const hit = store.get(key);
152
+ if (hit && (ttl === Infinity || Date.now() - hit.timestamp <= ttl)) return Promise.resolve(hit.value);
153
+ if (hit && staleWhileRevalidate) {
154
+ fetchFresh().then(reloadMounted, () => void 0);
155
+ return Promise.resolve(hit.value);
156
+ }
157
+ return fetchFresh();
163
158
  };
164
159
  }
165
- var [revalidationVersion, setRevalidationVersion] = createSignal(0);
166
- var revalidationKeys = /* @__PURE__ */ new Set();
167
- function revalidate(key) {
168
- const predicate = typeof key === "string" ? (k) => k.startsWith(key) : key;
169
- for (const k of globalCache.keys()) {
170
- if (predicate(k)) {
171
- globalCache.delete(k);
172
- revalidationKeys.add(k);
173
- }
160
+ function revalidate(key, store) {
161
+ const names = typeof key === "string" ? [key] : Array.isArray(key) ? key : null;
162
+ drop(names ?? key, store ?? currentResourceCache());
163
+ if (names && !store) tellOtherTabs(names);
164
+ }
165
+ function drop(what, store) {
166
+ const matches = Array.isArray(what) ? (k) => what.some((n) => k === n || k.startsWith(n + ":")) : what;
167
+ for (const k of [...store.keys()]) if (matches(k)) store.delete(k);
168
+ reloadMounted();
169
+ }
170
+ var TABS_KEY = "fluixi:revalidate";
171
+ function tellOtherTabs(names) {
172
+ try {
173
+ localStorage.setItem(TABS_KEY, JSON.stringify({ names, at: Date.now() + Math.random() }));
174
+ } catch {
174
175
  }
175
- setRevalidationVersion((v) => v + 1);
176
176
  }
177
+ if (typeof window !== "undefined") {
178
+ window.addEventListener("storage", (event) => {
179
+ if (event.key !== TABS_KEY || !event.newValue) return;
180
+ try {
181
+ const names = JSON.parse(event.newValue).names;
182
+ if (Array.isArray(names) && names.every((n) => typeof n === "string")) drop(names, currentResourceCache());
183
+ } catch {
184
+ }
185
+ });
186
+ }
187
+ globalThis[/* @__PURE__ */ Symbol.for("fluixi.revalidate")] = (names) => revalidate(names);
177
188
  function createAsync(fetcher, source) {
178
189
  const [data] = createResource(source || (() => true), fetcher);
179
190
  return data;
@@ -188,19 +199,39 @@ function getSubmissionSignal(url) {
188
199
  }
189
200
  return submissionState.get(url);
190
201
  }
191
- function action(fn, name) {
202
+ var submissionsState = /* @__PURE__ */ new Map();
203
+ function getSubmissionsSignal(url) {
204
+ if (!submissionsState.has(url)) submissionsState.set(url, createSignal([]));
205
+ return submissionsState.get(url);
206
+ }
207
+ function action(fn, name, options = {}) {
192
208
  const actionUrl = name || fn.name || "action";
193
209
  const submit = async (input) => {
194
210
  const [, setSubmission] = getSubmissionSignal(actionUrl);
195
- const clear = () => setSubmission(void 0);
196
- const retry = () => submit(input);
197
- setSubmission({
211
+ const [, setSubmissions] = getSubmissionsSignal(actionUrl);
212
+ let entry;
213
+ const drop2 = () => setSubmissions((list) => list.filter((s) => s !== entry));
214
+ const settle = (next) => {
215
+ setSubmissions((list) => list.map((s) => s === entry ? next : s));
216
+ entry = next;
217
+ };
218
+ const clear = () => {
219
+ setSubmission(void 0);
220
+ drop2();
221
+ };
222
+ const retry = () => {
223
+ drop2();
224
+ return submit(input);
225
+ };
226
+ entry = {
198
227
  input,
199
228
  // Approximate type for tracking
200
229
  pending: true,
201
230
  clear,
202
231
  retry
203
- });
232
+ };
233
+ setSubmission(entry);
234
+ setSubmissions((list) => [...list, entry]);
204
235
  try {
205
236
  const result = await fn(input);
206
237
  setSubmission({
@@ -210,26 +241,35 @@ function action(fn, name) {
210
241
  clear,
211
242
  retry
212
243
  });
244
+ drop2();
245
+ const stale = typeof options.invalidates === "function" ? options.invalidates(result) : options.invalidates;
246
+ for (const key of stale ?? []) revalidate(key);
213
247
  return result;
214
248
  } catch (error) {
215
- setSubmission({
249
+ const failed = {
216
250
  input,
217
251
  error,
218
252
  pending: false,
219
253
  clear,
220
254
  retry
221
- });
255
+ };
256
+ setSubmission(failed);
257
+ settle(failed);
222
258
  throw error;
223
259
  }
224
260
  };
225
261
  submit.url = actionUrl;
226
- submit.with = (args) => action((...a) => fn(args, ...a), name);
262
+ submit.with = (args) => action((...a) => fn(args, ...a), name, options);
227
263
  return submit;
228
264
  }
229
265
  function useSubmission(actionFn) {
230
266
  const [submission] = getSubmissionSignal(actionFn.url);
231
267
  return submission;
232
268
  }
269
+ function useSubmissions(actionFn) {
270
+ const [submissions] = getSubmissionsSignal(actionFn.url);
271
+ return submissions;
272
+ }
233
273
  function useAction(actionFn) {
234
274
  return actionFn;
235
275
  }
@@ -237,7 +277,13 @@ var RouteDataContext = createContext(void 0, "route-data");
237
277
  var RouteDataProvider = RouteDataContext.Provider;
238
278
 
239
279
  // src/lib/router/index.ts
240
- import { runWithOwner } from "@fluixi/reactive/signal";
280
+ import {
281
+ createMemoryHistory,
282
+ createBrowserHistory as createBrowserHistory2,
283
+ matchRoutes as matchRoutes2,
284
+ matchPath as matchPath2
285
+ } from "@fluixi/router";
286
+ import { runWithOwner as runWithOwner2 } from "@fluixi/reactive/signal";
241
287
  var RouterContext = createContext2(void 0, "router");
242
288
  var OutletContext = createContext2(void 0, "outlet");
243
289
  var LevelParamsContext = createContext2(void 0, "route-params");
@@ -250,11 +296,33 @@ function renderLayer(m, depth, base) {
250
296
  onCleanup(() => {
251
297
  cancelled = true;
252
298
  });
253
- const owner = getOwner();
254
- const [resource] = createResource2(
255
- () => loader({ params: base.params, location: base.location }, owner, () => cancelled)
256
- );
257
- data = resource;
299
+ const owner = getOwner2();
300
+ const router = useContext2(RouterContext);
301
+ let watching = false;
302
+ const trackDeps = (deps) => {
303
+ if (watching || cancelled) return;
304
+ watching = true;
305
+ runWithOwner(owner, () => {
306
+ let first = true;
307
+ createEffect(() => {
308
+ deps();
309
+ if (first) first = false;
310
+ else untrack(() => void refetch());
311
+ });
312
+ });
313
+ };
314
+ const [resource, { refetch }] = createResource2(() => {
315
+ const result = loader({ params: base.params, location: base.location }, owner, () => cancelled, trackDeps);
316
+ return router ? router.trackLoad(result) : result;
317
+ });
318
+ onCleanup(onRevalidate(() => void refetch()));
319
+ data = () => {
320
+ const error = resource.error;
321
+ if (error !== void 0 && error !== null) throw error;
322
+ return resource();
323
+ };
324
+ Object.defineProperty(data, "loading", { get: () => resource.loading });
325
+ Object.defineProperty(data, "state", { get: () => resource.state });
258
326
  }
259
327
  const buildPage = () => RouteDataContext2.Provider({
260
328
  value: data,
@@ -272,12 +340,15 @@ function renderLayer(m, depth, base) {
272
340
  });
273
341
  }
274
342
  });
275
- if (!data) return buildPage();
343
+ if (!data) return { view: buildPage(), ready: () => true };
276
344
  const res = data;
277
- const isLoading = () => res.loading;
345
+ const isLoading = () => {
346
+ const state = res.state;
347
+ return state === void 0 ? res.loading : state === "pending" || state === "unresolved";
348
+ };
278
349
  const [ready, setReady] = createSignal2(untrack(() => !isLoading()));
279
350
  createEffect(() => setReady(!isLoading()));
280
- return createMemo(() => ready() ? buildPage() : null);
351
+ return { view: createMemo(() => ready() ? buildPage() : null), ready };
281
352
  }
282
353
  function renderChild(ctx) {
283
354
  const childIdx = ctx.depth + 1;
@@ -288,17 +359,43 @@ function renderChild(ctx) {
288
359
  const c = ctx.matched()[childIdx]?.component;
289
360
  if (untrack(childComp) !== c) setChildComp(() => c);
290
361
  });
291
- return createMemo(() => {
362
+ const owner = getOwner2();
363
+ let shown = null;
364
+ let pending = null;
365
+ const drop2 = (layer) => layer?.dispose();
366
+ onCleanup(() => {
367
+ drop2(shown);
368
+ drop2(pending);
369
+ shown = pending = null;
370
+ });
371
+ const incoming = createMemo(() => {
292
372
  const component = childComp();
293
- if (!component) {
373
+ if (!component) return null;
374
+ const m = untrack(() => ctx.matched())[childIdx];
375
+ return runWithOwner(
376
+ owner,
377
+ () => createRoot((dispose) => ({
378
+ ...renderLayer(m, childIdx, { matched: ctx.matched, params: ctx.params, location: ctx.location }),
379
+ dispose
380
+ }))
381
+ );
382
+ });
383
+ return createMemo(() => {
384
+ const next = incoming();
385
+ if (!next) {
386
+ drop2(shown);
387
+ drop2(pending);
388
+ shown = pending = null;
294
389
  return ctx.notFound ? ctx.notFound() : null;
295
390
  }
296
- const m = untrack(() => ctx.matched())[childIdx];
297
- return renderLayer(m, childIdx, {
298
- matched: ctx.matched,
299
- params: ctx.params,
300
- location: ctx.location
301
- });
391
+ if (next === shown) return shown.view;
392
+ if (pending && pending !== next) drop2(pending);
393
+ pending = next;
394
+ if (shown && !next.ready()) return shown.view;
395
+ drop2(shown);
396
+ shown = next;
397
+ pending = null;
398
+ return shown.view;
302
399
  });
303
400
  }
304
401
  function renderNotFound(notFound) {
@@ -333,6 +430,8 @@ function Router(props) {
333
430
  history
334
431
  });
335
432
  const [state, setState] = createSignal2(router.state());
433
+ const [navigating, setNavigating] = createSignal2(false);
434
+ const [loads, setLoads] = createSignal2(0);
336
435
  let navigationId = 0;
337
436
  const takeMode = () => {
338
437
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
@@ -342,12 +441,15 @@ function Router(props) {
342
441
  const unsub = router.subscribe((next) => {
343
442
  const id = ++navigationId;
344
443
  const mode = takeMode();
444
+ setNavigating(true);
345
445
  const prepared = prepareComponents(
346
446
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
347
447
  );
348
448
  const commit = () => {
349
449
  setState(next);
350
- return flush();
450
+ const flushed = flush();
451
+ if (id === navigationId) setNavigating(false);
452
+ return flushed;
351
453
  };
352
454
  if (!prepared) {
353
455
  void runTransition(mode, commit);
@@ -369,7 +471,18 @@ function Router(props) {
369
471
  const ctx = {
370
472
  router,
371
473
  location: createMemo(() => state().location),
372
- params: createMemo(() => state().match?.params ?? {})
474
+ params: createMemo(() => state().match?.params ?? {}),
475
+ isRouting: () => navigating() || loads() > 0,
476
+ // Counted by the promise, not by an owner: a load a newer navigation superseded still
477
+ // settles and uncounts itself, and a resource seeded from the server never calls it.
478
+ trackLoad(result) {
479
+ if (result && typeof result.finally === "function") {
480
+ setLoads((n) => n + 1);
481
+ void result.finally(() => setLoads((n) => n - 1)).catch(() => {
482
+ });
483
+ }
484
+ return result;
485
+ }
373
486
  };
374
487
  const root = {
375
488
  depth: -1,
@@ -392,6 +505,21 @@ function Outlet() {
392
505
  if (!ctx) return null;
393
506
  return renderChild(ctx);
394
507
  }
508
+ function useIsRouting(options = {}) {
509
+ const routing = useContext2(RouterContext)?.isRouting ?? (() => false);
510
+ const delay = options.delay ?? 0;
511
+ if (delay <= 0) return routing;
512
+ const [late, setLate] = createSignal2(false);
513
+ createEffect(() => {
514
+ if (!routing()) {
515
+ setLate(false);
516
+ return;
517
+ }
518
+ const timer = setTimeout(() => setLate(true), delay);
519
+ onCleanup(() => clearTimeout(timer));
520
+ });
521
+ return () => routing() && late();
522
+ }
395
523
  function Redirect(props) {
396
524
  const ctx = useContext2(RouterContext);
397
525
  if (ctx && typeof window !== "undefined") {
@@ -407,7 +535,9 @@ var _ssrRouterStub = {
407
535
  router: { navigate: () => {
408
536
  } },
409
537
  location: () => ({ pathname: "", search: "", hash: "", query: {} }),
410
- params: () => ({})
538
+ params: () => ({}),
539
+ isRouting: () => false,
540
+ trackLoad: (result) => result
411
541
  };
412
542
  function useRouter() {
413
543
  const ctx = useContext2(RouterContext);
@@ -522,18 +652,31 @@ var LINK_OWN_PROPS = /* @__PURE__ */ new Set([
522
652
  ]);
523
653
  function Link(props) {
524
654
  const ctx = useContext2(RouterContext);
525
- const navigate = () => ctx?.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
655
+ const navigate = () => ctx.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
526
656
  const a = createNativeElement("a");
527
657
  setAttribute(a, "href", read(props.href) ?? "");
528
658
  if (props.slot) setAttribute(a, "slot", read(props.slot));
529
659
  for (const k of Object.keys(props)) {
530
660
  if (LINK_OWN_PROPS.has(k)) continue;
531
661
  const v = props[k];
532
- if (v != null && typeof v !== "function") setAttribute(a, k, String(v));
662
+ if (typeof v === "function") {
663
+ if (/^on[A-Z]/.test(k)) continue;
664
+ const read2 = v;
665
+ const apply = () => {
666
+ const value = read2();
667
+ if (value == null || value === false) a.removeAttribute?.(k);
668
+ else setAttribute(a, k, String(value));
669
+ };
670
+ if (typeof a.addEventListener === "function") createEffect(apply);
671
+ else apply();
672
+ continue;
673
+ }
674
+ if (v != null) setAttribute(a, k, String(v));
533
675
  }
534
676
  if (typeof a.addEventListener === "function") {
535
677
  a.addEventListener("click", (e) => {
536
678
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
679
+ if (!ctx) return;
537
680
  e.preventDefault();
538
681
  navigate();
539
682
  });
@@ -612,10 +755,11 @@ export {
612
755
  prefersReducedMotion,
613
756
  preloadRoutes,
614
757
  revalidate,
615
- runWithOwner,
758
+ runWithOwner2 as runWithOwner,
616
759
  select,
617
760
  supportsViewTransitions,
618
761
  useAction,
762
+ useIsRouting,
619
763
  useLevelParams,
620
764
  useLinkNavigation,
621
765
  useLocation,
@@ -627,5 +771,6 @@ export {
627
771
  useRouteData,
628
772
  useRouter,
629
773
  useSearchParams,
630
- useSubmission
774
+ useSubmission,
775
+ useSubmissions
631
776
  };
@@ -21,91 +21,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var context_exports = {};
22
22
  __export(context_exports, {
23
23
  composeProviders: () => composeProviders,
24
- createConsumer: () => createConsumer,
25
24
  createContext: () => createContext,
26
- createContextWithProvider: () => createContextWithProvider,
27
25
  createProvider: () => createProvider,
28
- useContext: () => useContext,
29
- useProvide: () => useProvide
26
+ requireContext: () => requireContext,
27
+ useContext: () => useContext
30
28
  });
31
29
  module.exports = __toCommonJS(context_exports);
32
30
  var import_dom = require("@fluixi/dom");
33
31
  var import_signal = require("@fluixi/reactive/signal");
34
- var import_signal2 = require("@fluixi/reactive/signal");
35
- var _warnedEagerProvider = false;
36
- function warnEagerProvider() {
37
- if (_warnedEagerProvider) return;
38
- _warnedEagerProvider = true;
39
- if (typeof process !== "undefined" && true) return;
40
- console.warn(
41
- "[fluixi] createProvider({ context, value, children }) reads `children` eagerly: the object literal materialises `children` BEFORE the context is stamped, so a routed <Outlet/> that has siblings inside the provider renders detached and its descendants see no context under SSR. Use the lazy form instead:\n createProvider(Context, value, props) // children stays a getter\n // or JSX: <Context.Provider value={...}>{props.children}</Context.Provider>"
42
- );
43
- }
44
- function createProvider(a, value, childProps) {
45
- const isLazyForm = !!a?.Provider;
46
- const context = isLazyForm ? a : a.context;
47
- const rawValue = isLazyForm ? value : a.value;
48
- const debug = isLazyForm ? false : a.debug ?? false;
49
- if (!isLazyForm) warnEagerProvider();
50
- const resolvedValue = typeof rawValue === "function" ? rawValue() : rawValue;
51
- const sourceProps = isLazyForm ? childProps : a;
52
- return context.Provider({
53
- value: resolvedValue,
54
- get children() {
55
- return sourceProps?.children;
56
- },
57
- debug
58
- });
59
- }
60
- function createContextWithProvider(defaultValue) {
61
- const context = createContext(defaultValue);
62
- const provider = (props) => {
63
- return createProvider(context, props.value, props);
64
- };
65
- return [context, provider];
66
- }
67
- function composeProviders(contexts, children) {
68
- if (contexts.length === 0) {
69
- return (0, import_signal2.readChildren)(() => children);
70
- }
71
- const [context, value] = contexts[0];
72
- const remaining = contexts.slice(1);
73
- return createProvider(context, value, {
74
- get children() {
75
- return composeProviders(remaining, children);
76
- }
77
- });
78
- }
79
- function createConsumer(context) {
80
- return (props) => {
81
- const value = useContext(context);
82
- return props.children(value);
83
- };
84
- }
85
- function useProvide(context, factory) {
86
- const existing = useContext(context);
87
- if (existing === context.defaultValue) {
88
- return factory();
89
- }
90
- return existing;
91
- }
92
32
  function createContext(defaultValue, name) {
93
33
  const context = (0, import_signal.createContext)(defaultValue, name);
94
- const Provider = (props) => {
95
- if (props.debug === true) {
96
- }
97
- return context.Provider({
34
+ const Provider = (props) => (
35
+ // Children stay a getter: the reactive Provider reads them after stamping the value on
36
+ // its owner. Reading `props.children` here would render them, and any <Outlet/> in them,
37
+ // before the context exists.
38
+ context.Provider({
98
39
  value: props.value,
99
40
  get children() {
100
41
  return props.children;
101
42
  },
102
43
  debug: props.debug
103
- });
104
- };
105
- if (typeof Symbol !== "undefined" && Symbol.for) {
106
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
107
- }
108
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
44
+ })
45
+ );
109
46
  return {
110
47
  id: context.id,
111
48
  Provider: (0, import_dom.asProvider)(Provider),
@@ -116,3 +53,29 @@ function useContext(context) {
116
53
  const value = (0, import_signal.useContext)(context);
117
54
  return value !== void 0 ? value : context.defaultValue;
118
55
  }
56
+ function createProvider(context, value, props) {
57
+ const resolved = typeof value === "function" ? value() : value;
58
+ return context.Provider({
59
+ value: resolved,
60
+ get children() {
61
+ return props.children;
62
+ }
63
+ });
64
+ }
65
+ function composeProviders(providers, props) {
66
+ const outermost = providers.reduceRight(
67
+ (inner, [context, value]) => ({
68
+ get children() {
69
+ return createProvider(context, value, inner);
70
+ }
71
+ }),
72
+ props
73
+ );
74
+ return outermost.children;
75
+ }
76
+ function requireContext(context, message) {
77
+ for (let owner = (0, import_signal.getOwner)(); owner; owner = owner.parent) {
78
+ if (owner.contexts?.has(context.id)) return useContext(context);
79
+ }
80
+ throw new Error(message ?? `[fluixi] No Provider for ${context.id.description ?? "this context"} above this component.`);
81
+ }