sibujs 4.12.0 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/browser.cjs +71 -18
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +6618 -6178
  4. package/dist/build.d.cts +554 -415
  5. package/dist/build.d.ts +554 -415
  6. package/dist/build.js +2091 -1735
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +13 -13
  9. package/dist/cdn.full.global.js +12 -12
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-WFRQ6D7H.js → chunk-22OPSWMF.js} +1 -1
  12. package/dist/{chunk-ZJCHY2IB.js → chunk-2NC56RGY.js} +1 -1
  13. package/dist/{chunk-NB7TFSJE.js → chunk-2O6JJCUR.js} +4 -4
  14. package/dist/{chunk-B2Y5BJSP.js → chunk-4UTGDVSA.js} +70 -16
  15. package/dist/{chunk-NAVLCP4S.js → chunk-5RIBLY2N.js} +4 -4
  16. package/dist/{chunk-T5O2CUHV.js → chunk-5XUGJM3J.js} +1 -1
  17. package/dist/{chunk-HXT2RNH5.js → chunk-ADZHPN2B.js} +1 -1
  18. package/dist/chunk-B36ADEIR.js +21 -0
  19. package/dist/{chunk-DFZL3AX7.js → chunk-C73VBPEF.js} +9 -5
  20. package/dist/{chunk-WH4U2VUE.js → chunk-CPQKWCJ2.js} +13 -5
  21. package/dist/{chunk-LEUEKK4G.js → chunk-EU7T7EB3.js} +2 -2
  22. package/dist/{chunk-QLG5IK7F.js → chunk-G3Z2FEBR.js} +4 -4
  23. package/dist/{chunk-ZPY2UAKM.js → chunk-J4X7ICH4.js} +3 -3
  24. package/dist/{chunk-XX4BRMWF.js → chunk-K4LNUT5L.js} +1 -1
  25. package/dist/{chunk-CI4W6XAU.js → chunk-KC62VURU.js} +3 -3
  26. package/dist/chunk-N5TBYXHW.js +17 -0
  27. package/dist/{chunk-653KGQS2.js → chunk-OIKD7YDY.js} +2 -2
  28. package/dist/{chunk-LYTUTXQP.js → chunk-PBJYCE2Z.js} +46 -4
  29. package/dist/{chunk-EMB5PG7V.js → chunk-QC3NP47A.js} +1 -1
  30. package/dist/{chunk-IHA62NGJ.js → chunk-QKFVMIAV.js} +1 -1
  31. package/dist/{chunk-LXBMVGVB.js → chunk-QZ47OZ25.js} +3 -3
  32. package/dist/{chunk-ZTYCCBU3.js → chunk-VFPLWUT7.js} +44 -26
  33. package/dist/{chunk-RK7EPQPG.js → chunk-YFTBCCVH.js} +1 -1
  34. package/dist/data.cjs +71 -18
  35. package/dist/data.js +5 -5
  36. package/dist/datePicker-DxuxWpgF.d.cts +285 -0
  37. package/dist/datePicker-DxuxWpgF.d.ts +285 -0
  38. package/dist/devtools.cjs +71 -18
  39. package/dist/devtools.js +4 -4
  40. package/dist/ecosystem.cjs +82 -21
  41. package/dist/ecosystem.js +7 -7
  42. package/dist/extras.cjs +131 -23
  43. package/dist/extras.d.cts +2 -2
  44. package/dist/extras.d.ts +2 -2
  45. package/dist/extras.js +21 -20
  46. package/dist/index.cjs +115 -37
  47. package/dist/index.d.cts +59 -5
  48. package/dist/index.d.ts +59 -5
  49. package/dist/index.js +10 -10
  50. package/dist/motion.cjs +69 -16
  51. package/dist/motion.js +3 -3
  52. package/dist/patterns.cjs +71 -18
  53. package/dist/patterns.js +5 -5
  54. package/dist/performance.cjs +69 -16
  55. package/dist/performance.js +4 -4
  56. package/dist/plugins.cjs +1421 -996
  57. package/dist/plugins.d.cts +132 -3
  58. package/dist/plugins.d.ts +132 -3
  59. package/dist/plugins.js +262 -11
  60. package/dist/routeMiddleware-B1sp4P7a.d.ts +688 -0
  61. package/dist/routeMiddleware-CedQTsLK.d.cts +688 -0
  62. package/dist/ssr.cjs +3607 -3524
  63. package/dist/ssr.d.cts +20 -671
  64. package/dist/ssr.d.ts +20 -671
  65. package/dist/ssr.js +15 -7
  66. package/dist/testing.cjs +71 -18
  67. package/dist/testing.js +2 -2
  68. package/dist/ui.cjs +78 -18
  69. package/dist/ui.js +8 -7
  70. package/dist/widgets.cjs +1352 -952
  71. package/dist/widgets.d.cts +100 -268
  72. package/dist/widgets.d.ts +100 -268
  73. package/dist/widgets.js +316 -7
  74. package/package.json +9 -1
package/dist/plugins.cjs CHANGED
@@ -980,6 +980,80 @@ var init_globalSingleton = __esm({
980
980
  }
981
981
  });
982
982
 
983
+ // src/utils/headEntry.ts
984
+ function isEmittableHeadAttrName(name) {
985
+ if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
986
+ const canonical = canonicalAttrName(name);
987
+ if (isEventHandlerAttr(canonical)) return false;
988
+ if (isHtmlContentAttribute(canonical)) return false;
989
+ return true;
990
+ }
991
+ function sanitizeHeadAttrValue(canonical, value) {
992
+ return resolveAttributeValue(canonical, value);
993
+ }
994
+ function findDuplicateAttributeName(names) {
995
+ const seen = /* @__PURE__ */ new Set();
996
+ for (const name of names) {
997
+ const canonical = canonicalAttrName(name);
998
+ if (seen.has(canonical)) return canonical;
999
+ seen.add(canonical);
1000
+ }
1001
+ return null;
1002
+ }
1003
+ function planEntry(props, resolver, applyRefreshPolicy) {
1004
+ const rawNames = Object.keys(props);
1005
+ const duplicate = findDuplicateAttributeName(rawNames);
1006
+ if (duplicate !== null) {
1007
+ return { kind: "drop", reason: `duplicate case-insensitive attribute ${JSON.stringify(duplicate)}` };
1008
+ }
1009
+ const attributes = /* @__PURE__ */ new Map();
1010
+ let reactive = false;
1011
+ for (const name of rawNames) {
1012
+ if (!Object.hasOwn(props, name)) continue;
1013
+ if (!isEmittableHeadAttrName(name)) continue;
1014
+ const authored = props[name];
1015
+ if (resolver.isReactiveValue?.(authored)) reactive = true;
1016
+ const canonical = canonicalAttrName(name);
1017
+ const sanitized = sanitizeHeadAttrValue(canonical, resolver.resolveValue(name, authored));
1018
+ if (sanitized === null) continue;
1019
+ attributes.set(canonical, sanitized);
1020
+ }
1021
+ if (attributes.size === 0) return { kind: "drop", reason: "no effective attributes remain" };
1022
+ const refresh = applyRefreshPolicy ? resolveMetaRefreshPolicy(attributes) : { kind: "not-refresh" };
1023
+ if (refresh.kind === "forbidden") return { kind: "drop", reason: refresh.reason };
1024
+ if (reactive && (refresh.kind === "allowed" || refresh.kind === "delay-only")) {
1025
+ return { kind: "drop", reason: "a reactive meta entry may not publish a native refresh directive" };
1026
+ }
1027
+ return { kind: "publish", attributes, refresh };
1028
+ }
1029
+ function planMetaEntry(props, resolver) {
1030
+ return planEntry(props, resolver, true);
1031
+ }
1032
+ function planHeadElementEntry(props, resolver) {
1033
+ return planEntry(props, resolver, false);
1034
+ }
1035
+ function escapeHeadAttrValue(value) {
1036
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1037
+ }
1038
+ function serializeHeadEntry(tag, attrs) {
1039
+ const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
1040
+ if (plan.kind === "drop") return "";
1041
+ const pairs = Array.from(plan.attributes, ([name, value]) => `${name}="${escapeHeadAttrValue(value)}"`).join(" ");
1042
+ return `<${tag} ${pairs} />`;
1043
+ }
1044
+ var SAFE_HEAD_ATTR_NAME, STATIC_VALUE_RESOLVER;
1045
+ var init_headEntry = __esm({
1046
+ "src/utils/headEntry.ts"() {
1047
+ "use strict";
1048
+ init_metaRefresh();
1049
+ init_sanitize();
1050
+ SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
1051
+ STATIC_VALUE_RESOLVER = {
1052
+ resolveValue: (_name, value) => String(value)
1053
+ };
1054
+ }
1055
+ });
1056
+
983
1057
  // src/utils/embedPolicy.ts
984
1058
  function currentEmbedAllowlist() {
985
1059
  const scoped = getRequestStore()?.caches?.get(REQUEST_CACHE)?.get("allowed");
@@ -1122,80 +1196,6 @@ var init_guards = __esm({
1122
1196
  }
1123
1197
  });
1124
1198
 
1125
- // src/utils/headEntry.ts
1126
- function isEmittableHeadAttrName(name) {
1127
- if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
1128
- const canonical = canonicalAttrName(name);
1129
- if (isEventHandlerAttr(canonical)) return false;
1130
- if (isHtmlContentAttribute(canonical)) return false;
1131
- return true;
1132
- }
1133
- function sanitizeHeadAttrValue(canonical, value) {
1134
- return resolveAttributeValue(canonical, value);
1135
- }
1136
- function findDuplicateAttributeName(names) {
1137
- const seen = /* @__PURE__ */ new Set();
1138
- for (const name of names) {
1139
- const canonical = canonicalAttrName(name);
1140
- if (seen.has(canonical)) return canonical;
1141
- seen.add(canonical);
1142
- }
1143
- return null;
1144
- }
1145
- function planEntry(props, resolver, applyRefreshPolicy) {
1146
- const rawNames = Object.keys(props);
1147
- const duplicate = findDuplicateAttributeName(rawNames);
1148
- if (duplicate !== null) {
1149
- return { kind: "drop", reason: `duplicate case-insensitive attribute ${JSON.stringify(duplicate)}` };
1150
- }
1151
- const attributes = /* @__PURE__ */ new Map();
1152
- let reactive = false;
1153
- for (const name of rawNames) {
1154
- if (!Object.hasOwn(props, name)) continue;
1155
- if (!isEmittableHeadAttrName(name)) continue;
1156
- const authored = props[name];
1157
- if (resolver.isReactiveValue?.(authored)) reactive = true;
1158
- const canonical = canonicalAttrName(name);
1159
- const sanitized = sanitizeHeadAttrValue(canonical, resolver.resolveValue(name, authored));
1160
- if (sanitized === null) continue;
1161
- attributes.set(canonical, sanitized);
1162
- }
1163
- if (attributes.size === 0) return { kind: "drop", reason: "no effective attributes remain" };
1164
- const refresh = applyRefreshPolicy ? resolveMetaRefreshPolicy(attributes) : { kind: "not-refresh" };
1165
- if (refresh.kind === "forbidden") return { kind: "drop", reason: refresh.reason };
1166
- if (reactive && (refresh.kind === "allowed" || refresh.kind === "delay-only")) {
1167
- return { kind: "drop", reason: "a reactive meta entry may not publish a native refresh directive" };
1168
- }
1169
- return { kind: "publish", attributes, refresh };
1170
- }
1171
- function planMetaEntry(props, resolver) {
1172
- return planEntry(props, resolver, true);
1173
- }
1174
- function planHeadElementEntry(props, resolver) {
1175
- return planEntry(props, resolver, false);
1176
- }
1177
- function escapeHeadAttrValue(value) {
1178
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1179
- }
1180
- function serializeHeadEntry(tag, attrs) {
1181
- const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
1182
- if (plan.kind === "drop") return "";
1183
- const pairs = Array.from(plan.attributes, ([name, value]) => `${name}="${escapeHeadAttrValue(value)}"`).join(" ");
1184
- return `<${tag} ${pairs} />`;
1185
- }
1186
- var SAFE_HEAD_ATTR_NAME, STATIC_VALUE_RESOLVER;
1187
- var init_headEntry = __esm({
1188
- "src/utils/headEntry.ts"() {
1189
- "use strict";
1190
- init_metaRefresh();
1191
- init_sanitize();
1192
- SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
1193
- STATIC_VALUE_RESOLVER = {
1194
- resolveValue: (_name, value) => String(value)
1195
- };
1196
- }
1197
- });
1198
-
1199
1199
  // src/platform/ssr.ts
1200
1200
  var ssr_exports = {};
1201
1201
  __export(ssr_exports, {
@@ -1943,10 +1943,13 @@ __export(plugins_exports, {
1943
1943
  renderRouteToString: () => renderRouteToString,
1944
1944
  replace: () => replace,
1945
1945
  resetPlugins: () => resetPlugins,
1946
+ resolveRouteHead: () => resolveRouteHead,
1946
1947
  resolveServerRoute: () => resolveServerRoute,
1947
1948
  route: () => route,
1949
+ routeHeadToHTML: () => routeHeadToHTML,
1948
1950
  router: () => router,
1949
1951
  routerPlugin: () => routerPlugin,
1952
+ routerReady: () => routerReady,
1950
1953
  routerState: () => routerState,
1951
1954
  satisfies: () => satisfies,
1952
1955
  serializeRouteState: () => serializeRouteState,
@@ -1962,100 +1965,219 @@ __export(plugins_exports, {
1962
1965
  });
1963
1966
  module.exports = __toCommonJS(plugins_exports);
1964
1967
 
1965
- // src/core/rendering/tagFactory.ts
1966
- init_dev();
1967
-
1968
- // src/reactivity/bindAttribute.ts
1969
- init_errors();
1970
- init_sanitize();
1971
-
1972
- // src/utils/setSafeAttribute.ts
1973
- init_dev();
1974
- init_elementPolicy();
1975
- init_sanitize();
1976
- var HTML_NS2 = "http://www.w3.org/1999/xhtml";
1977
- var SVG_NS2 = "http://www.w3.org/2000/svg";
1978
- var XLINK_NS = "http://www.w3.org/1999/xlink";
1979
- var XML_NS = "http://www.w3.org/XML/1998/namespace";
1980
- var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
1981
- function isHtmlElement(el) {
1982
- return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
1983
- }
1984
- function warnContextualRefusal(label2, name, refusal) {
1985
- devWarnLazy(() => {
1986
- const reasons = [
1987
- "",
1988
- 'the element would become a <meta http-equiv="refresh"> directive the shared refresh policy forbids (a destination outside the URL allowlist, or a directive too malformed to read unambiguously)',
1989
- "a <meta> whose http-equiv or content is reactive may never carry a refresh directive: a browser schedules the navigation as soon as the directive is valid, and nothing can withdraw it when the state changes back",
1990
- "a runtime value may not choose the program a <script> runs; name the script in static template source, or load it through Head({ script }) as an explicit trust decision",
1991
- "an SVG animation may not target a URL, event-handler or nested-document attribute; its to / values would be written into that attribute without passing the URL policy",
1992
- 'a runtime-chosen href may not become an applied <link rel="stylesheet">: a well-formed URL is not a trusted stylesheet. Name the stylesheet in static template source, or load a runtime-chosen one through Head({ link }) as an explicit trust decision'
1993
- ];
1994
- return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
1995
- });
1996
- }
1997
- function setProp(el, key, val) {
1998
- el[key] = val;
1999
- }
2000
- function namespaceFor(el, name) {
2001
- if (el.namespaceURI !== SVG_NS2) return null;
2002
- const lower = name.toLowerCase();
2003
- if (lower.startsWith("xlink:")) return XLINK_NS;
2004
- if (lower.startsWith("xml:")) return XML_NS;
2005
- return null;
2006
- }
2007
- function setSafeAttribute(el, name, value, options = {}) {
2008
- const written = commitSafeAttribute(el, name, value, options);
2009
- if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2010
- return written;
2011
- }
2012
- function commitSafeAttribute(el, name, value, options) {
2013
- const ns = namespaceFor(el, name);
2014
- const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2015
- if (isEventHandlerAttr(name)) {
2016
- if (DEV) {
2017
- devWarn(
2018
- `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
2019
- );
2020
- }
2021
- if (ns) el.removeAttributeNS(ns, localName);
2022
- else el.removeAttribute(name);
2023
- return false;
2024
- }
2025
- const pending = value == null || value === false ? null : value === true ? "" : String(value);
2026
- const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2027
- if (refusal !== ContextualRefusal.None) {
2028
- warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2029
- if (ns) el.removeAttributeNS(ns, localName);
2030
- else el.removeAttribute(name);
2031
- return false;
2032
- }
2033
- if (value == null) {
2034
- if (ns) el.removeAttributeNS(ns, localName);
2035
- else el.removeAttribute(name);
2036
- return true;
2037
- }
2038
- if (isHtmlContentAttribute(name)) {
2039
- if (DEV) {
2040
- devWarn(
2041
- `${options.label ?? "setSafeAttribute"}: refusing to set "${name}". The browser parses this attribute as a nested HTML document, so a generic string value cannot be made safe \u2014 attribute escaping is undone before the parse. Build the frame's content another way.`
2042
- );
2043
- }
2044
- if (ns) el.removeAttributeNS(ns, localName);
2045
- else el.removeAttribute(name);
2046
- return false;
2047
- }
2048
- const html2 = isHtmlElement(el);
2049
- const idlName = html2 ? name.toLowerCase() : name;
2050
- if (typeof value === "boolean") {
2051
- if (name.length > 5 && name.slice(0, 5).toLowerCase() === "aria-") {
2052
- const token = value ? "true" : "false";
2053
- if ((ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name)) !== token) {
2054
- if (ns) el.setAttributeNS(ns, name, token);
2055
- else el.setAttribute(name, token);
1968
+ // src/plugins/ecosystem.ts
1969
+ function createTestHarness() {
1970
+ let container;
1971
+ return {
1972
+ /** Set up a clean DOM container before each test */
1973
+ setup() {
1974
+ container = document.createElement("div");
1975
+ container.setAttribute("data-sibu-test", "true");
1976
+ document.body.appendChild(container);
1977
+ return container;
1978
+ },
1979
+ /** Tear down the DOM container after each test */
1980
+ teardown() {
1981
+ if (container?.parentNode) {
1982
+ container.parentNode.removeChild(container);
2056
1983
  }
2057
- return true;
2058
- }
1984
+ },
1985
+ /** Render a component into the test container */
1986
+ render(component) {
1987
+ const el = typeof component === "function" ? component() : component;
1988
+ container.appendChild(el);
1989
+ return el;
1990
+ },
1991
+ /** Get the test container */
1992
+ getContainer() {
1993
+ return container;
1994
+ },
1995
+ /** Wait for reactive updates to settle */
1996
+ async flush() {
1997
+ await new Promise((r) => setTimeout(r, 0));
1998
+ await new Promise((r) => requestAnimationFrame(() => r()));
1999
+ },
2000
+ /** Query within the container */
2001
+ query(selector) {
2002
+ return container.querySelector(selector);
2003
+ },
2004
+ /** Query all matching elements within the container */
2005
+ queryAll(selector) {
2006
+ return Array.from(container.querySelectorAll(selector));
2007
+ },
2008
+ /** Simulate a click event */
2009
+ click(el) {
2010
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
2011
+ },
2012
+ /** Simulate input value change */
2013
+ input(el, value) {
2014
+ el.value = value;
2015
+ el.dispatchEvent(new Event("input", { bubbles: true }));
2016
+ }
2017
+ };
2018
+ }
2019
+ var bundlerMetadata = {
2020
+ name: "sibu",
2021
+ sideEffects: false,
2022
+ modules: {
2023
+ core: ["html", "mount", "each", "slots", "fragment", "catch", "portal", "directives"],
2024
+ hooks: ["signal", "effect", "derived", "watch", "store", "ref", "array", "deepSignal"],
2025
+ plugins: ["router", "i18n"],
2026
+ components: ["ErrorBoundary", "Loading"],
2027
+ ssr: ["ssr"],
2028
+ advanced: ["globalStore", "machine", "optimistic", "timeTravel", "scheduler", "plugin"]
2029
+ },
2030
+ /** Generate import map for module resolution */
2031
+ generateImportMap(base2 = "/node_modules/sibu/") {
2032
+ const map2 = {};
2033
+ for (const [category, modules] of Object.entries(this.modules)) {
2034
+ for (const mod of modules) {
2035
+ const dir = category === "hooks" ? "core" : category;
2036
+ map2[`sibu/${mod}`] = `${base2}src/${dir}/${mod}.js`;
2037
+ }
2038
+ }
2039
+ return map2;
2040
+ }
2041
+ };
2042
+ function healthCheck() {
2043
+ const checks = [];
2044
+ const hasDOM = typeof document !== "undefined";
2045
+ checks.push({
2046
+ name: "DOM Environment",
2047
+ passed: hasDOM,
2048
+ message: hasDOM ? "DOM is available" : "No DOM environment (SSR mode)"
2049
+ });
2050
+ const hasRAF = typeof requestAnimationFrame !== "undefined";
2051
+ checks.push({
2052
+ name: "requestAnimationFrame",
2053
+ passed: hasRAF,
2054
+ message: hasRAF ? "Available" : "Not available (polyfill may be needed)"
2055
+ });
2056
+ const hasMO = typeof MutationObserver !== "undefined";
2057
+ checks.push({
2058
+ name: "MutationObserver",
2059
+ passed: hasMO,
2060
+ message: hasMO ? "Available" : "Not available (lifecycle hooks may not work)"
2061
+ });
2062
+ const allPassed = checks.every((c) => c.passed);
2063
+ return {
2064
+ status: allPassed ? "ok" : "warning",
2065
+ checks
2066
+ };
2067
+ }
2068
+ var env = {
2069
+ /** True when running in a browser with a DOM */
2070
+ isBrowser: typeof window !== "undefined" && typeof document !== "undefined",
2071
+ /** True when running in Node.js */
2072
+ isNode: typeof process !== "undefined" && !!process.versions?.node,
2073
+ /** True when running inside a Web Worker */
2074
+ isWorker: typeof self !== "undefined" && typeof self.importScripts === "function",
2075
+ /** True when running inside Deno */
2076
+ isDeno: typeof globalThis.Deno !== "undefined",
2077
+ /** True when running inside Bun */
2078
+ isBun: typeof globalThis.Bun !== "undefined",
2079
+ /** True when no window object is available (server-side rendering) */
2080
+ isSSR: typeof window === "undefined",
2081
+ /** True when NODE_ENV is not "production" */
2082
+ isDev: typeof process !== "undefined" && process.env?.NODE_ENV !== "production",
2083
+ /** True when NODE_ENV is "test" or VITEST is set */
2084
+ isTest: typeof process !== "undefined" && (process.env?.NODE_ENV === "test" || process.env?.VITEST === "true")
2085
+ };
2086
+
2087
+ // src/core/rendering/tagFactory.ts
2088
+ init_dev();
2089
+
2090
+ // src/reactivity/bindAttribute.ts
2091
+ init_errors();
2092
+ init_sanitize();
2093
+
2094
+ // src/utils/setSafeAttribute.ts
2095
+ init_dev();
2096
+ init_elementPolicy();
2097
+ init_sanitize();
2098
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
2099
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2100
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
2101
+ var XML_NS = "http://www.w3.org/XML/1998/namespace";
2102
+ var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
2103
+ function isHtmlElement(el) {
2104
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
2105
+ }
2106
+ function warnContextualRefusal(label2, name, refusal) {
2107
+ devWarnLazy(() => {
2108
+ const reasons = [
2109
+ "",
2110
+ 'the element would become a <meta http-equiv="refresh"> directive the shared refresh policy forbids (a destination outside the URL allowlist, or a directive too malformed to read unambiguously)',
2111
+ "a <meta> whose http-equiv or content is reactive may never carry a refresh directive: a browser schedules the navigation as soon as the directive is valid, and nothing can withdraw it when the state changes back",
2112
+ "a runtime value may not choose the program a <script> runs; name the script in static template source, or load it through Head({ script }) as an explicit trust decision",
2113
+ "an SVG animation may not target a URL, event-handler or nested-document attribute; its to / values would be written into that attribute without passing the URL policy",
2114
+ 'a runtime-chosen href may not become an applied <link rel="stylesheet">: a well-formed URL is not a trusted stylesheet. Name the stylesheet in static template source, or load a runtime-chosen one through Head({ link }) as an explicit trust decision'
2115
+ ];
2116
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
2117
+ });
2118
+ }
2119
+ function setProp(el, key, val) {
2120
+ el[key] = val;
2121
+ }
2122
+ function namespaceFor(el, name) {
2123
+ if (el.namespaceURI !== SVG_NS2) return null;
2124
+ const lower = name.toLowerCase();
2125
+ if (lower.startsWith("xlink:")) return XLINK_NS;
2126
+ if (lower.startsWith("xml:")) return XML_NS;
2127
+ return null;
2128
+ }
2129
+ function setSafeAttribute(el, name, value, options = {}) {
2130
+ const written = commitSafeAttribute(el, name, value, options);
2131
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2132
+ return written;
2133
+ }
2134
+ function commitSafeAttribute(el, name, value, options) {
2135
+ const ns = namespaceFor(el, name);
2136
+ const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2137
+ if (isEventHandlerAttr(name)) {
2138
+ if (DEV) {
2139
+ devWarn(
2140
+ `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
2141
+ );
2142
+ }
2143
+ if (ns) el.removeAttributeNS(ns, localName);
2144
+ else el.removeAttribute(name);
2145
+ return false;
2146
+ }
2147
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
2148
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2149
+ if (refusal !== ContextualRefusal.None) {
2150
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2151
+ if (ns) el.removeAttributeNS(ns, localName);
2152
+ else el.removeAttribute(name);
2153
+ return false;
2154
+ }
2155
+ if (value == null) {
2156
+ if (ns) el.removeAttributeNS(ns, localName);
2157
+ else el.removeAttribute(name);
2158
+ return true;
2159
+ }
2160
+ if (isHtmlContentAttribute(name)) {
2161
+ if (DEV) {
2162
+ devWarn(
2163
+ `${options.label ?? "setSafeAttribute"}: refusing to set "${name}". The browser parses this attribute as a nested HTML document, so a generic string value cannot be made safe \u2014 attribute escaping is undone before the parse. Build the frame's content another way.`
2164
+ );
2165
+ }
2166
+ if (ns) el.removeAttributeNS(ns, localName);
2167
+ else el.removeAttribute(name);
2168
+ return false;
2169
+ }
2170
+ const html2 = isHtmlElement(el);
2171
+ const idlName = html2 ? name.toLowerCase() : name;
2172
+ if (typeof value === "boolean") {
2173
+ if (name.length > 5 && name.slice(0, 5).toLowerCase() === "aria-") {
2174
+ const token = value ? "true" : "false";
2175
+ if ((ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name)) !== token) {
2176
+ if (ns) el.setAttributeNS(ns, name, token);
2177
+ else el.setAttribute(name, token);
2178
+ }
2179
+ return true;
2180
+ }
2059
2181
  if (html2 && BOOLEAN_IDL_ATTRS.has(idlName) && idlName in el) {
2060
2182
  setProp(el, idlName, value);
2061
2183
  } else if (value) {
@@ -2253,6 +2375,7 @@ function bindOwnerScope(fn) {
2253
2375
  if (scope === null) return fn;
2254
2376
  return (...args) => runWithOwnerScope(scope, () => fn(...args));
2255
2377
  }
2378
+ var currentStructOwner = null;
2256
2379
  var subscriberEpochCounter = 0;
2257
2380
  function retrack(effectFn, subscriber) {
2258
2381
  const prev = currentSubscriber;
@@ -2272,6 +2395,9 @@ function retrack(effectFn, subscriber) {
2272
2395
  if (prevScope !== null && !sub2._epoch && sub2._scope === void 0) sub2._scope = prevScope;
2273
2396
  const scope = sub2._scope === void 0 ? null : sub2._scope;
2274
2397
  if (scope !== prevScope) currentOwnerScope = scope;
2398
+ const prevStructOwner = currentStructOwner;
2399
+ if (prevStructOwner !== null && !sub2._epoch && !sub2._c && sub2._owner == null) sub2._owner = prevStructOwner;
2400
+ if (sub2._tok != null) currentStructOwner = sub2._tok;
2275
2401
  const epoch = ++subscriberEpochCounter;
2276
2402
  sub2._epoch = epoch;
2277
2403
  sub2._structDirty = false;
@@ -2285,6 +2411,7 @@ function retrack(effectFn, subscriber) {
2285
2411
  } finally {
2286
2412
  currentSubscriber = prev;
2287
2413
  currentOwnerScope = prevScope;
2414
+ currentStructOwner = prevStructOwner;
2288
2415
  if (savedDepth !== 0) {
2289
2416
  suspendDepth = savedDepth;
2290
2417
  suspendSavedSub = savedSuspendSub;
@@ -2341,7 +2468,25 @@ function track(effectFn, subscriber) {
2341
2468
  }
2342
2469
  return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
2343
2470
  }
2344
- function reactiveBinding(commit, ownerNode) {
2471
+ function reactiveBinding(commit, ownerNode, structural) {
2472
+ return createBinding(commit, ownerNode, structural === true)._dispose;
2473
+ }
2474
+ function structuralBinding(commit, ownerNode) {
2475
+ const sub2 = createBinding(commit, ownerNode, true);
2476
+ return {
2477
+ stop: sub2._dispose,
2478
+ run: (fn) => {
2479
+ const prev = currentStructOwner;
2480
+ currentStructOwner = sub2._tok ?? null;
2481
+ try {
2482
+ return fn();
2483
+ } finally {
2484
+ currentStructOwner = prev;
2485
+ }
2486
+ }
2487
+ };
2488
+ }
2489
+ function createBinding(commit, ownerNode, structural) {
2345
2490
  const run = () => {
2346
2491
  const s2 = subscriber;
2347
2492
  if (s2._disposed || s2._reentrant) return;
@@ -2364,19 +2509,25 @@ function reactiveBinding(commit, ownerNode) {
2364
2509
  subscriber._disposed = false;
2365
2510
  subscriber._errorPhase = "binding";
2366
2511
  subscriber._errorNode = ownerNode;
2512
+ subscriber._tok = null;
2513
+ subscriber._owner = null;
2514
+ if (structural) subscriber._tok = { sub: subscriber };
2367
2515
  try {
2368
2516
  run();
2369
2517
  } catch (err) {
2370
2518
  subscriber._disposed = true;
2371
2519
  subscriber._errorNode = void 0;
2520
+ if (subscriber._tok != null) subscriber._tok.sub = null;
2372
2521
  cleanup(subscriber);
2373
2522
  throw err;
2374
2523
  }
2375
- return subscriber._dispose ?? (subscriber._dispose = () => {
2524
+ subscriber._dispose = () => {
2376
2525
  subscriber._disposed = true;
2377
2526
  subscriber._errorNode = void 0;
2527
+ if (subscriber._tok != null) subscriber._tok.sub = null;
2378
2528
  cleanup(subscriber);
2379
- });
2529
+ };
2530
+ return subscriber;
2380
2531
  }
2381
2532
  function recordDependency(signal2) {
2382
2533
  if (!currentSubscriber) return;
@@ -2400,6 +2551,7 @@ function recordDependency(signal2) {
2400
2551
  }
2401
2552
  function cleanup(subscriber) {
2402
2553
  const sub2 = subscriber;
2554
+ if (sub2._owner != null) sub2._owner = null;
2403
2555
  let node = sub2.depsHead ?? null;
2404
2556
  sub2.depsHead = null;
2405
2557
  sub2.depsTail = null;
@@ -2459,20 +2611,41 @@ function drainQueue() {
2459
2611
  break;
2460
2612
  }
2461
2613
  const sub2 = pendingQueue[i2++];
2462
- pendingSet.delete(sub2);
2463
- if (quarantined.size > 0 && quarantined.has(sub2)) continue;
2464
- if (sub2._hasComputedDep === true && !depsChanged(sub2)) continue;
2465
- if (tickRepeat(sub2)) {
2466
- cycleError(sub2);
2467
- quarantined.add(sub2);
2468
- continue;
2469
- }
2470
- const pushed = beginDisposerCapture(sub2._cap ?? null);
2471
- try {
2472
- safeInvoke(sub2);
2473
- } finally {
2474
- if (pushed) endDisposerCapture();
2475
- }
2614
+ if (!pendingSet.delete(sub2)) continue;
2615
+ if (sub2._owner != null && pendingSet.size > 0) {
2616
+ runPendingOwners(sub2);
2617
+ if (pendingSet.has(sub2)) continue;
2618
+ }
2619
+ runQueued(sub2);
2620
+ }
2621
+ }
2622
+ function runPendingOwners(sub2) {
2623
+ for (; ; ) {
2624
+ let outer = null;
2625
+ for (let t2 = sub2._owner ?? null; t2 !== null; ) {
2626
+ const o = t2.sub;
2627
+ if (o === null) break;
2628
+ if (pendingSet.has(o)) outer = o;
2629
+ t2 = o._owner ?? null;
2630
+ }
2631
+ if (outer === null) return;
2632
+ pendingSet.delete(outer);
2633
+ runQueued(outer);
2634
+ }
2635
+ }
2636
+ function runQueued(sub2) {
2637
+ if (quarantined.size > 0 && quarantined.has(sub2)) return;
2638
+ if (sub2._hasComputedDep === true && !depsChanged(sub2)) return;
2639
+ if (tickRepeat(sub2)) {
2640
+ cycleError(sub2);
2641
+ quarantined.add(sub2);
2642
+ return;
2643
+ }
2644
+ const pushed = beginDisposerCapture(sub2._cap ?? null);
2645
+ try {
2646
+ safeInvoke(sub2);
2647
+ } finally {
2648
+ if (pushed) endDisposerCapture();
2476
2649
  }
2477
2650
  }
2478
2651
  function drainNotificationQueue() {
@@ -2608,7 +2781,7 @@ function forEachSubscriber(signal2, visit) {
2608
2781
  }
2609
2782
 
2610
2783
  // src/reactivity/track.ts
2611
- var _runtimeVersion = true ? "4.12.0" : "dev";
2784
+ var _runtimeVersion = true ? "4.13.1" : "dev";
2612
2785
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
2613
2786
  function resolveReactiveApi() {
2614
2787
  const g2 = globalThis;
@@ -2643,6 +2816,7 @@ function resolveReactiveApi() {
2643
2816
  getOwnerScope,
2644
2817
  runWithOwnerScope,
2645
2818
  bindOwnerScope,
2819
+ structuralBinding,
2646
2820
  version: _runtimeVersion
2647
2821
  };
2648
2822
  g2[REGISTRY_KEY] = local;
@@ -2669,6 +2843,7 @@ var forEachSubscriber2 = API.forEachSubscriber;
2669
2843
  var getOwnerScope2 = API.getOwnerScope ?? getOwnerScope;
2670
2844
  var runWithOwnerScope2 = API.runWithOwnerScope ?? runWithOwnerScope;
2671
2845
  var bindOwnerScope2 = API.bindOwnerScope ?? bindOwnerScope;
2846
+ var structuralBinding2 = API.structuralBinding ?? ((commit, ownerNode) => ({ stop: API.reactiveBinding(commit, ownerNode), run: (fn) => fn() }));
2672
2847
 
2673
2848
  // src/reactivity/bindAttribute.ts
2674
2849
  function bindAttribute(el, attr, getter) {
@@ -2764,9 +2939,10 @@ function refocus(el, snapshot) {
2764
2939
  field.setSelectionRange(start, end, snapshot.direction ?? "none");
2765
2940
  }
2766
2941
  }
2767
- function restoreFocusWithin(snapshot, nodes, where) {
2942
+ function restoreFocusWithin(snapshot, nodes, where, policy) {
2768
2943
  if (!snapshot) return;
2769
2944
  if (typeof document === "undefined") return;
2945
+ if (policy === "discard") return;
2770
2946
  const active = document.activeElement;
2771
2947
  if (active === snapshot.el) return;
2772
2948
  if (active && active !== document.body && active !== document.documentElement) return;
@@ -2783,10 +2959,17 @@ function restoreFocusWithin(snapshot, nodes, where) {
2783
2959
  }
2784
2960
  ambiguous = count > 1;
2785
2961
  }
2962
+ if (policy !== void 0 && typeof policy === "object") {
2963
+ const target = typeof policy.restoreTo === "function" ? policy.restoreTo() : policy.restoreTo;
2964
+ if (target?.isConnected) {
2965
+ target.focus();
2966
+ if (document.activeElement === target) return;
2967
+ }
2968
+ }
2786
2969
  devWarnLazy(() => {
2787
2970
  const id = snapshot.attr ? ` (${snapshot.attr}="${snapshot.value}")` : "";
2788
2971
  const why = ambiguous ? `several elements in the rebuilt subtree share ${snapshot.attr}="${snapshot.value}", so there is no way to tell which one the user was in \u2014 a shared name (a radio group) identifies a GROUP, not a control. Add a unique id or data-focus-key to each` : "no element in the rebuilt subtree carries the same identity. Give it a stable id, name, or data-focus-key that the rebuild reproduces";
2789
- return `${where}: a reactive rebuild discarded the focused element <${snapshot.tag.toLowerCase()}>${id} and could not re-establish it, so focus fell back to <body>. An in-progress edit ends here, and an in-progress IME composition is lost outright \u2014 a composition belongs to the DOM node, so it cannot survive the node being destroyed. ${why}, or stop the rebuild from happening: key the subtree with match(() => shapeKey(), { \u2026 }) so it is re-created only when the shape actually changes, and drive the rest with reactive attributes and text children, which mutate nodes in place instead of replacing them.`;
2972
+ return `${where}: a reactive rebuild discarded the focused element <${snapshot.tag.toLowerCase()}>${id} and could not re-establish it, so focus fell back to <body>. An in-progress edit ends here, and an in-progress IME composition is lost outright \u2014 a composition belongs to the DOM node, so it cannot survive the node being destroyed. ${why}, or stop the rebuild from happening: key the subtree with match(() => shapeKey(), { \u2026 }) so it is re-created only when the shape actually changes, and drive the rest with reactive attributes and text children, which mutate nodes in place instead of replacing them. If dropping focus is INTENDED here (closing a panel, say), pass { focus: "discard" } to when()/match() to silence this, or { focus: { restoreTo: element } } to send focus somewhere other than <body>.`;
2790
2973
  });
2791
2974
  }
2792
2975
 
@@ -2861,7 +3044,7 @@ function bindChildNode(placeholder, getter) {
2861
3044
  lastNodes = newNodes;
2862
3045
  restoreFocusWithin(focused, newNodes, "bindChildNode");
2863
3046
  }
2864
- return reactiveBinding2(commit, placeholder);
3047
+ return reactiveBinding2(commit, placeholder, true);
2865
3048
  }
2866
3049
 
2867
3050
  // src/core/rendering/tagFactory.ts
@@ -3518,37 +3701,451 @@ function getAvailableLocales() {
3518
3701
  return Object.keys(locales);
3519
3702
  }
3520
3703
 
3521
- // src/plugins/router.ts
3522
- init_dev();
3523
- init_dispose();
3524
-
3525
- // src/core/signals/effect.ts
3526
- init_dev();
3527
- init_errors();
3528
- init_ssr_context();
3529
- var _g2 = globalThis;
3530
- var MAX_RERUNS = 100;
3531
- function flushUserCleanups(ctx) {
3532
- const list = ctx.userCleanups;
3533
- if (list.length === 0) return;
3534
- ctx.userCleanups = [];
3535
- const scope = ctx.subscriber._scope ?? null;
3536
- if (scope === getOwnerScope2()) runCleanups(list);
3537
- else runWithOwnerScope2(scope, () => runCleanups(list));
3538
- }
3539
- function runCleanups(list) {
3540
- for (let i2 = list.length - 1; i2 >= 0; i2--) {
3541
- try {
3542
- list[i2]();
3543
- } catch (err) {
3544
- reportError(err, { phase: "cleanup" });
3704
+ // src/plugins/modular.ts
3705
+ function createModuleRegistry() {
3706
+ const modules = /* @__PURE__ */ new Map();
3707
+ const resolving = [];
3708
+ function resolveInternal(name) {
3709
+ const entry = modules.get(name);
3710
+ if (!entry) {
3711
+ throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
3545
3712
  }
3546
- }
3547
- }
3548
- function drainReruns(ctx) {
3549
- let reruns = 1;
3550
- do {
3551
- ctx.rerunPending = false;
3713
+ if (entry.state === "loaded") {
3714
+ return entry.value;
3715
+ }
3716
+ if (entry.state === "resolving") {
3717
+ const cycle = [...resolving.slice(resolving.indexOf(name)), name].join(" -> ");
3718
+ throw new Error(`[ModuleRegistry] Circular dependency detected: ${cycle}`);
3719
+ }
3720
+ entry.state = "resolving";
3721
+ resolving.push(name);
3722
+ try {
3723
+ for (const dep of entry.deps) {
3724
+ resolveInternal(dep);
3725
+ }
3726
+ entry.value = entry.factory();
3727
+ entry.state = "loaded";
3728
+ return entry.value;
3729
+ } catch (err) {
3730
+ entry.state = "unloaded";
3731
+ entry.value = void 0;
3732
+ throw err;
3733
+ } finally {
3734
+ resolving.pop();
3735
+ }
3736
+ }
3737
+ return {
3738
+ /** Register a module with its factory function and optional dependencies */
3739
+ register(name, factory, deps = []) {
3740
+ modules.set(name, { factory, deps, state: "unloaded" });
3741
+ },
3742
+ /** Resolve a module, loading its dependencies first */
3743
+ resolve(name) {
3744
+ return resolveInternal(name);
3745
+ },
3746
+ /** Check if a module is registered */
3747
+ has(name) {
3748
+ return modules.has(name);
3749
+ },
3750
+ /** List all registered module names */
3751
+ list() {
3752
+ return Array.from(modules.keys());
3753
+ },
3754
+ /** Get the full dependency graph (transitive) for a module */
3755
+ deps(name) {
3756
+ const entry = modules.get(name);
3757
+ if (!entry) {
3758
+ throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
3759
+ }
3760
+ const visited = /* @__PURE__ */ new Set();
3761
+ const result = [];
3762
+ function walk(modName) {
3763
+ const mod = modules.get(modName);
3764
+ if (!mod) return;
3765
+ for (const dep of mod.deps) {
3766
+ if (!visited.has(dep)) {
3767
+ visited.add(dep);
3768
+ walk(dep);
3769
+ result.push(dep);
3770
+ }
3771
+ }
3772
+ }
3773
+ walk(name);
3774
+ return result;
3775
+ },
3776
+ /** Reset all loaded modules back to unloaded state (useful for testing) */
3777
+ reset() {
3778
+ for (const entry of modules.values()) {
3779
+ entry.state = "unloaded";
3780
+ entry.value = void 0;
3781
+ }
3782
+ }
3783
+ };
3784
+ }
3785
+ function createBundle(modules) {
3786
+ const cache = /* @__PURE__ */ new Map();
3787
+ const bundle = {};
3788
+ for (const key of Object.keys(modules)) {
3789
+ Object.defineProperty(bundle, key, {
3790
+ get() {
3791
+ if (cache.has(key)) {
3792
+ return cache.get(key);
3793
+ }
3794
+ const value = modules[key]();
3795
+ cache.set(key, value);
3796
+ return value;
3797
+ },
3798
+ enumerable: true,
3799
+ configurable: false
3800
+ });
3801
+ }
3802
+ return bundle;
3803
+ }
3804
+ function lazyModule(loader) {
3805
+ let cached;
3806
+ let loadedFlag = false;
3807
+ let inFlight = null;
3808
+ return {
3809
+ get loaded() {
3810
+ return loadedFlag;
3811
+ },
3812
+ get() {
3813
+ if (loadedFlag) return Promise.resolve(cached);
3814
+ if (inFlight) return inFlight;
3815
+ let started;
3816
+ try {
3817
+ started = Promise.resolve(loader());
3818
+ } catch (err) {
3819
+ started = Promise.reject(err);
3820
+ }
3821
+ const attempt = started.then(
3822
+ (value) => {
3823
+ cached = value;
3824
+ loadedFlag = true;
3825
+ if (inFlight === attempt) inFlight = null;
3826
+ return value;
3827
+ },
3828
+ (err) => {
3829
+ if (inFlight === attempt) inFlight = null;
3830
+ throw err;
3831
+ }
3832
+ );
3833
+ inFlight = attempt;
3834
+ return attempt;
3835
+ }
3836
+ };
3837
+ }
3838
+ var MODULE_ENTRY_POINTS = {
3839
+ main: "./index.ts",
3840
+ data: "./data.ts",
3841
+ browser: "./browser.ts",
3842
+ patterns: "./patterns.ts",
3843
+ motion: "./motion.ts",
3844
+ ui: "./ui.ts",
3845
+ widgets: "./widgets.ts",
3846
+ ssr: "./ssr.ts",
3847
+ devtools: "./devtools.ts",
3848
+ performance: "./performance.ts",
3849
+ ecosystem: "./ecosystem.ts",
3850
+ plugins: "./plugins.ts",
3851
+ build: "./build.ts",
3852
+ testing: "./testing.ts",
3853
+ extras: "./extras.ts"
3854
+ };
3855
+ var CDN_ENTRY_POINTS = {
3856
+ cdn: "./dist/cdn.global.js",
3857
+ "cdn-dev": "./dist/cdn.dev.global.js",
3858
+ "cdn-full": "./dist/cdn.full.global.js",
3859
+ "cdn-full-dev": "./dist/cdn.full.dev.global.js"
3860
+ };
3861
+ var packageInfo = {
3862
+ name: "sibujs",
3863
+ version: true ? "4.13.1" : "dev",
3864
+ entryPoints: MODULE_ENTRY_POINTS,
3865
+ /**
3866
+ * Generate the package.json `exports` map. Module entries resolve to the
3867
+ * `.js` (ESM), `.cjs` and `.d.ts` files tsup emits into `dist/`; CDN entries
3868
+ * resolve to their prebuilt global script.
3869
+ */
3870
+ generateExportsMap() {
3871
+ const exportsMap = {};
3872
+ for (const [name, sourcePath] of Object.entries(MODULE_ENTRY_POINTS)) {
3873
+ const distPath = sourcePath.replace(/^\.\//, "./dist/").replace(/\.ts$/, "");
3874
+ const subpath = name === "main" ? "." : `./${name}`;
3875
+ exportsMap[subpath] = {
3876
+ types: `${distPath}.d.ts`,
3877
+ import: `${distPath}.js`,
3878
+ require: `${distPath}.cjs`
3879
+ };
3880
+ }
3881
+ for (const [name, file] of Object.entries(CDN_ENTRY_POINTS)) {
3882
+ exportsMap[`./${name}`] = { default: file };
3883
+ }
3884
+ return exportsMap;
3885
+ }
3886
+ };
3887
+
3888
+ // src/plugins/plugin.ts
3889
+ init_dev();
3890
+ init_errors();
3891
+
3892
+ // src/utils/adoptThenable.ts
3893
+ function adoptThenable(value) {
3894
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
3895
+ let then;
3896
+ try {
3897
+ then = value.then;
3898
+ } catch (error) {
3899
+ return Promise.reject(error);
3900
+ }
3901
+ if (typeof then !== "function") return null;
3902
+ return new Promise((resolve, reject) => {
3903
+ queueMicrotask(() => {
3904
+ try {
3905
+ Reflect.apply(then, value, [resolve, reject]);
3906
+ } catch (error) {
3907
+ reject(error);
3908
+ }
3909
+ });
3910
+ });
3911
+ }
3912
+
3913
+ // src/plugins/plugin.ts
3914
+ init_globalSingleton();
3915
+ var CANCELLED_BRAND = /* @__PURE__ */ Symbol.for("sibujs.plugins.installCancelled.v1");
3916
+ var _a;
3917
+ var PluginInstallCancelledError = class extends Error {
3918
+ constructor(pluginName) {
3919
+ super(`[Plugin] Installation of "${pluginName}" was cancelled before it was committed (registry reset).`);
3920
+ /**
3921
+ * Brand, not identity: the default registry is shared across duplicate copies
3922
+ * of this module, so the error a caller receives may come from another copy's
3923
+ * constructor. `instanceof` is taught to recognise the brand instead.
3924
+ */
3925
+ this[_a] = true;
3926
+ this.name = "PluginInstallCancelledError";
3927
+ this.pluginName = pluginName;
3928
+ }
3929
+ static [(_a = CANCELLED_BRAND, Symbol.hasInstance)](value) {
3930
+ return isPluginInstallCancelledError(value);
3931
+ }
3932
+ };
3933
+ function isPluginInstallCancelledError(value) {
3934
+ return typeof value === "object" && value !== null && value[CANCELLED_BRAND] === true;
3935
+ }
3936
+ function createPluginRegistry() {
3937
+ const installedPlugins = /* @__PURE__ */ new Set();
3938
+ const hooks = { init: [], mount: [], unmount: [], error: [] };
3939
+ const provided = /* @__PURE__ */ new Map();
3940
+ const installStack = /* @__PURE__ */ new Set();
3941
+ const pendingInstalls = /* @__PURE__ */ new Map();
3942
+ let generation = 0;
3943
+ const registry = {
3944
+ installedPlugins,
3945
+ hooks,
3946
+ provided,
3947
+ /**
3948
+ * Install a plugin as a transaction.
3949
+ *
3950
+ * Hooks and providers registered through `ctx` are staged and committed
3951
+ * only if `install()` returns; a throwing install leaves the registry
3952
+ * exactly as it found it (and can simply be retried). The plugin is marked
3953
+ * installed at commit, before its init hooks run.
3954
+ *
3955
+ * Each `plugin()` call is its own transaction: a dependency installed by a
3956
+ * nested `plugin()` call commits on its own and stays installed even if the
3957
+ * outer installation later fails.
3958
+ */
3959
+ plugin(p2, options) {
3960
+ if (installedPlugins.has(p2.name)) {
3961
+ console.warn(`[Plugin] "${p2.name}" is already installed.`);
3962
+ return;
3963
+ }
3964
+ if (installStack.has(p2.name)) {
3965
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (recursive installation).`);
3966
+ }
3967
+ if (pendingInstalls.has(p2.name)) {
3968
+ throw new Error(`[Plugin] "${p2.name}" is already being installed (asynchronous installation in flight).`);
3969
+ }
3970
+ const staged = { init: [], mount: [], unmount: [], error: [] };
3971
+ const stagedProvided = /* @__PURE__ */ new Map();
3972
+ let committed = false;
3973
+ const myGeneration = generation;
3974
+ const stale = () => {
3975
+ if (myGeneration === generation) return false;
3976
+ if (DEV) devWarn(`plugin: "${p2.name}" registered after the registry was reset; the registration was ignored.`);
3977
+ return true;
3978
+ };
3979
+ const target = () => stale() ? null : committed ? hooks : staged;
3980
+ const ctx = {
3981
+ onInit: (cb) => void target()?.init.push(cb),
3982
+ onMount: (cb) => void target()?.mount.push(cb),
3983
+ onUnmount: (cb) => void target()?.unmount.push(cb),
3984
+ onError: (cb) => void target()?.error.push(cb),
3985
+ provide: (key, value) => {
3986
+ if (stale()) return;
3987
+ (committed ? provided : stagedProvided).set(key, value);
3988
+ }
3989
+ };
3990
+ const commit = () => {
3991
+ if (myGeneration !== generation) return false;
3992
+ hooks.init.push(...staged.init);
3993
+ hooks.mount.push(...staged.mount);
3994
+ hooks.unmount.push(...staged.unmount);
3995
+ hooks.error.push(...staged.error);
3996
+ for (const [key, value] of stagedProvided) provided.set(key, value);
3997
+ installedPlugins.add(p2.name);
3998
+ committed = true;
3999
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
4000
+ for (const cb of staged.init.slice()) {
4001
+ try {
4002
+ cb();
4003
+ } catch (e) {
4004
+ console.error(`[Plugin] "${p2.name}" init error:`, e);
4005
+ }
4006
+ }
4007
+ return true;
4008
+ };
4009
+ installStack.add(p2.name);
4010
+ let pending;
4011
+ try {
4012
+ pending = adoptThenable(p2.install(ctx, options));
4013
+ } finally {
4014
+ installStack.delete(p2.name);
4015
+ }
4016
+ if (!pending) {
4017
+ if (!commit()) throw new PluginInstallCancelledError(p2.name);
4018
+ return;
4019
+ }
4020
+ if (myGeneration === generation) pendingInstalls.set(p2.name, myGeneration);
4021
+ const settled = pending.then(
4022
+ () => {
4023
+ if (commit()) return;
4024
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
4025
+ throw new PluginInstallCancelledError(p2.name);
4026
+ },
4027
+ (err) => {
4028
+ if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
4029
+ reportError(err, { phase: "async", name: `plugin(${p2.name})` });
4030
+ throw err;
4031
+ }
4032
+ );
4033
+ settled.catch(() => {
4034
+ });
4035
+ return settled;
4036
+ },
4037
+ inject(key, defaultValue) {
4038
+ if (provided.has(key)) return provided.get(key);
4039
+ if (defaultValue !== void 0) return defaultValue;
4040
+ throw new Error(`[Plugin] No provider found for key "${key}"`);
4041
+ },
4042
+ triggerMount(element) {
4043
+ const snapshot = hooks.mount.slice();
4044
+ for (const hook of snapshot) {
4045
+ try {
4046
+ hook(element);
4047
+ } catch (e) {
4048
+ console.error("[Plugin] Mount hook error:", e);
4049
+ }
4050
+ }
4051
+ },
4052
+ triggerUnmount(element) {
4053
+ const snapshot = hooks.unmount.slice();
4054
+ for (const hook of snapshot) {
4055
+ try {
4056
+ hook(element);
4057
+ } catch (e) {
4058
+ console.error("[Plugin] Unmount hook error:", e);
4059
+ }
4060
+ }
4061
+ },
4062
+ triggerError(error) {
4063
+ const snapshot = hooks.error.slice();
4064
+ for (const hook of snapshot) {
4065
+ try {
4066
+ hook(error);
4067
+ } catch (e) {
4068
+ console.error("[Plugin] Error hook error:", e);
4069
+ }
4070
+ }
4071
+ },
4072
+ reset() {
4073
+ generation++;
4074
+ pendingInstalls.clear();
4075
+ installedPlugins.clear();
4076
+ hooks.init.length = 0;
4077
+ hooks.mount.length = 0;
4078
+ hooks.unmount.length = 0;
4079
+ hooks.error.length = 0;
4080
+ provided.clear();
4081
+ }
4082
+ };
4083
+ return registry;
4084
+ }
4085
+ var _defaults = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.plugins.defaultRegistry.v1"), () => ({
4086
+ registry: createPluginRegistry(),
4087
+ touched: false
4088
+ }));
4089
+ function createPlugin(name, install) {
4090
+ return { name, install };
4091
+ }
4092
+ function plugin(plugin2, options) {
4093
+ _defaults.touched = true;
4094
+ return _defaults.registry.plugin(plugin2, options);
4095
+ }
4096
+ function inject(key, defaultValue) {
4097
+ return _defaults.registry.inject(key, defaultValue);
4098
+ }
4099
+ function triggerPluginMount(element) {
4100
+ _defaults.registry.triggerMount(element);
4101
+ }
4102
+ function triggerPluginUnmount(element) {
4103
+ _defaults.registry.triggerUnmount(element);
4104
+ }
4105
+ function triggerPluginError(error) {
4106
+ _defaults.registry.triggerError(error);
4107
+ }
4108
+ function resetPlugins() {
4109
+ _defaults.registry.reset();
4110
+ _defaults.touched = false;
4111
+ }
4112
+ function setDefaultPluginRegistry(registry) {
4113
+ if (_defaults.touched && _defaults.registry.installedPlugins.size > 0) {
4114
+ console.warn(
4115
+ "[Plugin] Replacing default plugin registry while plugins are already installed on the singleton. This may indicate mixed singleton/registry usage."
4116
+ );
4117
+ }
4118
+ _defaults.registry = registry;
4119
+ _defaults.touched = true;
4120
+ }
4121
+
4122
+ // src/core/signals/effect.ts
4123
+ init_dev();
4124
+ init_errors();
4125
+ init_ssr_context();
4126
+ var _g2 = globalThis;
4127
+ var MAX_RERUNS = 100;
4128
+ function flushUserCleanups(ctx) {
4129
+ const list = ctx.userCleanups;
4130
+ if (list.length === 0) return;
4131
+ ctx.userCleanups = [];
4132
+ const scope = ctx.subscriber._scope ?? null;
4133
+ if (scope === getOwnerScope2()) runCleanups(list);
4134
+ else runWithOwnerScope2(scope, () => runCleanups(list));
4135
+ }
4136
+ function runCleanups(list) {
4137
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
4138
+ try {
4139
+ list[i2]();
4140
+ } catch (err) {
4141
+ reportError(err, { phase: "cleanup" });
4142
+ }
4143
+ }
4144
+ }
4145
+ function drainReruns(ctx) {
4146
+ let reruns = 1;
4147
+ do {
4148
+ ctx.rerunPending = false;
3552
4149
  if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3553
4150
  retrack2(ctx.bodyFn, ctx.subscriber);
3554
4151
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
@@ -3659,6 +4256,324 @@ function effect(effectFn, options) {
3659
4256
  return () => disposeEffect(ctx);
3660
4257
  }
3661
4258
 
4259
+ // src/utils/documentResources.ts
4260
+ init_globalSingleton();
4261
+
4262
+ // src/utils/singletonResource.ts
4263
+ function singletonResource(io) {
4264
+ let original = null;
4265
+ let captured = false;
4266
+ const stack = [];
4267
+ function apply() {
4268
+ if (stack.length > 0) {
4269
+ io.write(stack[stack.length - 1].value);
4270
+ return;
4271
+ }
4272
+ if (captured) {
4273
+ io.write(original);
4274
+ captured = false;
4275
+ original = null;
4276
+ }
4277
+ }
4278
+ return {
4279
+ acquire(value) {
4280
+ if (!captured) {
4281
+ original = io.read();
4282
+ captured = true;
4283
+ }
4284
+ const entry = { value, released: false };
4285
+ stack.push(entry);
4286
+ apply();
4287
+ return {
4288
+ set(next) {
4289
+ if (entry.released) return;
4290
+ entry.value = next;
4291
+ apply();
4292
+ },
4293
+ release() {
4294
+ if (entry.released) return;
4295
+ entry.released = true;
4296
+ const index = stack.indexOf(entry);
4297
+ if (index !== -1) stack.splice(index, 1);
4298
+ apply();
4299
+ }
4300
+ };
4301
+ },
4302
+ ownerCount() {
4303
+ return stack.length;
4304
+ }
4305
+ };
4306
+ }
4307
+
4308
+ // src/utils/documentResources.ts
4309
+ var INERT_LEASE = {
4310
+ set() {
4311
+ },
4312
+ release() {
4313
+ }
4314
+ };
4315
+ function inertLease() {
4316
+ return INERT_LEASE;
4317
+ }
4318
+ var titleResource = globalSingleton(
4319
+ /* @__PURE__ */ Symbol.for("sibujs.documentTitle.v1"),
4320
+ () => singletonResource({
4321
+ read: () => document.title,
4322
+ write: (value) => {
4323
+ document.title = value;
4324
+ }
4325
+ })
4326
+ );
4327
+ var baseResource = globalSingleton(
4328
+ /* @__PURE__ */ Symbol.for("sibujs.documentBase.v1"),
4329
+ () => singletonResource({
4330
+ read: () => {
4331
+ const el = document.head.querySelector("base");
4332
+ if (!el) return null;
4333
+ return {
4334
+ href: el.getAttribute("href") ?? void 0,
4335
+ target: el.getAttribute("target") ?? void 0
4336
+ };
4337
+ },
4338
+ write: (spec) => {
4339
+ const existing = document.head.querySelector("base");
4340
+ if (spec === null) {
4341
+ existing?.remove();
4342
+ return;
4343
+ }
4344
+ const el = existing ?? document.head.appendChild(document.createElement("base"));
4345
+ if (spec.href === void 0) el.removeAttribute("href");
4346
+ else el.setAttribute("href", spec.href);
4347
+ if (spec.target === void 0) el.removeAttribute("target");
4348
+ else el.setAttribute("target", spec.target);
4349
+ }
4350
+ })
4351
+ );
4352
+ var scrollRestorationResource = globalSingleton(
4353
+ /* @__PURE__ */ Symbol.for("sibujs.historyScrollRestoration.v1"),
4354
+ () => singletonResource({
4355
+ read: () => history.scrollRestoration,
4356
+ write: (mode) => {
4357
+ history.scrollRestoration = mode;
4358
+ }
4359
+ })
4360
+ );
4361
+ function acquireTitle(value) {
4362
+ if (typeof document === "undefined") return inertLease();
4363
+ return titleResource.acquire(value);
4364
+ }
4365
+
4366
+ // src/plugins/routeHead.ts
4367
+ init_headEntry();
4368
+ init_sanitize();
4369
+ var OG_FIELDS = [
4370
+ ["title", "og:title"],
4371
+ ["description", "og:description"],
4372
+ ["image", "og:image"],
4373
+ ["url", "og:url"],
4374
+ ["type", "og:type"],
4375
+ ["siteName", "og:site_name"]
4376
+ ];
4377
+ var OG_URL_PROPERTIES = /* @__PURE__ */ new Set(["og:image", "og:url"]);
4378
+ var ROUTE_HEAD_ATTR = "data-sibu-head";
4379
+ var BASE_TITLE_ATTR = "data-sibu-base-title";
4380
+ function routeHasHead(matched, defaults) {
4381
+ if (defaults) return true;
4382
+ for (const record of matched) if (record.head) return true;
4383
+ return false;
4384
+ }
4385
+ function resolveValue(value, route2) {
4386
+ if (value == null) return null;
4387
+ const v = typeof value === "function" ? value(route2) : value;
4388
+ return v == null ? null : String(v);
4389
+ }
4390
+ function mergeHeads(route2, defaults) {
4391
+ const layers = [];
4392
+ if (defaults) layers.push(defaults);
4393
+ for (const record of route2.matched) {
4394
+ const head2 = record.head;
4395
+ if (head2) layers.push(head2);
4396
+ }
4397
+ const merged = {};
4398
+ const og = {};
4399
+ const meta2 = /* @__PURE__ */ new Map();
4400
+ for (const layer of layers) {
4401
+ if (layer.title !== void 0) merged.title = layer.title;
4402
+ if (layer.description !== void 0) merged.description = layer.description;
4403
+ if (layer.canonical !== void 0) merged.canonical = layer.canonical;
4404
+ if (layer.og) {
4405
+ for (const [field] of OG_FIELDS) if (layer.og[field] !== void 0) og[field] = layer.og[field];
4406
+ }
4407
+ for (const entry of layer.meta ?? []) {
4408
+ const id = metaIdentity(entry);
4409
+ if (id) meta2.set(id, entry);
4410
+ }
4411
+ }
4412
+ merged.og = og;
4413
+ merged.meta = Array.from(meta2.values());
4414
+ return merged;
4415
+ }
4416
+ function metaIdentity(entry) {
4417
+ const name = typeof entry.name === "string" ? entry.name : null;
4418
+ if (name) return `name:${name.toLowerCase()}`;
4419
+ const property = typeof entry.property === "string" ? entry.property : null;
4420
+ if (property) return `property:${property.toLowerCase()}`;
4421
+ return null;
4422
+ }
4423
+ function applyTemplate(title2, template2) {
4424
+ if (!template2) return title2;
4425
+ return typeof template2 === "function" ? template2(title2) : template2.replace("%s", title2);
4426
+ }
4427
+ function resolveRouteHead(route2, defaults) {
4428
+ const merged = mergeHeads(route2, defaults);
4429
+ const ownTitle = resolveValue(merged.title, route2);
4430
+ const routeSetTitle = route2.matched.some((r) => r.head?.title != null);
4431
+ const title2 = ownTitle === null ? null : routeSetTitle ? applyTemplate(ownTitle, defaults?.titleTemplate) : ownTitle;
4432
+ const description = resolveValue(merged.description, route2);
4433
+ const canonical = resolveValue(merged.canonical, route2);
4434
+ const tags = [];
4435
+ const push2 = (key, tag, attrs) => {
4436
+ const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
4437
+ if (plan.kind === "drop") return;
4438
+ tags.push({ key, tag, attrs: Object.fromEntries(plan.attributes) });
4439
+ };
4440
+ if (description !== null) push2("meta:name:description", "meta", { name: "description", content: description });
4441
+ if (canonical !== null) push2("link:rel:canonical", "link", { rel: "canonical", href: canonical });
4442
+ const og = merged.og ?? {};
4443
+ for (const [field, property] of OG_FIELDS) {
4444
+ let value = og[field] === void 0 ? void 0 : resolveValue(og[field], route2);
4445
+ if (value === void 0) {
4446
+ if (field === "title") value = ownTitle;
4447
+ else if (field === "description") value = description;
4448
+ else if (field === "url") value = canonical;
4449
+ else value = null;
4450
+ }
4451
+ if (value === null) continue;
4452
+ if (OG_URL_PROPERTIES.has(property)) {
4453
+ value = sanitizeUrl(value);
4454
+ if (value === "") continue;
4455
+ }
4456
+ push2(`meta:property:${property}`, "meta", { property, content: value });
4457
+ }
4458
+ for (const entry of merged.meta ?? []) {
4459
+ const id = metaIdentity(entry);
4460
+ if (!id) continue;
4461
+ const attrs = {};
4462
+ for (const [name, v] of Object.entries(entry)) {
4463
+ const resolved = resolveValue(v, route2);
4464
+ if (resolved !== null) attrs[name] = resolved;
4465
+ }
4466
+ push2(`meta:${id}`, "meta", attrs);
4467
+ }
4468
+ return { title: title2, tags };
4469
+ }
4470
+ function escapeText(value) {
4471
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4472
+ }
4473
+ function routeHeadToHTML(head2) {
4474
+ const parts = [];
4475
+ if (head2.title !== null) parts.push(`<title>${escapeText(head2.title)}</title>`);
4476
+ for (const t2 of head2.tags) {
4477
+ const html2 = serializeHeadEntry(t2.tag, t2.attrs);
4478
+ if (html2) parts.push(html2.replace(/ \/>$/, ` ${ROUTE_HEAD_ATTR}="" />`));
4479
+ }
4480
+ return parts.join("\n");
4481
+ }
4482
+ function selectorFor(key) {
4483
+ const [tag, attr, ...rest] = key.split(":");
4484
+ const value = rest.join(":").replace(/["\\]/g, "\\$&");
4485
+ return `${tag}[${attr}="${value}" i]`;
4486
+ }
4487
+ function createRouteHeadController(defaults) {
4488
+ const [current, setCurrent] = signal(null);
4489
+ const owned = /* @__PURE__ */ new Map();
4490
+ const initial = new Set(Array.from(document.head.children));
4491
+ const titleEl = document.head.querySelector("title");
4492
+ const baseTitle = titleEl?.getAttribute(BASE_TITLE_ATTR) ?? document.title;
4493
+ let titleLease = null;
4494
+ let firstApply = true;
4495
+ const publish = (tag) => {
4496
+ const existing = owned.get(tag.key);
4497
+ if (existing && sameAttrs(existing, tag.attrs)) return;
4498
+ if (!existing) {
4499
+ for (const el of Array.from(document.head.querySelectorAll(selectorFor(tag.key)))) {
4500
+ if (initial.has(el)) {
4501
+ el.remove();
4502
+ initial.delete(el);
4503
+ }
4504
+ }
4505
+ }
4506
+ const next = document.createElement(tag.tag);
4507
+ for (const [name, value] of Object.entries(tag.attrs)) next.setAttribute(name, value);
4508
+ next.setAttribute(ROUTE_HEAD_ATTR, "");
4509
+ if (existing?.parentNode) existing.replaceWith(next);
4510
+ else document.head.appendChild(next);
4511
+ owned.set(tag.key, next);
4512
+ };
4513
+ const apply = (head2) => {
4514
+ if (firstApply) {
4515
+ firstApply = false;
4516
+ for (const el of Array.from(document.head.querySelectorAll(`[${ROUTE_HEAD_ATTR}]`))) {
4517
+ if (initial.has(el) && !head2.tags.some((t2) => el.matches(selectorFor(t2.key)))) {
4518
+ el.remove();
4519
+ initial.delete(el);
4520
+ }
4521
+ }
4522
+ }
4523
+ const keep = /* @__PURE__ */ new Set();
4524
+ for (const tag of head2.tags) {
4525
+ keep.add(tag.key);
4526
+ publish(tag);
4527
+ }
4528
+ for (const [key, el] of owned) {
4529
+ if (keep.has(key)) continue;
4530
+ el.remove();
4531
+ owned.delete(key);
4532
+ }
4533
+ const title2 = head2.title ?? baseTitle;
4534
+ if (titleLease) titleLease.set(title2);
4535
+ else titleLease = acquireTitle(title2);
4536
+ };
4537
+ const stop2 = effect(() => {
4538
+ const route2 = current();
4539
+ if (route2 === null) return;
4540
+ const head2 = resolveRouteHead(route2, defaults);
4541
+ untracked2(() => apply(head2));
4542
+ });
4543
+ return {
4544
+ update(route2) {
4545
+ setCurrent(route2);
4546
+ },
4547
+ dispose() {
4548
+ stop2();
4549
+ for (const el of owned.values()) el.remove();
4550
+ owned.clear();
4551
+ titleLease?.release();
4552
+ titleLease = null;
4553
+ }
4554
+ };
4555
+ }
4556
+ function sameAttrs(el, attrs) {
4557
+ const names = Object.keys(attrs);
4558
+ if (el.attributes.length !== names.length + 1) return false;
4559
+ for (const name of names) if (el.getAttribute(name) !== attrs[name]) return false;
4560
+ return true;
4561
+ }
4562
+
4563
+ // src/plugins/router.ts
4564
+ init_dev();
4565
+ init_dispose();
4566
+
4567
+ // src/utils/prerenderHook.ts
4568
+ var HOST_KEY = /* @__PURE__ */ Symbol.for("sibujs.prerender.v1");
4569
+ function host() {
4570
+ return globalThis[HOST_KEY];
4571
+ }
4572
+ function prerenderWait(promise) {
4573
+ host()?.wait(promise);
4574
+ return promise;
4575
+ }
4576
+
3662
4577
  // src/plugins/router.ts
3663
4578
  init_sanitize();
3664
4579
  function splitRouteUrl(url) {
@@ -4338,6 +5253,15 @@ var _SibuRouter = class _SibuRouter {
4338
5253
  // to superseded work. The router's own initial location resolution is
4339
5254
  // excluded — it establishes the bootstrap route rather than superseding it.
4340
5255
  this.navEpoch = 0;
5256
+ // Created the first time a committed route needs a managed <head>.
5257
+ this.headController = null;
5258
+ // `readyPromise` plus the matched components loaded; created on the first
5259
+ // `routerReady()` call only, so an app that never asks pays nothing and its
5260
+ // outlets load exactly as before.
5261
+ this.loadedPromise = null;
5262
+ this.readyPromise = new Promise((resolve) => {
5263
+ this.resolveReady = resolve;
5264
+ });
4341
5265
  this.options = {
4342
5266
  mode: "history",
4343
5267
  base: "",
@@ -4375,12 +5299,31 @@ var _SibuRouter = class _SibuRouter {
4375
5299
  }
4376
5300
  }
4377
5301
  queueMicrotask(() => {
5302
+ let initial = Promise.resolve();
4378
5303
  if (!this.navigator.isNavigating) {
4379
- this.handleLocationChange(true, true);
5304
+ initial = this.handleLocationChange(true, true);
4380
5305
  }
4381
5306
  this.isReadySetter(true);
5307
+ prerenderWait(initial.then(this.resolveReady, this.resolveReady));
4382
5308
  });
4383
5309
  }
5310
+ /** Load the component of every record the current route matched, without instantiating any. */
5311
+ async preloadMatched() {
5312
+ const current = untracked2(() => this.currentRouteGetter());
5313
+ for (const record of current.matched) {
5314
+ if ("redirect" in record) continue;
5315
+ try {
5316
+ await this.loader.preloadPlan(record, current.path);
5317
+ } catch {
5318
+ this.loader.clearErrors();
5319
+ }
5320
+ }
5321
+ }
5322
+ /** Settles after the initial navigation and its component loads; see `routerReady()`. */
5323
+ whenReady() {
5324
+ this.loadedPromise ?? (this.loadedPromise = this.readyPromise.then(() => this.destroyed ? void 0 : this.preloadMatched()));
5325
+ return this.loadedPromise;
5326
+ }
4384
5327
  createInitialRoute() {
4385
5328
  return {
4386
5329
  path: "/",
@@ -4394,7 +5337,7 @@ var _SibuRouter = class _SibuRouter {
4394
5337
  }
4395
5338
  handleLocationChange(skipHistory = true, initialResolution = false) {
4396
5339
  const path2 = this.getCurrentPath();
4397
- this.navigate(path2, { replace: true, skipHistory, initialResolution }).catch((err) => {
5340
+ return this.navigate(path2, { replace: true, skipHistory, initialResolution }).catch((err) => {
4398
5341
  console.error("[Router] Error during location change navigation:", err);
4399
5342
  });
4400
5343
  }
@@ -4551,9 +5494,18 @@ var _SibuRouter = class _SibuRouter {
4551
5494
  }
4552
5495
  if (!options.initialResolution) this.navEpoch++;
4553
5496
  this.currentRouteSetter(to);
5497
+ this.updateHead(to);
4554
5498
  this.guards.runAfterEach(to, from);
4555
5499
  this.handleScrollBehavior(to, from);
4556
5500
  }
5501
+ updateHead(to) {
5502
+ if (typeof document === "undefined" || this.destroyed) return;
5503
+ if (this.headController === null) {
5504
+ if (!routeHasHead(to.matched, this.options.head)) return;
5505
+ this.headController = createRouteHeadController(this.options.head);
5506
+ }
5507
+ this.headController.update(to);
5508
+ }
4557
5509
  resolvePath(to) {
4558
5510
  if (typeof to === "string") return to;
4559
5511
  let path2 = to.path || "";
@@ -4642,14 +5594,14 @@ var _SibuRouter = class _SibuRouter {
4642
5594
  * anything. Pair with {@link instantiateComponent} at the commit boundary.
4643
5595
  */
4644
5596
  async loadPlan(route2, routePath) {
4645
- return this.loader.loadPlan(route2, routePath);
5597
+ return prerenderWait(this.loader.loadPlan(route2, routePath));
4646
5598
  }
4647
5599
  /**
4648
5600
  * Create exactly one real instance from an already-resolved plan. With a
4649
5601
  * `scope`, the factory runs in it (see `RouteScope`).
4650
5602
  */
4651
5603
  async instantiateComponent(plan, route2, routePath, scope) {
4652
- return this.loader.instantiate(plan, route2, routePath, scope);
5604
+ return prerenderWait(this.loader.instantiate(plan, route2, routePath, scope));
4653
5605
  }
4654
5606
  /** Resolve a route's module/factory without creating any component instance. */
4655
5607
  async preloadPlan(route2, routePath) {
@@ -4705,6 +5657,9 @@ var _SibuRouter = class _SibuRouter {
4705
5657
  this.guards.clear();
4706
5658
  this.loader.clearCache();
4707
5659
  this.isReadySetter(false);
5660
+ this.headController?.dispose();
5661
+ this.headController = null;
5662
+ this.resolveReady();
4708
5663
  }
4709
5664
  // Cache management
4710
5665
  clearCache() {
@@ -4943,6 +5898,10 @@ function router() {
4943
5898
  afterEach: (hook) => _routerRef.current?.afterEach(hook)
4944
5899
  };
4945
5900
  }
5901
+ function routerReady() {
5902
+ if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
5903
+ return _routerRef.current.whenReady();
5904
+ }
4946
5905
  function navigate(to, options) {
4947
5906
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4948
5907
  return _routerRef.current.navigate(to, options);
@@ -5965,570 +6924,278 @@ function hydrateRouter(routes, options) {
5965
6924
  replaceChildrenSafely2(container);
5966
6925
  }).catch((err) => {
5967
6926
  if (typeof console !== "undefined") {
5968
- console.error("[SibuJS routerSSR] failed to load hydrate:", err);
5969
- }
5970
- });
5971
- }
5972
- function createSSRRouter(routes) {
5973
- return {
5974
- resolve(url) {
5975
- return resolveServerRoute(url, routes);
5976
- },
5977
- renderToString(url) {
5978
- return renderRouteToString(url, routes);
5979
- },
5980
- renderToDocument(url, options) {
5981
- return renderRouteToDocument(url, routes, options);
5982
- }
5983
- };
5984
- }
5985
- function escapeHtmlLocal(str) {
5986
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5987
- }
5988
- function escapeAttrLocal(str) {
5989
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5990
- }
5991
-
5992
- // src/plugins/plugin.ts
5993
- init_dev();
5994
- init_errors();
5995
-
5996
- // src/utils/adoptThenable.ts
5997
- function adoptThenable(value) {
5998
- if (value === null || typeof value !== "object" && typeof value !== "function") return null;
5999
- let then;
6000
- try {
6001
- then = value.then;
6002
- } catch (error) {
6003
- return Promise.reject(error);
6004
- }
6005
- if (typeof then !== "function") return null;
6006
- return new Promise((resolve, reject) => {
6007
- queueMicrotask(() => {
6008
- try {
6009
- Reflect.apply(then, value, [resolve, reject]);
6010
- } catch (error) {
6011
- reject(error);
6012
- }
6013
- });
6014
- });
6015
- }
6016
-
6017
- // src/plugins/plugin.ts
6018
- init_globalSingleton();
6019
- var CANCELLED_BRAND = /* @__PURE__ */ Symbol.for("sibujs.plugins.installCancelled.v1");
6020
- var _a;
6021
- var PluginInstallCancelledError = class extends Error {
6022
- constructor(pluginName) {
6023
- super(`[Plugin] Installation of "${pluginName}" was cancelled before it was committed (registry reset).`);
6024
- /**
6025
- * Brand, not identity: the default registry is shared across duplicate copies
6026
- * of this module, so the error a caller receives may come from another copy's
6027
- * constructor. `instanceof` is taught to recognise the brand instead.
6028
- */
6029
- this[_a] = true;
6030
- this.name = "PluginInstallCancelledError";
6031
- this.pluginName = pluginName;
6032
- }
6033
- static [(_a = CANCELLED_BRAND, Symbol.hasInstance)](value) {
6034
- return isPluginInstallCancelledError(value);
6035
- }
6036
- };
6037
- function isPluginInstallCancelledError(value) {
6038
- return typeof value === "object" && value !== null && value[CANCELLED_BRAND] === true;
6039
- }
6040
- function createPluginRegistry() {
6041
- const installedPlugins = /* @__PURE__ */ new Set();
6042
- const hooks = { init: [], mount: [], unmount: [], error: [] };
6043
- const provided = /* @__PURE__ */ new Map();
6044
- const installStack = /* @__PURE__ */ new Set();
6045
- const pendingInstalls = /* @__PURE__ */ new Map();
6046
- let generation = 0;
6047
- const registry = {
6048
- installedPlugins,
6049
- hooks,
6050
- provided,
6051
- /**
6052
- * Install a plugin as a transaction.
6053
- *
6054
- * Hooks and providers registered through `ctx` are staged and committed
6055
- * only if `install()` returns; a throwing install leaves the registry
6056
- * exactly as it found it (and can simply be retried). The plugin is marked
6057
- * installed at commit, before its init hooks run.
6058
- *
6059
- * Each `plugin()` call is its own transaction: a dependency installed by a
6060
- * nested `plugin()` call commits on its own and stays installed even if the
6061
- * outer installation later fails.
6062
- */
6063
- plugin(p2, options) {
6064
- if (installedPlugins.has(p2.name)) {
6065
- console.warn(`[Plugin] "${p2.name}" is already installed.`);
6066
- return;
6067
- }
6068
- if (installStack.has(p2.name)) {
6069
- throw new Error(`[Plugin] "${p2.name}" is already being installed (recursive installation).`);
6070
- }
6071
- if (pendingInstalls.has(p2.name)) {
6072
- throw new Error(`[Plugin] "${p2.name}" is already being installed (asynchronous installation in flight).`);
6073
- }
6074
- const staged = { init: [], mount: [], unmount: [], error: [] };
6075
- const stagedProvided = /* @__PURE__ */ new Map();
6076
- let committed = false;
6077
- const myGeneration = generation;
6078
- const stale = () => {
6079
- if (myGeneration === generation) return false;
6080
- if (DEV) devWarn(`plugin: "${p2.name}" registered after the registry was reset; the registration was ignored.`);
6081
- return true;
6082
- };
6083
- const target = () => stale() ? null : committed ? hooks : staged;
6084
- const ctx = {
6085
- onInit: (cb) => void target()?.init.push(cb),
6086
- onMount: (cb) => void target()?.mount.push(cb),
6087
- onUnmount: (cb) => void target()?.unmount.push(cb),
6088
- onError: (cb) => void target()?.error.push(cb),
6089
- provide: (key, value) => {
6090
- if (stale()) return;
6091
- (committed ? provided : stagedProvided).set(key, value);
6092
- }
6093
- };
6094
- const commit = () => {
6095
- if (myGeneration !== generation) return false;
6096
- hooks.init.push(...staged.init);
6097
- hooks.mount.push(...staged.mount);
6098
- hooks.unmount.push(...staged.unmount);
6099
- hooks.error.push(...staged.error);
6100
- for (const [key, value] of stagedProvided) provided.set(key, value);
6101
- installedPlugins.add(p2.name);
6102
- committed = true;
6103
- if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
6104
- for (const cb of staged.init.slice()) {
6105
- try {
6106
- cb();
6107
- } catch (e) {
6108
- console.error(`[Plugin] "${p2.name}" init error:`, e);
6109
- }
6110
- }
6111
- return true;
6112
- };
6113
- installStack.add(p2.name);
6114
- let pending;
6115
- try {
6116
- pending = adoptThenable(p2.install(ctx, options));
6117
- } finally {
6118
- installStack.delete(p2.name);
6119
- }
6120
- if (!pending) {
6121
- if (!commit()) throw new PluginInstallCancelledError(p2.name);
6122
- return;
6123
- }
6124
- if (myGeneration === generation) pendingInstalls.set(p2.name, myGeneration);
6125
- const settled = pending.then(
6126
- () => {
6127
- if (commit()) return;
6128
- if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
6129
- throw new PluginInstallCancelledError(p2.name);
6130
- },
6131
- (err) => {
6132
- if (pendingInstalls.get(p2.name) === myGeneration) pendingInstalls.delete(p2.name);
6133
- reportError(err, { phase: "async", name: `plugin(${p2.name})` });
6134
- throw err;
6135
- }
6136
- );
6137
- settled.catch(() => {
6138
- });
6139
- return settled;
6140
- },
6141
- inject(key, defaultValue) {
6142
- if (provided.has(key)) return provided.get(key);
6143
- if (defaultValue !== void 0) return defaultValue;
6144
- throw new Error(`[Plugin] No provider found for key "${key}"`);
6145
- },
6146
- triggerMount(element) {
6147
- const snapshot = hooks.mount.slice();
6148
- for (const hook of snapshot) {
6149
- try {
6150
- hook(element);
6151
- } catch (e) {
6152
- console.error("[Plugin] Mount hook error:", e);
6153
- }
6154
- }
6155
- },
6156
- triggerUnmount(element) {
6157
- const snapshot = hooks.unmount.slice();
6158
- for (const hook of snapshot) {
6159
- try {
6160
- hook(element);
6161
- } catch (e) {
6162
- console.error("[Plugin] Unmount hook error:", e);
6163
- }
6164
- }
6165
- },
6166
- triggerError(error) {
6167
- const snapshot = hooks.error.slice();
6168
- for (const hook of snapshot) {
6169
- try {
6170
- hook(error);
6171
- } catch (e) {
6172
- console.error("[Plugin] Error hook error:", e);
6173
- }
6174
- }
6175
- },
6176
- reset() {
6177
- generation++;
6178
- pendingInstalls.clear();
6179
- installedPlugins.clear();
6180
- hooks.init.length = 0;
6181
- hooks.mount.length = 0;
6182
- hooks.unmount.length = 0;
6183
- hooks.error.length = 0;
6184
- provided.clear();
6185
- }
6186
- };
6187
- return registry;
6188
- }
6189
- var _defaults = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.plugins.defaultRegistry.v1"), () => ({
6190
- registry: createPluginRegistry(),
6191
- touched: false
6192
- }));
6193
- function createPlugin(name, install) {
6194
- return { name, install };
6195
- }
6196
- function plugin(plugin2, options) {
6197
- _defaults.touched = true;
6198
- return _defaults.registry.plugin(plugin2, options);
6199
- }
6200
- function inject(key, defaultValue) {
6201
- return _defaults.registry.inject(key, defaultValue);
6202
- }
6203
- function triggerPluginMount(element) {
6204
- _defaults.registry.triggerMount(element);
6205
- }
6206
- function triggerPluginUnmount(element) {
6207
- _defaults.registry.triggerUnmount(element);
6927
+ console.error("[SibuJS routerSSR] failed to load hydrate:", err);
6928
+ }
6929
+ });
6208
6930
  }
6209
- function triggerPluginError(error) {
6210
- _defaults.registry.triggerError(error);
6931
+ function createSSRRouter(routes) {
6932
+ return {
6933
+ resolve(url) {
6934
+ return resolveServerRoute(url, routes);
6935
+ },
6936
+ renderToString(url) {
6937
+ return renderRouteToString(url, routes);
6938
+ },
6939
+ renderToDocument(url, options) {
6940
+ return renderRouteToDocument(url, routes, options);
6941
+ }
6942
+ };
6211
6943
  }
6212
- function resetPlugins() {
6213
- _defaults.registry.reset();
6214
- _defaults.touched = false;
6944
+ function escapeHtmlLocal(str) {
6945
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6215
6946
  }
6216
- function setDefaultPluginRegistry(registry) {
6217
- if (_defaults.touched && _defaults.registry.installedPlugins.size > 0) {
6218
- console.warn(
6219
- "[Plugin] Replacing default plugin registry while plugins are already installed on the singleton. This may indicate mixed singleton/registry usage."
6220
- );
6221
- }
6222
- _defaults.registry = registry;
6223
- _defaults.touched = true;
6947
+ function escapeAttrLocal(str) {
6948
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6224
6949
  }
6225
6950
 
6226
- // src/plugins/modular.ts
6227
- function createModuleRegistry() {
6228
- const modules = /* @__PURE__ */ new Map();
6229
- const resolving = [];
6230
- function resolveInternal(name) {
6231
- const entry = modules.get(name);
6232
- if (!entry) {
6233
- throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
6234
- }
6235
- if (entry.state === "loaded") {
6236
- return entry.value;
6237
- }
6238
- if (entry.state === "resolving") {
6239
- const cycle = [...resolving.slice(resolving.indexOf(name)), name].join(" -> ");
6240
- throw new Error(`[ModuleRegistry] Circular dependency detected: ${cycle}`);
6951
+ // src/plugins/startup.ts
6952
+ init_errors();
6953
+ init_ssr();
6954
+ init_sanitize();
6955
+ function preloadCritical(resources) {
6956
+ if (typeof document === "undefined") return;
6957
+ for (const resource of resources) {
6958
+ const href = sanitizeUrl(resource.href);
6959
+ if (!href) continue;
6960
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
6961
+ const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
6962
+ if (existing) continue;
6963
+ const link2 = document.createElement("link");
6964
+ link2.rel = "preload";
6965
+ link2.href = href;
6966
+ link2.setAttribute("as", resource.as);
6967
+ if (resource.type) {
6968
+ link2.type = resource.type;
6241
6969
  }
6242
- entry.state = "resolving";
6243
- resolving.push(name);
6244
- try {
6245
- for (const dep of entry.deps) {
6246
- resolveInternal(dep);
6247
- }
6248
- entry.value = entry.factory();
6249
- entry.state = "loaded";
6250
- return entry.value;
6251
- } catch (err) {
6252
- entry.state = "unloaded";
6253
- entry.value = void 0;
6254
- throw err;
6255
- } finally {
6256
- resolving.pop();
6970
+ if (resource.crossOrigin) {
6971
+ link2.crossOrigin = resource.crossOrigin;
6257
6972
  }
6973
+ document.head.appendChild(link2);
6258
6974
  }
6259
- return {
6260
- /** Register a module with its factory function and optional dependencies */
6261
- register(name, factory, deps = []) {
6262
- modules.set(name, { factory, deps, state: "unloaded" });
6263
- },
6264
- /** Resolve a module, loading its dependencies first */
6265
- resolve(name) {
6266
- return resolveInternal(name);
6267
- },
6268
- /** Check if a module is registered */
6269
- has(name) {
6270
- return modules.has(name);
6271
- },
6272
- /** List all registered module names */
6273
- list() {
6274
- return Array.from(modules.keys());
6275
- },
6276
- /** Get the full dependency graph (transitive) for a module */
6277
- deps(name) {
6278
- const entry = modules.get(name);
6279
- if (!entry) {
6280
- throw new Error(`[ModuleRegistry] Module "${name}" is not registered.`);
6281
- }
6282
- const visited = /* @__PURE__ */ new Set();
6283
- const result = [];
6284
- function walk(modName) {
6285
- const mod = modules.get(modName);
6286
- if (!mod) return;
6287
- for (const dep of mod.deps) {
6288
- if (!visited.has(dep)) {
6289
- visited.add(dep);
6290
- walk(dep);
6291
- result.push(dep);
6292
- }
6293
- }
6294
- }
6295
- walk(name);
6296
- return result;
6297
- },
6298
- /** Reset all loaded modules back to unloaded state (useful for testing) */
6299
- reset() {
6300
- for (const entry of modules.values()) {
6301
- entry.state = "unloaded";
6302
- entry.value = void 0;
6303
- }
6304
- }
6305
- };
6306
6975
  }
6307
- function createBundle(modules) {
6308
- const cache = /* @__PURE__ */ new Map();
6309
- const bundle = {};
6310
- for (const key of Object.keys(modules)) {
6311
- Object.defineProperty(bundle, key, {
6312
- get() {
6313
- if (cache.has(key)) {
6314
- return cache.get(key);
6315
- }
6316
- const value = modules[key]();
6317
- cache.set(key, value);
6318
- return value;
6319
- },
6320
- enumerable: true,
6321
- configurable: false
6322
- });
6976
+ function assertCacheSize(name, value) {
6977
+ if (!Number.isInteger(value) || value < 0) {
6978
+ throw new RangeError(`[Startup] ${name} must be a non-negative integer, got ${value}`);
6323
6979
  }
6324
- return bundle;
6325
6980
  }
6326
- function lazyModule(loader) {
6327
- let cached;
6328
- let loadedFlag = false;
6329
- let inFlight = null;
6330
- return {
6331
- get loaded() {
6332
- return loadedFlag;
6333
- },
6334
- get() {
6335
- if (loadedFlag) return Promise.resolve(cached);
6336
- if (inFlight) return inFlight;
6337
- let started;
6338
- try {
6339
- started = Promise.resolve(loader());
6340
- } catch (err) {
6341
- started = Promise.reject(err);
6981
+ function makeRoomFor(cache, key, maxSize, isValid) {
6982
+ if (maxSize === 0) return false;
6983
+ if (cache.has(key)) return true;
6984
+ if (cache.size < maxSize) return true;
6985
+ for (const [k, entry] of cache) {
6986
+ if (!isValid(entry)) cache.delete(k);
6987
+ }
6988
+ while (cache.size >= maxSize) {
6989
+ let oldestKey = null;
6990
+ let oldestTime = Infinity;
6991
+ for (const [k, entry] of cache) {
6992
+ if (entry.timestamp < oldestTime) {
6993
+ oldestTime = entry.timestamp;
6994
+ oldestKey = k;
6342
6995
  }
6343
- const attempt = started.then(
6344
- (value) => {
6345
- cached = value;
6346
- loadedFlag = true;
6347
- if (inFlight === attempt) inFlight = null;
6348
- return value;
6349
- },
6350
- (err) => {
6351
- if (inFlight === attempt) inFlight = null;
6352
- throw err;
6353
- }
6354
- );
6355
- inFlight = attempt;
6356
- return attempt;
6357
6996
  }
6358
- };
6997
+ if (oldestKey === null) break;
6998
+ cache.delete(oldestKey);
6999
+ }
7000
+ return true;
6359
7001
  }
6360
- var MODULE_ENTRY_POINTS = {
6361
- main: "./index.ts",
6362
- data: "./data.ts",
6363
- browser: "./browser.ts",
6364
- patterns: "./patterns.ts",
6365
- motion: "./motion.ts",
6366
- ui: "./ui.ts",
6367
- widgets: "./widgets.ts",
6368
- ssr: "./ssr.ts",
6369
- devtools: "./devtools.ts",
6370
- performance: "./performance.ts",
6371
- ecosystem: "./ecosystem.ts",
6372
- plugins: "./plugins.ts",
6373
- build: "./build.ts",
6374
- testing: "./testing.ts",
6375
- extras: "./extras.ts"
6376
- };
6377
- var CDN_ENTRY_POINTS = {
6378
- cdn: "./dist/cdn.global.js",
6379
- "cdn-dev": "./dist/cdn.dev.global.js",
6380
- "cdn-full": "./dist/cdn.full.global.js",
6381
- "cdn-full-dev": "./dist/cdn.full.dev.global.js"
6382
- };
6383
- var packageInfo = {
6384
- name: "sibujs",
6385
- version: true ? "4.12.0" : "dev",
6386
- entryPoints: MODULE_ENTRY_POINTS,
6387
- /**
6388
- * Generate the package.json `exports` map. Module entries resolve to the
6389
- * `.js` (ESM), `.cjs` and `.d.ts` files tsup emits into `dist/`; CDN entries
6390
- * resolve to their prebuilt global script.
6391
- */
6392
- generateExportsMap() {
6393
- const exportsMap = {};
6394
- for (const [name, sourcePath] of Object.entries(MODULE_ENTRY_POINTS)) {
6395
- const distPath = sourcePath.replace(/^\.\//, "./dist/").replace(/\.ts$/, "");
6396
- const subpath = name === "main" ? "." : `./${name}`;
6397
- exportsMap[subpath] = {
6398
- types: `${distPath}.d.ts`,
6399
- import: `${distPath}.js`,
6400
- require: `${distPath}.cjs`
6401
- };
6402
- }
6403
- for (const [name, file] of Object.entries(CDN_ENTRY_POINTS)) {
6404
- exportsMap[`./${name}`] = { default: file };
6405
- }
6406
- return exportsMap;
7002
+ function prerenderRoutes(routes, options) {
7003
+ const maxCacheSize = options?.maxCacheSize ?? 50;
7004
+ const cacheTTL = options?.cacheTTL ?? 0;
7005
+ assertCacheSize("maxCacheSize", maxCacheSize);
7006
+ const cache = /* @__PURE__ */ new Map();
7007
+ function isValid(entry) {
7008
+ if (cacheTTL === 0) return true;
7009
+ return Date.now() - entry.timestamp < cacheTTL;
7010
+ }
7011
+ for (const route2 of routes) {
7012
+ if (!makeRoomFor(cache, route2.path, maxCacheSize, isValid)) break;
7013
+ const html2 = renderToString(route2.component());
7014
+ cache.set(route2.path, { html: html2, timestamp: Date.now() });
6407
7015
  }
6408
- };
6409
-
6410
- // src/plugins/ecosystem.ts
6411
- function createTestHarness() {
6412
- let container;
6413
7016
  return {
6414
- /** Set up a clean DOM container before each test */
6415
- setup() {
6416
- container = document.createElement("div");
6417
- container.setAttribute("data-sibu-test", "true");
6418
- document.body.appendChild(container);
6419
- return container;
7017
+ /** Get cached HTML for a route, or undefined if not cached or expired */
7018
+ get(path2) {
7019
+ const entry = cache.get(path2);
7020
+ if (!entry) return void 0;
7021
+ if (!isValid(entry)) {
7022
+ cache.delete(path2);
7023
+ return void 0;
7024
+ }
7025
+ return entry.html;
6420
7026
  },
6421
- /** Tear down the DOM container after each test */
6422
- teardown() {
6423
- if (container?.parentNode) {
6424
- container.parentNode.removeChild(container);
7027
+ /** Check if a route has been prerendered and is still valid */
7028
+ has(path2) {
7029
+ const entry = cache.get(path2);
7030
+ if (!entry) return false;
7031
+ if (!isValid(entry)) {
7032
+ cache.delete(path2);
7033
+ return false;
6425
7034
  }
7035
+ return true;
6426
7036
  },
6427
- /** Render a component into the test container */
6428
- render(component) {
6429
- const el = typeof component === "function" ? component() : component;
6430
- container.appendChild(el);
6431
- return el;
7037
+ /** Invalidate a cached route */
7038
+ invalidate(path2) {
7039
+ cache.delete(path2);
6432
7040
  },
6433
- /** Get the test container */
6434
- getContainer() {
6435
- return container;
7041
+ /** Clear all cached routes */
7042
+ clear() {
7043
+ cache.clear();
6436
7044
  },
6437
- /** Wait for reactive updates to settle */
6438
- async flush() {
6439
- await new Promise((r) => setTimeout(r, 0));
6440
- await new Promise((r) => requestAnimationFrame(() => r()));
7045
+ /** Get cache statistics */
7046
+ stats() {
7047
+ if (cacheTTL > 0) {
7048
+ for (const [key, entry] of cache) {
7049
+ if (!isValid(entry)) {
7050
+ cache.delete(key);
7051
+ }
7052
+ }
7053
+ }
7054
+ return {
7055
+ size: cache.size,
7056
+ routes: Array.from(cache.keys())
7057
+ };
7058
+ }
7059
+ };
7060
+ }
7061
+ function createSSRCache(config) {
7062
+ const maxSize = config?.maxSize ?? 100;
7063
+ const defaultTTL = config?.defaultTTL ?? 6e4;
7064
+ assertCacheSize("maxSize", maxSize);
7065
+ const cache = /* @__PURE__ */ new Map();
7066
+ let hits = 0;
7067
+ let misses = 0;
7068
+ function isValid(entry) {
7069
+ if (entry.ttl === 0) return true;
7070
+ return Date.now() - entry.timestamp < entry.ttl;
7071
+ }
7072
+ return {
7073
+ /** Get cached HTML for a key, or undefined if not cached or expired */
7074
+ get(key) {
7075
+ const entry = cache.get(key);
7076
+ if (!entry) {
7077
+ misses++;
7078
+ return void 0;
7079
+ }
7080
+ if (!isValid(entry)) {
7081
+ cache.delete(key);
7082
+ misses++;
7083
+ return void 0;
7084
+ }
7085
+ hits++;
7086
+ return entry.html;
6441
7087
  },
6442
- /** Query within the container */
6443
- query(selector) {
6444
- return container.querySelector(selector);
7088
+ /** Cache an HTML string with an optional TTL override */
7089
+ set(key, html2, ttl) {
7090
+ if (!makeRoomFor(cache, key, maxSize, isValid)) return;
7091
+ cache.set(key, {
7092
+ html: html2,
7093
+ timestamp: Date.now(),
7094
+ ttl: ttl ?? defaultTTL
7095
+ });
6445
7096
  },
6446
- /** Query all matching elements within the container */
6447
- queryAll(selector) {
6448
- return Array.from(container.querySelectorAll(selector));
7097
+ /** Check if a key exists in the cache and is still valid */
7098
+ has(key) {
7099
+ const entry = cache.get(key);
7100
+ if (!entry) return false;
7101
+ if (!isValid(entry)) {
7102
+ cache.delete(key);
7103
+ return false;
7104
+ }
7105
+ return true;
6449
7106
  },
6450
- /** Simulate a click event */
6451
- click(el) {
6452
- el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
7107
+ /** Invalidate a cached entry */
7108
+ invalidate(key) {
7109
+ cache.delete(key);
6453
7110
  },
6454
- /** Simulate input value change */
6455
- input(el, value) {
6456
- el.value = value;
6457
- el.dispatchEvent(new Event("input", { bubbles: true }));
7111
+ /** Clear all cached entries and reset statistics */
7112
+ clear() {
7113
+ cache.clear();
7114
+ hits = 0;
7115
+ misses = 0;
7116
+ },
7117
+ /** Get cache statistics including hit/miss counts */
7118
+ stats() {
7119
+ return { size: cache.size, hits, misses };
6458
7120
  }
6459
7121
  };
6460
7122
  }
6461
- var bundlerMetadata = {
6462
- name: "sibu",
6463
- sideEffects: false,
6464
- modules: {
6465
- core: ["html", "mount", "each", "slots", "fragment", "catch", "portal", "directives"],
6466
- hooks: ["signal", "effect", "derived", "watch", "store", "ref", "array", "deepSignal"],
6467
- plugins: ["router", "i18n"],
6468
- components: ["ErrorBoundary", "Loading"],
6469
- ssr: ["ssr"],
6470
- advanced: ["globalStore", "machine", "optimistic", "timeTravel", "scheduler", "plugin"]
6471
- },
6472
- /** Generate import map for module resolution */
6473
- generateImportMap(base2 = "/node_modules/sibu/") {
6474
- const map2 = {};
6475
- for (const [category, modules] of Object.entries(this.modules)) {
6476
- for (const mod of modules) {
6477
- const dir = category === "hooks" ? "core" : category;
6478
- map2[`sibu/${mod}`] = `${base2}src/${dir}/${mod}.js`;
7123
+ var DEFER_IDLE_TIMEOUT_MS = 2e3;
7124
+ function deferNonCritical(tasks) {
7125
+ if (tasks.length === 0) return;
7126
+ const schedule = typeof requestIdleCallback !== "undefined" ? (cb) => {
7127
+ requestIdleCallback(cb, { timeout: DEFER_IDLE_TIMEOUT_MS });
7128
+ } : (cb) => {
7129
+ setTimeout(cb, 1);
7130
+ };
7131
+ const queue = [...tasks];
7132
+ let index = 0;
7133
+ function processNext(deadline) {
7134
+ let ranOne = false;
7135
+ while (index < queue.length) {
7136
+ if (ranOne && deadline && deadline.timeRemaining() < 1) {
7137
+ schedule(processNext);
7138
+ return;
7139
+ }
7140
+ try {
7141
+ queue[index]();
7142
+ } catch (e) {
7143
+ reportError(e, { phase: "scheduler", name: "deferNonCritical" });
6479
7144
  }
7145
+ index++;
7146
+ ranOne = true;
6480
7147
  }
6481
- return map2;
6482
7148
  }
6483
- };
6484
- function healthCheck() {
6485
- const checks = [];
6486
- const hasDOM = typeof document !== "undefined";
6487
- checks.push({
6488
- name: "DOM Environment",
6489
- passed: hasDOM,
6490
- message: hasDOM ? "DOM is available" : "No DOM environment (SSR mode)"
6491
- });
6492
- const hasRAF = typeof requestAnimationFrame !== "undefined";
6493
- checks.push({
6494
- name: "requestAnimationFrame",
6495
- passed: hasRAF,
6496
- message: hasRAF ? "Available" : "Not available (polyfill may be needed)"
6497
- });
6498
- const hasMO = typeof MutationObserver !== "undefined";
6499
- checks.push({
6500
- name: "MutationObserver",
6501
- passed: hasMO,
6502
- message: hasMO ? "Available" : "Not available (lifecycle hooks may not work)"
6503
- });
6504
- const allPassed = checks.every((c) => c.passed);
6505
- return {
6506
- status: allPassed ? "ok" : "warning",
6507
- checks
6508
- };
7149
+ schedule(processNext);
7150
+ }
7151
+ function createBootSequence() {
7152
+ const criticalTasks = [];
7153
+ const deferredTasks = [];
7154
+ return {
7155
+ /** Add a critical task that runs immediately during boot */
7156
+ critical(name, task) {
7157
+ criticalTasks.push({ name, task });
7158
+ },
7159
+ /** Add a deferred task that runs after all critical tasks complete */
7160
+ defer(name, task) {
7161
+ deferredTasks.push({ name, task });
7162
+ },
7163
+ /** Execute the boot sequence: critical tasks first, then deferred */
7164
+ async boot() {
7165
+ const timing = {};
7166
+ const errors = [];
7167
+ for (const entry of criticalTasks) {
7168
+ const start = Date.now();
7169
+ try {
7170
+ await entry.task();
7171
+ } catch (e) {
7172
+ errors.push({
7173
+ name: entry.name,
7174
+ error: e instanceof Error ? e : new Error(String(e))
7175
+ });
7176
+ }
7177
+ timing[entry.name] = Date.now() - start;
7178
+ }
7179
+ for (const entry of deferredTasks) {
7180
+ const start = Date.now();
7181
+ try {
7182
+ await entry.task();
7183
+ } catch (e) {
7184
+ errors.push({
7185
+ name: entry.name,
7186
+ error: e instanceof Error ? e : new Error(String(e))
7187
+ });
7188
+ }
7189
+ timing[entry.name] = Date.now() - start;
7190
+ }
7191
+ return { timing, errors };
7192
+ }
7193
+ };
6509
7194
  }
6510
- var env = {
6511
- /** True when running in a browser with a DOM */
6512
- isBrowser: typeof window !== "undefined" && typeof document !== "undefined",
6513
- /** True when running in Node.js */
6514
- isNode: typeof process !== "undefined" && !!process.versions?.node,
6515
- /** True when running inside a Web Worker */
6516
- isWorker: typeof self !== "undefined" && typeof self.importScripts === "function",
6517
- /** True when running inside Deno */
6518
- isDeno: typeof globalThis.Deno !== "undefined",
6519
- /** True when running inside Bun */
6520
- isBun: typeof globalThis.Bun !== "undefined",
6521
- /** True when no window object is available (server-side rendering) */
6522
- isSSR: typeof window === "undefined",
6523
- /** True when NODE_ENV is not "production" */
6524
- isDev: typeof process !== "undefined" && process.env?.NODE_ENV !== "production",
6525
- /** True when NODE_ENV is "test" or VITEST is set */
6526
- isTest: typeof process !== "undefined" && (process.env?.NODE_ENV === "test" || process.env?.VITEST === "true")
6527
- };
6528
7195
 
6529
7196
  // src/plugins/versioning.ts
6530
7197
  init_globalSingleton();
6531
- var VERSION = true ? "4.12.0" : "dev";
7198
+ var VERSION = true ? "4.13.1" : "dev";
6532
7199
  var NUM = "(?:0|[1-9]\\d*)";
6533
7200
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
6534
7201
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -6806,251 +7473,6 @@ function extractRangeTarget(range) {
6806
7473
  const match = range.match(/[\d]+\.[\d]+\.[\d]+(?:-[\w.]+)?/);
6807
7474
  return match ? match[0] : null;
6808
7475
  }
6809
-
6810
- // src/plugins/startup.ts
6811
- init_errors();
6812
- init_ssr();
6813
- init_sanitize();
6814
- function preloadCritical(resources) {
6815
- if (typeof document === "undefined") return;
6816
- for (const resource of resources) {
6817
- const href = sanitizeUrl(resource.href);
6818
- if (!href) continue;
6819
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
6820
- const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
6821
- if (existing) continue;
6822
- const link2 = document.createElement("link");
6823
- link2.rel = "preload";
6824
- link2.href = href;
6825
- link2.setAttribute("as", resource.as);
6826
- if (resource.type) {
6827
- link2.type = resource.type;
6828
- }
6829
- if (resource.crossOrigin) {
6830
- link2.crossOrigin = resource.crossOrigin;
6831
- }
6832
- document.head.appendChild(link2);
6833
- }
6834
- }
6835
- function assertCacheSize(name, value) {
6836
- if (!Number.isInteger(value) || value < 0) {
6837
- throw new RangeError(`[Startup] ${name} must be a non-negative integer, got ${value}`);
6838
- }
6839
- }
6840
- function makeRoomFor(cache, key, maxSize, isValid) {
6841
- if (maxSize === 0) return false;
6842
- if (cache.has(key)) return true;
6843
- if (cache.size < maxSize) return true;
6844
- for (const [k, entry] of cache) {
6845
- if (!isValid(entry)) cache.delete(k);
6846
- }
6847
- while (cache.size >= maxSize) {
6848
- let oldestKey = null;
6849
- let oldestTime = Infinity;
6850
- for (const [k, entry] of cache) {
6851
- if (entry.timestamp < oldestTime) {
6852
- oldestTime = entry.timestamp;
6853
- oldestKey = k;
6854
- }
6855
- }
6856
- if (oldestKey === null) break;
6857
- cache.delete(oldestKey);
6858
- }
6859
- return true;
6860
- }
6861
- function prerenderRoutes(routes, options) {
6862
- const maxCacheSize = options?.maxCacheSize ?? 50;
6863
- const cacheTTL = options?.cacheTTL ?? 0;
6864
- assertCacheSize("maxCacheSize", maxCacheSize);
6865
- const cache = /* @__PURE__ */ new Map();
6866
- function isValid(entry) {
6867
- if (cacheTTL === 0) return true;
6868
- return Date.now() - entry.timestamp < cacheTTL;
6869
- }
6870
- for (const route2 of routes) {
6871
- if (!makeRoomFor(cache, route2.path, maxCacheSize, isValid)) break;
6872
- const html2 = renderToString(route2.component());
6873
- cache.set(route2.path, { html: html2, timestamp: Date.now() });
6874
- }
6875
- return {
6876
- /** Get cached HTML for a route, or undefined if not cached or expired */
6877
- get(path2) {
6878
- const entry = cache.get(path2);
6879
- if (!entry) return void 0;
6880
- if (!isValid(entry)) {
6881
- cache.delete(path2);
6882
- return void 0;
6883
- }
6884
- return entry.html;
6885
- },
6886
- /** Check if a route has been prerendered and is still valid */
6887
- has(path2) {
6888
- const entry = cache.get(path2);
6889
- if (!entry) return false;
6890
- if (!isValid(entry)) {
6891
- cache.delete(path2);
6892
- return false;
6893
- }
6894
- return true;
6895
- },
6896
- /** Invalidate a cached route */
6897
- invalidate(path2) {
6898
- cache.delete(path2);
6899
- },
6900
- /** Clear all cached routes */
6901
- clear() {
6902
- cache.clear();
6903
- },
6904
- /** Get cache statistics */
6905
- stats() {
6906
- if (cacheTTL > 0) {
6907
- for (const [key, entry] of cache) {
6908
- if (!isValid(entry)) {
6909
- cache.delete(key);
6910
- }
6911
- }
6912
- }
6913
- return {
6914
- size: cache.size,
6915
- routes: Array.from(cache.keys())
6916
- };
6917
- }
6918
- };
6919
- }
6920
- function createSSRCache(config) {
6921
- const maxSize = config?.maxSize ?? 100;
6922
- const defaultTTL = config?.defaultTTL ?? 6e4;
6923
- assertCacheSize("maxSize", maxSize);
6924
- const cache = /* @__PURE__ */ new Map();
6925
- let hits = 0;
6926
- let misses = 0;
6927
- function isValid(entry) {
6928
- if (entry.ttl === 0) return true;
6929
- return Date.now() - entry.timestamp < entry.ttl;
6930
- }
6931
- return {
6932
- /** Get cached HTML for a key, or undefined if not cached or expired */
6933
- get(key) {
6934
- const entry = cache.get(key);
6935
- if (!entry) {
6936
- misses++;
6937
- return void 0;
6938
- }
6939
- if (!isValid(entry)) {
6940
- cache.delete(key);
6941
- misses++;
6942
- return void 0;
6943
- }
6944
- hits++;
6945
- return entry.html;
6946
- },
6947
- /** Cache an HTML string with an optional TTL override */
6948
- set(key, html2, ttl) {
6949
- if (!makeRoomFor(cache, key, maxSize, isValid)) return;
6950
- cache.set(key, {
6951
- html: html2,
6952
- timestamp: Date.now(),
6953
- ttl: ttl ?? defaultTTL
6954
- });
6955
- },
6956
- /** Check if a key exists in the cache and is still valid */
6957
- has(key) {
6958
- const entry = cache.get(key);
6959
- if (!entry) return false;
6960
- if (!isValid(entry)) {
6961
- cache.delete(key);
6962
- return false;
6963
- }
6964
- return true;
6965
- },
6966
- /** Invalidate a cached entry */
6967
- invalidate(key) {
6968
- cache.delete(key);
6969
- },
6970
- /** Clear all cached entries and reset statistics */
6971
- clear() {
6972
- cache.clear();
6973
- hits = 0;
6974
- misses = 0;
6975
- },
6976
- /** Get cache statistics including hit/miss counts */
6977
- stats() {
6978
- return { size: cache.size, hits, misses };
6979
- }
6980
- };
6981
- }
6982
- var DEFER_IDLE_TIMEOUT_MS = 2e3;
6983
- function deferNonCritical(tasks) {
6984
- if (tasks.length === 0) return;
6985
- const schedule = typeof requestIdleCallback !== "undefined" ? (cb) => {
6986
- requestIdleCallback(cb, { timeout: DEFER_IDLE_TIMEOUT_MS });
6987
- } : (cb) => {
6988
- setTimeout(cb, 1);
6989
- };
6990
- const queue = [...tasks];
6991
- let index = 0;
6992
- function processNext(deadline) {
6993
- let ranOne = false;
6994
- while (index < queue.length) {
6995
- if (ranOne && deadline && deadline.timeRemaining() < 1) {
6996
- schedule(processNext);
6997
- return;
6998
- }
6999
- try {
7000
- queue[index]();
7001
- } catch (e) {
7002
- reportError(e, { phase: "scheduler", name: "deferNonCritical" });
7003
- }
7004
- index++;
7005
- ranOne = true;
7006
- }
7007
- }
7008
- schedule(processNext);
7009
- }
7010
- function createBootSequence() {
7011
- const criticalTasks = [];
7012
- const deferredTasks = [];
7013
- return {
7014
- /** Add a critical task that runs immediately during boot */
7015
- critical(name, task) {
7016
- criticalTasks.push({ name, task });
7017
- },
7018
- /** Add a deferred task that runs after all critical tasks complete */
7019
- defer(name, task) {
7020
- deferredTasks.push({ name, task });
7021
- },
7022
- /** Execute the boot sequence: critical tasks first, then deferred */
7023
- async boot() {
7024
- const timing = {};
7025
- const errors = [];
7026
- for (const entry of criticalTasks) {
7027
- const start = Date.now();
7028
- try {
7029
- await entry.task();
7030
- } catch (e) {
7031
- errors.push({
7032
- name: entry.name,
7033
- error: e instanceof Error ? e : new Error(String(e))
7034
- });
7035
- }
7036
- timing[entry.name] = Date.now() - start;
7037
- }
7038
- for (const entry of deferredTasks) {
7039
- const start = Date.now();
7040
- try {
7041
- await entry.task();
7042
- } catch (e) {
7043
- errors.push({
7044
- name: entry.name,
7045
- error: e instanceof Error ? e : new Error(String(e))
7046
- });
7047
- }
7048
- timing[entry.name] = Date.now() - start;
7049
- }
7050
- return { timing, errors };
7051
- }
7052
- };
7053
- }
7054
7476
  // Annotate the CommonJS export names for ESM import in node:
7055
7477
  0 && (module.exports = {
7056
7478
  KeepAliveRoute,
@@ -7117,10 +7539,13 @@ function createBootSequence() {
7117
7539
  renderRouteToString,
7118
7540
  replace,
7119
7541
  resetPlugins,
7542
+ resolveRouteHead,
7120
7543
  resolveServerRoute,
7121
7544
  route,
7545
+ routeHeadToHTML,
7122
7546
  router,
7123
7547
  routerPlugin,
7548
+ routerReady,
7124
7549
  routerState,
7125
7550
  satisfies,
7126
7551
  serializeRouteState,