sibujs 4.12.0 → 4.13.1

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 (74) hide show
  1. package/dist/browser.cjs +71 -18
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +6618 -6178
  4. package/dist/build.d.cts +554 -415
  5. package/dist/build.d.ts +554 -415
  6. package/dist/build.js +2091 -1735
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +13 -13
  9. package/dist/cdn.full.global.js +12 -12
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-WFRQ6D7H.js → chunk-22OPSWMF.js} +1 -1
  12. package/dist/{chunk-ZJCHY2IB.js → chunk-2NC56RGY.js} +1 -1
  13. package/dist/{chunk-NB7TFSJE.js → chunk-2O6JJCUR.js} +4 -4
  14. package/dist/{chunk-B2Y5BJSP.js → chunk-4UTGDVSA.js} +70 -16
  15. package/dist/{chunk-NAVLCP4S.js → chunk-5RIBLY2N.js} +4 -4
  16. package/dist/{chunk-T5O2CUHV.js → chunk-5XUGJM3J.js} +1 -1
  17. package/dist/{chunk-HXT2RNH5.js → chunk-ADZHPN2B.js} +1 -1
  18. package/dist/chunk-B36ADEIR.js +21 -0
  19. package/dist/{chunk-DFZL3AX7.js → chunk-C73VBPEF.js} +9 -5
  20. package/dist/{chunk-WH4U2VUE.js → chunk-CPQKWCJ2.js} +13 -5
  21. package/dist/{chunk-LEUEKK4G.js → chunk-EU7T7EB3.js} +2 -2
  22. package/dist/{chunk-QLG5IK7F.js → chunk-G3Z2FEBR.js} +4 -4
  23. package/dist/{chunk-ZPY2UAKM.js → chunk-J4X7ICH4.js} +3 -3
  24. package/dist/{chunk-XX4BRMWF.js → chunk-K4LNUT5L.js} +1 -1
  25. package/dist/{chunk-CI4W6XAU.js → chunk-KC62VURU.js} +3 -3
  26. package/dist/chunk-N5TBYXHW.js +17 -0
  27. package/dist/{chunk-653KGQS2.js → chunk-OIKD7YDY.js} +2 -2
  28. package/dist/{chunk-LYTUTXQP.js → chunk-PBJYCE2Z.js} +46 -4
  29. package/dist/{chunk-EMB5PG7V.js → chunk-QC3NP47A.js} +1 -1
  30. package/dist/{chunk-IHA62NGJ.js → chunk-QKFVMIAV.js} +1 -1
  31. package/dist/{chunk-LXBMVGVB.js → chunk-QZ47OZ25.js} +3 -3
  32. package/dist/{chunk-ZTYCCBU3.js → chunk-VFPLWUT7.js} +44 -26
  33. package/dist/{chunk-RK7EPQPG.js → chunk-YFTBCCVH.js} +1 -1
  34. package/dist/data.cjs +71 -18
  35. package/dist/data.js +5 -5
  36. package/dist/datePicker-DxuxWpgF.d.cts +285 -0
  37. package/dist/datePicker-DxuxWpgF.d.ts +285 -0
  38. package/dist/devtools.cjs +71 -18
  39. package/dist/devtools.js +4 -4
  40. package/dist/ecosystem.cjs +82 -21
  41. package/dist/ecosystem.js +7 -7
  42. package/dist/extras.cjs +131 -23
  43. package/dist/extras.d.cts +2 -2
  44. package/dist/extras.d.ts +2 -2
  45. package/dist/extras.js +21 -20
  46. package/dist/index.cjs +115 -37
  47. package/dist/index.d.cts +59 -5
  48. package/dist/index.d.ts +59 -5
  49. package/dist/index.js +10 -10
  50. package/dist/motion.cjs +69 -16
  51. package/dist/motion.js +3 -3
  52. package/dist/patterns.cjs +71 -18
  53. package/dist/patterns.js +5 -5
  54. package/dist/performance.cjs +69 -16
  55. package/dist/performance.js +4 -4
  56. package/dist/plugins.cjs +1421 -996
  57. package/dist/plugins.d.cts +132 -3
  58. package/dist/plugins.d.ts +132 -3
  59. package/dist/plugins.js +262 -11
  60. package/dist/routeMiddleware-B1sp4P7a.d.ts +688 -0
  61. package/dist/routeMiddleware-CedQTsLK.d.cts +688 -0
  62. package/dist/ssr.cjs +3607 -3524
  63. package/dist/ssr.d.cts +20 -671
  64. package/dist/ssr.d.ts +20 -671
  65. package/dist/ssr.js +15 -7
  66. package/dist/testing.cjs +71 -18
  67. package/dist/testing.js +2 -2
  68. package/dist/ui.cjs +78 -18
  69. package/dist/ui.js +8 -7
  70. package/dist/widgets.cjs +1352 -952
  71. package/dist/widgets.d.cts +100 -268
  72. package/dist/widgets.d.ts +100 -268
  73. package/dist/widgets.js +316 -7
  74. package/package.json +9 -1
package/dist/plugins.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ prerenderWait
3
+ } from "./chunk-B36ADEIR.js";
1
4
  import {
2
5
  MigrationStorageError,
3
6
  VERSION,
@@ -19,7 +22,10 @@ import {
19
22
  preloadCritical,
20
23
  prerenderRoutes,
21
24
  satisfies
22
- } from "./chunk-653KGQS2.js";
25
+ } from "./chunk-OIKD7YDY.js";
26
+ import {
27
+ acquireTitle
28
+ } from "./chunk-52XFPGSN.js";
23
29
  import {
24
30
  PluginInstallCancelledError,
25
31
  createPlugin,
@@ -35,9 +41,12 @@ import {
35
41
  } from "./chunk-J6FW5TV6.js";
36
42
  import {
37
43
  span
38
- } from "./chunk-T5O2CUHV.js";
44
+ } from "./chunk-5XUGJM3J.js";
39
45
  import {
46
+ STATIC_VALUE_RESOLVER,
40
47
  escapeScriptJson,
48
+ planHeadElementEntry,
49
+ planMetaEntry,
41
50
  renderToString,
42
51
  serializeHeadEntry
43
52
  } from "./chunk-2F4PSJ5Z.js";
@@ -49,17 +58,17 @@ import {
49
58
  import {
50
59
  appendChildren,
51
60
  resolveClassValue
52
- } from "./chunk-WH4U2VUE.js";
61
+ } from "./chunk-CPQKWCJ2.js";
53
62
  import {
54
63
  setSafeAttribute
55
- } from "./chunk-XX4BRMWF.js";
64
+ } from "./chunk-K4LNUT5L.js";
56
65
  import "./chunk-F2UWOZ6J.js";
57
66
  import {
58
67
  globalSingleton
59
68
  } from "./chunk-7ZHH77QA.js";
60
69
  import {
61
70
  effect
62
- } from "./chunk-EMB5PG7V.js";
71
+ } from "./chunk-QC3NP47A.js";
63
72
  import {
64
73
  sanitizeUrl,
65
74
  stripControlChars
@@ -70,14 +79,14 @@ import {
70
79
  import "./chunk-5HZXGZ6T.js";
71
80
  import {
72
81
  signal
73
- } from "./chunk-IHA62NGJ.js";
82
+ } from "./chunk-QKFVMIAV.js";
74
83
  import {
75
84
  bindOwnerScope,
76
85
  getOwnerScope,
77
86
  runWithOwnerScope,
78
87
  track,
79
88
  untracked
80
- } from "./chunk-B2Y5BJSP.js";
89
+ } from "./chunk-4UTGDVSA.js";
81
90
  import {
82
91
  devWarn,
83
92
  dispose,
@@ -158,6 +167,201 @@ function getAvailableLocales() {
158
167
  return Object.keys(locales);
159
168
  }
160
169
 
170
+ // src/plugins/routeHead.ts
171
+ var OG_FIELDS = [
172
+ ["title", "og:title"],
173
+ ["description", "og:description"],
174
+ ["image", "og:image"],
175
+ ["url", "og:url"],
176
+ ["type", "og:type"],
177
+ ["siteName", "og:site_name"]
178
+ ];
179
+ var OG_URL_PROPERTIES = /* @__PURE__ */ new Set(["og:image", "og:url"]);
180
+ var ROUTE_HEAD_ATTR = "data-sibu-head";
181
+ var BASE_TITLE_ATTR = "data-sibu-base-title";
182
+ function routeHasHead(matched, defaults) {
183
+ if (defaults) return true;
184
+ for (const record of matched) if (record.head) return true;
185
+ return false;
186
+ }
187
+ function resolveValue(value, route2) {
188
+ if (value == null) return null;
189
+ const v = typeof value === "function" ? value(route2) : value;
190
+ return v == null ? null : String(v);
191
+ }
192
+ function mergeHeads(route2, defaults) {
193
+ const layers = [];
194
+ if (defaults) layers.push(defaults);
195
+ for (const record of route2.matched) {
196
+ const head = record.head;
197
+ if (head) layers.push(head);
198
+ }
199
+ const merged = {};
200
+ const og = {};
201
+ const meta = /* @__PURE__ */ new Map();
202
+ for (const layer of layers) {
203
+ if (layer.title !== void 0) merged.title = layer.title;
204
+ if (layer.description !== void 0) merged.description = layer.description;
205
+ if (layer.canonical !== void 0) merged.canonical = layer.canonical;
206
+ if (layer.og) {
207
+ for (const [field] of OG_FIELDS) if (layer.og[field] !== void 0) og[field] = layer.og[field];
208
+ }
209
+ for (const entry of layer.meta ?? []) {
210
+ const id = metaIdentity(entry);
211
+ if (id) meta.set(id, entry);
212
+ }
213
+ }
214
+ merged.og = og;
215
+ merged.meta = Array.from(meta.values());
216
+ return merged;
217
+ }
218
+ function metaIdentity(entry) {
219
+ const name = typeof entry.name === "string" ? entry.name : null;
220
+ if (name) return `name:${name.toLowerCase()}`;
221
+ const property = typeof entry.property === "string" ? entry.property : null;
222
+ if (property) return `property:${property.toLowerCase()}`;
223
+ return null;
224
+ }
225
+ function applyTemplate(title, template) {
226
+ if (!template) return title;
227
+ return typeof template === "function" ? template(title) : template.replace("%s", title);
228
+ }
229
+ function resolveRouteHead(route2, defaults) {
230
+ const merged = mergeHeads(route2, defaults);
231
+ const ownTitle = resolveValue(merged.title, route2);
232
+ const routeSetTitle = route2.matched.some((r) => r.head?.title != null);
233
+ const title = ownTitle === null ? null : routeSetTitle ? applyTemplate(ownTitle, defaults?.titleTemplate) : ownTitle;
234
+ const description = resolveValue(merged.description, route2);
235
+ const canonical = resolveValue(merged.canonical, route2);
236
+ const tags = [];
237
+ const push2 = (key, tag, attrs) => {
238
+ const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
239
+ if (plan.kind === "drop") return;
240
+ tags.push({ key, tag, attrs: Object.fromEntries(plan.attributes) });
241
+ };
242
+ if (description !== null) push2("meta:name:description", "meta", { name: "description", content: description });
243
+ if (canonical !== null) push2("link:rel:canonical", "link", { rel: "canonical", href: canonical });
244
+ const og = merged.og ?? {};
245
+ for (const [field, property] of OG_FIELDS) {
246
+ let value = og[field] === void 0 ? void 0 : resolveValue(og[field], route2);
247
+ if (value === void 0) {
248
+ if (field === "title") value = ownTitle;
249
+ else if (field === "description") value = description;
250
+ else if (field === "url") value = canonical;
251
+ else value = null;
252
+ }
253
+ if (value === null) continue;
254
+ if (OG_URL_PROPERTIES.has(property)) {
255
+ value = sanitizeUrl(value);
256
+ if (value === "") continue;
257
+ }
258
+ push2(`meta:property:${property}`, "meta", { property, content: value });
259
+ }
260
+ for (const entry of merged.meta ?? []) {
261
+ const id = metaIdentity(entry);
262
+ if (!id) continue;
263
+ const attrs = {};
264
+ for (const [name, v] of Object.entries(entry)) {
265
+ const resolved = resolveValue(v, route2);
266
+ if (resolved !== null) attrs[name] = resolved;
267
+ }
268
+ push2(`meta:${id}`, "meta", attrs);
269
+ }
270
+ return { title, tags };
271
+ }
272
+ function escapeText(value) {
273
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
274
+ }
275
+ function routeHeadToHTML(head) {
276
+ const parts = [];
277
+ if (head.title !== null) parts.push(`<title>${escapeText(head.title)}</title>`);
278
+ for (const t2 of head.tags) {
279
+ const html = serializeHeadEntry(t2.tag, t2.attrs);
280
+ if (html) parts.push(html.replace(/ \/>$/, ` ${ROUTE_HEAD_ATTR}="" />`));
281
+ }
282
+ return parts.join("\n");
283
+ }
284
+ function selectorFor(key) {
285
+ const [tag, attr, ...rest] = key.split(":");
286
+ const value = rest.join(":").replace(/["\\]/g, "\\$&");
287
+ return `${tag}[${attr}="${value}" i]`;
288
+ }
289
+ function createRouteHeadController(defaults) {
290
+ const [current, setCurrent] = signal(null);
291
+ const owned = /* @__PURE__ */ new Map();
292
+ const initial = new Set(Array.from(document.head.children));
293
+ const titleEl = document.head.querySelector("title");
294
+ const baseTitle = titleEl?.getAttribute(BASE_TITLE_ATTR) ?? document.title;
295
+ let titleLease = null;
296
+ let firstApply = true;
297
+ const publish = (tag) => {
298
+ const existing = owned.get(tag.key);
299
+ if (existing && sameAttrs(existing, tag.attrs)) return;
300
+ if (!existing) {
301
+ for (const el of Array.from(document.head.querySelectorAll(selectorFor(tag.key)))) {
302
+ if (initial.has(el)) {
303
+ el.remove();
304
+ initial.delete(el);
305
+ }
306
+ }
307
+ }
308
+ const next = document.createElement(tag.tag);
309
+ for (const [name, value] of Object.entries(tag.attrs)) next.setAttribute(name, value);
310
+ next.setAttribute(ROUTE_HEAD_ATTR, "");
311
+ if (existing?.parentNode) existing.replaceWith(next);
312
+ else document.head.appendChild(next);
313
+ owned.set(tag.key, next);
314
+ };
315
+ const apply = (head) => {
316
+ if (firstApply) {
317
+ firstApply = false;
318
+ for (const el of Array.from(document.head.querySelectorAll(`[${ROUTE_HEAD_ATTR}]`))) {
319
+ if (initial.has(el) && !head.tags.some((t2) => el.matches(selectorFor(t2.key)))) {
320
+ el.remove();
321
+ initial.delete(el);
322
+ }
323
+ }
324
+ }
325
+ const keep = /* @__PURE__ */ new Set();
326
+ for (const tag of head.tags) {
327
+ keep.add(tag.key);
328
+ publish(tag);
329
+ }
330
+ for (const [key, el] of owned) {
331
+ if (keep.has(key)) continue;
332
+ el.remove();
333
+ owned.delete(key);
334
+ }
335
+ const title = head.title ?? baseTitle;
336
+ if (titleLease) titleLease.set(title);
337
+ else titleLease = acquireTitle(title);
338
+ };
339
+ const stop = effect(() => {
340
+ const route2 = current();
341
+ if (route2 === null) return;
342
+ const head = resolveRouteHead(route2, defaults);
343
+ untracked(() => apply(head));
344
+ });
345
+ return {
346
+ update(route2) {
347
+ setCurrent(route2);
348
+ },
349
+ dispose() {
350
+ stop();
351
+ for (const el of owned.values()) el.remove();
352
+ owned.clear();
353
+ titleLease?.release();
354
+ titleLease = null;
355
+ }
356
+ };
357
+ }
358
+ function sameAttrs(el, attrs) {
359
+ const names = Object.keys(attrs);
360
+ if (el.attributes.length !== names.length + 1) return false;
361
+ for (const name of names) if (el.getAttribute(name) !== attrs[name]) return false;
362
+ return true;
363
+ }
364
+
161
365
  // src/plugins/router.ts
162
366
  function splitRouteUrl(url) {
163
367
  const hashIndex = url.indexOf("#");
@@ -836,6 +1040,15 @@ var _SibuRouter = class _SibuRouter {
836
1040
  // to superseded work. The router's own initial location resolution is
837
1041
  // excluded — it establishes the bootstrap route rather than superseding it.
838
1042
  this.navEpoch = 0;
1043
+ // Created the first time a committed route needs a managed <head>.
1044
+ this.headController = null;
1045
+ // `readyPromise` plus the matched components loaded; created on the first
1046
+ // `routerReady()` call only, so an app that never asks pays nothing and its
1047
+ // outlets load exactly as before.
1048
+ this.loadedPromise = null;
1049
+ this.readyPromise = new Promise((resolve) => {
1050
+ this.resolveReady = resolve;
1051
+ });
839
1052
  this.options = {
840
1053
  mode: "history",
841
1054
  base: "",
@@ -873,12 +1086,31 @@ var _SibuRouter = class _SibuRouter {
873
1086
  }
874
1087
  }
875
1088
  queueMicrotask(() => {
1089
+ let initial = Promise.resolve();
876
1090
  if (!this.navigator.isNavigating) {
877
- this.handleLocationChange(true, true);
1091
+ initial = this.handleLocationChange(true, true);
878
1092
  }
879
1093
  this.isReadySetter(true);
1094
+ prerenderWait(initial.then(this.resolveReady, this.resolveReady));
880
1095
  });
881
1096
  }
1097
+ /** Load the component of every record the current route matched, without instantiating any. */
1098
+ async preloadMatched() {
1099
+ const current = untracked(() => this.currentRouteGetter());
1100
+ for (const record of current.matched) {
1101
+ if ("redirect" in record) continue;
1102
+ try {
1103
+ await this.loader.preloadPlan(record, current.path);
1104
+ } catch {
1105
+ this.loader.clearErrors();
1106
+ }
1107
+ }
1108
+ }
1109
+ /** Settles after the initial navigation and its component loads; see `routerReady()`. */
1110
+ whenReady() {
1111
+ this.loadedPromise ?? (this.loadedPromise = this.readyPromise.then(() => this.destroyed ? void 0 : this.preloadMatched()));
1112
+ return this.loadedPromise;
1113
+ }
882
1114
  createInitialRoute() {
883
1115
  return {
884
1116
  path: "/",
@@ -892,7 +1124,7 @@ var _SibuRouter = class _SibuRouter {
892
1124
  }
893
1125
  handleLocationChange(skipHistory = true, initialResolution = false) {
894
1126
  const path = this.getCurrentPath();
895
- this.navigate(path, { replace: true, skipHistory, initialResolution }).catch((err) => {
1127
+ return this.navigate(path, { replace: true, skipHistory, initialResolution }).catch((err) => {
896
1128
  console.error("[Router] Error during location change navigation:", err);
897
1129
  });
898
1130
  }
@@ -1049,9 +1281,18 @@ var _SibuRouter = class _SibuRouter {
1049
1281
  }
1050
1282
  if (!options.initialResolution) this.navEpoch++;
1051
1283
  this.currentRouteSetter(to);
1284
+ this.updateHead(to);
1052
1285
  this.guards.runAfterEach(to, from);
1053
1286
  this.handleScrollBehavior(to, from);
1054
1287
  }
1288
+ updateHead(to) {
1289
+ if (typeof document === "undefined" || this.destroyed) return;
1290
+ if (this.headController === null) {
1291
+ if (!routeHasHead(to.matched, this.options.head)) return;
1292
+ this.headController = createRouteHeadController(this.options.head);
1293
+ }
1294
+ this.headController.update(to);
1295
+ }
1055
1296
  resolvePath(to) {
1056
1297
  if (typeof to === "string") return to;
1057
1298
  let path = to.path || "";
@@ -1140,14 +1381,14 @@ var _SibuRouter = class _SibuRouter {
1140
1381
  * anything. Pair with {@link instantiateComponent} at the commit boundary.
1141
1382
  */
1142
1383
  async loadPlan(route2, routePath) {
1143
- return this.loader.loadPlan(route2, routePath);
1384
+ return prerenderWait(this.loader.loadPlan(route2, routePath));
1144
1385
  }
1145
1386
  /**
1146
1387
  * Create exactly one real instance from an already-resolved plan. With a
1147
1388
  * `scope`, the factory runs in it (see `RouteScope`).
1148
1389
  */
1149
1390
  async instantiateComponent(plan, route2, routePath, scope) {
1150
- return this.loader.instantiate(plan, route2, routePath, scope);
1391
+ return prerenderWait(this.loader.instantiate(plan, route2, routePath, scope));
1151
1392
  }
1152
1393
  /** Resolve a route's module/factory without creating any component instance. */
1153
1394
  async preloadPlan(route2, routePath) {
@@ -1203,6 +1444,9 @@ var _SibuRouter = class _SibuRouter {
1203
1444
  this.guards.clear();
1204
1445
  this.loader.clearCache();
1205
1446
  this.isReadySetter(false);
1447
+ this.headController?.dispose();
1448
+ this.headController = null;
1449
+ this.resolveReady();
1206
1450
  }
1207
1451
  // Cache management
1208
1452
  clearCache() {
@@ -1441,6 +1685,10 @@ function router() {
1441
1685
  afterEach: (hook) => _routerRef.current?.afterEach(hook)
1442
1686
  };
1443
1687
  }
1688
+ function routerReady() {
1689
+ if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1690
+ return _routerRef.current.whenReady();
1691
+ }
1444
1692
  function navigate(to, options) {
1445
1693
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1446
1694
  return _routerRef.current.navigate(to, options);
@@ -2547,10 +2795,13 @@ export {
2547
2795
  renderRouteToString,
2548
2796
  replace,
2549
2797
  resetPlugins,
2798
+ resolveRouteHead,
2550
2799
  resolveServerRoute,
2551
2800
  route,
2801
+ routeHeadToHTML,
2552
2802
  router,
2553
2803
  routerPlugin,
2804
+ routerReady,
2554
2805
  routerState,
2555
2806
  satisfies,
2556
2807
  serializeRouteState,