sibujs 1.4.0 → 2.0.0

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 (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/plugins.cjs CHANGED
@@ -26,12 +26,12 @@ function isDev() {
26
26
  }
27
27
  function devAssert(condition, message) {
28
28
  if (_isDev && !condition) {
29
- throw new Error(`[Sibu] ${message}`);
29
+ throw new Error(`[SibuJS] ${message}`);
30
30
  }
31
31
  }
32
32
  function devWarn(message) {
33
33
  if (_isDev) {
34
- console.warn(`[Sibu] ${message}`);
34
+ console.warn(`[SibuJS] ${message}`);
35
35
  }
36
36
  }
37
37
  var _isDev;
@@ -47,14 +47,47 @@ function sanitizeUrl(url) {
47
47
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
48
48
  if (!trimmed) return "";
49
49
  const lower = trimmed.toLowerCase();
50
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
51
- return "";
50
+ let schemeEnd = -1;
51
+ for (let i2 = 0; i2 < lower.length; i2++) {
52
+ const ch = lower.charCodeAt(i2);
53
+ if (ch === 58) {
54
+ schemeEnd = i2;
55
+ break;
56
+ }
57
+ if (ch === 47 || ch === 63 || ch === 35) break;
52
58
  }
59
+ if (schemeEnd === -1) return trimmed;
60
+ const scheme = lower.slice(0, schemeEnd + 1);
61
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
62
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
53
63
  return trimmed;
54
64
  }
65
+ function sanitizeSrcset(value) {
66
+ const parts = value.split(",");
67
+ const out = [];
68
+ for (let i2 = 0; i2 < parts.length; i2++) {
69
+ const part = parts[i2].trim();
70
+ if (!part) continue;
71
+ const m = part.match(/^(\S+)(\s+.+)?$/);
72
+ if (!m) continue;
73
+ const safe = sanitizeUrl(m[1]);
74
+ if (!safe) continue;
75
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
76
+ }
77
+ return out.join(", ");
78
+ }
55
79
  function sanitizeCSSValue(value) {
56
- const lower = value.toLowerCase().replace(/\s+/g, "");
57
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
80
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
81
+ const code2 = Number.parseInt(hex, 16);
82
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
83
+ try {
84
+ return String.fromCodePoint(code2);
85
+ } catch {
86
+ return "";
87
+ }
88
+ });
89
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
90
+ if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
58
91
  return "";
59
92
  }
60
93
  return value;
@@ -62,11 +95,56 @@ function sanitizeCSSValue(value) {
62
95
  function isUrlAttribute(attr) {
63
96
  return URL_ATTRIBUTES.has(attr);
64
97
  }
65
- var URL_ATTRIBUTES;
98
+ var SAFE_URL_PROTOCOLS, URL_ATTRIBUTES;
66
99
  var init_sanitize = __esm({
67
100
  "src/utils/sanitize.ts"() {
68
101
  "use strict";
69
- URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
102
+ SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
103
+ URL_ATTRIBUTES = /* @__PURE__ */ new Set([
104
+ "href",
105
+ "xlink:href",
106
+ "src",
107
+ "action",
108
+ "formaction",
109
+ "formtarget",
110
+ "cite",
111
+ "poster",
112
+ "background",
113
+ "srcset",
114
+ "ping",
115
+ "data"
116
+ ]);
117
+ }
118
+ });
119
+
120
+ // src/core/ssr-context.ts
121
+ function getSSRStore() {
122
+ if (als) {
123
+ const s2 = als.getStore();
124
+ if (s2) return s2;
125
+ }
126
+ return fallbackStore;
127
+ }
128
+ function isSSR() {
129
+ return getSSRStore().ssr;
130
+ }
131
+ var als, fallbackStore;
132
+ var init_ssr_context = __esm({
133
+ "src/core/ssr-context.ts"() {
134
+ "use strict";
135
+ als = null;
136
+ try {
137
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
138
+ const req = Function("return typeof require==='function'?require:null")();
139
+ if (req) {
140
+ const mod = req("node:async_hooks");
141
+ als = new mod.AsyncLocalStorage();
142
+ }
143
+ }
144
+ } catch {
145
+ als = null;
146
+ }
147
+ fallbackStore = { ssr: false, suspenseIdCounter: 0 };
70
148
  }
71
149
  });
72
150
 
@@ -100,7 +178,7 @@ function isEventHandlerAttr2(name) {
100
178
  return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
101
179
  }
102
180
  function ssrErrorComment(err) {
103
- if (_isDev7) {
181
+ if (_isDev8) {
104
182
  const msg = escapeHtml(err instanceof Error ? err.message : String(err));
105
183
  return `<!--SSR error: ${safeCommentText(msg)}-->`;
106
184
  }
@@ -130,10 +208,10 @@ function renderToString(element) {
130
208
  }
131
209
  const tag = element.tagName.toLowerCase();
132
210
  if (tag === "script" || tag === "style") {
133
- return _isDev7 ? `<!--ssr:${tag}-stripped-->` : "";
211
+ return _isDev8 ? `<!--ssr:${tag}-stripped-->` : "";
134
212
  }
135
213
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
136
- return _isDev7 ? "<!--ssr:invalid-tag-->" : "";
214
+ return _isDev8 ? "<!--ssr:invalid-tag-->" : "";
137
215
  }
138
216
  let html2 = `<${tag}`;
139
217
  for (const attr of Array.from(element.attributes)) {
@@ -174,9 +252,9 @@ function hydrate(component, container, options = {}) {
174
252
  const first = mismatches[0];
175
253
  if (options.onMismatch) {
176
254
  options.onMismatch(first);
177
- } else if (_isDev7) {
255
+ } else if (_isDev8) {
178
256
  console.warn(
179
- `[Sibu hydration] ${first.message}
257
+ `[SibuJS hydration] ${first.message}
180
258
  at ${first.path}
181
259
  server: ${first.serverValue}
182
260
  client: ${first.clientValue}`
@@ -184,17 +262,9 @@ function hydrate(component, container, options = {}) {
184
262
  }
185
263
  }
186
264
  }
187
- hydrateNode(container.firstElementChild, clientTree);
265
+ container.replaceChildren(clientTree);
188
266
  container.setAttribute("data-sibu-hydrated", "true");
189
267
  }
190
- function hydrateNode(serverNode, clientNode) {
191
- if (!serverNode) return;
192
- const serverChildren = Array.from(serverNode.children);
193
- const clientChildren = Array.from(clientNode.children);
194
- for (let i2 = 0; i2 < Math.min(serverChildren.length, clientChildren.length); i2++) {
195
- hydrateNode(serverChildren[i2], clientChildren[i2]);
196
- }
197
- }
198
268
  function collectMismatches(serverNode, clientNode, path2, out, max = 5) {
199
269
  if (out.length >= max) return;
200
270
  const nodePath = path2 || clientNode?.tagName?.toLowerCase() || "(root)";
@@ -372,11 +442,11 @@ async function* renderToStream(element) {
372
442
  }
373
443
  const tag = element.tagName.toLowerCase();
374
444
  if (tag === "script" || tag === "style") {
375
- if (_isDev7) yield `<!--ssr:${tag}-stripped-->`;
445
+ if (_isDev8) yield `<!--ssr:${tag}-stripped-->`;
376
446
  return;
377
447
  }
378
448
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
379
- if (_isDev7) yield "<!--ssr:invalid-tag-->";
449
+ if (_isDev8) yield "<!--ssr:invalid-tag-->";
380
450
  return;
381
451
  }
382
452
  let openTag = `<${tag}`;
@@ -424,12 +494,15 @@ function renderToReadableStream(element) {
424
494
  controller.enqueue(value);
425
495
  }
426
496
  },
427
- cancel() {
428
- generator.return(void 0);
497
+ async cancel() {
498
+ await generator.return(void 0);
429
499
  }
430
500
  });
431
501
  }
432
502
  function island(id, component) {
503
+ if (!SAFE_ID.test(id)) {
504
+ throw new Error(`[SibuJS SSR] island: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`);
505
+ }
433
506
  const el = component();
434
507
  el.setAttribute("data-sibu-island", id);
435
508
  return el;
@@ -442,8 +515,9 @@ function hydrateIslands(container, islands) {
442
515
  const factory = islands[id];
443
516
  if (typeof factory !== "function") continue;
444
517
  const clientTree = factory();
445
- hydrateNode(marker2, clientTree);
446
- marker2.setAttribute("data-sibu-hydrated", "true");
518
+ clientTree.setAttribute("data-sibu-island", id);
519
+ clientTree.setAttribute("data-sibu-hydrated", "true");
520
+ marker2.replaceWith(clientTree);
447
521
  }
448
522
  container.setAttribute("data-sibu-hydrated", "partial");
449
523
  }
@@ -460,8 +534,9 @@ function hydrateProgressively(container, islands, options) {
460
534
  for (const entry of entries) {
461
535
  if (entry.isIntersecting) {
462
536
  const clientTree = factory();
463
- hydrateNode(marker2, clientTree);
464
- marker2.setAttribute("data-sibu-hydrated", "true");
537
+ clientTree.setAttribute("data-sibu-island", id);
538
+ clientTree.setAttribute("data-sibu-hydrated", "true");
539
+ marker2.replaceWith(clientTree);
465
540
  observer.disconnect();
466
541
  break;
467
542
  }
@@ -478,18 +553,36 @@ function hydrateProgressively(container, islands, options) {
478
553
  };
479
554
  }
480
555
  function resetSSRState() {
481
- suspenseIdCounter = 0;
556
+ getSSRStore().suspenseIdCounter = 0;
557
+ }
558
+ function noop() {
482
559
  }
483
560
  function ssrSuspense(props) {
484
- const id = `sibu-sus-${suspenseIdCounter++}`;
561
+ const store = getSSRStore();
562
+ const id = `sibu-sus-${store.suspenseIdCounter++}`;
563
+ const timeoutMs = props.timeoutMs ?? 3e4;
485
564
  const fallbackEl = props.fallback();
486
565
  const wrapper = document.createElement("div");
487
566
  wrapper.setAttribute("data-sibu-suspense-id", id);
488
567
  wrapper.appendChild(fallbackEl);
489
- const promise = props.content().then((resolvedEl) => ({
490
- id,
491
- html: renderToString(resolvedEl)
492
- }));
568
+ const fallbackHtml = renderToString(fallbackEl);
569
+ let timer;
570
+ const timeoutPromise = new Promise((_, reject) => {
571
+ timer = setTimeout(() => reject(new Error(`[SibuJS SSR] ssrSuspense timed out after ${timeoutMs}ms`)), timeoutMs);
572
+ });
573
+ const raced = Promise.race([props.content(), timeoutPromise]);
574
+ const promise = raced.then(
575
+ (resolvedEl) => {
576
+ if (timer) clearTimeout(timer);
577
+ return { id, html: renderToString(resolvedEl) };
578
+ },
579
+ (err) => {
580
+ if (timer) clearTimeout(timer);
581
+ if (_isDev8) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
582
+ return { id, html: fallbackHtml };
583
+ }
584
+ );
585
+ promise.catch(noop);
493
586
  return { element: wrapper, promise };
494
587
  }
495
588
  function suspenseSwapScript(id, nonce) {
@@ -515,14 +608,26 @@ async function* renderToSuspenseStream(element, pendingBoundaries = [], options)
515
608
  function escapeScriptJson(json) {
516
609
  return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
517
610
  }
518
- function serializeState(state, nonce) {
519
- const json = escapeScriptJson(JSON.stringify(state));
611
+ function serializeState(state, nonce, options) {
612
+ const rawJson = JSON.stringify(state);
613
+ const maxBytes = options?.maxBytes ?? DEFAULT_MAX_SSR_BYTES;
614
+ const byteLen = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(rawJson).byteLength : Buffer.byteLength(rawJson, "utf8");
615
+ if (byteLen > maxBytes) {
616
+ throw new Error(`[SibuJS SSR] serializeState: payload (${byteLen} bytes) exceeds maxBytes (${maxBytes})`);
617
+ }
618
+ const json = escapeScriptJson(rawJson);
520
619
  const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
521
620
  return `<script${nonceAttr}>window.${SSR_DATA_ATTR}=${json}</script>`;
522
621
  }
523
622
  function deserializeState(validate) {
524
623
  if (typeof window === "undefined") return void 0;
525
- const raw = window[SSR_DATA_ATTR];
624
+ if (_isDev8 && !validate) {
625
+ console.warn(
626
+ "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
627
+ );
628
+ }
629
+ const w = window;
630
+ const raw = w[SSR_DATA_ATTR];
526
631
  if (raw === void 0) return void 0;
527
632
  if (validate && !validate(raw)) return void 0;
528
633
  return raw;
@@ -533,13 +638,14 @@ function escapeHtml(str) {
533
638
  function escapeAttr(str) {
534
639
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
535
640
  }
536
- var _isDev7, SAFE_ATTR_NAME, URL_ATTRS, VOID_ELEMENTS, suspenseIdCounter, SAFE_SUSPENSE_ID, SSR_DATA_ATTR;
641
+ var _isDev8, SAFE_ATTR_NAME, URL_ATTRS, VOID_ELEMENTS, SAFE_ID, SAFE_SUSPENSE_ID, SSR_DATA_ATTR, DEFAULT_MAX_SSR_BYTES;
537
642
  var init_ssr = __esm({
538
643
  "src/platform/ssr.ts"() {
539
644
  "use strict";
540
645
  init_dev();
646
+ init_ssr_context();
541
647
  init_sanitize();
542
- _isDev7 = isDev();
648
+ _isDev8 = isDev();
543
649
  SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
544
650
  URL_ATTRS = /* @__PURE__ */ new Set([
545
651
  "href",
@@ -571,9 +677,10 @@ var init_ssr = __esm({
571
677
  "track",
572
678
  "wbr"
573
679
  ]);
574
- suspenseIdCounter = 0;
680
+ SAFE_ID = /^[A-Za-z0-9_-]+$/;
575
681
  SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
576
682
  SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
683
+ DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
577
684
  }
578
685
  });
579
686
 
@@ -588,6 +695,7 @@ __export(plugins_exports, {
588
695
  Suspense: () => Suspense,
589
696
  Trans: () => Trans,
590
697
  VERSION: () => VERSION,
698
+ __removeRouterPagehideHandler: () => __removeRouterPagehideHandler,
591
699
  addRoute: () => addRoute,
592
700
  afterEach: () => afterEach,
593
701
  back: () => back,
@@ -603,6 +711,7 @@ __export(plugins_exports, {
603
711
  createMigrationRunner: () => createMigrationRunner,
604
712
  createModuleRegistry: () => createModuleRegistry,
605
713
  createPlugin: () => createPlugin,
714
+ createPluginRegistry: () => createPluginRegistry,
606
715
  createRouter: () => createRouter,
607
716
  createSSRCache: () => createSSRCache,
608
717
  createSSRRouter: () => createSSRRouter,
@@ -645,6 +754,7 @@ __export(plugins_exports, {
645
754
  routerState: () => routerState,
646
755
  satisfies: () => satisfies,
647
756
  serializeRouteState: () => serializeRouteState,
757
+ setDefaultPluginRegistry: () => setDefaultPluginRegistry,
648
758
  setLocale: () => setLocale,
649
759
  setRouteTransition: () => setRouteTransition,
650
760
  setRoutes: () => setRoutes,
@@ -655,6 +765,9 @@ __export(plugins_exports, {
655
765
  });
656
766
  module.exports = __toCommonJS(plugins_exports);
657
767
 
768
+ // src/core/rendering/tagFactory.ts
769
+ init_dev();
770
+
658
771
  // src/reactivity/bindAttribute.ts
659
772
  init_dev();
660
773
  init_sanitize();
@@ -666,11 +779,11 @@ var subscriberStack = new Array(32);
666
779
  var stackCapacity = 32;
667
780
  var stackTop = -1;
668
781
  var currentSubscriber = null;
669
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
670
782
  var SUBS = "__s";
671
783
  var notifyDepth = 0;
672
784
  var pendingQueue = [];
673
785
  var pendingSet = /* @__PURE__ */ new Set();
786
+ var propagateStack = [];
674
787
  function safeInvoke(sub2) {
675
788
  try {
676
789
  sub2();
@@ -716,7 +829,6 @@ function recordDependency(signal2) {
716
829
  let subs = signal2[SUBS];
717
830
  if (!subs) {
718
831
  subs = /* @__PURE__ */ new Set();
719
- signalSubscribers.set(signal2, subs);
720
832
  signal2[SUBS] = subs;
721
833
  }
722
834
  subs.add(currentSubscriber);
@@ -726,42 +838,46 @@ function recordDependency(signal2) {
726
838
  signal2.__f = void 0;
727
839
  }
728
840
  }
841
+ var maxDrainIterations = 1e5;
729
842
  function propagateDirty(sub2) {
730
843
  sub2();
731
- let sig = sub2._sig;
732
- while (sig) {
844
+ const rootSig = sub2._sig;
845
+ if (!rootSig) return;
846
+ const stack = propagateStack;
847
+ const baseLen = stack.length;
848
+ stack.push(rootSig);
849
+ while (stack.length > baseLen) {
850
+ const sig = stack.pop();
733
851
  const first = sig.__f;
734
852
  if (first) {
735
853
  if (first._c) {
736
854
  const nSig = first._sig;
737
- nSig._d = true;
738
- sig = nSig;
739
- continue;
740
- }
741
- if (!pendingSet.has(first)) {
855
+ if (!nSig._d) {
856
+ nSig._d = true;
857
+ stack.push(nSig);
858
+ }
859
+ } else if (!pendingSet.has(first)) {
742
860
  pendingSet.add(first);
743
861
  pendingQueue.push(first);
744
862
  }
745
- break;
863
+ continue;
746
864
  }
747
865
  const subs = sig[SUBS];
748
- if (!subs) break;
749
- let nextSig;
866
+ if (!subs) continue;
750
867
  for (const s2 of subs) {
751
868
  if (s2._c) {
752
- s2();
753
869
  const nSig = s2._sig;
754
- if (nSig && !nextSig) {
755
- nextSig = nSig;
756
- } else if (nSig) {
757
- propagateDirty(s2);
870
+ if (nSig && !nSig._d) {
871
+ nSig._d = true;
872
+ stack.push(nSig);
873
+ } else if (!nSig) {
874
+ s2();
758
875
  }
759
876
  } else if (!pendingSet.has(s2)) {
760
877
  pendingSet.add(s2);
761
878
  pendingQueue.push(s2);
762
879
  }
763
880
  }
764
- sig = nextSig;
765
881
  }
766
882
  }
767
883
  function notifySubscribers(signal2) {
@@ -785,13 +901,23 @@ function notifySubscribers(signal2) {
785
901
  }
786
902
  let i2 = 0;
787
903
  while (i2 < pendingQueue.length) {
904
+ if (i2 >= maxDrainIterations) {
905
+ if (typeof console !== "undefined") {
906
+ console.error(
907
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
908
+ );
909
+ }
910
+ break;
911
+ }
788
912
  safeInvoke(pendingQueue[i2]);
789
913
  i2++;
790
914
  }
791
915
  } finally {
792
- pendingQueue.length = 0;
793
- pendingSet.clear();
794
916
  notifyDepth--;
917
+ if (notifyDepth === 0) {
918
+ pendingQueue.length = 0;
919
+ pendingSet.clear();
920
+ }
795
921
  }
796
922
  return;
797
923
  }
@@ -811,30 +937,48 @@ function notifySubscribers(signal2) {
811
937
  notifyDepth++;
812
938
  try {
813
939
  let directCount = 0;
940
+ let hasComputedSub = false;
814
941
  for (const sub2 of subs) {
942
+ if (sub2._c) hasComputedSub = true;
815
943
  pendingQueue[directCount++] = sub2;
816
944
  }
817
- for (let i3 = 0; i3 < directCount; i3++) {
818
- if (pendingQueue[i3]._c) {
819
- propagateDirty(pendingQueue[i3]);
945
+ if (!hasComputedSub) {
946
+ for (let i3 = 0; i3 < directCount; i3++) {
947
+ safeInvoke(pendingQueue[i3]);
820
948
  }
821
- }
822
- for (let i3 = 0; i3 < directCount; i3++) {
823
- if (!pendingQueue[i3]._c) {
824
- if (!pendingSet.has(pendingQueue[i3])) {
825
- safeInvoke(pendingQueue[i3]);
949
+ } else {
950
+ for (let i3 = 0; i3 < directCount; i3++) {
951
+ if (pendingQueue[i3]._c) {
952
+ propagateDirty(pendingQueue[i3]);
953
+ }
954
+ }
955
+ for (let i3 = 0; i3 < directCount; i3++) {
956
+ const sub2 = pendingQueue[i3];
957
+ if (!sub2._c && !pendingSet.has(sub2)) {
958
+ pendingSet.add(sub2);
959
+ safeInvoke(sub2);
826
960
  }
827
961
  }
828
962
  }
829
963
  let i2 = directCount;
830
964
  while (i2 < pendingQueue.length) {
965
+ if (i2 - directCount >= maxDrainIterations) {
966
+ if (typeof console !== "undefined") {
967
+ console.error(
968
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
969
+ );
970
+ }
971
+ break;
972
+ }
831
973
  safeInvoke(pendingQueue[i2]);
832
974
  i2++;
833
975
  }
834
976
  } finally {
835
- pendingQueue.length = 0;
836
- pendingSet.clear();
837
977
  notifyDepth--;
978
+ if (notifyDepth === 0) {
979
+ pendingQueue.length = 0;
980
+ pendingSet.clear();
981
+ }
838
982
  }
839
983
  }
840
984
  function cleanup(subscriber) {
@@ -845,7 +989,9 @@ function cleanup(subscriber) {
845
989
  if (subs) {
846
990
  subs.delete(subscriber);
847
991
  if (singleDep.__f === subscriber) {
848
- singleDep.__f = void 0;
992
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
993
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
994
+ singleDep.__f = subs.values().next().value;
849
995
  }
850
996
  }
851
997
  sub2._dep = void 0;
@@ -858,7 +1004,9 @@ function cleanup(subscriber) {
858
1004
  if (subs) {
859
1005
  subs.delete(subscriber);
860
1006
  if (signal2.__f === subscriber) {
861
- signal2.__f = void 0;
1007
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
1008
+ } else if (subs.size === 1 && signal2.__f === void 0) {
1009
+ signal2.__f = subs.values().next().value;
862
1010
  }
863
1011
  }
864
1012
  }
@@ -867,6 +1015,9 @@ function cleanup(subscriber) {
867
1015
 
868
1016
  // src/reactivity/bindAttribute.ts
869
1017
  var _isDev3 = isDev();
1018
+ function setProp(el, key, val) {
1019
+ el[key] = val;
1020
+ }
870
1021
  function isEventHandlerAttr(name) {
871
1022
  if (name.length < 3) return false;
872
1023
  const lower = name.toLowerCase();
@@ -892,7 +1043,7 @@ function bindAttribute(el, attr, getter) {
892
1043
  }
893
1044
  if (typeof value === "boolean") {
894
1045
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
895
- el[attr] = value;
1046
+ setProp(el, attr, value);
896
1047
  } else if (value) {
897
1048
  el.setAttribute(attr, "");
898
1049
  } else {
@@ -902,7 +1053,7 @@ function bindAttribute(el, attr, getter) {
902
1053
  }
903
1054
  const str = String(value);
904
1055
  if ((attr === "value" || attr === "checked") && attr in el) {
905
- el[attr] = attr === "checked" ? Boolean(value) : str;
1056
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
906
1057
  } else {
907
1058
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
908
1059
  }
@@ -940,24 +1091,29 @@ function bindChildNode(placeholder, getter) {
940
1091
  let newNodes;
941
1092
  if (Array.isArray(result)) {
942
1093
  newNodes = [];
1094
+ const seen = /* @__PURE__ */ new Set();
943
1095
  for (let i2 = 0; i2 < result.length; i2++) {
944
1096
  const item = result[i2];
945
1097
  if (item == null || typeof item === "boolean") continue;
946
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1098
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1099
+ if (seen.has(node)) {
1100
+ if (_isDev4)
1101
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1102
+ continue;
1103
+ }
1104
+ seen.add(node);
1105
+ newNodes.push(node);
947
1106
  }
948
1107
  } else {
949
1108
  const node = result instanceof Node ? result : document.createTextNode(String(result));
950
1109
  newNodes = [node];
951
1110
  }
952
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
953
- if (reused) {
1111
+ let reused;
1112
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1113
+ const lastSet = new Set(lastNodes);
1114
+ reused = /* @__PURE__ */ new Set();
954
1115
  for (let i2 = 0; i2 < newNodes.length; i2++) {
955
- for (let j = 0; j < lastNodes.length; j++) {
956
- if (newNodes[i2] === lastNodes[j]) {
957
- reused.add(newNodes[i2]);
958
- break;
959
- }
960
- }
1116
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
961
1117
  }
962
1118
  }
963
1119
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1004,7 +1160,7 @@ function dispose(node) {
1004
1160
  while (stack.length > 0) {
1005
1161
  const current = stack.pop();
1006
1162
  order.push(current);
1007
- const children = current.childNodes;
1163
+ const children = Array.from(current.childNodes);
1008
1164
  for (let i2 = 0; i2 < children.length; i2++) {
1009
1165
  stack.push(children[i2]);
1010
1166
  }
@@ -1013,15 +1169,65 @@ function dispose(node) {
1013
1169
  const current = order[i2];
1014
1170
  const disposers = elementDisposers.get(current);
1015
1171
  if (disposers) {
1016
- if (_isDev5) activeBindingCount -= disposers.length;
1017
- for (const d of disposers) d();
1172
+ const snapshot = disposers.slice();
1018
1173
  elementDisposers.delete(current);
1174
+ if (_isDev5) activeBindingCount -= snapshot.length;
1175
+ for (const d of snapshot) {
1176
+ try {
1177
+ d();
1178
+ } catch (err) {
1179
+ if (_isDev5 && typeof console !== "undefined") {
1180
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1181
+ }
1182
+ }
1183
+ }
1184
+ let extraPasses = 0;
1185
+ while (extraPasses++ < 8) {
1186
+ const added = elementDisposers.get(current);
1187
+ if (!added || added.length === 0) break;
1188
+ const moreSnapshot = added.slice();
1189
+ elementDisposers.delete(current);
1190
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
1191
+ for (const d of moreSnapshot) {
1192
+ try {
1193
+ d();
1194
+ } catch (err) {
1195
+ if (_isDev5 && typeof console !== "undefined") {
1196
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1019
1201
  }
1020
1202
  }
1021
1203
  }
1022
1204
 
1023
1205
  // src/core/rendering/tagFactory.ts
1024
1206
  var SVG_NS = "http://www.w3.org/2000/svg";
1207
+ var _isDev6 = isDev();
1208
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1209
+ function validateTagName(tag) {
1210
+ const lower = tag.toLowerCase();
1211
+ if (BLOCKED_TAGS.has(lower)) {
1212
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1213
+ }
1214
+ }
1215
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1216
+ "config",
1217
+ "location",
1218
+ "history",
1219
+ "document",
1220
+ "window",
1221
+ "navigator",
1222
+ "name",
1223
+ "top",
1224
+ "parent",
1225
+ "self",
1226
+ "frames"
1227
+ ]);
1228
+ function setProp2(el, key, val) {
1229
+ el[key] = val;
1230
+ }
1025
1231
  var kebabCache = /* @__PURE__ */ new Map();
1026
1232
  function toKebab(prop) {
1027
1233
  let cached = kebabCache.get(prop);
@@ -1146,79 +1352,103 @@ function appendChildren(el, nodes) {
1146
1352
  }
1147
1353
  }
1148
1354
  }
1149
- var tagFactory = (tag, ns) => (first, second) => {
1150
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1151
- if (first === void 0) return el;
1152
- if (typeof first === "string") {
1153
- if (second !== void 0) {
1154
- el.setAttribute("class", first);
1155
- appendChildren(el, second);
1355
+ var tagFactory = (tag, ns) => {
1356
+ return (first, second) => {
1357
+ validateTagName(tag);
1358
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1359
+ if (first === void 0) return el;
1360
+ if (typeof first === "string") {
1361
+ if (second !== void 0) {
1362
+ el.setAttribute("class", first);
1363
+ appendChildren(el, second);
1364
+ return el;
1365
+ }
1366
+ el.textContent = first;
1156
1367
  return el;
1157
1368
  }
1158
- el.textContent = first;
1159
- return el;
1160
- }
1161
- if (typeof first === "number") {
1162
- el.textContent = String(first);
1163
- return el;
1164
- }
1165
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1166
- appendChildren(el, first);
1167
- return el;
1168
- }
1169
- const props = first;
1170
- const pClass = props.class;
1171
- if (pClass != null) applyClass(el, pClass);
1172
- const pId = props.id;
1173
- if (pId != null) el.id = pId;
1174
- const pNodes = second !== void 0 ? second : props.nodes;
1175
- if (pNodes != null) appendChildren(el, pNodes);
1176
- const pOn = props.on;
1177
- if (pOn) {
1178
- for (const ev in pOn) {
1179
- el.addEventListener(ev, pOn[ev]);
1180
- }
1181
- }
1182
- const pStyle = props.style;
1183
- if (pStyle != null) applyStyle(el, pStyle);
1184
- const pRef = props.ref;
1185
- if (pRef) pRef.current = el;
1186
- for (const key in props) {
1187
- switch (key) {
1188
- case "class":
1189
- case "id":
1190
- case "nodes":
1191
- case "on":
1192
- case "style":
1193
- case "ref":
1194
- case "onElement":
1195
- continue;
1196
- // already handled above / below
1197
- default: {
1198
- const value = props[key];
1199
- if (value == null) continue;
1200
- if (key[0] === "o" && key[1] === "n") continue;
1201
- if (typeof value === "function") {
1202
- registerDisposer(el, bindAttribute(el, key, value));
1203
- } else if (typeof value === "boolean") {
1204
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1205
- el[key] = value;
1206
- } else if (value) {
1207
- el.setAttribute(key, "");
1369
+ if (typeof first === "number") {
1370
+ el.textContent = String(first);
1371
+ return el;
1372
+ }
1373
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1374
+ appendChildren(el, first);
1375
+ return el;
1376
+ }
1377
+ const props = first;
1378
+ const pClass = props.class;
1379
+ if (pClass != null) applyClass(el, pClass);
1380
+ const pId = props.id;
1381
+ if (pId != null) {
1382
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1383
+ devWarn(
1384
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1385
+ );
1386
+ }
1387
+ el.id = pId;
1388
+ }
1389
+ const pNodes = second !== void 0 ? second : props.nodes;
1390
+ if (pNodes != null) appendChildren(el, pNodes);
1391
+ const pOn = props.on;
1392
+ if (pOn) {
1393
+ for (const ev in pOn) {
1394
+ const handler = pOn[ev];
1395
+ if (typeof handler === "function") {
1396
+ el.addEventListener(ev, handler);
1397
+ } else if (_isDev6) {
1398
+ devWarn(
1399
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1400
+ );
1401
+ }
1402
+ }
1403
+ }
1404
+ const pStyle = props.style;
1405
+ if (pStyle != null) applyStyle(el, pStyle);
1406
+ const pRef = props.ref;
1407
+ if (pRef) pRef.current = el;
1408
+ for (const key in props) {
1409
+ switch (key) {
1410
+ case "class":
1411
+ case "id":
1412
+ case "nodes":
1413
+ case "on":
1414
+ case "style":
1415
+ case "ref":
1416
+ case "onElement":
1417
+ continue;
1418
+ // already handled above / below
1419
+ default: {
1420
+ const value = props[key];
1421
+ if (value == null) continue;
1422
+ const lkey = key.toLowerCase();
1423
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1424
+ if (typeof value === "function") {
1425
+ registerDisposer(el, bindAttribute(el, key, value));
1426
+ } else if (typeof value === "boolean") {
1427
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1428
+ setProp2(el, key, value);
1429
+ } else if (value) {
1430
+ el.setAttribute(key, "");
1431
+ } else {
1432
+ el.removeAttribute(key);
1433
+ }
1208
1434
  } else {
1209
- el.removeAttribute(key);
1435
+ const str = String(value);
1436
+ if (lkey === "srcset") {
1437
+ el.setAttribute(key, sanitizeSrcset(str));
1438
+ } else if (isUrlAttribute(lkey)) {
1439
+ el.setAttribute(key, sanitizeUrl(str));
1440
+ } else {
1441
+ el.setAttribute(key, str);
1442
+ }
1210
1443
  }
1211
- } else {
1212
- const str = String(value);
1213
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1214
1444
  }
1215
1445
  }
1216
1446
  }
1217
- }
1218
- if (props.onElement && typeof props.onElement === "function") {
1219
- props.onElement(el);
1220
- }
1221
- return el;
1447
+ if (props.onElement && typeof props.onElement === "function") {
1448
+ props.onElement(el);
1449
+ }
1450
+ return el;
1451
+ };
1222
1452
  };
1223
1453
 
1224
1454
  // src/core/rendering/html.ts
@@ -1371,10 +1601,10 @@ function enqueueBatchedSignal(signal2) {
1371
1601
  // src/core/signals/signal.ts
1372
1602
  init_dev();
1373
1603
  var _g = globalThis;
1374
- var _isDev6 = isDev();
1604
+ var _isDev7 = isDev();
1375
1605
  function signal(initial, options) {
1376
1606
  const state = { value: initial };
1377
- const debugName = _isDev6 ? options?.name : void 0;
1607
+ const debugName = _isDev7 ? options?.name : void 0;
1378
1608
  const equalsFn = options?.equals;
1379
1609
  if (debugName) {
1380
1610
  state.__name = debugName;
@@ -1388,7 +1618,7 @@ function signal(initial, options) {
1388
1618
  function set(next) {
1389
1619
  const newValue = typeof next === "function" ? next(state.value) : next;
1390
1620
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
1391
- if (_isDev6) {
1621
+ if (_isDev7) {
1392
1622
  const oldValue = state.value;
1393
1623
  state.value = newValue;
1394
1624
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -1400,7 +1630,7 @@ function signal(initial, options) {
1400
1630
  notifySubscribers(state);
1401
1631
  }
1402
1632
  }
1403
- if (_isDev6) {
1633
+ if (_isDev7) {
1404
1634
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1405
1635
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
1406
1636
  }
@@ -1425,9 +1655,7 @@ function t(key, params) {
1425
1655
  return params ? message.replace(/\{(\w+)\}/g, (_, p2) => String(params[p2] ?? "")) : message;
1426
1656
  }
1427
1657
  function Trans(key, params) {
1428
- return span({
1429
- nodes: () => t(key, params)
1430
- });
1658
+ return span(() => t(key, params));
1431
1659
  }
1432
1660
  function hasTranslation(key) {
1433
1661
  const locale = currentLocale();
@@ -1439,40 +1667,93 @@ function getAvailableLocales() {
1439
1667
 
1440
1668
  // src/core/signals/effect.ts
1441
1669
  init_dev();
1442
-
1443
- // src/core/ssr-context.ts
1444
- var ssrMode = false;
1445
- function isSSR() {
1446
- return ssrMode;
1447
- }
1448
-
1449
- // src/core/signals/effect.ts
1670
+ init_ssr_context();
1450
1671
  var _g2 = globalThis;
1451
1672
  function effect(effectFn, options) {
1452
1673
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1453
1674
  if (isSSR()) return () => {
1454
1675
  };
1455
1676
  const onError = options?.onError;
1677
+ let userCleanups = [];
1678
+ const onCleanup = (fn) => {
1679
+ userCleanups.push(fn);
1680
+ };
1681
+ const runUserCleanups = () => {
1682
+ if (userCleanups.length === 0) return;
1683
+ const list = userCleanups;
1684
+ userCleanups = [];
1685
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
1686
+ try {
1687
+ list[i2]();
1688
+ } catch (err) {
1689
+ if (typeof console !== "undefined") {
1690
+ console.warn("[SibuJS effect] onCleanup threw:", err);
1691
+ }
1692
+ }
1693
+ }
1694
+ };
1695
+ const invokeBody = () => effectFn(onCleanup);
1456
1696
  const wrappedFn = onError ? () => {
1457
1697
  try {
1458
- effectFn();
1698
+ invokeBody();
1459
1699
  } catch (err) {
1460
1700
  onError(err);
1461
1701
  }
1462
- } : effectFn;
1702
+ } : invokeBody;
1463
1703
  let cleanupHandle = () => {
1464
1704
  };
1705
+ let running = false;
1465
1706
  const subscriber = () => {
1466
- cleanupHandle();
1467
- cleanupHandle = track(wrappedFn, subscriber);
1707
+ if (running) {
1708
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1709
+ console.warn(
1710
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
1711
+ );
1712
+ }
1713
+ return;
1714
+ }
1715
+ running = true;
1716
+ try {
1717
+ runUserCleanups();
1718
+ cleanupHandle();
1719
+ cleanupHandle = track(wrappedFn, subscriber);
1720
+ } finally {
1721
+ running = false;
1722
+ }
1468
1723
  };
1469
- cleanupHandle = track(wrappedFn, subscriber);
1724
+ running = true;
1725
+ try {
1726
+ cleanupHandle = track(wrappedFn, subscriber);
1727
+ } finally {
1728
+ running = false;
1729
+ }
1470
1730
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1471
1731
  if (hook) hook.emit("effect:create", { effectFn });
1732
+ let disposed = false;
1472
1733
  return () => {
1734
+ if (disposed) return;
1735
+ disposed = true;
1473
1736
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1474
- if (h) h.emit("effect:destroy", { effectFn });
1475
- cleanupHandle();
1737
+ if (h) {
1738
+ try {
1739
+ h.emit("effect:destroy", { effectFn });
1740
+ } catch {
1741
+ }
1742
+ }
1743
+ try {
1744
+ runUserCleanups();
1745
+ } catch (err) {
1746
+ if (typeof console !== "undefined") {
1747
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1748
+ }
1749
+ }
1750
+ try {
1751
+ cleanupHandle();
1752
+ } catch (err) {
1753
+ if (typeof console !== "undefined") {
1754
+ console.warn("[SibuJS effect] dispose threw:", err);
1755
+ }
1756
+ }
1476
1757
  };
1477
1758
  }
1478
1759
 
@@ -1598,9 +1879,12 @@ var RouteMatcher = class {
1598
1879
  return this.namedRoutes.get(name) || null;
1599
1880
  }
1600
1881
  matchPattern(path2, routePath) {
1601
- if (routePath === "*" || routePath.endsWith("/*")) {
1882
+ if (routePath === "*") {
1883
+ return { params: { pathMatch: path2 } };
1884
+ }
1885
+ if (routePath.endsWith("/*")) {
1602
1886
  const basePath = routePath.slice(0, -2);
1603
- if (path2.startsWith(basePath)) {
1887
+ if (path2 === basePath || path2.startsWith(`${basePath}/`)) {
1604
1888
  return { params: { pathMatch: path2.slice(basePath.length) } };
1605
1889
  }
1606
1890
  return null;
@@ -1719,6 +2003,8 @@ var GuardManager = class {
1719
2003
  const next = (result) => {
1720
2004
  if (resolved || signal2.aborted) return;
1721
2005
  resolved = true;
2006
+ clearTimeout(timeoutId);
2007
+ signal2.removeEventListener("abort", abortHandler);
1722
2008
  if (result instanceof Error) {
1723
2009
  reject(result);
1724
2010
  } else if (result === false) {
@@ -1732,10 +2018,10 @@ var GuardManager = class {
1732
2018
  const abortHandler = () => {
1733
2019
  if (!resolved) {
1734
2020
  resolved = true;
2021
+ clearTimeout(timeoutId);
1735
2022
  reject(new Error("Navigation aborted"));
1736
2023
  }
1737
2024
  };
1738
- signal2.addEventListener("abort", abortHandler);
1739
2025
  const timeoutId = setTimeout(() => {
1740
2026
  if (!resolved) {
1741
2027
  resolved = true;
@@ -1743,6 +2029,7 @@ var GuardManager = class {
1743
2029
  reject(new Error("Guard timeout"));
1744
2030
  }
1745
2031
  }, this.timeout);
2032
+ signal2.addEventListener("abort", abortHandler);
1746
2033
  try {
1747
2034
  guard(to, from, next);
1748
2035
  } catch (error) {
@@ -1753,12 +2040,6 @@ var GuardManager = class {
1753
2040
  reject(error instanceof Error ? error : new Error(String(error)));
1754
2041
  }
1755
2042
  }
1756
- Promise.resolve().then(() => {
1757
- if (resolved) {
1758
- clearTimeout(timeoutId);
1759
- signal2.removeEventListener("abort", abortHandler);
1760
- }
1761
- });
1762
2043
  });
1763
2044
  }
1764
2045
  addBeforeEach(guard) {
@@ -1843,9 +2124,11 @@ var ComponentLoader = class {
1843
2124
  }
1844
2125
  return component;
1845
2126
  } catch (error) {
1846
- throw new Error(
2127
+ const wrapped = new Error(
1847
2128
  `Failed to load component for route "${routePath}": ${error instanceof Error ? error.message : String(error)}`
1848
2129
  );
2130
+ wrapped.cause = error;
2131
+ throw wrapped;
1849
2132
  }
1850
2133
  }
1851
2134
  isAsyncComponent(comp) {
@@ -2031,10 +2314,13 @@ var _SibuRouter = class _SibuRouter {
2031
2314
  }
2032
2315
  if ("redirect" in route2) {
2033
2316
  const redirectPath = typeof route2.redirect === "function" ? route2.redirect(to) : route2.redirect;
2034
- if (typeof redirectPath === "string" && /^https?:\/\/|^\/\//i.test(redirectPath)) {
2035
- console.warn(
2036
- `[SibuJS Router] Redirect to absolute URL "${redirectPath}" detected. Use relative paths for safer redirects.`
2037
- );
2317
+ if (typeof redirectPath === "string" && /^(https?:)?\/\//i.test(redirectPath)) {
2318
+ if (typeof console !== "undefined") {
2319
+ console.error(
2320
+ `[SibuJS Router] Refusing absolute/protocol-relative redirect "${redirectPath}" \u2014 open-redirect risk.`
2321
+ );
2322
+ }
2323
+ throw new NavigationFailureError("aborted", from, to);
2038
2324
  }
2039
2325
  if (typeof redirectPath === "string" && !isSafeNavigationTarget(redirectPath)) {
2040
2326
  throw new NavigationFailureError("aborted", from, to);
@@ -2237,6 +2523,7 @@ function createRouter(routesOrOptions, options = {}) {
2237
2523
  routes = [];
2238
2524
  }
2239
2525
  globalRouter = new SibuRouter(routes, options);
2526
+ ensureRouterPagehide();
2240
2527
  return globalRouter;
2241
2528
  }
2242
2529
  function setRoutes(routes) {
@@ -2310,7 +2597,9 @@ function Route() {
2310
2597
  let currentTopRoute = null;
2311
2598
  const cleanupNodes = () => {
2312
2599
  [currentNode, loadingNode, errorNode].forEach((node) => {
2313
- if (node?.parentNode) {
2600
+ if (!node) return;
2601
+ dispose(node);
2602
+ if (node.parentNode) {
2314
2603
  node.parentNode.removeChild(node);
2315
2604
  }
2316
2605
  });
@@ -2341,13 +2630,24 @@ function Route() {
2341
2630
  loadingNode = null;
2342
2631
  }
2343
2632
  };
2344
- const showError = (error) => {
2633
+ const showError = (error, routeDef) => {
2345
2634
  if (!anchor.parentNode) return;
2346
2635
  cleanupNodes();
2347
2636
  errorNode = document.createElement("div");
2348
2637
  errorNode.className = "route-error";
2349
2638
  errorNode.setAttribute("role", "alert");
2350
2639
  errorNode.setAttribute("aria-live", "assertive");
2640
+ if (routeDef && "component" in routeDef) {
2641
+ const src = routeDef.component.toString();
2642
+ const importMatch = src.match(/import\(["']([^"']+)["']\)/);
2643
+ if (importMatch) {
2644
+ errorNode.setAttribute("data-component-source", importMatch[1]);
2645
+ }
2646
+ if (routeDef.component.name) {
2647
+ errorNode.setAttribute("data-component-name", routeDef.component.name);
2648
+ }
2649
+ }
2650
+ errorNode.__routeError = error;
2351
2651
  const title2 = document.createElement("h3");
2352
2652
  title2.textContent = "Route Error";
2353
2653
  title2.className = "route-error-title";
@@ -2358,12 +2658,14 @@ function Route() {
2358
2658
  retryButton.textContent = "Retry";
2359
2659
  retryButton.className = "route-error-retry";
2360
2660
  retryButton.type = "button";
2361
- retryButton.addEventListener("click", () => {
2661
+ const onRetryClick = () => {
2362
2662
  if (globalRouter) {
2363
2663
  globalRouter.clearErrorCache();
2364
2664
  update();
2365
2665
  }
2366
- });
2666
+ };
2667
+ retryButton.addEventListener("click", onRetryClick);
2668
+ registerDisposer(retryButton, () => retryButton.removeEventListener("click", onRetryClick));
2367
2669
  errorNode.appendChild(title2);
2368
2670
  errorNode.appendChild(message);
2369
2671
  errorNode.appendChild(retryButton);
@@ -2395,7 +2697,11 @@ function Route() {
2395
2697
  currentTopRoute = routeDef;
2396
2698
  if ("redirect" in routeDef) {
2397
2699
  const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
2398
- queueMicrotask(() => globalRouter?.navigate(redirectPath));
2700
+ queueMicrotask(() => {
2701
+ globalRouter?.navigate(redirectPath).catch((err) => {
2702
+ if (typeof console !== "undefined") console.error("[router] redirect failed:", err);
2703
+ });
2704
+ });
2399
2705
  return;
2400
2706
  }
2401
2707
  if ("component" in routeDef) {
@@ -2414,7 +2720,7 @@ function Route() {
2414
2720
  } catch (error) {
2415
2721
  hideLoading();
2416
2722
  console.error("[Route] Component error:", error);
2417
- showError(error instanceof Error ? error : new Error(String(error)));
2723
+ showError(error instanceof Error ? error : new Error(String(error)), routeDef);
2418
2724
  }
2419
2725
  }
2420
2726
  } catch (error) {
@@ -2434,13 +2740,16 @@ function Route() {
2434
2740
  await originalUpdate();
2435
2741
  routeInitialized = true;
2436
2742
  };
2437
- track(wrappedUpdate);
2743
+ const routeTeardown = track(wrappedUpdate);
2438
2744
  if (!routeInitialized) {
2439
2745
  queueMicrotask(() => {
2440
2746
  if (!routeInitialized && anchor.parentNode) wrappedUpdate();
2441
2747
  });
2442
2748
  }
2443
- routeCleanups.push(cleanupNodes);
2749
+ routeCleanups.push(() => {
2750
+ routeTeardown();
2751
+ cleanupNodes();
2752
+ });
2444
2753
  return anchor;
2445
2754
  }
2446
2755
  function KeepAliveRoute(options) {
@@ -2469,7 +2778,11 @@ function KeepAliveRoute(options) {
2469
2778
  const { route: routeDef } = match;
2470
2779
  if ("redirect" in routeDef) {
2471
2780
  const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
2472
- queueMicrotask(() => globalRouter?.navigate(redirectPath));
2781
+ queueMicrotask(() => {
2782
+ globalRouter?.navigate(redirectPath).catch((err) => {
2783
+ if (typeof console !== "undefined") console.error("[router] redirect failed:", err);
2784
+ });
2785
+ });
2473
2786
  return;
2474
2787
  }
2475
2788
  if (!("component" in routeDef)) return;
@@ -2539,13 +2852,14 @@ function KeepAliveRoute(options) {
2539
2852
  await update();
2540
2853
  initialized = true;
2541
2854
  };
2542
- track(wrappedUpdate);
2855
+ const kaTeardown = track(wrappedUpdate);
2543
2856
  if (!initialized) {
2544
2857
  queueMicrotask(() => {
2545
2858
  if (!initialized && anchor.parentNode) wrappedUpdate();
2546
2859
  });
2547
2860
  }
2548
2861
  routeCleanups.push(() => {
2862
+ kaTeardown();
2549
2863
  for (const node of cache.values()) {
2550
2864
  dispose(node);
2551
2865
  if (node.parentNode) node.parentNode.removeChild(node);
@@ -2612,12 +2926,18 @@ function RouterLink(props) {
2612
2926
  }
2613
2927
  });
2614
2928
  }
2615
- link2.addEventListener("click", (e) => {
2929
+ const onLinkClick = (e) => {
2616
2930
  if (target || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
2617
2931
  return;
2618
2932
  }
2619
2933
  e.preventDefault();
2620
- globalRouter?.navigate(to, { replace: replace2 });
2934
+ globalRouter?.navigate(to, { replace: replace2 }).catch((err) => {
2935
+ if (typeof console !== "undefined") console.error("[router] link navigate failed:", err);
2936
+ });
2937
+ };
2938
+ link2.addEventListener("click", onLinkClick);
2939
+ registerDisposer(link2, () => {
2940
+ link2.removeEventListener("click", onLinkClick);
2621
2941
  });
2622
2942
  return link2;
2623
2943
  }
@@ -2728,10 +3048,21 @@ function destroyRouter() {
2728
3048
  globalRouter = null;
2729
3049
  }
2730
3050
  }
2731
- if (typeof window !== "undefined") {
2732
- window.addEventListener("beforeunload", () => {
2733
- destroyRouter();
2734
- });
3051
+ var _routerPagehideHandler = null;
3052
+ function ensureRouterPagehide() {
3053
+ if (_routerPagehideHandler || typeof window === "undefined") return;
3054
+ _routerPagehideHandler = (event) => {
3055
+ if (event.persisted === false) {
3056
+ destroyRouter();
3057
+ }
3058
+ };
3059
+ window.addEventListener("pagehide", _routerPagehideHandler);
3060
+ }
3061
+ function __removeRouterPagehideHandler() {
3062
+ if (_routerPagehideHandler && typeof window !== "undefined") {
3063
+ window.removeEventListener("pagehide", _routerPagehideHandler);
3064
+ _routerPagehideHandler = null;
3065
+ }
2735
3066
  }
2736
3067
  function Outlet() {
2737
3068
  const anchor = document.createComment("route-outlet-nested");
@@ -2757,12 +3088,20 @@ function Outlet() {
2757
3088
  console.error("[Outlet] Failed to render child route:", error);
2758
3089
  }
2759
3090
  };
2760
- track(update);
3091
+ const outletTeardown = track(update);
2761
3092
  if (!anchor.parentNode) {
2762
3093
  queueMicrotask(() => {
2763
3094
  if (anchor.parentNode) update();
2764
3095
  });
2765
3096
  }
3097
+ routeCleanups.push(() => {
3098
+ outletTeardown();
3099
+ if (currentNode) {
3100
+ dispose(currentNode);
3101
+ if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
3102
+ currentNode = null;
3103
+ }
3104
+ });
2766
3105
  return anchor;
2767
3106
  }
2768
3107
  function addRoute(route2, parentPath) {
@@ -3080,6 +3419,10 @@ function hydrateRouter(routes, options) {
3080
3419
  if (resolved.component) {
3081
3420
  hydrate2(resolved.component, container);
3082
3421
  }
3422
+ }).catch((err) => {
3423
+ if (typeof console !== "undefined") {
3424
+ console.error("[SibuJS routerSSR] failed to load hydrate:", err);
3425
+ }
3083
3426
  });
3084
3427
  }
3085
3428
  }
@@ -3153,83 +3496,117 @@ function escapeAttrLocal(str) {
3153
3496
  }
3154
3497
 
3155
3498
  // src/plugins/plugin.ts
3156
- var installedPlugins = /* @__PURE__ */ new Set();
3157
- var hooks = {
3158
- init: [],
3159
- mount: [],
3160
- unmount: [],
3161
- error: []
3162
- };
3163
- var provided = /* @__PURE__ */ new Map();
3499
+ function createPluginRegistry() {
3500
+ const installedPlugins = /* @__PURE__ */ new Set();
3501
+ const hooks = { init: [], mount: [], unmount: [], error: [] };
3502
+ const provided = /* @__PURE__ */ new Map();
3503
+ const registry = {
3504
+ installedPlugins,
3505
+ hooks,
3506
+ provided,
3507
+ plugin(p2, options) {
3508
+ if (installedPlugins.has(p2.name)) {
3509
+ console.warn(`[Plugin] "${p2.name}" is already installed.`);
3510
+ return;
3511
+ }
3512
+ const ctx = {
3513
+ onInit: (cb) => hooks.init.push(cb),
3514
+ onMount: (cb) => hooks.mount.push(cb),
3515
+ onUnmount: (cb) => hooks.unmount.push(cb),
3516
+ onError: (cb) => hooks.error.push(cb),
3517
+ provide: (key, value) => provided.set(key, value)
3518
+ };
3519
+ const initHooksBefore = hooks.init.length;
3520
+ p2.install(ctx, options);
3521
+ installedPlugins.add(p2.name);
3522
+ const justAdded = hooks.init.slice(initHooksBefore);
3523
+ for (const cb of justAdded) {
3524
+ try {
3525
+ cb();
3526
+ } catch (e) {
3527
+ console.error(`[Plugin] "${p2.name}" init error:`, e);
3528
+ }
3529
+ }
3530
+ },
3531
+ inject(key, defaultValue) {
3532
+ if (provided.has(key)) return provided.get(key);
3533
+ if (defaultValue !== void 0) return defaultValue;
3534
+ throw new Error(`[Plugin] No provider found for key "${key}"`);
3535
+ },
3536
+ triggerMount(element) {
3537
+ const snapshot = hooks.mount.slice();
3538
+ for (const hook of snapshot) {
3539
+ try {
3540
+ hook(element);
3541
+ } catch (e) {
3542
+ console.error("[Plugin] Mount hook error:", e);
3543
+ }
3544
+ }
3545
+ },
3546
+ triggerUnmount(element) {
3547
+ const snapshot = hooks.unmount.slice();
3548
+ for (const hook of snapshot) {
3549
+ try {
3550
+ hook(element);
3551
+ } catch (e) {
3552
+ console.error("[Plugin] Unmount hook error:", e);
3553
+ }
3554
+ }
3555
+ },
3556
+ triggerError(error) {
3557
+ const snapshot = hooks.error.slice();
3558
+ for (const hook of snapshot) {
3559
+ try {
3560
+ hook(error);
3561
+ } catch (e) {
3562
+ console.error("[Plugin] Error hook error:", e);
3563
+ }
3564
+ }
3565
+ },
3566
+ reset() {
3567
+ installedPlugins.clear();
3568
+ hooks.init.length = 0;
3569
+ hooks.mount.length = 0;
3570
+ hooks.unmount.length = 0;
3571
+ hooks.error.length = 0;
3572
+ provided.clear();
3573
+ }
3574
+ };
3575
+ return registry;
3576
+ }
3577
+ var defaultRegistry = createPluginRegistry();
3578
+ var defaultRegistryTouched = false;
3164
3579
  function createPlugin(name, install) {
3165
3580
  return { name, install };
3166
3581
  }
3167
3582
  function plugin(plugin2, options) {
3168
- if (installedPlugins.has(plugin2.name)) {
3169
- console.warn(`[Plugin] "${plugin2.name}" is already installed.`);
3170
- return;
3171
- }
3172
- const ctx = {
3173
- onInit: (cb) => hooks.init.push(cb),
3174
- onMount: (cb) => hooks.mount.push(cb),
3175
- onUnmount: (cb) => hooks.unmount.push(cb),
3176
- onError: (cb) => hooks.error.push(cb),
3177
- provide: (key, value) => provided.set(key, value)
3178
- };
3179
- const initHooksBefore = hooks.init.length;
3180
- plugin2.install(ctx, options);
3181
- installedPlugins.add(plugin2.name);
3182
- for (let i2 = initHooksBefore; i2 < hooks.init.length; i2++) {
3183
- try {
3184
- hooks.init[i2]();
3185
- } catch (e) {
3186
- console.error(`[Plugin] "${plugin2.name}" init error:`, e);
3187
- }
3188
- }
3583
+ defaultRegistryTouched = true;
3584
+ defaultRegistry.plugin(plugin2, options);
3189
3585
  }
3190
3586
  function inject(key, defaultValue) {
3191
- if (provided.has(key)) {
3192
- return provided.get(key);
3193
- }
3194
- if (defaultValue !== void 0) {
3195
- return defaultValue;
3196
- }
3197
- throw new Error(`[Plugin] No provider found for key "${key}"`);
3587
+ return defaultRegistry.inject(key, defaultValue);
3198
3588
  }
3199
3589
  function triggerPluginMount(element) {
3200
- for (const hook of hooks.mount) {
3201
- try {
3202
- hook(element);
3203
- } catch (e) {
3204
- console.error("[Plugin] Mount hook error:", e);
3205
- }
3206
- }
3590
+ defaultRegistry.triggerMount(element);
3207
3591
  }
3208
3592
  function triggerPluginUnmount(element) {
3209
- for (const hook of hooks.unmount) {
3210
- try {
3211
- hook(element);
3212
- } catch (e) {
3213
- console.error("[Plugin] Unmount hook error:", e);
3214
- }
3215
- }
3593
+ defaultRegistry.triggerUnmount(element);
3216
3594
  }
3217
3595
  function triggerPluginError(error) {
3218
- for (const hook of hooks.error) {
3219
- try {
3220
- hook(error);
3221
- } catch (e) {
3222
- console.error("[Plugin] Error hook error:", e);
3223
- }
3224
- }
3596
+ defaultRegistry.triggerError(error);
3225
3597
  }
3226
3598
  function resetPlugins() {
3227
- installedPlugins.clear();
3228
- hooks.init.length = 0;
3229
- hooks.mount.length = 0;
3230
- hooks.unmount.length = 0;
3231
- hooks.error.length = 0;
3232
- provided.clear();
3599
+ defaultRegistry.reset();
3600
+ defaultRegistryTouched = false;
3601
+ }
3602
+ function setDefaultPluginRegistry(registry) {
3603
+ if (defaultRegistryTouched && defaultRegistry.installedPlugins.size > 0) {
3604
+ console.warn(
3605
+ "[Plugin] Replacing default plugin registry while plugins are already installed on the singleton. This may indicate mixed singleton/registry usage."
3606
+ );
3607
+ }
3608
+ defaultRegistry = registry;
3609
+ defaultRegistryTouched = true;
3233
3610
  }
3234
3611
 
3235
3612
  // src/plugins/modular.ts
@@ -3707,7 +4084,8 @@ init_ssr();
3707
4084
  function preloadCritical(resources) {
3708
4085
  if (typeof document === "undefined") return;
3709
4086
  for (const resource of resources) {
3710
- const existing = document.querySelector(`link[rel="preload"][href="${resource.href}"]`);
4087
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource.href) : resource.href.replace(/["\\]/g, "\\$&");
4088
+ const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
3711
4089
  if (existing) continue;
3712
4090
  const link2 = document.createElement("link");
3713
4091
  link2.rel = "preload";
@@ -3942,6 +4320,7 @@ function createBootSequence() {
3942
4320
  Suspense,
3943
4321
  Trans,
3944
4322
  VERSION,
4323
+ __removeRouterPagehideHandler,
3945
4324
  addRoute,
3946
4325
  afterEach,
3947
4326
  back,
@@ -3957,6 +4336,7 @@ function createBootSequence() {
3957
4336
  createMigrationRunner,
3958
4337
  createModuleRegistry,
3959
4338
  createPlugin,
4339
+ createPluginRegistry,
3960
4340
  createRouter,
3961
4341
  createSSRCache,
3962
4342
  createSSRRouter,
@@ -3999,6 +4379,7 @@ function createBootSequence() {
3999
4379
  routerState,
4000
4380
  satisfies,
4001
4381
  serializeRouteState,
4382
+ setDefaultPluginRegistry,
4002
4383
  setLocale,
4003
4384
  setRouteTransition,
4004
4385
  setRoutes,