@fluixi/core 1.0.0-alpha.91 → 1.0.0-alpha.93

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 +547 -232
  7. package/dist/src/index.mjs +547 -223
  8. package/dist/src/lib/client/chunk-QT3RFKH6.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-I66EJO2Y.mjs → router-ADTS55OP.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 +546 -231
  25. package/dist/src/lib/core.d.ts +3 -1
  26. package/dist/src/lib/core.d.ts.map +1 -1
  27. package/dist/src/lib/core.js +5 -3
  28. package/dist/src/lib/core.mjs +546 -222
  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-SKXDXBGV.mjs +0 -133
@@ -1,4 +1,4 @@
1
- /*! @fluixi/core v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/lib/router/index.ts
4
4
  import {
@@ -6,8 +6,10 @@ import {
6
6
  createMemo as createMemo2,
7
7
  createResource as createResource3,
8
8
  createEffect,
9
- getOwner,
9
+ createRoot,
10
+ getOwner as getOwner2,
10
11
  onCleanup,
12
+ runWithOwner,
11
13
  untrack as untrack2,
12
14
  flush
13
15
  } from "@fluixi/reactive/signal";
@@ -76,92 +78,35 @@ import {
76
78
  matchRoutes,
77
79
  matchPath
78
80
  } from "@fluixi/router";
79
- import {
80
- createMemoryHistory,
81
- createBrowserHistory as createBrowserHistory2,
82
- matchRoutes as matchRoutes2,
83
- matchPath as matchPath2
84
- } from "@fluixi/router";
85
81
 
86
82
  // src/lib/router/data.ts
87
83
  import {
88
84
  createResource,
89
85
  createSignal
90
86
  } from "@fluixi/reactive/signal";
91
-
92
- // src/lib/server/hydration.ts
93
- import { isServer as domIsServer } from "@fluixi/dom";
94
- var hydrating = false;
95
- var hydrationDataMap = /* @__PURE__ */ new Map();
96
- function isHydrating() {
97
- return hydrating;
98
- }
99
- function setHydrationData(key, data) {
100
- hydrationDataMap.set(key, data);
101
- }
102
- function getHydrationData(key) {
103
- return hydrationDataMap.get(key);
104
- }
105
- function clearHydrationData() {
106
- hydrationDataMap.clear();
107
- }
108
- function cleanupHydration() {
109
- if (typeof window === "undefined") {
110
- return;
111
- }
112
- const script = document.getElementById("__FLUIXI_HYDRATION__");
113
- if (script) {
114
- script.remove();
115
- }
116
- const walker = document.createTreeWalker(
117
- document.body,
118
- NodeFilter.SHOW_COMMENT,
119
- null
120
- );
121
- const comments = [];
122
- let node;
123
- while (node = walker.nextNode()) {
124
- const comment = node;
125
- const text = comment.nodeValue || "";
126
- if (text.startsWith("#") || text.startsWith("/")) {
127
- comments.push(comment);
128
- }
129
- }
130
- comments.forEach((comment) => comment.remove());
131
- clearHydrationData();
132
- }
133
- if (typeof window !== "undefined") {
134
- window.addEventListener("load", () => {
135
- setTimeout(cleanupHydration, 100);
136
- });
137
- }
138
- function isServerEnvironment() {
139
- return domIsServer();
140
- }
87
+ import { currentResourceCache } from "@fluixi/reactive/signal";
141
88
 
142
89
  // src/lib/context/context.ts
143
90
  import { asProvider } from "@fluixi/dom";
144
- import { createContext as createReactiveContext, useContext as useReactiveContext } from "@fluixi/reactive/signal";
145
91
  import {
146
- readChildren
92
+ createContext as createReactiveContext,
93
+ getOwner,
94
+ useContext as useReactiveContext
147
95
  } from "@fluixi/reactive/signal";
148
96
  function createContext(defaultValue, name) {
149
97
  const context = createReactiveContext(defaultValue, name);
150
- const Provider = (props) => {
151
- if (props.debug === true) {
152
- }
153
- return context.Provider({
98
+ const Provider = (props) => (
99
+ // Children stay a getter: the reactive Provider reads them after stamping the value on
100
+ // its owner. Reading `props.children` here would render them, and any <Outlet/> in them,
101
+ // before the context exists.
102
+ context.Provider({
154
103
  value: props.value,
155
104
  get children() {
156
105
  return props.children;
157
106
  },
158
107
  debug: props.debug
159
- });
160
- };
161
- if (typeof Symbol !== "undefined" && Symbol.for) {
162
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
163
- }
164
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
108
+ })
109
+ );
165
110
  return {
166
111
  id: context.id,
167
112
  Provider: asProvider(Provider),
@@ -170,53 +115,73 @@ function createContext(defaultValue, name) {
170
115
  }
171
116
 
172
117
  // src/lib/router/data.ts
173
- var globalCache = /* @__PURE__ */ new Map();
174
- var inflightRequests = /* @__PURE__ */ new Map();
175
- function cache(fn, name) {
176
- return async (...args) => {
118
+ var revalidationListeners = /* @__PURE__ */ new Set();
119
+ function onRevalidate(listener) {
120
+ revalidationListeners.add(listener);
121
+ return () => void revalidationListeners.delete(listener);
122
+ }
123
+ var reloadMounted = () => {
124
+ for (const listener of [...revalidationListeners]) listener();
125
+ };
126
+ function cache(fn, name, options = {}) {
127
+ const { ttl = Infinity, staleWhileRevalidate = true } = options;
128
+ return (...args) => {
129
+ const store = options.cache ?? currentResourceCache();
177
130
  const key = name + ":" + JSON.stringify(args);
178
- if (isHydrating() || typeof window !== "undefined") {
179
- const hydrated = getHydrationData(key);
180
- if (hydrated !== void 0) {
181
- return hydrated;
182
- }
183
- }
184
- if (globalCache.has(key)) {
185
- return globalCache.get(key);
186
- }
187
- if (inflightRequests.has(key)) {
188
- return inflightRequests.get(key);
189
- }
190
- const promise = Promise.resolve(fn(...args)).then(
191
- (val) => {
192
- globalCache.set(key, val);
193
- inflightRequests.delete(key);
194
- if (isServerEnvironment()) {
195
- setHydrationData(key, val);
131
+ const fetchFresh = () => {
132
+ const running = store.inflight.get(key);
133
+ if (running) return running;
134
+ const promise = Promise.resolve(fn(...args)).then(
135
+ (value) => {
136
+ store.set(key, { value, timestamp: Date.now() });
137
+ store.inflight.delete(key);
138
+ return value;
139
+ },
140
+ (error) => {
141
+ store.inflight.delete(key);
142
+ throw error;
196
143
  }
197
- return val;
198
- },
199
- (err) => {
200
- inflightRequests.delete(key);
201
- throw err;
202
- }
203
- );
204
- inflightRequests.set(key, promise);
205
- return promise;
144
+ );
145
+ store.inflight.set(key, promise);
146
+ return promise;
147
+ };
148
+ const hit = store.get(key);
149
+ if (hit && (ttl === Infinity || Date.now() - hit.timestamp <= ttl)) return Promise.resolve(hit.value);
150
+ if (hit && staleWhileRevalidate) {
151
+ fetchFresh().then(reloadMounted, () => void 0);
152
+ return Promise.resolve(hit.value);
153
+ }
154
+ return fetchFresh();
206
155
  };
207
156
  }
208
- var [revalidationVersion, setRevalidationVersion] = createSignal(0);
209
- var revalidationKeys = /* @__PURE__ */ new Set();
210
- function revalidate(key) {
211
- const predicate = typeof key === "string" ? (k) => k.startsWith(key) : key;
212
- for (const k of globalCache.keys()) {
213
- if (predicate(k)) {
214
- globalCache.delete(k);
215
- revalidationKeys.add(k);
216
- }
157
+ function revalidate(key, store) {
158
+ const names = typeof key === "string" ? [key] : Array.isArray(key) ? key : null;
159
+ drop(names ?? key, store ?? currentResourceCache());
160
+ if (names && !store) tellOtherTabs(names);
161
+ }
162
+ function drop(what, store) {
163
+ const matches = Array.isArray(what) ? (k) => what.some((n) => k === n || k.startsWith(n + ":")) : what;
164
+ for (const k of [...store.keys()]) if (matches(k)) store.delete(k);
165
+ reloadMounted();
166
+ }
167
+ var TABS_KEY = "fluixi:revalidate";
168
+ function tellOtherTabs(names) {
169
+ try {
170
+ localStorage.setItem(TABS_KEY, JSON.stringify({ names, at: Date.now() + Math.random() }));
171
+ } catch {
217
172
  }
218
- setRevalidationVersion((v) => v + 1);
219
173
  }
174
+ if (typeof window !== "undefined") {
175
+ window.addEventListener("storage", (event) => {
176
+ if (event.key !== TABS_KEY || !event.newValue) return;
177
+ try {
178
+ const names = JSON.parse(event.newValue).names;
179
+ if (Array.isArray(names) && names.every((n) => typeof n === "string")) drop(names, currentResourceCache());
180
+ } catch {
181
+ }
182
+ });
183
+ }
184
+ globalThis[/* @__PURE__ */ Symbol.for("fluixi.revalidate")] = (names) => revalidate(names);
220
185
  function createAsync(fetcher, source) {
221
186
  const [data] = createResource(source || (() => true), fetcher);
222
187
  return data;
@@ -231,19 +196,39 @@ function getSubmissionSignal(url) {
231
196
  }
232
197
  return submissionState.get(url);
233
198
  }
234
- function action(fn, name) {
199
+ var submissionsState = /* @__PURE__ */ new Map();
200
+ function getSubmissionsSignal(url) {
201
+ if (!submissionsState.has(url)) submissionsState.set(url, createSignal([]));
202
+ return submissionsState.get(url);
203
+ }
204
+ function action(fn, name, options = {}) {
235
205
  const actionUrl = name || fn.name || "action";
236
206
  const submit = async (input) => {
237
207
  const [, setSubmission] = getSubmissionSignal(actionUrl);
238
- const clear = () => setSubmission(void 0);
239
- const retry = () => submit(input);
240
- setSubmission({
208
+ const [, setSubmissions] = getSubmissionsSignal(actionUrl);
209
+ let entry;
210
+ const drop2 = () => setSubmissions((list) => list.filter((s) => s !== entry));
211
+ const settle = (next) => {
212
+ setSubmissions((list) => list.map((s) => s === entry ? next : s));
213
+ entry = next;
214
+ };
215
+ const clear = () => {
216
+ setSubmission(void 0);
217
+ drop2();
218
+ };
219
+ const retry = () => {
220
+ drop2();
221
+ return submit(input);
222
+ };
223
+ entry = {
241
224
  input,
242
225
  // Approximate type for tracking
243
226
  pending: true,
244
227
  clear,
245
228
  retry
246
- });
229
+ };
230
+ setSubmission(entry);
231
+ setSubmissions((list) => [...list, entry]);
247
232
  try {
248
233
  const result = await fn(input);
249
234
  setSubmission({
@@ -253,32 +238,49 @@ function action(fn, name) {
253
238
  clear,
254
239
  retry
255
240
  });
241
+ drop2();
242
+ const stale = typeof options.invalidates === "function" ? options.invalidates(result) : options.invalidates;
243
+ for (const key of stale ?? []) revalidate(key);
256
244
  return result;
257
245
  } catch (error) {
258
- setSubmission({
246
+ const failed = {
259
247
  input,
260
248
  error,
261
249
  pending: false,
262
250
  clear,
263
251
  retry
264
- });
252
+ };
253
+ setSubmission(failed);
254
+ settle(failed);
265
255
  throw error;
266
256
  }
267
257
  };
268
258
  submit.url = actionUrl;
269
- submit.with = (args) => action((...a) => fn(args, ...a), name);
259
+ submit.with = (args) => action((...a) => fn(args, ...a), name, options);
270
260
  return submit;
271
261
  }
272
262
  function useSubmission(actionFn) {
273
263
  const [submission] = getSubmissionSignal(actionFn.url);
274
264
  return submission;
275
265
  }
266
+ function useSubmissions(actionFn) {
267
+ const [submissions] = getSubmissionsSignal(actionFn.url);
268
+ return submissions;
269
+ }
276
270
  function useAction(actionFn) {
277
271
  return actionFn;
278
272
  }
279
273
  var RouteDataContext = createContext(void 0, "route-data");
280
274
  var RouteDataProvider = RouteDataContext.Provider;
281
275
 
276
+ // src/lib/router/index.ts
277
+ import {
278
+ createMemoryHistory,
279
+ createBrowserHistory as createBrowserHistory2,
280
+ matchRoutes as matchRoutes2,
281
+ matchPath as matchPath2
282
+ } from "@fluixi/router";
283
+
282
284
  // src/lib/render/lazy.ts
283
285
  import { createComponent } from "@fluixi/dom";
284
286
  import {
@@ -328,7 +330,7 @@ function lazy(loader, options) {
328
330
  }
329
331
 
330
332
  // src/lib/router/index.ts
331
- import { runWithOwner } from "@fluixi/reactive/signal";
333
+ import { runWithOwner as runWithOwner2 } from "@fluixi/reactive/signal";
332
334
  var RouterContext = createContext2(void 0, "router");
333
335
  var OutletContext = createContext2(void 0, "outlet");
334
336
  var LevelParamsContext = createContext2(void 0, "route-params");
@@ -341,11 +343,33 @@ function renderLayer(m, depth, base) {
341
343
  onCleanup(() => {
342
344
  cancelled = true;
343
345
  });
344
- const owner = getOwner();
345
- const [resource] = createResource3(
346
- () => loader({ params: base.params, location: base.location }, owner, () => cancelled)
347
- );
348
- data = resource;
346
+ const owner = getOwner2();
347
+ const router = useContext2(RouterContext);
348
+ let watching = false;
349
+ const trackDeps = (deps) => {
350
+ if (watching || cancelled) return;
351
+ watching = true;
352
+ runWithOwner(owner, () => {
353
+ let first = true;
354
+ createEffect(() => {
355
+ deps();
356
+ if (first) first = false;
357
+ else untrack2(() => void refetch());
358
+ });
359
+ });
360
+ };
361
+ const [resource, { refetch }] = createResource3(() => {
362
+ const result = loader({ params: base.params, location: base.location }, owner, () => cancelled, trackDeps);
363
+ return router ? router.trackLoad(result) : result;
364
+ });
365
+ onCleanup(onRevalidate(() => void refetch()));
366
+ data = () => {
367
+ const error = resource.error;
368
+ if (error !== void 0 && error !== null) throw error;
369
+ return resource();
370
+ };
371
+ Object.defineProperty(data, "loading", { get: () => resource.loading });
372
+ Object.defineProperty(data, "state", { get: () => resource.state });
349
373
  }
350
374
  const buildPage = () => RouteDataContext2.Provider({
351
375
  value: data,
@@ -363,12 +387,15 @@ function renderLayer(m, depth, base) {
363
387
  });
364
388
  }
365
389
  });
366
- if (!data) return buildPage();
390
+ if (!data) return { view: buildPage(), ready: () => true };
367
391
  const res = data;
368
- const isLoading = () => res.loading;
392
+ const isLoading = () => {
393
+ const state = res.state;
394
+ return state === void 0 ? res.loading : state === "pending" || state === "unresolved";
395
+ };
369
396
  const [ready, setReady] = createSignal2(untrack2(() => !isLoading()));
370
397
  createEffect(() => setReady(!isLoading()));
371
- return createMemo2(() => ready() ? buildPage() : null);
398
+ return { view: createMemo2(() => ready() ? buildPage() : null), ready };
372
399
  }
373
400
  function renderChild(ctx) {
374
401
  const childIdx = ctx.depth + 1;
@@ -379,17 +406,43 @@ function renderChild(ctx) {
379
406
  const c = ctx.matched()[childIdx]?.component;
380
407
  if (untrack2(childComp) !== c) setChildComp(() => c);
381
408
  });
382
- return createMemo2(() => {
409
+ const owner = getOwner2();
410
+ let shown = null;
411
+ let pending = null;
412
+ const drop2 = (layer) => layer?.dispose();
413
+ onCleanup(() => {
414
+ drop2(shown);
415
+ drop2(pending);
416
+ shown = pending = null;
417
+ });
418
+ const incoming = createMemo2(() => {
383
419
  const component = childComp();
384
- if (!component) {
420
+ if (!component) return null;
421
+ const m = untrack2(() => ctx.matched())[childIdx];
422
+ return runWithOwner(
423
+ owner,
424
+ () => createRoot((dispose) => ({
425
+ ...renderLayer(m, childIdx, { matched: ctx.matched, params: ctx.params, location: ctx.location }),
426
+ dispose
427
+ }))
428
+ );
429
+ });
430
+ return createMemo2(() => {
431
+ const next = incoming();
432
+ if (!next) {
433
+ drop2(shown);
434
+ drop2(pending);
435
+ shown = pending = null;
385
436
  return ctx.notFound ? ctx.notFound() : null;
386
437
  }
387
- const m = untrack2(() => ctx.matched())[childIdx];
388
- return renderLayer(m, childIdx, {
389
- matched: ctx.matched,
390
- params: ctx.params,
391
- location: ctx.location
392
- });
438
+ if (next === shown) return shown.view;
439
+ if (pending && pending !== next) drop2(pending);
440
+ pending = next;
441
+ if (shown && !next.ready()) return shown.view;
442
+ drop2(shown);
443
+ shown = next;
444
+ pending = null;
445
+ return shown.view;
393
446
  });
394
447
  }
395
448
  function renderNotFound(notFound) {
@@ -424,6 +477,8 @@ function Router(props) {
424
477
  history
425
478
  });
426
479
  const [state, setState] = createSignal2(router.state());
480
+ const [navigating, setNavigating] = createSignal2(false);
481
+ const [loads, setLoads] = createSignal2(0);
427
482
  let navigationId = 0;
428
483
  const takeMode = () => {
429
484
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
@@ -433,12 +488,15 @@ function Router(props) {
433
488
  const unsub = router.subscribe((next) => {
434
489
  const id = ++navigationId;
435
490
  const mode = takeMode();
491
+ setNavigating(true);
436
492
  const prepared = prepareComponents(
437
493
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
438
494
  );
439
495
  const commit = () => {
440
496
  setState(next);
441
- return flush();
497
+ const flushed = flush();
498
+ if (id === navigationId) setNavigating(false);
499
+ return flushed;
442
500
  };
443
501
  if (!prepared) {
444
502
  void runTransition(mode, commit);
@@ -460,7 +518,18 @@ function Router(props) {
460
518
  const ctx = {
461
519
  router,
462
520
  location: createMemo2(() => state().location),
463
- params: createMemo2(() => state().match?.params ?? {})
521
+ params: createMemo2(() => state().match?.params ?? {}),
522
+ isRouting: () => navigating() || loads() > 0,
523
+ // Counted by the promise, not by an owner: a load a newer navigation superseded still
524
+ // settles and uncounts itself, and a resource seeded from the server never calls it.
525
+ trackLoad(result) {
526
+ if (result && typeof result.finally === "function") {
527
+ setLoads((n) => n + 1);
528
+ void result.finally(() => setLoads((n) => n - 1)).catch(() => {
529
+ });
530
+ }
531
+ return result;
532
+ }
464
533
  };
465
534
  const root = {
466
535
  depth: -1,
@@ -483,6 +552,21 @@ function Outlet() {
483
552
  if (!ctx) return null;
484
553
  return renderChild(ctx);
485
554
  }
555
+ function useIsRouting(options = {}) {
556
+ const routing = useContext2(RouterContext)?.isRouting ?? (() => false);
557
+ const delay = options.delay ?? 0;
558
+ if (delay <= 0) return routing;
559
+ const [late, setLate] = createSignal2(false);
560
+ createEffect(() => {
561
+ if (!routing()) {
562
+ setLate(false);
563
+ return;
564
+ }
565
+ const timer = setTimeout(() => setLate(true), delay);
566
+ onCleanup(() => clearTimeout(timer));
567
+ });
568
+ return () => routing() && late();
569
+ }
486
570
  function Redirect(props) {
487
571
  const ctx = useContext2(RouterContext);
488
572
  if (ctx && typeof window !== "undefined") {
@@ -498,7 +582,9 @@ var _ssrRouterStub = {
498
582
  router: { navigate: () => {
499
583
  } },
500
584
  location: () => ({ pathname: "", search: "", hash: "", query: {} }),
501
- params: () => ({})
585
+ params: () => ({}),
586
+ isRouting: () => false,
587
+ trackLoad: (result) => result
502
588
  };
503
589
  function useRouter() {
504
590
  const ctx = useContext2(RouterContext);
@@ -613,18 +699,31 @@ var LINK_OWN_PROPS = /* @__PURE__ */ new Set([
613
699
  ]);
614
700
  function Link(props) {
615
701
  const ctx = useContext2(RouterContext);
616
- const navigate = () => ctx?.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
702
+ const navigate = () => ctx.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
617
703
  const a = createNativeElement("a");
618
704
  setAttribute(a, "href", read(props.href) ?? "");
619
705
  if (props.slot) setAttribute(a, "slot", read(props.slot));
620
706
  for (const k of Object.keys(props)) {
621
707
  if (LINK_OWN_PROPS.has(k)) continue;
622
708
  const v = props[k];
623
- if (v != null && typeof v !== "function") setAttribute(a, k, String(v));
709
+ if (typeof v === "function") {
710
+ if (/^on[A-Z]/.test(k)) continue;
711
+ const read2 = v;
712
+ const apply = () => {
713
+ const value = read2();
714
+ if (value == null || value === false) a.removeAttribute?.(k);
715
+ else setAttribute(a, k, String(value));
716
+ };
717
+ if (typeof a.addEventListener === "function") createEffect(apply);
718
+ else apply();
719
+ continue;
720
+ }
721
+ if (v != null) setAttribute(a, k, String(v));
624
722
  }
625
723
  if (typeof a.addEventListener === "function") {
626
724
  a.addEventListener("click", (e) => {
627
725
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
726
+ if (!ctx) return;
628
727
  e.preventDefault();
629
728
  navigate();
630
729
  });
@@ -703,10 +802,11 @@ export {
703
802
  prefersReducedMotion,
704
803
  preloadRoutes,
705
804
  revalidate,
706
- runWithOwner,
805
+ runWithOwner2 as runWithOwner,
707
806
  select,
708
807
  supportsViewTransitions,
709
808
  useAction,
809
+ useIsRouting,
710
810
  useLevelParams,
711
811
  useLinkNavigation,
712
812
  useLocation,
@@ -718,5 +818,6 @@ export {
718
818
  useRouteData,
719
819
  useRouter,
720
820
  useSearchParams,
721
- useSubmission
821
+ useSubmission,
822
+ useSubmissions
722
823
  };
@@ -1,4 +1,4 @@
1
- /*! @fluixi/core v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,4 +1,4 @@
1
- /*! @fluixi/core v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/lib/server/hydration.ts
4
4
  import { isServer as domIsServer } from "@fluixi/dom";
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/storage/cache.ts
21
+ var cache_exports = {};
22
+ __export(cache_exports, {
23
+ storageCache: () => storageCache
24
+ });
25
+ module.exports = __toCommonJS(cache_exports);
26
+ function storageCache(storage, options = {}) {
27
+ const inflight = /* @__PURE__ */ new Map();
28
+ return {
29
+ get(key) {
30
+ try {
31
+ const entry = storage.getEntry(key);
32
+ return entry ? { value: entry.value, timestamp: entry.createdAt } : void 0;
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ },
37
+ set(key, entry) {
38
+ try {
39
+ storage.set(key, entry.value, options.ttl !== void 0 ? { ttl: options.ttl } : void 0);
40
+ } catch {
41
+ }
42
+ },
43
+ delete: (key) => storage.remove(key),
44
+ keys: () => storage.keys(),
45
+ clear() {
46
+ storage.clear();
47
+ inflight.clear();
48
+ },
49
+ inflight
50
+ };
51
+ }
@@ -0,0 +1,18 @@
1
+ import type { ResourceCache } from '@fluixi/reactive/cached-resource';
2
+ import type { TypedStorage } from '@fluixi/storage';
3
+ /**
4
+ * A `cachedResource` cache kept in a storage, so fetched data outlives the page: session
5
+ * storage for the tab, local storage across visits. Values pass through the storage's
6
+ * serializer, so they must be plain data. In-flight fetches stay in memory.
7
+ *
8
+ * Give it its own namespace, since `clearCachedResources()` clears that storage, and a `ttl`
9
+ * so entries do not pile up after the resource that wrote them is gone.
10
+ *
11
+ * @example
12
+ * const cache = storageCache(useStorage({ scope: 'session', namespace: 'cache' }), { ttl: 10 * 60_000 });
13
+ * const orgs = cachedResource(() => api.get('/orgs'), { name: 'orgs', ttl: 60_000, cache });
14
+ */
15
+ export declare function storageCache(storage: TypedStorage<any>, options?: {
16
+ ttl?: number;
17
+ }): ResourceCache;
18
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/lib/storage/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,aAAa,CA2BtG"}