@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,4 +1,4 @@
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
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  select: () => select,
43
43
  supportsViewTransitions: () => supportsViewTransitions,
44
44
  useAction: () => useAction,
45
+ useIsRouting: () => useIsRouting,
45
46
  useLevelParams: () => useLevelParams,
46
47
  useLinkNavigation: () => useLinkNavigation,
47
48
  useLocation: () => useLocation,
@@ -53,7 +54,8 @@ __export(index_exports, {
53
54
  useRouteData: () => useRouteData,
54
55
  useRouter: () => useRouter,
55
56
  useSearchParams: () => useSearchParams,
56
- useSubmission: () => useSubmission
57
+ useSubmission: () => useSubmission,
58
+ useSubmissions: () => useSubmissions
57
59
  });
58
60
  module.exports = __toCommonJS(index_exports);
59
61
  var import_signal5 = require("@fluixi/reactive/signal");
@@ -107,144 +109,110 @@ function runTransition(mode, commit) {
107
109
  }
108
110
 
109
111
  // src/lib/router/index.ts
110
- var import_dom4 = require("@fluixi/dom");
112
+ var import_dom3 = require("@fluixi/dom");
111
113
  var import_signal6 = require("@fluixi/reactive/signal");
112
114
  var import_object = require("@fluixi/utils/object");
113
115
  var import_compare = require("@fluixi/utils/compare");
114
116
  var import_router = require("@fluixi/router");
115
- var import_router2 = require("@fluixi/router");
116
117
 
117
118
  // src/lib/router/data.ts
119
+ var import_signal2 = require("@fluixi/reactive/signal");
118
120
  var import_signal3 = require("@fluixi/reactive/signal");
119
121
 
120
- // src/lib/server/hydration.ts
121
- var import_dom = require("@fluixi/dom");
122
- var hydrating = false;
123
- var hydrationDataMap = /* @__PURE__ */ new Map();
124
- function isHydrating() {
125
- return hydrating;
126
- }
127
- function setHydrationData(key, data) {
128
- hydrationDataMap.set(key, data);
129
- }
130
- function getHydrationData(key) {
131
- return hydrationDataMap.get(key);
132
- }
133
- function clearHydrationData() {
134
- hydrationDataMap.clear();
135
- }
136
- function cleanupHydration() {
137
- if (typeof window === "undefined") {
138
- return;
139
- }
140
- const script = document.getElementById("__FLUIXI_HYDRATION__");
141
- if (script) {
142
- script.remove();
143
- }
144
- const walker = document.createTreeWalker(
145
- document.body,
146
- NodeFilter.SHOW_COMMENT,
147
- null
148
- );
149
- const comments = [];
150
- let node;
151
- while (node = walker.nextNode()) {
152
- const comment = node;
153
- const text = comment.nodeValue || "";
154
- if (text.startsWith("#") || text.startsWith("/")) {
155
- comments.push(comment);
156
- }
157
- }
158
- comments.forEach((comment) => comment.remove());
159
- clearHydrationData();
160
- }
161
- if (typeof window !== "undefined") {
162
- window.addEventListener("load", () => {
163
- setTimeout(cleanupHydration, 100);
164
- });
165
- }
166
- function isServerEnvironment() {
167
- return (0, import_dom.isServer)();
168
- }
169
-
170
122
  // src/lib/context/context.ts
171
- var import_dom2 = require("@fluixi/dom");
123
+ var import_dom = require("@fluixi/dom");
172
124
  var import_signal = require("@fluixi/reactive/signal");
173
- var import_signal2 = require("@fluixi/reactive/signal");
174
125
  function createContext(defaultValue, name) {
175
126
  const context = (0, import_signal.createContext)(defaultValue, name);
176
- const Provider = (props) => {
177
- if (props.debug === true) {
178
- }
179
- return context.Provider({
127
+ const Provider = (props) => (
128
+ // Children stay a getter: the reactive Provider reads them after stamping the value on
129
+ // its owner. Reading `props.children` here would render them, and any <Outlet/> in them,
130
+ // before the context exists.
131
+ context.Provider({
180
132
  value: props.value,
181
133
  get children() {
182
134
  return props.children;
183
135
  },
184
136
  debug: props.debug
185
- });
186
- };
187
- if (typeof Symbol !== "undefined" && Symbol.for) {
188
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
189
- }
190
- Provider.$$typeof = /* @__PURE__ */ Symbol.for("react.provider");
137
+ })
138
+ );
191
139
  return {
192
140
  id: context.id,
193
- Provider: (0, import_dom2.asProvider)(Provider),
141
+ Provider: (0, import_dom.asProvider)(Provider),
194
142
  defaultValue
195
143
  };
196
144
  }
197
145
 
198
146
  // src/lib/router/data.ts
199
- var globalCache = /* @__PURE__ */ new Map();
200
- var inflightRequests = /* @__PURE__ */ new Map();
201
- function cache(fn, name) {
202
- return async (...args) => {
147
+ var revalidationListeners = /* @__PURE__ */ new Set();
148
+ function onRevalidate(listener) {
149
+ revalidationListeners.add(listener);
150
+ return () => void revalidationListeners.delete(listener);
151
+ }
152
+ var reloadMounted = () => {
153
+ for (const listener of [...revalidationListeners]) listener();
154
+ };
155
+ function cache(fn, name, options = {}) {
156
+ const { ttl = Infinity, staleWhileRevalidate = true } = options;
157
+ return (...args) => {
158
+ const store = options.cache ?? (0, import_signal3.currentResourceCache)();
203
159
  const key = name + ":" + JSON.stringify(args);
204
- if (isHydrating() || typeof window !== "undefined") {
205
- const hydrated = getHydrationData(key);
206
- if (hydrated !== void 0) {
207
- return hydrated;
208
- }
209
- }
210
- if (globalCache.has(key)) {
211
- return globalCache.get(key);
212
- }
213
- if (inflightRequests.has(key)) {
214
- return inflightRequests.get(key);
215
- }
216
- const promise = Promise.resolve(fn(...args)).then(
217
- (val) => {
218
- globalCache.set(key, val);
219
- inflightRequests.delete(key);
220
- if (isServerEnvironment()) {
221
- setHydrationData(key, val);
160
+ const fetchFresh = () => {
161
+ const running = store.inflight.get(key);
162
+ if (running) return running;
163
+ const promise = Promise.resolve(fn(...args)).then(
164
+ (value) => {
165
+ store.set(key, { value, timestamp: Date.now() });
166
+ store.inflight.delete(key);
167
+ return value;
168
+ },
169
+ (error) => {
170
+ store.inflight.delete(key);
171
+ throw error;
222
172
  }
223
- return val;
224
- },
225
- (err) => {
226
- inflightRequests.delete(key);
227
- throw err;
228
- }
229
- );
230
- inflightRequests.set(key, promise);
231
- return promise;
173
+ );
174
+ store.inflight.set(key, promise);
175
+ return promise;
176
+ };
177
+ const hit = store.get(key);
178
+ if (hit && (ttl === Infinity || Date.now() - hit.timestamp <= ttl)) return Promise.resolve(hit.value);
179
+ if (hit && staleWhileRevalidate) {
180
+ fetchFresh().then(reloadMounted, () => void 0);
181
+ return Promise.resolve(hit.value);
182
+ }
183
+ return fetchFresh();
232
184
  };
233
185
  }
234
- var [revalidationVersion, setRevalidationVersion] = (0, import_signal3.createSignal)(0);
235
- var revalidationKeys = /* @__PURE__ */ new Set();
236
- function revalidate(key) {
237
- const predicate = typeof key === "string" ? (k) => k.startsWith(key) : key;
238
- for (const k of globalCache.keys()) {
239
- if (predicate(k)) {
240
- globalCache.delete(k);
241
- revalidationKeys.add(k);
242
- }
186
+ function revalidate(key, store) {
187
+ const names = typeof key === "string" ? [key] : Array.isArray(key) ? key : null;
188
+ drop(names ?? key, store ?? (0, import_signal3.currentResourceCache)());
189
+ if (names && !store) tellOtherTabs(names);
190
+ }
191
+ function drop(what, store) {
192
+ const matches = Array.isArray(what) ? (k) => what.some((n) => k === n || k.startsWith(n + ":")) : what;
193
+ for (const k of [...store.keys()]) if (matches(k)) store.delete(k);
194
+ reloadMounted();
195
+ }
196
+ var TABS_KEY = "fluixi:revalidate";
197
+ function tellOtherTabs(names) {
198
+ try {
199
+ localStorage.setItem(TABS_KEY, JSON.stringify({ names, at: Date.now() + Math.random() }));
200
+ } catch {
243
201
  }
244
- setRevalidationVersion((v) => v + 1);
245
202
  }
203
+ if (typeof window !== "undefined") {
204
+ window.addEventListener("storage", (event) => {
205
+ if (event.key !== TABS_KEY || !event.newValue) return;
206
+ try {
207
+ const names = JSON.parse(event.newValue).names;
208
+ if (Array.isArray(names) && names.every((n) => typeof n === "string")) drop(names, (0, import_signal3.currentResourceCache)());
209
+ } catch {
210
+ }
211
+ });
212
+ }
213
+ globalThis[/* @__PURE__ */ Symbol.for("fluixi.revalidate")] = (names) => revalidate(names);
246
214
  function createAsync(fetcher, source) {
247
- const [data] = (0, import_signal3.createResource)(source || (() => true), fetcher);
215
+ const [data] = (0, import_signal2.createResource)(source || (() => true), fetcher);
248
216
  return data;
249
217
  }
250
218
  var submissionState = /* @__PURE__ */ new Map();
@@ -252,24 +220,44 @@ function getSubmissionSignal(url) {
252
220
  if (!submissionState.has(url)) {
253
221
  submissionState.set(
254
222
  url,
255
- (0, import_signal3.createSignal)(void 0)
223
+ (0, import_signal2.createSignal)(void 0)
256
224
  );
257
225
  }
258
226
  return submissionState.get(url);
259
227
  }
260
- function action(fn, name) {
228
+ var submissionsState = /* @__PURE__ */ new Map();
229
+ function getSubmissionsSignal(url) {
230
+ if (!submissionsState.has(url)) submissionsState.set(url, (0, import_signal2.createSignal)([]));
231
+ return submissionsState.get(url);
232
+ }
233
+ function action(fn, name, options = {}) {
261
234
  const actionUrl = name || fn.name || "action";
262
235
  const submit = async (input) => {
263
236
  const [, setSubmission] = getSubmissionSignal(actionUrl);
264
- const clear = () => setSubmission(void 0);
265
- const retry = () => submit(input);
266
- setSubmission({
237
+ const [, setSubmissions] = getSubmissionsSignal(actionUrl);
238
+ let entry;
239
+ const drop2 = () => setSubmissions((list) => list.filter((s) => s !== entry));
240
+ const settle = (next) => {
241
+ setSubmissions((list) => list.map((s) => s === entry ? next : s));
242
+ entry = next;
243
+ };
244
+ const clear = () => {
245
+ setSubmission(void 0);
246
+ drop2();
247
+ };
248
+ const retry = () => {
249
+ drop2();
250
+ return submit(input);
251
+ };
252
+ entry = {
267
253
  input,
268
254
  // Approximate type for tracking
269
255
  pending: true,
270
256
  clear,
271
257
  retry
272
- });
258
+ };
259
+ setSubmission(entry);
260
+ setSubmissions((list) => [...list, entry]);
273
261
  try {
274
262
  const result = await fn(input);
275
263
  setSubmission({
@@ -279,34 +267,46 @@ function action(fn, name) {
279
267
  clear,
280
268
  retry
281
269
  });
270
+ drop2();
271
+ const stale = typeof options.invalidates === "function" ? options.invalidates(result) : options.invalidates;
272
+ for (const key of stale ?? []) revalidate(key);
282
273
  return result;
283
274
  } catch (error) {
284
- setSubmission({
275
+ const failed = {
285
276
  input,
286
277
  error,
287
278
  pending: false,
288
279
  clear,
289
280
  retry
290
- });
281
+ };
282
+ setSubmission(failed);
283
+ settle(failed);
291
284
  throw error;
292
285
  }
293
286
  };
294
287
  submit.url = actionUrl;
295
- submit.with = (args) => action((...a) => fn(args, ...a), name);
288
+ submit.with = (args) => action((...a) => fn(args, ...a), name, options);
296
289
  return submit;
297
290
  }
298
291
  function useSubmission(actionFn) {
299
292
  const [submission] = getSubmissionSignal(actionFn.url);
300
293
  return submission;
301
294
  }
295
+ function useSubmissions(actionFn) {
296
+ const [submissions] = getSubmissionsSignal(actionFn.url);
297
+ return submissions;
298
+ }
302
299
  function useAction(actionFn) {
303
300
  return actionFn;
304
301
  }
305
302
  var RouteDataContext = createContext(void 0, "route-data");
306
303
  var RouteDataProvider = RouteDataContext.Provider;
307
304
 
305
+ // src/lib/router/index.ts
306
+ var import_router2 = require("@fluixi/router");
307
+
308
308
  // src/lib/render/lazy.ts
309
- var import_dom3 = require("@fluixi/dom");
309
+ var import_dom2 = require("@fluixi/dom");
310
310
  var import_signal4 = require("@fluixi/reactive/signal");
311
311
  function fromChunk(value) {
312
312
  if (value != null && (typeof value === "object" || typeof value === "function")) {
@@ -340,7 +340,7 @@ function lazy(loader, options) {
340
340
  }
341
341
  return (0, import_signal4.createMemo)(() => {
342
342
  const C = component();
343
- return C ? fromChunk((0, import_dom3.createComponent)(C, props)) : void 0;
343
+ return C ? fromChunk((0, import_dom2.createComponent)(C, props)) : void 0;
344
344
  });
345
345
  });
346
346
  LazyWrapper.preload = () => load().then(() => void 0);
@@ -364,10 +364,32 @@ function renderLayer(m, depth, base) {
364
364
  cancelled = true;
365
365
  });
366
366
  const owner = (0, import_signal5.getOwner)();
367
- const [resource] = (0, import_signal5.createResource)(
368
- () => loader({ params: base.params, location: base.location }, owner, () => cancelled)
369
- );
370
- data = resource;
367
+ const router = (0, import_signal6.useContext)(RouterContext);
368
+ let watching = false;
369
+ const trackDeps = (deps) => {
370
+ if (watching || cancelled) return;
371
+ watching = true;
372
+ (0, import_signal5.runWithOwner)(owner, () => {
373
+ let first = true;
374
+ (0, import_signal5.createEffect)(() => {
375
+ deps();
376
+ if (first) first = false;
377
+ else (0, import_signal5.untrack)(() => void refetch());
378
+ });
379
+ });
380
+ };
381
+ const [resource, { refetch }] = (0, import_signal5.createResource)(() => {
382
+ const result = loader({ params: base.params, location: base.location }, owner, () => cancelled, trackDeps);
383
+ return router ? router.trackLoad(result) : result;
384
+ });
385
+ (0, import_signal5.onCleanup)(onRevalidate(() => void refetch()));
386
+ data = () => {
387
+ const error = resource.error;
388
+ if (error !== void 0 && error !== null) throw error;
389
+ return resource();
390
+ };
391
+ Object.defineProperty(data, "loading", { get: () => resource.loading });
392
+ Object.defineProperty(data, "state", { get: () => resource.state });
371
393
  }
372
394
  const buildPage = () => RouteDataContext2.Provider({
373
395
  value: data,
@@ -378,19 +400,22 @@ function renderLayer(m, depth, base) {
378
400
  return OutletContext.Provider({
379
401
  value: { depth, ...base },
380
402
  get children() {
381
- return (0, import_dom4.createComponent)(m.component, {});
403
+ return (0, import_dom3.createComponent)(m.component, {});
382
404
  }
383
405
  });
384
406
  }
385
407
  });
386
408
  }
387
409
  });
388
- if (!data) return buildPage();
410
+ if (!data) return { view: buildPage(), ready: () => true };
389
411
  const res = data;
390
- const isLoading = () => res.loading;
412
+ const isLoading = () => {
413
+ const state = res.state;
414
+ return state === void 0 ? res.loading : state === "pending" || state === "unresolved";
415
+ };
391
416
  const [ready, setReady] = (0, import_signal5.createSignal)((0, import_signal5.untrack)(() => !isLoading()));
392
417
  (0, import_signal5.createEffect)(() => setReady(!isLoading()));
393
- return (0, import_signal5.createMemo)(() => ready() ? buildPage() : null);
418
+ return { view: (0, import_signal5.createMemo)(() => ready() ? buildPage() : null), ready };
394
419
  }
395
420
  function renderChild(ctx) {
396
421
  const childIdx = ctx.depth + 1;
@@ -401,38 +426,64 @@ function renderChild(ctx) {
401
426
  const c = ctx.matched()[childIdx]?.component;
402
427
  if ((0, import_signal5.untrack)(childComp) !== c) setChildComp(() => c);
403
428
  });
404
- return (0, import_signal5.createMemo)(() => {
429
+ const owner = (0, import_signal5.getOwner)();
430
+ let shown = null;
431
+ let pending = null;
432
+ const drop2 = (layer) => layer?.dispose();
433
+ (0, import_signal5.onCleanup)(() => {
434
+ drop2(shown);
435
+ drop2(pending);
436
+ shown = pending = null;
437
+ });
438
+ const incoming = (0, import_signal5.createMemo)(() => {
405
439
  const component = childComp();
406
- if (!component) {
440
+ if (!component) return null;
441
+ const m = (0, import_signal5.untrack)(() => ctx.matched())[childIdx];
442
+ return (0, import_signal5.runWithOwner)(
443
+ owner,
444
+ () => (0, import_signal5.createRoot)((dispose) => ({
445
+ ...renderLayer(m, childIdx, { matched: ctx.matched, params: ctx.params, location: ctx.location }),
446
+ dispose
447
+ }))
448
+ );
449
+ });
450
+ return (0, import_signal5.createMemo)(() => {
451
+ const next = incoming();
452
+ if (!next) {
453
+ drop2(shown);
454
+ drop2(pending);
455
+ shown = pending = null;
407
456
  return ctx.notFound ? ctx.notFound() : null;
408
457
  }
409
- const m = (0, import_signal5.untrack)(() => ctx.matched())[childIdx];
410
- return renderLayer(m, childIdx, {
411
- matched: ctx.matched,
412
- params: ctx.params,
413
- location: ctx.location
414
- });
458
+ if (next === shown) return shown.view;
459
+ if (pending && pending !== next) drop2(pending);
460
+ pending = next;
461
+ if (shown && !next.ready()) return shown.view;
462
+ drop2(shown);
463
+ shown = next;
464
+ pending = null;
465
+ return shown.view;
415
466
  });
416
467
  }
417
468
  function renderNotFound(notFound) {
418
469
  if (notFound == null) return buildDefaultNotFound();
419
- return typeof notFound === "function" ? (0, import_dom4.createComponent)(notFound, {}) : notFound;
470
+ return typeof notFound === "function" ? (0, import_dom3.createComponent)(notFound, {}) : notFound;
420
471
  }
421
472
  function buildDefaultNotFound() {
422
- const el = (0, import_dom4.createNativeElement)("div");
423
- (0, import_dom4.setAttribute)(el, "class", "fx-not-found");
424
- (0, import_dom4.setAttribute)(el, "role", "main");
425
- (0, import_dom4.setAttribute)(
473
+ const el = (0, import_dom3.createNativeElement)("div");
474
+ (0, import_dom3.setAttribute)(el, "class", "fx-not-found");
475
+ (0, import_dom3.setAttribute)(el, "role", "main");
476
+ (0, import_dom3.setAttribute)(
426
477
  el,
427
478
  "style",
428
479
  "min-height:60vh;display:grid;place-content:center;gap:.25rem;text-align:center;font-family:system-ui,-apple-system,sans-serif"
429
480
  );
430
- const h1 = (0, import_dom4.createNativeElement)("h1");
431
- (0, import_dom4.setAttribute)(h1, "style", "margin:0;font-size:2.5rem;font-weight:700");
432
- (0, import_dom4.insert)(h1, "404");
433
- const p = (0, import_dom4.createNativeElement)("p");
434
- (0, import_dom4.setAttribute)(p, "style", "margin:0;opacity:.7");
435
- (0, import_dom4.insert)(p, "This page could not be found.");
481
+ const h1 = (0, import_dom3.createNativeElement)("h1");
482
+ (0, import_dom3.setAttribute)(h1, "style", "margin:0;font-size:2.5rem;font-weight:700");
483
+ (0, import_dom3.insert)(h1, "404");
484
+ const p = (0, import_dom3.createNativeElement)("p");
485
+ (0, import_dom3.setAttribute)(p, "style", "margin:0;opacity:.7");
486
+ (0, import_dom3.insert)(p, "This page could not be found.");
436
487
  el.appendChild(h1);
437
488
  el.appendChild(p);
438
489
  return el;
@@ -446,6 +497,8 @@ function Router(props) {
446
497
  history
447
498
  });
448
499
  const [state, setState] = (0, import_signal5.createSignal)(router.state());
500
+ const [navigating, setNavigating] = (0, import_signal5.createSignal)(false);
501
+ const [loads, setLoads] = (0, import_signal5.createSignal)(0);
449
502
  let navigationId = 0;
450
503
  const takeMode = () => {
451
504
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
@@ -455,12 +508,15 @@ function Router(props) {
455
508
  const unsub = router.subscribe((next) => {
456
509
  const id = ++navigationId;
457
510
  const mode = takeMode();
511
+ setNavigating(true);
458
512
  const prepared = prepareComponents(
459
513
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
460
514
  );
461
515
  const commit = () => {
462
516
  setState(next);
463
- return (0, import_signal5.flush)();
517
+ const flushed = (0, import_signal5.flush)();
518
+ if (id === navigationId) setNavigating(false);
519
+ return flushed;
464
520
  };
465
521
  if (!prepared) {
466
522
  void runTransition(mode, commit);
@@ -482,7 +538,18 @@ function Router(props) {
482
538
  const ctx = {
483
539
  router,
484
540
  location: (0, import_signal5.createMemo)(() => state().location),
485
- params: (0, import_signal5.createMemo)(() => state().match?.params ?? {})
541
+ params: (0, import_signal5.createMemo)(() => state().match?.params ?? {}),
542
+ isRouting: () => navigating() || loads() > 0,
543
+ // Counted by the promise, not by an owner: a load a newer navigation superseded still
544
+ // settles and uncounts itself, and a resource seeded from the server never calls it.
545
+ trackLoad(result) {
546
+ if (result && typeof result.finally === "function") {
547
+ setLoads((n) => n + 1);
548
+ void result.finally(() => setLoads((n) => n - 1)).catch(() => {
549
+ });
550
+ }
551
+ return result;
552
+ }
486
553
  };
487
554
  const root = {
488
555
  depth: -1,
@@ -505,6 +572,21 @@ function Outlet() {
505
572
  if (!ctx) return null;
506
573
  return renderChild(ctx);
507
574
  }
575
+ function useIsRouting(options = {}) {
576
+ const routing = (0, import_signal6.useContext)(RouterContext)?.isRouting ?? (() => false);
577
+ const delay = options.delay ?? 0;
578
+ if (delay <= 0) return routing;
579
+ const [late, setLate] = (0, import_signal5.createSignal)(false);
580
+ (0, import_signal5.createEffect)(() => {
581
+ if (!routing()) {
582
+ setLate(false);
583
+ return;
584
+ }
585
+ const timer = setTimeout(() => setLate(true), delay);
586
+ (0, import_signal5.onCleanup)(() => clearTimeout(timer));
587
+ });
588
+ return () => routing() && late();
589
+ }
508
590
  function Redirect(props) {
509
591
  const ctx = (0, import_signal6.useContext)(RouterContext);
510
592
  if (ctx && typeof window !== "undefined") {
@@ -520,7 +602,9 @@ var _ssrRouterStub = {
520
602
  router: { navigate: () => {
521
603
  } },
522
604
  location: () => ({ pathname: "", search: "", hash: "", query: {} }),
523
- params: () => ({})
605
+ params: () => ({}),
606
+ isRouting: () => false,
607
+ trackLoad: (result) => result
524
608
  };
525
609
  function useRouter() {
526
610
  const ctx = (0, import_signal6.useContext)(RouterContext);
@@ -635,24 +719,37 @@ var LINK_OWN_PROPS = /* @__PURE__ */ new Set([
635
719
  ]);
636
720
  function Link(props) {
637
721
  const ctx = (0, import_signal6.useContext)(RouterContext);
638
- const navigate = () => ctx?.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
639
- const a = (0, import_dom4.createNativeElement)("a");
640
- (0, import_dom4.setAttribute)(a, "href", read(props.href) ?? "");
641
- if (props.slot) (0, import_dom4.setAttribute)(a, "slot", read(props.slot));
722
+ const navigate = () => ctx.router.navigate(read(props.href) ?? "", { replace: read(props.replace) });
723
+ const a = (0, import_dom3.createNativeElement)("a");
724
+ (0, import_dom3.setAttribute)(a, "href", read(props.href) ?? "");
725
+ if (props.slot) (0, import_dom3.setAttribute)(a, "slot", read(props.slot));
642
726
  for (const k of Object.keys(props)) {
643
727
  if (LINK_OWN_PROPS.has(k)) continue;
644
728
  const v = props[k];
645
- if (v != null && typeof v !== "function") (0, import_dom4.setAttribute)(a, k, String(v));
729
+ if (typeof v === "function") {
730
+ if (/^on[A-Z]/.test(k)) continue;
731
+ const read2 = v;
732
+ const apply = () => {
733
+ const value = read2();
734
+ if (value == null || value === false) a.removeAttribute?.(k);
735
+ else (0, import_dom3.setAttribute)(a, k, String(value));
736
+ };
737
+ if (typeof a.addEventListener === "function") (0, import_signal5.createEffect)(apply);
738
+ else apply();
739
+ continue;
740
+ }
741
+ if (v != null) (0, import_dom3.setAttribute)(a, k, String(v));
646
742
  }
647
743
  if (typeof a.addEventListener === "function") {
648
744
  a.addEventListener("click", (e) => {
649
745
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
746
+ if (!ctx) return;
650
747
  e.preventDefault();
651
748
  navigate();
652
749
  });
653
750
  } else {
654
751
  const base = read(props.className ?? props.class) ?? "";
655
- if (base) (0, import_dom4.setAttribute)(a, "class", base);
752
+ if (base) (0, import_dom3.setAttribute)(a, "class", base);
656
753
  }
657
754
  if (typeof a.addEventListener === "function") {
658
755
  (0, import_signal5.createEffect)(() => {
@@ -665,7 +762,7 @@ function Link(props) {
665
762
  });
666
763
  }
667
764
  const linkChildren = props.children;
668
- if (linkChildren != null) (0, import_dom4.insert)(a, linkChildren);
765
+ if (linkChildren != null) (0, import_dom3.insert)(a, linkChildren);
669
766
  return a;
670
767
  }
671
768
  var A = Link;
@@ -673,14 +770,14 @@ function Form(props) {
673
770
  const action2 = props.action;
674
771
  const cls = props.className ?? props.class;
675
772
  const url = action2.url ?? "";
676
- const form = (0, import_dom4.createNativeElement)("form");
677
- (0, import_dom4.setAttribute)(form, "method", "post");
678
- if (url) (0, import_dom4.setAttribute)(form, "action", url);
679
- if (cls) (0, import_dom4.setAttribute)(form, "class", cls);
773
+ const form = (0, import_dom3.createNativeElement)("form");
774
+ (0, import_dom3.setAttribute)(form, "method", "post");
775
+ if (url) (0, import_dom3.setAttribute)(form, "action", url);
776
+ if (cls) (0, import_dom3.setAttribute)(form, "class", cls);
680
777
  for (const k of Object.keys(props)) {
681
778
  if (k === "action" || k === "children" || k === "class" || k === "className") continue;
682
779
  const v = props[k];
683
- if (v != null && typeof v !== "function") (0, import_dom4.setAttribute)(form, k, String(v));
780
+ if (v != null && typeof v !== "function") (0, import_dom3.setAttribute)(form, k, String(v));
684
781
  }
685
782
  if (typeof form.addEventListener === "function") {
686
783
  form.addEventListener("submit", (e) => {
@@ -694,7 +791,7 @@ function Form(props) {
694
791
  });
695
792
  }
696
793
  const children = props.children;
697
- if (children != null) (0, import_dom4.insert)(form, children);
794
+ if (children != null) (0, import_dom3.insert)(form, children);
698
795
  return form;
699
796
  }
700
797
  async function preloadRoutes(routes, pathname) {