sibujs 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
@@ -1,59 +1,87 @@
1
+ import {
2
+ acquireBase,
3
+ acquireScrollRestorationMode,
4
+ acquireTitle
5
+ } from "./chunk-52XFPGSN.js";
1
6
  import {
2
7
  div,
3
8
  span
4
- } from "./chunk-3Z2NGWQI.js";
9
+ } from "./chunk-ES2GERY2.js";
10
+ import {
11
+ STATIC_VALUE_RESOLVER,
12
+ planHeadElementEntry,
13
+ planMetaEntry
14
+ } from "./chunk-HYCCIYNS.js";
15
+ import {
16
+ sanitizeUrl
17
+ } from "./chunk-5INI7D2L.js";
5
18
  import {
6
19
  globalSingleton
7
20
  } from "./chunk-7ZHH77QA.js";
8
- import {
9
- isEventHandlerAttr,
10
- sanitizeUrl,
11
- stripControlChars
12
- } from "./chunk-L3GAGWCC.js";
13
21
  import {
14
22
  effect
15
- } from "./chunk-BUKYCWHX.js";
23
+ } from "./chunk-PBQEW5VW.js";
16
24
  import {
17
- registerDisposer
18
- } from "./chunk-5VGSK6D2.js";
25
+ registerDisposer,
26
+ replaceChildrenSafely
27
+ } from "./chunk-DHDZ7IVN.js";
19
28
  import {
20
29
  batch,
21
30
  signal
22
- } from "./chunk-KZBEVFXZ.js";
31
+ } from "./chunk-W55YHPEP.js";
23
32
 
24
33
  // src/platform/head.ts
25
- var HEAD_URL_ATTRS = /* @__PURE__ */ new Set(["href", "src"]);
26
- function sanitizeHeadAttr(key, value) {
27
- if (HEAD_URL_ATTRS.has(key)) return sanitizeUrl(value);
28
- return value;
29
- }
30
- function isDangerousRefreshContent(content) {
31
- const normalized = stripControlChars(content).toLowerCase();
32
- return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
33
- }
34
- function getMetaAttr(metaProps, name) {
35
- for (const k in metaProps) {
36
- if (k.toLowerCase() === name) return metaProps[k];
37
- }
38
- return void 0;
39
- }
40
- function isDangerousMetaRefresh(metaProps) {
41
- const httpEquiv = getMetaAttr(metaProps, "http-equiv");
42
- if (typeof httpEquiv !== "string") return false;
43
- if (httpEquiv.toLowerCase() !== "refresh") return false;
44
- const content = getMetaAttr(metaProps, "content");
45
- if (typeof content !== "string") return false;
46
- return isDangerousRefreshContent(content);
47
- }
48
- var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
49
- function isSafeHeadAttr(name) {
50
- if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
51
- if (isEventHandlerAttr(name)) return false;
52
- return true;
53
- }
54
34
  function escapeScriptJsonLocal(json) {
55
35
  return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
56
36
  }
37
+ var CLIENT_RESOLVER = {
38
+ resolveValue: (_name, value) => typeof value === "function" ? String(value()) : String(value),
39
+ isReactiveValue: (value) => typeof value === "function"
40
+ };
41
+ function applyMetaEntry(metaProps, managedElements) {
42
+ let current = null;
43
+ const retract = () => {
44
+ if (!current) return;
45
+ const index = managedElements.indexOf(current);
46
+ if (index !== -1) managedElements.splice(index, 1);
47
+ current.remove();
48
+ current = null;
49
+ };
50
+ const alreadyPublished = (attributes) => {
51
+ if (!current || current.attributes.length !== attributes.size) return false;
52
+ for (const [name, value] of attributes) {
53
+ if (current.getAttribute(name) !== value) return false;
54
+ }
55
+ return true;
56
+ };
57
+ const publish = (attributes) => {
58
+ if (alreadyPublished(attributes)) return;
59
+ const next = document.createElement("meta");
60
+ for (const [name, value] of attributes) next.setAttribute(name, value);
61
+ const index = current ? managedElements.indexOf(current) : -1;
62
+ if (current?.parentNode) {
63
+ current.replaceWith(next);
64
+ } else {
65
+ current?.remove();
66
+ document.head.appendChild(next);
67
+ }
68
+ if (index !== -1) managedElements[index] = next;
69
+ else managedElements.push(next);
70
+ current = next;
71
+ };
72
+ const stopEffect = effect(() => {
73
+ const plan = planMetaEntry(metaProps, CLIENT_RESOLVER);
74
+ if (plan.kind === "drop") {
75
+ retract();
76
+ return;
77
+ }
78
+ publish(plan.attributes);
79
+ });
80
+ return () => {
81
+ stopEffect();
82
+ retract();
83
+ };
84
+ }
57
85
  function Head(props) {
58
86
  const anchor = document.createComment("sibu-head");
59
87
  const managedElements = [];
@@ -70,78 +98,52 @@ function Head(props) {
70
98
  cleanup();
71
99
  if (props.title) {
72
100
  if (typeof props.title === "function") {
73
- const cleanupFn = effect(() => {
74
- document.title = props.title();
101
+ const getter = props.title;
102
+ let lease = null;
103
+ const stopEffect = effect(() => {
104
+ const next = getter();
105
+ if (lease) lease.set(next);
106
+ else lease = acquireTitle(next);
107
+ });
108
+ effectCleanups.push(() => {
109
+ stopEffect();
110
+ lease?.release();
111
+ lease = null;
75
112
  });
76
- effectCleanups.push(cleanupFn);
77
113
  } else {
78
- document.title = props.title;
114
+ const lease = acquireTitle(props.title);
115
+ effectCleanups.push(() => lease.release());
79
116
  }
80
117
  }
81
118
  if (props.meta) {
82
119
  for (const metaProps of props.meta) {
83
- if (isDangerousMetaRefresh(metaProps)) continue;
84
- const httpEquiv = getMetaAttr(metaProps, "http-equiv");
85
- const isRefreshNow = () => {
86
- const eq = typeof httpEquiv === "function" ? httpEquiv() : httpEquiv;
87
- return typeof eq === "string" && eq.toLowerCase() === "refresh";
88
- };
89
- const el = document.createElement("meta");
90
- for (const [key, value] of Object.entries(metaProps)) {
91
- if (!isSafeHeadAttr(key)) continue;
92
- const isContent = key.toLowerCase() === "content";
93
- if (typeof value === "function") {
94
- const cleanupFn = effect(() => {
95
- const resolved = value();
96
- if (isContent && isRefreshNow() && isDangerousRefreshContent(resolved)) {
97
- el.removeAttribute(key);
98
- return;
99
- }
100
- el.setAttribute(key, sanitizeHeadAttr(key, resolved));
101
- });
102
- effectCleanups.push(cleanupFn);
103
- } else {
104
- if (isContent && isRefreshNow() && isDangerousRefreshContent(value)) continue;
105
- el.setAttribute(key, sanitizeHeadAttr(key, value));
106
- }
107
- }
108
- document.head.appendChild(el);
109
- managedElements.push(el);
120
+ const stop = applyMetaEntry(metaProps, managedElements);
121
+ effectCleanups.push(stop);
110
122
  }
111
123
  }
112
- if (props.link) {
113
- for (const linkProps of props.link) {
114
- const el = document.createElement("link");
115
- for (const [key, value] of Object.entries(linkProps)) {
116
- if (!isSafeHeadAttr(key)) continue;
117
- el.setAttribute(key, sanitizeHeadAttr(key, value));
118
- }
119
- document.head.appendChild(el);
120
- managedElements.push(el);
121
- }
122
- }
123
- if (props.script) {
124
- for (const scriptProps of props.script) {
125
- const el = document.createElement("script");
126
- for (const [key, value] of Object.entries(scriptProps)) {
127
- if (!isSafeHeadAttr(key)) continue;
128
- el.setAttribute(key, sanitizeHeadAttr(key, value));
129
- }
124
+ for (const [tag, entries] of [
125
+ ["link", props.link],
126
+ ["script", props.script]
127
+ ]) {
128
+ if (!entries) continue;
129
+ for (const entryProps of entries) {
130
+ const plan = planHeadElementEntry(entryProps, STATIC_VALUE_RESOLVER);
131
+ if (plan.kind === "drop") continue;
132
+ const el = document.createElement(tag);
133
+ for (const [name, value] of plan.attributes) el.setAttribute(name, value);
130
134
  document.head.appendChild(el);
131
135
  managedElements.push(el);
132
136
  }
133
137
  }
134
138
  if (props.base) {
135
- const existing = document.head.querySelector("base");
136
- if (existing) existing.remove();
137
- const el = document.createElement("base");
139
+ const spec = {};
138
140
  if (props.base.href) {
139
141
  const safeHref = sanitizeUrl(props.base.href);
140
- if (safeHref) el.href = safeHref;
142
+ if (safeHref) spec.href = safeHref;
141
143
  }
142
- if (props.base.target) el.target = props.base.target;
143
- document.head.appendChild(el);
144
- managedElements.push(el);
144
+ if (props.base.target) spec.target = props.base.target;
145
+ const lease = acquireBase(spec);
146
+ effectCleanups.push(() => lease.release());
145
147
  }
146
148
  };
147
149
  apply();
@@ -164,7 +166,9 @@ function setCanonical(url) {
164
166
  link.rel = "canonical";
165
167
  document.head.appendChild(link);
166
168
  }
167
- link.href = sanitizeUrl(url);
169
+ const safe = sanitizeUrl(url);
170
+ if (safe) link.setAttribute("href", safe);
171
+ else link.removeAttribute("href");
168
172
  }
169
173
 
170
174
  // src/platform/worker.ts
@@ -390,22 +394,34 @@ var instanceCache = globalSingleton(
390
394
  /* @__PURE__ */ Symbol.for("sibujs.wasm.instanceCache.v1"),
391
395
  () => /* @__PURE__ */ new Map()
392
396
  );
397
+ var inFlightCache = globalSingleton(
398
+ /* @__PURE__ */ Symbol.for("sibujs.wasm.inFlightCache.v1"),
399
+ () => /* @__PURE__ */ new Map()
400
+ );
401
+ var cacheState = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.wasm.cacheState.v1"), () => ({ generation: 0 }));
402
+ function isCurrentGeneration(generation) {
403
+ return generation === cacheState.generation;
404
+ }
393
405
  function wasm(source, config = {}) {
394
406
  const [instance, setInstance] = signal(null);
395
407
  const [loading, setLoading] = signal(true);
396
408
  const [error, setError] = signal(null);
397
409
  const cacheKey = config.cacheKey || (typeof source === "string" ? source : void 0);
410
+ let runId = 0;
398
411
  async function load() {
412
+ const myRun = ++runId;
399
413
  setLoading(true);
400
414
  setError(null);
401
415
  setInstance(null);
402
416
  try {
403
- const wasmInstance = await loadWasmModule(source, config.imports, cacheKey);
417
+ const wasmInstance = await loadWasmModuleWithOptions(source, { ...config, cacheKey });
418
+ if (myRun !== runId) return;
404
419
  setInstance(wasmInstance.exports);
405
420
  } catch (err) {
421
+ if (myRun !== runId) return;
406
422
  setError(err instanceof Error ? err : new Error(String(err)));
407
423
  } finally {
408
- setLoading(false);
424
+ if (myRun === runId) setLoading(false);
409
425
  }
410
426
  }
411
427
  load();
@@ -418,9 +434,9 @@ function wasm(source, config = {}) {
418
434
  };
419
435
  }
420
436
  async function loadWasmModule(source, imports, cacheKey) {
421
- const isOptionsBag = !!(imports && ("allowedOrigins" in imports || "unsafelyAllowAnyOrigin" in imports));
422
- const opts = isOptionsBag ? imports : { imports, cacheKey };
423
- const wasmImports = opts.imports;
437
+ return loadWasmModuleWithOptions(source, { imports, cacheKey });
438
+ }
439
+ async function loadWasmModuleWithOptions(source, opts = {}) {
424
440
  const key = opts.cacheKey || (typeof source === "string" ? source : void 0);
425
441
  if (typeof source === "string") {
426
442
  const allowed = opts.allowedOrigins ?? [];
@@ -440,44 +456,50 @@ async function loadWasmModule(source, imports, cacheKey) {
440
456
  );
441
457
  }
442
458
  }
443
- if (key) {
444
- const cachedInstance = instanceCache.get(key);
445
- if (cachedInstance) {
446
- return cachedInstance;
447
- }
448
- }
449
- let module;
459
+ const generation = cacheState.generation;
460
+ if (!key) return instantiateWasm(source, opts.imports, void 0, generation);
461
+ const cachedInstance = instanceCache.get(key);
462
+ if (cachedInstance) return cachedInstance;
463
+ const inFlight = inFlightCache.get(key);
464
+ if (inFlight) return inFlight;
465
+ const pending = instantiateWasm(source, opts.imports, key, generation).then((instance) => {
466
+ if (isCurrentGeneration(generation)) instanceCache.set(key, instance);
467
+ return instance;
468
+ });
469
+ inFlightCache.set(key, pending);
470
+ void pending.catch(() => {
471
+ }).then(() => {
472
+ if (inFlightCache.get(key) === pending) inFlightCache.delete(key);
473
+ });
474
+ return pending;
475
+ }
476
+ async function instantiateWasm(source, wasmImports, key, generation) {
450
477
  const cachedModule = key ? moduleCache.get(key) : void 0;
451
478
  if (cachedModule) {
452
- module = cachedModule;
453
- } else {
454
- let bytes;
455
- if (typeof source === "string") {
456
- if (typeof WebAssembly.instantiateStreaming === "function") {
457
- const response2 = fetch(source);
458
- const result = await WebAssembly.instantiateStreaming(response2, wasmImports || {});
459
- if (key) {
460
- moduleCache.set(key, result.module);
461
- instanceCache.set(key, result.instance);
462
- }
463
- return result.instance;
464
- }
465
- const response = await fetch(source);
466
- bytes = await response.arrayBuffer();
467
- } else if (source instanceof Uint8Array) {
468
- bytes = source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
469
- } else {
470
- bytes = source;
479
+ return WebAssembly.instantiate(cachedModule, wasmImports || {});
480
+ }
481
+ let bytes;
482
+ if (typeof source === "string") {
483
+ if (typeof WebAssembly.instantiateStreaming === "function") {
484
+ const response2 = fetch(source);
485
+ const result = await WebAssembly.instantiateStreaming(response2, wasmImports || {});
486
+ if (key && isCurrentGeneration(generation)) moduleCache.set(key, result.module);
487
+ return result.instance;
471
488
  }
472
- module = await WebAssembly.compile(bytes);
473
- if (key) moduleCache.set(key, module);
489
+ const response = await fetch(source);
490
+ bytes = await response.arrayBuffer();
491
+ } else if (source instanceof Uint8Array) {
492
+ bytes = source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
493
+ } else {
494
+ bytes = source;
474
495
  }
475
- const instance = await WebAssembly.instantiate(module, wasmImports || {});
476
- if (key) instanceCache.set(key, instance);
477
- return instance;
496
+ const module = await WebAssembly.compile(bytes);
497
+ if (key && isCurrentGeneration(generation)) moduleCache.set(key, module);
498
+ return WebAssembly.instantiate(module, wasmImports || {});
478
499
  }
479
500
  async function preloadWasm(url, options = {}) {
480
501
  if (moduleCache.has(url)) return;
502
+ const generation = cacheState.generation;
481
503
  const allowed = options.allowedOrigins ?? [];
482
504
  if (allowed.length > 0) {
483
505
  let parsed;
@@ -502,7 +524,7 @@ async function preloadWasm(url, options = {}) {
502
524
  const bytes = await response.arrayBuffer();
503
525
  module = await WebAssembly.compile(bytes);
504
526
  }
505
- moduleCache.set(url, module);
527
+ if (isCurrentGeneration(generation)) moduleCache.set(url, module);
506
528
  }
507
529
  function createWasmBridge(instance) {
508
530
  const exports = instance.exports;
@@ -542,8 +564,10 @@ function createWasmBridge(instance) {
542
564
  };
543
565
  }
544
566
  function clearWasmCache() {
567
+ cacheState.generation++;
545
568
  moduleCache.clear();
546
569
  instanceCache.clear();
570
+ inFlightCache.clear();
547
571
  }
548
572
  function isWasmCached(key) {
549
573
  return moduleCache.has(key);
@@ -565,14 +589,13 @@ function createMicroApp(config) {
565
589
  }
566
590
  let mounted = false;
567
591
  function mount(component) {
568
- root.replaceChildren();
569
592
  const el = component();
570
- root.appendChild(el);
593
+ replaceChildrenSafely(root, el);
571
594
  mounted = true;
572
595
  }
573
596
  function unmount() {
574
597
  if (!mounted) return;
575
- root.replaceChildren();
598
+ replaceChildrenSafely(root);
576
599
  mounted = false;
577
600
  }
578
601
  return { mount, unmount, element: host };
@@ -619,18 +642,21 @@ function defineRemoteComponent(name, loader) {
619
642
  class: "sibu-remote",
620
643
  "data-remote-component": name
621
644
  });
622
- container.appendChild(span({ class: "sibu-remote-loading", nodes: "Loading..." }));
645
+ container.appendChild(span({ class: "sibu-remote-loading" }, "Loading..."));
646
+ let disposed = false;
647
+ registerDisposer(container, () => {
648
+ disposed = true;
649
+ });
623
650
  loader().then((mod) => {
624
651
  cached = mod.default;
625
- const rendered = cached();
626
- container.replaceChildren(rendered);
652
+ if (disposed) return;
653
+ replaceChildrenSafely(container, cached());
627
654
  }).catch((err) => {
655
+ if (disposed) return;
628
656
  const message = err instanceof Error ? err.message : String(err);
629
- container.replaceChildren(
630
- div({
631
- class: "sibu-remote-error",
632
- nodes: `Failed to load remote component "${name}": ${message}`
633
- })
657
+ replaceChildrenSafely(
658
+ container,
659
+ div({ class: "sibu-remote-error" }, `Failed to load remote component "${name}": ${message}`)
634
660
  );
635
661
  });
636
662
  return container;
@@ -686,11 +712,16 @@ function serviceWorker(scriptUrl, options) {
686
712
  const [isReady, setIsReady] = signal(false);
687
713
  const [isUpdateAvailable, setIsUpdateAvailable] = signal(false);
688
714
  const [error, setError] = signal(null);
689
- let disposed = false;
715
+ let unregistered = false;
716
+ let unregisterRequested = false;
717
+ let registrationPromise = null;
718
+ let arrivedRegistration = null;
719
+ let pendingUnregister = null;
690
720
  let updateFoundHandler = null;
691
721
  let stateChangeHandler = null;
692
722
  let trackedWorker = null;
693
723
  let trackedReg = null;
724
+ const supported = typeof navigator !== "undefined" && !!navigator.serviceWorker;
694
725
  function detachListeners() {
695
726
  if (trackedReg && updateFoundHandler) {
696
727
  trackedReg.removeEventListener("updatefound", updateFoundHandler);
@@ -703,33 +734,53 @@ function serviceWorker(scriptUrl, options) {
703
734
  trackedWorker = null;
704
735
  trackedReg = null;
705
736
  }
706
- if ("serviceWorker" in navigator) {
707
- navigator.serviceWorker.register(scriptUrl, options).then((reg) => {
708
- if (disposed) return;
709
- setRegistration(reg);
710
- setIsReady(true);
711
- trackedReg = reg;
712
- updateFoundHandler = () => {
713
- if (disposed) return;
714
- const newWorker = reg.installing;
715
- if (newWorker) {
716
- if (trackedWorker && stateChangeHandler) {
717
- trackedWorker.removeEventListener("statechange", stateChangeHandler);
718
- }
719
- trackedWorker = newWorker;
720
- stateChangeHandler = () => {
721
- if (disposed) return;
722
- if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
723
- setIsUpdateAvailable(true);
724
- }
725
- };
726
- newWorker.addEventListener("statechange", stateChangeHandler);
737
+ function attachListeners(reg) {
738
+ trackedReg = reg;
739
+ updateFoundHandler = () => {
740
+ if (unregistered) return;
741
+ const newWorker = reg.installing;
742
+ if (newWorker) {
743
+ if (trackedWorker && stateChangeHandler) {
744
+ trackedWorker.removeEventListener("statechange", stateChangeHandler);
727
745
  }
728
- };
729
- reg.addEventListener("updatefound", updateFoundHandler);
730
- }).catch((err) => {
731
- if (disposed) return;
732
- setError(err instanceof Error ? err : new Error(String(err)));
746
+ trackedWorker = newWorker;
747
+ stateChangeHandler = () => {
748
+ if (unregistered) return;
749
+ if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
750
+ setIsUpdateAvailable(true);
751
+ }
752
+ };
753
+ newWorker.addEventListener("statechange", stateChangeHandler);
754
+ }
755
+ };
756
+ reg.addEventListener("updatefound", updateFoundHandler);
757
+ }
758
+ function activate(reg) {
759
+ setRegistration(reg);
760
+ setIsReady(true);
761
+ attachListeners(reg);
762
+ }
763
+ function recoverFailedUnregister() {
764
+ unregisterRequested = false;
765
+ if (!registration() && arrivedRegistration) {
766
+ activate(arrivedRegistration);
767
+ arrivedRegistration = null;
768
+ }
769
+ }
770
+ function adopt(reg) {
771
+ if (unregisterRequested) {
772
+ arrivedRegistration = reg;
773
+ return null;
774
+ }
775
+ activate(reg);
776
+ return reg;
777
+ }
778
+ if (supported) {
779
+ registrationPromise = navigator.serviceWorker.register(scriptUrl, options).then(adopt).catch((err) => {
780
+ if (!unregistered) {
781
+ setError(err instanceof Error ? err : new Error(String(err)));
782
+ }
783
+ return null;
733
784
  });
734
785
  }
735
786
  async function update() {
@@ -738,19 +789,43 @@ function serviceWorker(scriptUrl, options) {
738
789
  await reg.update();
739
790
  }
740
791
  }
741
- async function unregister() {
742
- disposed = true;
743
- detachListeners();
744
- const reg = registration();
745
- if (reg) {
746
- const result = await reg.unregister();
747
- if (result) {
748
- setRegistration(null);
749
- setIsReady(false);
750
- }
751
- return result;
792
+ async function performUnregister() {
793
+ unregisterRequested = true;
794
+ if (registrationPromise) await registrationPromise.catch(() => null);
795
+ const reg = registration() ?? arrivedRegistration;
796
+ if (!reg) {
797
+ unregisterRequested = false;
798
+ return false;
799
+ }
800
+ let result;
801
+ try {
802
+ result = await reg.unregister();
803
+ } catch (err) {
804
+ recoverFailedUnregister();
805
+ throw err;
806
+ }
807
+ if (result) {
808
+ detachListeners();
809
+ setRegistration(null);
810
+ setIsReady(false);
811
+ setIsUpdateAvailable(false);
812
+ arrivedRegistration = null;
813
+ unregistered = true;
814
+ } else {
815
+ recoverFailedUnregister();
752
816
  }
753
- return false;
817
+ return result;
818
+ }
819
+ async function unregister() {
820
+ if (!supported || unregistered) return false;
821
+ if (pendingUnregister) return pendingUnregister;
822
+ const request = performUnregister();
823
+ pendingUnregister = request;
824
+ void request.catch(() => {
825
+ }).then(() => {
826
+ if (pendingUnregister === request) pendingUnregister = null;
827
+ });
828
+ return request;
754
829
  }
755
830
  return { registration, isReady, isUpdateAvailable, error, update, unregister };
756
831
  }
@@ -855,17 +930,39 @@ function createAction(actionFn) {
855
930
  }
856
931
 
857
932
  // src/platform/scrollRestoration.ts
933
+ var DEFAULT_STATE_KEY = "__sibuScrollKey";
858
934
  function scrollRestoration(options) {
859
935
  const mode = options?.mode ?? "auto";
936
+ const stateKey = options?.stateKey ?? DEFAULT_STATE_KEY;
860
937
  const positions = /* @__PURE__ */ new Map();
861
938
  let popstateHandler = null;
862
939
  let currentKey = null;
863
- const save = (key) => {
940
+ if (options?.getKey) {
941
+ try {
942
+ currentKey = options.getKey();
943
+ } catch {
944
+ currentKey = null;
945
+ }
946
+ }
947
+ const record = (key) => {
864
948
  if (typeof window !== "undefined") {
865
949
  positions.set(key, { x: window.scrollX, y: window.scrollY });
866
950
  }
867
951
  currentKey = key;
868
952
  };
953
+ const tagCurrentEntry = (key) => {
954
+ if (typeof window === "undefined" || typeof history === "undefined") return;
955
+ try {
956
+ const existing = history.state ?? {};
957
+ if (existing[stateKey] === key) return;
958
+ history.replaceState({ ...existing, [stateKey]: key }, "");
959
+ } catch {
960
+ }
961
+ };
962
+ const save = (key) => {
963
+ record(key);
964
+ if (mode === "auto") tagCurrentEntry(key);
965
+ };
869
966
  const restore = (key) => {
870
967
  const pos = positions.get(key);
871
968
  if (pos && typeof window !== "undefined") {
@@ -876,11 +973,32 @@ function scrollRestoration(options) {
876
973
  const getPosition = (key) => {
877
974
  return positions.get(key);
878
975
  };
976
+ const onNavigation = (key) => {
977
+ if (mode !== "auto") return;
978
+ if (currentKey !== null && currentKey !== key) record(currentKey);
979
+ currentKey = key;
980
+ tagCurrentEntry(key);
981
+ };
982
+ let nativeModeLease = null;
879
983
  if (mode === "auto" && typeof window !== "undefined") {
880
- popstateHandler = () => {
881
- if (currentKey) {
882
- save(currentKey);
984
+ nativeModeLease = acquireScrollRestorationMode("manual");
985
+ if (currentKey !== null) tagCurrentEntry(currentKey);
986
+ popstateHandler = (event) => {
987
+ const state = event.state ?? null;
988
+ const rawDestination = state ? state[stateKey] : void 0;
989
+ const destination = typeof rawDestination === "string" ? rawDestination : null;
990
+ let outgoing = currentKey;
991
+ if (outgoing === null && options?.getKey) {
992
+ try {
993
+ outgoing = options.getKey();
994
+ } catch {
995
+ outgoing = null;
996
+ }
997
+ }
998
+ if (outgoing !== null && outgoing !== destination) {
999
+ record(outgoing);
883
1000
  }
1001
+ if (destination !== null) restore(destination);
884
1002
  };
885
1003
  window.addEventListener("popstate", popstateHandler);
886
1004
  }
@@ -889,9 +1007,11 @@ function scrollRestoration(options) {
889
1007
  window.removeEventListener("popstate", popstateHandler);
890
1008
  popstateHandler = null;
891
1009
  }
1010
+ nativeModeLease?.release();
1011
+ nativeModeLease = null;
892
1012
  positions.clear();
893
1013
  };
894
- return { save, restore, getPosition, dispose };
1014
+ return { save, restore, onNavigation, getPosition, dispose };
895
1015
  }
896
1016
 
897
1017
  // src/platform/routeMiddleware.ts
@@ -934,6 +1054,7 @@ export {
934
1054
  createWorkerPool,
935
1055
  wasm,
936
1056
  loadWasmModule,
1057
+ loadWasmModuleWithOptions,
937
1058
  preloadWasm,
938
1059
  createWasmBridge,
939
1060
  clearWasmCache,