sibujs 3.2.2 → 3.3.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 (60) hide show
  1. package/dist/browser.cjs +217 -27
  2. package/dist/browser.js +5 -5
  3. package/dist/build.cjs +236 -105
  4. package/dist/build.js +14 -14
  5. package/dist/cdn.global.js +7 -7
  6. package/dist/{chunk-5N74TKLD.js → chunk-37BUKSLH.js} +1 -1
  7. package/dist/{chunk-BGNLPNGV.js → chunk-4UUMSLSL.js} +4 -4
  8. package/dist/{chunk-2UPRY23K.js → chunk-5VGSK6D2.js} +1 -1
  9. package/dist/{chunk-OYLPZO4N.js → chunk-6LTFHJQG.js} +4 -4
  10. package/dist/{chunk-VOCE4NNK.js → chunk-AMIKDMLP.js} +5 -5
  11. package/dist/{chunk-LMLD24FC.js → chunk-COY6PUD2.js} +7 -1
  12. package/dist/{chunk-YFDGQWDA.js → chunk-F7FXQ3QS.js} +1 -1
  13. package/dist/{chunk-V2MTG5FT.js → chunk-IKLYI3RF.js} +33 -16
  14. package/dist/{chunk-HMJFCBRR.js → chunk-L3GAGWCC.js} +10 -1
  15. package/dist/{chunk-X67UYC74.js → chunk-LU2MQXQQ.js} +14 -24
  16. package/dist/{chunk-4JCAUOLN.js → chunk-M5KBNOSJ.js} +22 -12
  17. package/dist/{chunk-FDY42FIU.js → chunk-MHBCEJQO.js} +2 -2
  18. package/dist/{chunk-RLUJL2MV.js → chunk-NHKQKKZU.js} +6 -7
  19. package/dist/{chunk-HXMS4SNP.js → chunk-R3QEDXFS.js} +2 -2
  20. package/dist/{chunk-NPIEEKPT.js → chunk-RYMOSG5B.js} +6 -6
  21. package/dist/{chunk-H6PCHJZQ.js → chunk-SLMFA3ZZ.js} +11 -3
  22. package/dist/{chunk-Z2FWAE4B.js → chunk-TEFZT5PJ.js} +84 -16
  23. package/dist/{chunk-7XDYVJLE.js → chunk-ULLTNDRA.js} +6 -6
  24. package/dist/{chunk-GOUM4JCT.js → chunk-VOVVTOEA.js} +2 -2
  25. package/dist/{chunk-FOI23UJL.js → chunk-WL7BIR6O.js} +1 -1
  26. package/dist/{chunk-JYXOEYI4.js → chunk-WW6DAGGR.js} +5 -5
  27. package/dist/{chunk-C427DVQF.js → chunk-WZG2SZOT.js} +22 -5
  28. package/dist/{chunk-NFYWLRUO.js → chunk-XH2RTYEQ.js} +3 -3
  29. package/dist/{chunk-2C4E3HBM.js → chunk-Z37APKBV.js} +5 -5
  30. package/dist/{chunk-RDRSWYNP.js → chunk-ZUVLC7TM.js} +1 -1
  31. package/dist/data.cjs +168 -24
  32. package/dist/data.js +8 -8
  33. package/dist/devtools.cjs +252 -32
  34. package/dist/devtools.js +7 -7
  35. package/dist/ecosystem.cjs +205 -64
  36. package/dist/ecosystem.js +10 -10
  37. package/dist/extras.cjs +245 -112
  38. package/dist/extras.js +24 -24
  39. package/dist/index.cjs +236 -105
  40. package/dist/index.d.cts +72 -8
  41. package/dist/index.d.ts +72 -8
  42. package/dist/index.js +14 -14
  43. package/dist/motion.cjs +336 -14
  44. package/dist/motion.js +4 -4
  45. package/dist/patterns.cjs +168 -24
  46. package/dist/patterns.js +6 -6
  47. package/dist/performance.cjs +305 -32
  48. package/dist/performance.js +8 -8
  49. package/dist/plugins.cjs +268 -69
  50. package/dist/plugins.js +12 -12
  51. package/dist/{ssr-2QDQ27EV.js → ssr-6D67RAVB.js} +3 -3
  52. package/dist/ssr.cjs +205 -63
  53. package/dist/ssr.js +11 -11
  54. package/dist/testing.cjs +415 -12
  55. package/dist/testing.js +16 -10
  56. package/dist/ui.cjs +248 -36
  57. package/dist/ui.js +9 -9
  58. package/dist/widgets.cjs +176 -26
  59. package/dist/widgets.js +8 -8
  60. package/package.json +1 -1
@@ -218,7 +218,13 @@ async function processInChunks(items, processor, chunkSize = 50) {
218
218
 
219
219
  // src/core/dev.ts
220
220
  function isDev() {
221
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
221
+ return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
222
+ // The bare `__SIBU_DEV__` is a bundler define that only exists in
223
+ // production builds; under the test runner it is always undefined, so
224
+ // this branch is unreachable here.
225
+ /* v8 ignore next 2 */
226
+ typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
227
+ );
222
228
  }
223
229
  var _isDev = isDev();
224
230
  function devAssert(condition, message) {
@@ -232,7 +238,7 @@ function devWarn(message) {
232
238
  }
233
239
  }
234
240
 
235
- // src/reactivity/track.ts
241
+ // src/reactivity/track-core.ts
236
242
  var _isDev2 = isDev();
237
243
  var POOL_MAX = 4096;
238
244
  var nodePool = [];
@@ -316,6 +322,7 @@ function unlinkSub(node) {
316
322
  else sub.depsTail = prev;
317
323
  }
318
324
  var currentSubscriber = null;
325
+ var suspendSavedSub = null;
319
326
  var notifyDepth = 0;
320
327
  var pendingQueue = [];
321
328
  var pendingSet = /* @__PURE__ */ new Set();
@@ -327,6 +334,35 @@ function safeInvoke(sub) {
327
334
  if (_isDev2) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
328
335
  }
329
336
  }
337
+ var suspendDepth = 0;
338
+ var trackingSuspended = false;
339
+ function suspendTracking() {
340
+ if (suspendDepth === 0) {
341
+ suspendSavedSub = currentSubscriber;
342
+ currentSubscriber = null;
343
+ trackingSuspended = true;
344
+ }
345
+ suspendDepth++;
346
+ }
347
+ function resumeTracking() {
348
+ suspendDepth--;
349
+ if (suspendDepth === 0) {
350
+ currentSubscriber = suspendSavedSub;
351
+ suspendSavedSub = null;
352
+ trackingSuspended = false;
353
+ }
354
+ }
355
+ function isTrackingSuspended() {
356
+ return trackingSuspended;
357
+ }
358
+ function untracked(fn) {
359
+ suspendTracking();
360
+ try {
361
+ return fn();
362
+ } finally {
363
+ resumeTracking();
364
+ }
365
+ }
330
366
  var subscriberEpochCounter = 0;
331
367
  function retrack(effectFn, subscriber) {
332
368
  const prev = currentSubscriber;
@@ -359,6 +395,51 @@ function retrack(effectFn, subscriber) {
359
395
  }
360
396
  }
361
397
  }
398
+ function track(effectFn, subscriber) {
399
+ if (!subscriber) return reactiveBinding(effectFn);
400
+ cleanup(subscriber);
401
+ const prev = currentSubscriber;
402
+ currentSubscriber = subscriber;
403
+ try {
404
+ effectFn();
405
+ } finally {
406
+ currentSubscriber = prev;
407
+ const sub2 = subscriber;
408
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
409
+ const sig = n.sig;
410
+ sig.__activeNode = n.prevActive;
411
+ n.prevActive = null;
412
+ }
413
+ }
414
+ const sub = subscriber;
415
+ return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
416
+ }
417
+ function reactiveBinding(commit) {
418
+ const run = () => {
419
+ const s = subscriber;
420
+ if (s._disposed || s._reentrant) return;
421
+ s._reentrant = true;
422
+ try {
423
+ retrack(commit, subscriber);
424
+ } finally {
425
+ s._reentrant = false;
426
+ }
427
+ };
428
+ const subscriber = run;
429
+ subscriber.depsHead = null;
430
+ subscriber.depsTail = null;
431
+ subscriber._epoch = 0;
432
+ subscriber._structDirty = false;
433
+ subscriber._runEpoch = 0;
434
+ subscriber._runs = 0;
435
+ subscriber._reentrant = false;
436
+ subscriber._disposed = false;
437
+ run();
438
+ return subscriber._dispose ?? (subscriber._dispose = () => {
439
+ subscriber._disposed = true;
440
+ cleanup(subscriber);
441
+ });
442
+ }
362
443
  function recordDependency(signal2) {
363
444
  if (!currentSubscriber) return;
364
445
  const sub = currentSubscriber;
@@ -391,6 +472,16 @@ function cleanup(subscriber) {
391
472
  var maxSubscriberRepeats = 50;
392
473
  var maxDrainIterations = 1e6;
393
474
  var drainEpoch = 0;
475
+ function setMaxSubscriberRepeats(n) {
476
+ const prev = maxSubscriberRepeats;
477
+ if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
478
+ return prev;
479
+ }
480
+ function setMaxDrainIterations(n) {
481
+ const prev = maxDrainIterations;
482
+ if (Number.isFinite(n) && n > 0) maxDrainIterations = Math.floor(n);
483
+ return prev;
484
+ }
394
485
  function tickRepeat(sub) {
395
486
  const s = sub;
396
487
  if (s._runEpoch !== drainEpoch) {
@@ -432,6 +523,20 @@ function drainQueue() {
432
523
  safeInvoke(sub);
433
524
  }
434
525
  }
526
+ function drainNotificationQueue() {
527
+ if (notifyDepth > 0) return;
528
+ notifyDepth++;
529
+ drainEpoch++;
530
+ try {
531
+ drainQueue();
532
+ } finally {
533
+ notifyDepth--;
534
+ if (notifyDepth === 0) {
535
+ pendingQueue.length = 0;
536
+ pendingSet.clear();
537
+ }
538
+ }
539
+ }
435
540
  function propagateDirty(sub) {
436
541
  sub();
437
542
  const rootSig = sub._sig;
@@ -464,6 +569,22 @@ function propagateDirty(sub) {
464
569
  }
465
570
  }
466
571
  }
572
+ function queueSignalNotification(signal2) {
573
+ const sig = signal2;
574
+ let node = sig.subsHead ?? null;
575
+ while (node) {
576
+ const s = node.sub;
577
+ if (s) {
578
+ if (s._c) {
579
+ propagateDirty(s);
580
+ } else if (!pendingSet.has(s)) {
581
+ pendingSet.add(s);
582
+ pendingQueue.push(s);
583
+ }
584
+ }
585
+ node = node.sigNext;
586
+ }
587
+ }
467
588
  function notifySubscribers(signal2) {
468
589
  const sig = signal2;
469
590
  const head = sig.subsHead;
@@ -509,6 +630,85 @@ function notifySubscribers(signal2) {
509
630
  }
510
631
  }
511
632
  }
633
+ function getSubscriberCount(signal2) {
634
+ return signal2.__sc ?? 0;
635
+ }
636
+ function getSubscriberDeps(subscriber) {
637
+ const sub = subscriber;
638
+ const out = [];
639
+ let node = sub.depsHead ?? null;
640
+ while (node) {
641
+ if (node.sig) out.push(node.sig);
642
+ node = node.subNext;
643
+ }
644
+ return out;
645
+ }
646
+ function forEachSubscriber(signal2, visit) {
647
+ let node = signal2.subsHead ?? null;
648
+ while (node) {
649
+ const s = node.sub;
650
+ if (s) visit(s);
651
+ node = node.sigNext;
652
+ }
653
+ }
654
+
655
+ // src/reactivity/track.ts
656
+ var _isDev3 = isDev();
657
+ var _runtimeVersion = typeof __SIBU_VERSION__ !== "undefined" ? __SIBU_VERSION__ : "dev";
658
+ var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
659
+ function resolveReactiveApi() {
660
+ const g = globalThis;
661
+ const existing = g[REGISTRY_KEY];
662
+ if (existing) {
663
+ if (_isDev3 && !existing.__dupWarned) {
664
+ existing.__dupWarned = true;
665
+ devWarn(
666
+ `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
667
+ );
668
+ }
669
+ return existing;
670
+ }
671
+ const local = {
672
+ suspendTracking,
673
+ resumeTracking,
674
+ isTrackingSuspended,
675
+ untracked,
676
+ retrack,
677
+ track,
678
+ reactiveBinding,
679
+ recordDependency,
680
+ cleanup,
681
+ setMaxSubscriberRepeats,
682
+ setMaxDrainIterations,
683
+ drainNotificationQueue,
684
+ queueSignalNotification,
685
+ notifySubscribers,
686
+ getSubscriberCount,
687
+ getSubscriberDeps,
688
+ forEachSubscriber,
689
+ version: _runtimeVersion
690
+ };
691
+ g[REGISTRY_KEY] = local;
692
+ return local;
693
+ }
694
+ var API = resolveReactiveApi();
695
+ var suspendTracking2 = API.suspendTracking;
696
+ var resumeTracking2 = API.resumeTracking;
697
+ var isTrackingSuspended2 = API.isTrackingSuspended;
698
+ var untracked2 = API.untracked;
699
+ var retrack2 = API.retrack;
700
+ var track2 = API.track;
701
+ var reactiveBinding2 = API.reactiveBinding;
702
+ var recordDependency2 = API.recordDependency;
703
+ var cleanup2 = API.cleanup;
704
+ var setMaxSubscriberRepeats2 = API.setMaxSubscriberRepeats;
705
+ var setMaxDrainIterations2 = API.setMaxDrainIterations;
706
+ var drainNotificationQueue2 = API.drainNotificationQueue;
707
+ var queueSignalNotification2 = API.queueSignalNotification;
708
+ var notifySubscribers2 = API.notifySubscribers;
709
+ var getSubscriberCount2 = API.getSubscriberCount;
710
+ var getSubscriberDeps2 = API.getSubscriberDeps;
711
+ var forEachSubscriber2 = API.forEachSubscriber;
512
712
 
513
713
  // src/core/ssr-context.ts
514
714
  var als = null;
@@ -559,7 +759,7 @@ function drainReruns(ctx) {
559
759
  do {
560
760
  ctx.rerunPending = false;
561
761
  if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
562
- retrack(ctx.bodyFn, ctx.subscriber);
762
+ retrack2(ctx.bodyFn, ctx.subscriber);
563
763
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
564
764
  if (ctx.rerunPending) {
565
765
  ctx.rerunPending = false;
@@ -588,7 +788,7 @@ function disposeEffect(ctx) {
588
788
  }
589
789
  }
590
790
  try {
591
- cleanup(ctx.subscriber);
791
+ cleanup2(ctx.subscriber);
592
792
  } catch (err) {
593
793
  if (typeof console !== "undefined") {
594
794
  console.warn("[SibuJS effect] dispose threw:", err);
@@ -633,7 +833,7 @@ function effect(effectFn, options) {
633
833
  try {
634
834
  ctx.rerunPending = false;
635
835
  if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
636
- retrack(ctx.bodyFn, sub);
836
+ retrack2(ctx.bodyFn, sub);
637
837
  if (ctx.rerunPending) drainReruns(ctx);
638
838
  } finally {
639
839
  ctx.running = false;
@@ -649,7 +849,7 @@ function effect(effectFn, options) {
649
849
  ctx.subscriber = sub;
650
850
  ctx.running = true;
651
851
  try {
652
- retrack(ctx.bodyFn, ctx.subscriber);
852
+ retrack2(ctx.bodyFn, ctx.subscriber);
653
853
  if (ctx.rerunPending) drainReruns(ctx);
654
854
  } finally {
655
855
  ctx.running = false;
@@ -663,15 +863,56 @@ function effect(effectFn, options) {
663
863
  // src/reactivity/batch.ts
664
864
  var batchDepth = 0;
665
865
  var pendingSignals = /* @__PURE__ */ new Set();
666
- function enqueueBatchedSignal(signal2) {
866
+ function batchImpl(fn) {
867
+ batchDepth++;
868
+ try {
869
+ return fn();
870
+ } finally {
871
+ batchDepth--;
872
+ if (batchDepth === 0) {
873
+ flushBatch();
874
+ }
875
+ }
876
+ }
877
+ function enqueueBatchedSignalImpl(signal2) {
667
878
  if (batchDepth === 0) return false;
668
879
  pendingSignals.add(signal2);
669
880
  return true;
670
881
  }
882
+ function isBatchingImpl() {
883
+ return batchDepth > 0;
884
+ }
885
+ function flushBatch() {
886
+ try {
887
+ for (const signal2 of pendingSignals) {
888
+ queueSignalNotification2(signal2);
889
+ }
890
+ } finally {
891
+ pendingSignals.clear();
892
+ }
893
+ drainNotificationQueue2();
894
+ }
895
+ var BATCH_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.batch.v1");
896
+ function resolveBatchApi() {
897
+ const g = globalThis;
898
+ const existing = g[BATCH_REGISTRY_KEY];
899
+ if (existing) return existing;
900
+ const local = {
901
+ batch: batchImpl,
902
+ enqueueBatchedSignal: enqueueBatchedSignalImpl,
903
+ isBatching: isBatchingImpl
904
+ };
905
+ g[BATCH_REGISTRY_KEY] = local;
906
+ return local;
907
+ }
908
+ var API2 = resolveBatchApi();
909
+ var batch = API2.batch;
910
+ var enqueueBatchedSignal = API2.enqueueBatchedSignal;
911
+ var isBatching = API2.isBatching;
671
912
 
672
913
  // src/core/signals/signal.ts
673
914
  var _g2 = globalThis;
674
- var _isDev3 = isDev();
915
+ var _isDev4 = isDev();
675
916
  function signal(initial, options) {
676
917
  const state = {
677
918
  value: initial,
@@ -682,11 +923,11 @@ function signal(initial, options) {
682
923
  __activeNode: null,
683
924
  __name: void 0
684
925
  };
685
- const debugName = _isDev3 ? options?.name : void 0;
926
+ const debugName = _isDev4 ? options?.name : void 0;
686
927
  const equalsFn = options?.equals;
687
928
  if (debugName) state.__name = debugName;
688
929
  function get() {
689
- recordDependency(state);
930
+ recordDependency2(state);
690
931
  return state.value;
691
932
  }
692
933
  get.__signal = state;
@@ -699,15 +940,15 @@ function signal(initial, options) {
699
940
  if (equalsFn(prev, newValue)) return;
700
941
  state.value = newValue;
701
942
  state.__v++;
702
- if (_isDev3) {
943
+ if (_isDev4) {
703
944
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
704
945
  if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
705
946
  }
706
947
  if (!enqueueBatchedSignal(state)) {
707
- notifySubscribers(state);
948
+ notifySubscribers2(state);
708
949
  }
709
950
  };
710
- } else if (_isDev3) {
951
+ } else if (_isDev4) {
711
952
  set = (next) => {
712
953
  const prev = state.value;
713
954
  const newValue = typeof next === "function" ? next(prev) : next;
@@ -717,7 +958,7 @@ function signal(initial, options) {
717
958
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
718
959
  if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
719
960
  if (!enqueueBatchedSignal(state)) {
720
- notifySubscribers(state);
961
+ notifySubscribers2(state);
721
962
  }
722
963
  };
723
964
  } else {
@@ -728,11 +969,11 @@ function signal(initial, options) {
728
969
  state.value = newValue;
729
970
  state.__v++;
730
971
  if (!enqueueBatchedSignal(state)) {
731
- notifySubscribers(state);
972
+ notifySubscribers2(state);
732
973
  }
733
974
  };
734
975
  }
735
- if (_isDev3) {
976
+ if (_isDev4) {
736
977
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
737
978
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
738
979
  }
@@ -778,8 +1019,33 @@ function uniqueId(suffix) {
778
1019
  return suffix ? `${id}-${suffix}` : id;
779
1020
  }
780
1021
 
1022
+ // src/utils/sanitize.ts
1023
+ function stripControlChars(value) {
1024
+ return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
1025
+ }
1026
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1027
+ function sanitizeUrl(url) {
1028
+ const trimmed = stripControlChars(url).trim();
1029
+ if (!trimmed) return "";
1030
+ const lower = trimmed.toLowerCase();
1031
+ let schemeEnd = -1;
1032
+ for (let i = 0; i < lower.length; i++) {
1033
+ const ch = lower.charCodeAt(i);
1034
+ if (ch === 58) {
1035
+ schemeEnd = i;
1036
+ break;
1037
+ }
1038
+ if (ch === 47 || ch === 63 || ch === 35) break;
1039
+ }
1040
+ if (schemeEnd === -1) return trimmed;
1041
+ const scheme = lower.slice(0, schemeEnd + 1);
1042
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1043
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1044
+ return trimmed;
1045
+ }
1046
+
781
1047
  // src/performance/domRecycler.ts
782
- var _isDev4 = isDev();
1048
+ var _isDev5 = isDev();
783
1049
  var DOMPool = class {
784
1050
  constructor(maxSize = 50) {
785
1051
  this.pools = /* @__PURE__ */ new Map();
@@ -806,7 +1072,7 @@ var DOMPool = class {
806
1072
  * is almost certainly a bug.
807
1073
  */
808
1074
  release(element) {
809
- if (_isDev4 && element.isConnected) {
1075
+ if (_isDev5 && element.isConnected) {
810
1076
  devWarn(
811
1077
  "DOMPool.release() called on a still-connected element. Detach it from the DOM first (remove() / dispose())."
812
1078
  );
@@ -853,11 +1119,13 @@ function getDOMPool() {
853
1119
  }
854
1120
  var preloadedResources = /* @__PURE__ */ new Set();
855
1121
  function preloadResource(url, type = "fetch") {
856
- if (preloadedResources.has(url)) return;
857
- preloadedResources.add(url);
1122
+ const safe = sanitizeUrl(url);
1123
+ if (!safe) return;
1124
+ if (preloadedResources.has(safe)) return;
1125
+ preloadedResources.add(safe);
858
1126
  const link = document.createElement("link");
859
1127
  link.rel = "preload";
860
- link.href = url;
1128
+ link.href = safe;
861
1129
  switch (type) {
862
1130
  case "script":
863
1131
  link.setAttribute("as", "script");
@@ -875,11 +1143,13 @@ function preloadResource(url, type = "fetch") {
875
1143
  document.head.appendChild(link);
876
1144
  }
877
1145
  function prefetch(url) {
878
- if (preloadedResources.has(url)) return;
879
- preloadedResources.add(url);
1146
+ const safe = sanitizeUrl(url);
1147
+ if (!safe) return;
1148
+ if (preloadedResources.has(safe)) return;
1149
+ preloadedResources.add(safe);
880
1150
  const link = document.createElement("link");
881
1151
  link.rel = "prefetch";
882
- link.href = url;
1152
+ link.href = safe;
883
1153
  document.head.appendChild(link);
884
1154
  }
885
1155
  function preloadImage(src) {
@@ -914,7 +1184,7 @@ function noSideEffect(fn) {
914
1184
  }
915
1185
 
916
1186
  // src/platform/ssr.ts
917
- var _isDev5 = isDev();
1187
+ var _isDev6 = isDev();
918
1188
  function trustHTML(html) {
919
1189
  return html;
920
1190
  }
@@ -1073,7 +1343,7 @@ function denormalize(id, entities, schema) {
1073
1343
 
1074
1344
  // src/core/rendering/dispose.ts
1075
1345
  var elementDisposers = /* @__PURE__ */ new WeakMap();
1076
- var _isDev6 = isDev();
1346
+ var _isDev7 = isDev();
1077
1347
  var activeBindingCount = 0;
1078
1348
  function dispose(node) {
1079
1349
  const stack = [node];
@@ -1092,12 +1362,12 @@ function dispose(node) {
1092
1362
  if (disposers) {
1093
1363
  const snapshot = disposers.slice();
1094
1364
  elementDisposers.delete(current);
1095
- if (_isDev6) activeBindingCount -= snapshot.length;
1365
+ if (_isDev7) activeBindingCount -= snapshot.length;
1096
1366
  for (const d of snapshot) {
1097
1367
  try {
1098
1368
  d();
1099
1369
  } catch (err) {
1100
- if (_isDev6 && typeof console !== "undefined") {
1370
+ if (_isDev7 && typeof console !== "undefined") {
1101
1371
  console.warn("[SibuJS] Disposer threw during cleanup:", err);
1102
1372
  }
1103
1373
  }
@@ -1108,12 +1378,12 @@ function dispose(node) {
1108
1378
  if (!added || added.length === 0) break;
1109
1379
  const moreSnapshot = added.slice();
1110
1380
  elementDisposers.delete(current);
1111
- if (_isDev6) activeBindingCount -= moreSnapshot.length;
1381
+ if (_isDev7) activeBindingCount -= moreSnapshot.length;
1112
1382
  for (const d of moreSnapshot) {
1113
1383
  try {
1114
1384
  d();
1115
1385
  } catch (err) {
1116
- if (_isDev6 && typeof console !== "undefined") {
1386
+ if (_isDev7 && typeof console !== "undefined") {
1117
1387
  console.warn("[SibuJS] Disposer threw during cleanup:", err);
1118
1388
  }
1119
1389
  }
@@ -1307,11 +1577,14 @@ function lazyChunk(id, loader, registry, fallback) {
1307
1577
  }
1308
1578
  function preloadModule(url) {
1309
1579
  if (typeof document === "undefined") return;
1310
- const existing = document.querySelector(`link[href="${url}"][rel="modulepreload"]`);
1580
+ const safe = sanitizeUrl(url);
1581
+ if (!safe) return;
1582
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(safe) : safe.replace(/["\\]/g, "\\$&");
1583
+ const existing = document.querySelector(`link[rel="modulepreload"][href="${safeHref}"]`);
1311
1584
  if (existing) return;
1312
1585
  const link = document.createElement("link");
1313
1586
  link.rel = "modulepreload";
1314
- link.href = url;
1587
+ link.href = safe;
1315
1588
  document.head.appendChild(link);
1316
1589
  }
1317
1590
  function preloadModules(urls) {
@@ -33,17 +33,17 @@ import {
33
33
  transitionState,
34
34
  uniqueId,
35
35
  yieldToMain
36
- } from "./chunk-4JCAUOLN.js";
36
+ } from "./chunk-M5KBNOSJ.js";
37
37
  import {
38
38
  trustHTML
39
- } from "./chunk-HXMS4SNP.js";
40
- import "./chunk-2UPRY23K.js";
41
- import "./chunk-HMJFCBRR.js";
42
- import "./chunk-FDY42FIU.js";
39
+ } from "./chunk-R3QEDXFS.js";
40
+ import "./chunk-5VGSK6D2.js";
41
+ import "./chunk-L3GAGWCC.js";
42
+ import "./chunk-MHBCEJQO.js";
43
43
  import "./chunk-GOJMFRBL.js";
44
- import "./chunk-C427DVQF.js";
45
- import "./chunk-Z2FWAE4B.js";
46
- import "./chunk-LMLD24FC.js";
44
+ import "./chunk-WZG2SZOT.js";
45
+ import "./chunk-TEFZT5PJ.js";
46
+ import "./chunk-COY6PUD2.js";
47
47
  export {
48
48
  DOMPool,
49
49
  Features,