sibujs 1.5.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 (207) hide show
  1. package/dist/browser.cjs +238 -69
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +916 -292
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/chunk-3JHCYHWN.js +125 -0
  11. package/dist/{chunk-VAPYJN4X.js → chunk-3LR7GLWQ.js} +93 -23
  12. package/dist/{chunk-RJ46C3CS.js → chunk-3NSGB5JN.js} +71 -20
  13. package/dist/{chunk-XUEEGU5O.js → chunk-52YJLLRO.js} +16 -4
  14. package/dist/{chunk-XHK6BDAJ.js → chunk-54EDRCEF.js} +25 -8
  15. package/dist/chunk-7JDB7I65.js +1327 -0
  16. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  17. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  18. package/dist/{chunk-BGN5ZMP4.js → chunk-GTBNNBJ6.js} +14 -2
  19. package/dist/chunk-HB24TBAF.js +121 -0
  20. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  21. package/dist/{chunk-BGTHZHJ5.js → chunk-JA6667UN.js} +188 -44
  22. package/dist/{chunk-7GRNSCFT.js → chunk-JXMMDLBY.js} +306 -183
  23. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  24. package/dist/{chunk-SFKNRVCU.js → chunk-KLRMB5ZS.js} +135 -79
  25. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  26. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  27. package/dist/{chunk-BMPL52BF.js → chunk-MIUAXB7K.js} +118 -66
  28. package/dist/{chunk-JCDUJN2F.js → chunk-ND2664SF.js} +486 -153
  29. package/dist/{chunk-VQDZK23A.js → chunk-O2MNQFLP.js} +181 -66
  30. package/dist/{chunk-NHUC2QWH.js → chunk-R73P76YZ.js} +1 -1
  31. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  32. package/dist/chunk-UCS6AMJ7.js +79 -0
  33. package/dist/{chunk-K4G4ZQNR.js → chunk-VLPPXTYG.js} +84 -38
  34. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +410 -99
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +513 -223
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +475 -144
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3355 -1541
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +920 -292
  54. package/dist/index.d.cts +71 -8
  55. package/dist/index.d.ts +71 -8
  56. package/dist/index.js +28 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  59. package/dist/motion.cjs +77 -34
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +335 -69
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +279 -108
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +635 -260
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +642 -222
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +252 -63
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +463 -137
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +977 -94
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K5ZUMYVS.js +0 -89
  136. package/dist/chunk-KQPDEVVS.js +0 -398
  137. package/dist/chunk-L6JRBDNS.js +0 -60
  138. package/dist/chunk-LA6KQEDU.js +0 -712
  139. package/dist/chunk-MB6QFH3I.js +0 -2776
  140. package/dist/chunk-MDVXJWFN.js +0 -304
  141. package/dist/chunk-MEZVEBPN.js +0 -2008
  142. package/dist/chunk-MK4ERFYL.js +0 -2249
  143. package/dist/chunk-MLKGABMK.js +0 -9
  144. package/dist/chunk-MQ5GOYPH.js +0 -2249
  145. package/dist/chunk-MYRV7VDM.js +0 -742
  146. package/dist/chunk-N6IZB6KJ.js +0 -567
  147. package/dist/chunk-NEKUBFPT.js +0 -60
  148. package/dist/chunk-NMRUZALC.js +0 -1097
  149. package/dist/chunk-NYVAC6P5.js +0 -37
  150. package/dist/chunk-NZIIMDWI.js +0 -84
  151. package/dist/chunk-OF7UZIVB.js +0 -725
  152. package/dist/chunk-P3XWXJZU.js +0 -282
  153. package/dist/chunk-P6W3STU4.js +0 -2249
  154. package/dist/chunk-PBHF5WKN.js +0 -616
  155. package/dist/chunk-PDZQY43A.js +0 -616
  156. package/dist/chunk-PTQJDMRT.js +0 -146
  157. package/dist/chunk-PZEGYCF5.js +0 -61
  158. package/dist/chunk-QBMDLBU2.js +0 -975
  159. package/dist/chunk-QWZG56ET.js +0 -2744
  160. package/dist/chunk-RQGQSLQK.js +0 -725
  161. package/dist/chunk-SDLZDHKP.js +0 -107
  162. package/dist/chunk-TDGZL5CU.js +0 -365
  163. package/dist/chunk-TNQWPPE6.js +0 -37
  164. package/dist/chunk-TSOKIX5Z.js +0 -654
  165. package/dist/chunk-UHNL42EF.js +0 -2730
  166. package/dist/chunk-UNXCEF6S.js +0 -21
  167. package/dist/chunk-V2XTI523.js +0 -347
  168. package/dist/chunk-VAU366PN.js +0 -2241
  169. package/dist/chunk-VMVDTCXB.js +0 -712
  170. package/dist/chunk-VQNQZCWJ.js +0 -61
  171. package/dist/chunk-VRW3FULF.js +0 -725
  172. package/dist/chunk-WADYRCO2.js +0 -304
  173. package/dist/chunk-WILQZRO4.js +0 -282
  174. package/dist/chunk-WR5D4EGH.js +0 -26
  175. package/dist/chunk-WUHJISPP.js +0 -298
  176. package/dist/chunk-XYU6TZOW.js +0 -182
  177. package/dist/chunk-Y6GP4QGG.js +0 -276
  178. package/dist/chunk-YECR7UIA.js +0 -347
  179. package/dist/chunk-YUTWTI4B.js +0 -654
  180. package/dist/chunk-Z65KYU7I.js +0 -26
  181. package/dist/chunk-Z6POF5YC.js +0 -975
  182. package/dist/chunk-ZBJP6WFL.js +0 -482
  183. package/dist/chunk-ZD6OAMTH.js +0 -277
  184. package/dist/chunk-ZWKZCBO6.js +0 -317
  185. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  186. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  187. package/dist/contracts-DOrhwbke.d.cts +0 -245
  188. package/dist/contracts-DOrhwbke.d.ts +0 -245
  189. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  190. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  191. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  192. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  193. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  194. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  195. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  196. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  197. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  198. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  199. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  200. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  201. package/dist/ssr-3RXHP5ES.js +0 -38
  202. package/dist/ssr-6GIMY5MX.js +0 -38
  203. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  204. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  205. package/dist/ssr-WKUPVSSK.js +0 -36
  206. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  207. 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,8 +1169,10 @@ 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) {
1172
+ const snapshot = disposers.slice();
1173
+ elementDisposers.delete(current);
1174
+ if (_isDev5) activeBindingCount -= snapshot.length;
1175
+ for (const d of snapshot) {
1018
1176
  try {
1019
1177
  d();
1020
1178
  } catch (err) {
@@ -1023,13 +1181,53 @@ function dispose(node) {
1023
1181
  }
1024
1182
  }
1025
1183
  }
1026
- elementDisposers.delete(current);
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
+ }
1027
1201
  }
1028
1202
  }
1029
1203
  }
1030
1204
 
1031
1205
  // src/core/rendering/tagFactory.ts
1032
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
+ }
1033
1231
  var kebabCache = /* @__PURE__ */ new Map();
1034
1232
  function toKebab(prop) {
1035
1233
  let cached = kebabCache.get(prop);
@@ -1154,79 +1352,103 @@ function appendChildren(el, nodes) {
1154
1352
  }
1155
1353
  }
1156
1354
  }
1157
- var tagFactory = (tag, ns) => (first, second) => {
1158
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1159
- if (first === void 0) return el;
1160
- if (typeof first === "string") {
1161
- if (second !== void 0) {
1162
- el.setAttribute("class", first);
1163
- 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;
1164
1367
  return el;
1165
1368
  }
1166
- el.textContent = first;
1167
- return el;
1168
- }
1169
- if (typeof first === "number") {
1170
- el.textContent = String(first);
1171
- return el;
1172
- }
1173
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1174
- appendChildren(el, first);
1175
- return el;
1176
- }
1177
- const props = first;
1178
- const pClass = props.class;
1179
- if (pClass != null) applyClass(el, pClass);
1180
- const pId = props.id;
1181
- if (pId != null) el.id = pId;
1182
- const pNodes = second !== void 0 ? second : props.nodes;
1183
- if (pNodes != null) appendChildren(el, pNodes);
1184
- const pOn = props.on;
1185
- if (pOn) {
1186
- for (const ev in pOn) {
1187
- el.addEventListener(ev, pOn[ev]);
1188
- }
1189
- }
1190
- const pStyle = props.style;
1191
- if (pStyle != null) applyStyle(el, pStyle);
1192
- const pRef = props.ref;
1193
- if (pRef) pRef.current = el;
1194
- for (const key in props) {
1195
- switch (key) {
1196
- case "class":
1197
- case "id":
1198
- case "nodes":
1199
- case "on":
1200
- case "style":
1201
- case "ref":
1202
- case "onElement":
1203
- continue;
1204
- // already handled above / below
1205
- default: {
1206
- const value = props[key];
1207
- if (value == null) continue;
1208
- if (key[0] === "o" && key[1] === "n") continue;
1209
- if (typeof value === "function") {
1210
- registerDisposer(el, bindAttribute(el, key, value));
1211
- } else if (typeof value === "boolean") {
1212
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1213
- el[key] = value;
1214
- } else if (value) {
1215
- 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
+ }
1216
1434
  } else {
1217
- 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
+ }
1218
1443
  }
1219
- } else {
1220
- const str = String(value);
1221
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1222
1444
  }
1223
1445
  }
1224
1446
  }
1225
- }
1226
- if (props.onElement && typeof props.onElement === "function") {
1227
- props.onElement(el);
1228
- }
1229
- return el;
1447
+ if (props.onElement && typeof props.onElement === "function") {
1448
+ props.onElement(el);
1449
+ }
1450
+ return el;
1451
+ };
1230
1452
  };
1231
1453
 
1232
1454
  // src/core/rendering/html.ts
@@ -1379,10 +1601,10 @@ function enqueueBatchedSignal(signal2) {
1379
1601
  // src/core/signals/signal.ts
1380
1602
  init_dev();
1381
1603
  var _g = globalThis;
1382
- var _isDev6 = isDev();
1604
+ var _isDev7 = isDev();
1383
1605
  function signal(initial, options) {
1384
1606
  const state = { value: initial };
1385
- const debugName = _isDev6 ? options?.name : void 0;
1607
+ const debugName = _isDev7 ? options?.name : void 0;
1386
1608
  const equalsFn = options?.equals;
1387
1609
  if (debugName) {
1388
1610
  state.__name = debugName;
@@ -1396,7 +1618,7 @@ function signal(initial, options) {
1396
1618
  function set(next) {
1397
1619
  const newValue = typeof next === "function" ? next(state.value) : next;
1398
1620
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
1399
- if (_isDev6) {
1621
+ if (_isDev7) {
1400
1622
  const oldValue = state.value;
1401
1623
  state.value = newValue;
1402
1624
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -1408,7 +1630,7 @@ function signal(initial, options) {
1408
1630
  notifySubscribers(state);
1409
1631
  }
1410
1632
  }
1411
- if (_isDev6) {
1633
+ if (_isDev7) {
1412
1634
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1413
1635
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
1414
1636
  }
@@ -1445,40 +1667,93 @@ function getAvailableLocales() {
1445
1667
 
1446
1668
  // src/core/signals/effect.ts
1447
1669
  init_dev();
1448
-
1449
- // src/core/ssr-context.ts
1450
- var ssrMode = false;
1451
- function isSSR() {
1452
- return ssrMode;
1453
- }
1454
-
1455
- // src/core/signals/effect.ts
1670
+ init_ssr_context();
1456
1671
  var _g2 = globalThis;
1457
1672
  function effect(effectFn, options) {
1458
1673
  devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1459
1674
  if (isSSR()) return () => {
1460
1675
  };
1461
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);
1462
1696
  const wrappedFn = onError ? () => {
1463
1697
  try {
1464
- effectFn();
1698
+ invokeBody();
1465
1699
  } catch (err) {
1466
1700
  onError(err);
1467
1701
  }
1468
- } : effectFn;
1702
+ } : invokeBody;
1469
1703
  let cleanupHandle = () => {
1470
1704
  };
1705
+ let running = false;
1471
1706
  const subscriber = () => {
1472
- cleanupHandle();
1473
- 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
+ }
1474
1723
  };
1475
- cleanupHandle = track(wrappedFn, subscriber);
1724
+ running = true;
1725
+ try {
1726
+ cleanupHandle = track(wrappedFn, subscriber);
1727
+ } finally {
1728
+ running = false;
1729
+ }
1476
1730
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1477
1731
  if (hook) hook.emit("effect:create", { effectFn });
1732
+ let disposed = false;
1478
1733
  return () => {
1734
+ if (disposed) return;
1735
+ disposed = true;
1479
1736
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1480
- if (h) h.emit("effect:destroy", { effectFn });
1481
- 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
+ }
1482
1757
  };
1483
1758
  }
1484
1759
 
@@ -1849,9 +2124,11 @@ var ComponentLoader = class {
1849
2124
  }
1850
2125
  return component;
1851
2126
  } catch (error) {
1852
- throw new Error(
2127
+ const wrapped = new Error(
1853
2128
  `Failed to load component for route "${routePath}": ${error instanceof Error ? error.message : String(error)}`
1854
2129
  );
2130
+ wrapped.cause = error;
2131
+ throw wrapped;
1855
2132
  }
1856
2133
  }
1857
2134
  isAsyncComponent(comp) {
@@ -2037,10 +2314,13 @@ var _SibuRouter = class _SibuRouter {
2037
2314
  }
2038
2315
  if ("redirect" in route2) {
2039
2316
  const redirectPath = typeof route2.redirect === "function" ? route2.redirect(to) : route2.redirect;
2040
- if (typeof redirectPath === "string" && /^https?:\/\/|^\/\//i.test(redirectPath)) {
2041
- console.warn(
2042
- `[SibuJS Router] Redirect to absolute URL "${redirectPath}" detected. Use relative paths for safer redirects.`
2043
- );
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);
2044
2324
  }
2045
2325
  if (typeof redirectPath === "string" && !isSafeNavigationTarget(redirectPath)) {
2046
2326
  throw new NavigationFailureError("aborted", from, to);
@@ -2243,6 +2523,7 @@ function createRouter(routesOrOptions, options = {}) {
2243
2523
  routes = [];
2244
2524
  }
2245
2525
  globalRouter = new SibuRouter(routes, options);
2526
+ ensureRouterPagehide();
2246
2527
  return globalRouter;
2247
2528
  }
2248
2529
  function setRoutes(routes) {
@@ -2316,7 +2597,9 @@ function Route() {
2316
2597
  let currentTopRoute = null;
2317
2598
  const cleanupNodes = () => {
2318
2599
  [currentNode, loadingNode, errorNode].forEach((node) => {
2319
- if (node?.parentNode) {
2600
+ if (!node) return;
2601
+ dispose(node);
2602
+ if (node.parentNode) {
2320
2603
  node.parentNode.removeChild(node);
2321
2604
  }
2322
2605
  });
@@ -2347,13 +2630,24 @@ function Route() {
2347
2630
  loadingNode = null;
2348
2631
  }
2349
2632
  };
2350
- const showError = (error) => {
2633
+ const showError = (error, routeDef) => {
2351
2634
  if (!anchor.parentNode) return;
2352
2635
  cleanupNodes();
2353
2636
  errorNode = document.createElement("div");
2354
2637
  errorNode.className = "route-error";
2355
2638
  errorNode.setAttribute("role", "alert");
2356
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;
2357
2651
  const title2 = document.createElement("h3");
2358
2652
  title2.textContent = "Route Error";
2359
2653
  title2.className = "route-error-title";
@@ -2364,12 +2658,14 @@ function Route() {
2364
2658
  retryButton.textContent = "Retry";
2365
2659
  retryButton.className = "route-error-retry";
2366
2660
  retryButton.type = "button";
2367
- retryButton.addEventListener("click", () => {
2661
+ const onRetryClick = () => {
2368
2662
  if (globalRouter) {
2369
2663
  globalRouter.clearErrorCache();
2370
2664
  update();
2371
2665
  }
2372
- });
2666
+ };
2667
+ retryButton.addEventListener("click", onRetryClick);
2668
+ registerDisposer(retryButton, () => retryButton.removeEventListener("click", onRetryClick));
2373
2669
  errorNode.appendChild(title2);
2374
2670
  errorNode.appendChild(message);
2375
2671
  errorNode.appendChild(retryButton);
@@ -2401,7 +2697,11 @@ function Route() {
2401
2697
  currentTopRoute = routeDef;
2402
2698
  if ("redirect" in routeDef) {
2403
2699
  const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
2404
- 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
+ });
2405
2705
  return;
2406
2706
  }
2407
2707
  if ("component" in routeDef) {
@@ -2420,7 +2720,7 @@ function Route() {
2420
2720
  } catch (error) {
2421
2721
  hideLoading();
2422
2722
  console.error("[Route] Component error:", error);
2423
- showError(error instanceof Error ? error : new Error(String(error)));
2723
+ showError(error instanceof Error ? error : new Error(String(error)), routeDef);
2424
2724
  }
2425
2725
  }
2426
2726
  } catch (error) {
@@ -2440,13 +2740,16 @@ function Route() {
2440
2740
  await originalUpdate();
2441
2741
  routeInitialized = true;
2442
2742
  };
2443
- track(wrappedUpdate);
2743
+ const routeTeardown = track(wrappedUpdate);
2444
2744
  if (!routeInitialized) {
2445
2745
  queueMicrotask(() => {
2446
2746
  if (!routeInitialized && anchor.parentNode) wrappedUpdate();
2447
2747
  });
2448
2748
  }
2449
- routeCleanups.push(cleanupNodes);
2749
+ routeCleanups.push(() => {
2750
+ routeTeardown();
2751
+ cleanupNodes();
2752
+ });
2450
2753
  return anchor;
2451
2754
  }
2452
2755
  function KeepAliveRoute(options) {
@@ -2475,7 +2778,11 @@ function KeepAliveRoute(options) {
2475
2778
  const { route: routeDef } = match;
2476
2779
  if ("redirect" in routeDef) {
2477
2780
  const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
2478
- 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
+ });
2479
2786
  return;
2480
2787
  }
2481
2788
  if (!("component" in routeDef)) return;
@@ -2545,13 +2852,14 @@ function KeepAliveRoute(options) {
2545
2852
  await update();
2546
2853
  initialized = true;
2547
2854
  };
2548
- track(wrappedUpdate);
2855
+ const kaTeardown = track(wrappedUpdate);
2549
2856
  if (!initialized) {
2550
2857
  queueMicrotask(() => {
2551
2858
  if (!initialized && anchor.parentNode) wrappedUpdate();
2552
2859
  });
2553
2860
  }
2554
2861
  routeCleanups.push(() => {
2862
+ kaTeardown();
2555
2863
  for (const node of cache.values()) {
2556
2864
  dispose(node);
2557
2865
  if (node.parentNode) node.parentNode.removeChild(node);
@@ -2618,12 +2926,18 @@ function RouterLink(props) {
2618
2926
  }
2619
2927
  });
2620
2928
  }
2621
- link2.addEventListener("click", (e) => {
2929
+ const onLinkClick = (e) => {
2622
2930
  if (target || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
2623
2931
  return;
2624
2932
  }
2625
2933
  e.preventDefault();
2626
- 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);
2627
2941
  });
2628
2942
  return link2;
2629
2943
  }
@@ -2734,10 +3048,21 @@ function destroyRouter() {
2734
3048
  globalRouter = null;
2735
3049
  }
2736
3050
  }
2737
- if (typeof window !== "undefined") {
2738
- window.addEventListener("beforeunload", () => {
2739
- destroyRouter();
2740
- });
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
+ }
2741
3066
  }
2742
3067
  function Outlet() {
2743
3068
  const anchor = document.createComment("route-outlet-nested");
@@ -2763,12 +3088,20 @@ function Outlet() {
2763
3088
  console.error("[Outlet] Failed to render child route:", error);
2764
3089
  }
2765
3090
  };
2766
- track(update);
3091
+ const outletTeardown = track(update);
2767
3092
  if (!anchor.parentNode) {
2768
3093
  queueMicrotask(() => {
2769
3094
  if (anchor.parentNode) update();
2770
3095
  });
2771
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
+ });
2772
3105
  return anchor;
2773
3106
  }
2774
3107
  function addRoute(route2, parentPath) {
@@ -3086,6 +3419,10 @@ function hydrateRouter(routes, options) {
3086
3419
  if (resolved.component) {
3087
3420
  hydrate2(resolved.component, container);
3088
3421
  }
3422
+ }).catch((err) => {
3423
+ if (typeof console !== "undefined") {
3424
+ console.error("[SibuJS routerSSR] failed to load hydrate:", err);
3425
+ }
3089
3426
  });
3090
3427
  }
3091
3428
  }
@@ -3159,83 +3496,117 @@ function escapeAttrLocal(str) {
3159
3496
  }
3160
3497
 
3161
3498
  // src/plugins/plugin.ts
3162
- var installedPlugins = /* @__PURE__ */ new Set();
3163
- var hooks = {
3164
- init: [],
3165
- mount: [],
3166
- unmount: [],
3167
- error: []
3168
- };
3169
- 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;
3170
3579
  function createPlugin(name, install) {
3171
3580
  return { name, install };
3172
3581
  }
3173
3582
  function plugin(plugin2, options) {
3174
- if (installedPlugins.has(plugin2.name)) {
3175
- console.warn(`[Plugin] "${plugin2.name}" is already installed.`);
3176
- return;
3177
- }
3178
- const ctx = {
3179
- onInit: (cb) => hooks.init.push(cb),
3180
- onMount: (cb) => hooks.mount.push(cb),
3181
- onUnmount: (cb) => hooks.unmount.push(cb),
3182
- onError: (cb) => hooks.error.push(cb),
3183
- provide: (key, value) => provided.set(key, value)
3184
- };
3185
- const initHooksBefore = hooks.init.length;
3186
- plugin2.install(ctx, options);
3187
- installedPlugins.add(plugin2.name);
3188
- for (let i2 = initHooksBefore; i2 < hooks.init.length; i2++) {
3189
- try {
3190
- hooks.init[i2]();
3191
- } catch (e) {
3192
- console.error(`[Plugin] "${plugin2.name}" init error:`, e);
3193
- }
3194
- }
3583
+ defaultRegistryTouched = true;
3584
+ defaultRegistry.plugin(plugin2, options);
3195
3585
  }
3196
3586
  function inject(key, defaultValue) {
3197
- if (provided.has(key)) {
3198
- return provided.get(key);
3199
- }
3200
- if (defaultValue !== void 0) {
3201
- return defaultValue;
3202
- }
3203
- throw new Error(`[Plugin] No provider found for key "${key}"`);
3587
+ return defaultRegistry.inject(key, defaultValue);
3204
3588
  }
3205
3589
  function triggerPluginMount(element) {
3206
- for (const hook of hooks.mount) {
3207
- try {
3208
- hook(element);
3209
- } catch (e) {
3210
- console.error("[Plugin] Mount hook error:", e);
3211
- }
3212
- }
3590
+ defaultRegistry.triggerMount(element);
3213
3591
  }
3214
3592
  function triggerPluginUnmount(element) {
3215
- for (const hook of hooks.unmount) {
3216
- try {
3217
- hook(element);
3218
- } catch (e) {
3219
- console.error("[Plugin] Unmount hook error:", e);
3220
- }
3221
- }
3593
+ defaultRegistry.triggerUnmount(element);
3222
3594
  }
3223
3595
  function triggerPluginError(error) {
3224
- for (const hook of hooks.error) {
3225
- try {
3226
- hook(error);
3227
- } catch (e) {
3228
- console.error("[Plugin] Error hook error:", e);
3229
- }
3230
- }
3596
+ defaultRegistry.triggerError(error);
3231
3597
  }
3232
3598
  function resetPlugins() {
3233
- installedPlugins.clear();
3234
- hooks.init.length = 0;
3235
- hooks.mount.length = 0;
3236
- hooks.unmount.length = 0;
3237
- hooks.error.length = 0;
3238
- 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;
3239
3610
  }
3240
3611
 
3241
3612
  // src/plugins/modular.ts
@@ -3713,7 +4084,8 @@ init_ssr();
3713
4084
  function preloadCritical(resources) {
3714
4085
  if (typeof document === "undefined") return;
3715
4086
  for (const resource of resources) {
3716
- 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}"]`);
3717
4089
  if (existing) continue;
3718
4090
  const link2 = document.createElement("link");
3719
4091
  link2.rel = "preload";
@@ -3948,6 +4320,7 @@ function createBootSequence() {
3948
4320
  Suspense,
3949
4321
  Trans,
3950
4322
  VERSION,
4323
+ __removeRouterPagehideHandler,
3951
4324
  addRoute,
3952
4325
  afterEach,
3953
4326
  back,
@@ -3963,6 +4336,7 @@ function createBootSequence() {
3963
4336
  createMigrationRunner,
3964
4337
  createModuleRegistry,
3965
4338
  createPlugin,
4339
+ createPluginRegistry,
3966
4340
  createRouter,
3967
4341
  createSSRCache,
3968
4342
  createSSRRouter,
@@ -4005,6 +4379,7 @@ function createBootSequence() {
4005
4379
  routerState,
4006
4380
  satisfies,
4007
4381
  serializeRouteState,
4382
+ setDefaultPluginRegistry,
4008
4383
  setLocale,
4009
4384
  setRouteTransition,
4010
4385
  setRoutes,