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
package/dist/ui.cjs CHANGED
@@ -107,8 +107,104 @@ function devWarn(message) {
107
107
  }
108
108
  }
109
109
 
110
+ // src/core/errors.ts
111
+ var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
112
+ function state() {
113
+ const g = globalThis;
114
+ const existing = g[ERROR_STATE_KEY];
115
+ if (existing) return existing;
116
+ const created = { handler: null, handlerDepth: 0 };
117
+ g[ERROR_STATE_KEY] = created;
118
+ return created;
119
+ }
120
+ function normalizeError(value) {
121
+ if (value instanceof Error) return value;
122
+ const message = typeof value === "string" ? value : `Non-Error value thrown: ${safeStringify(value)}`;
123
+ return new Error(message, { cause: value });
124
+ }
125
+ function safeStringify(value) {
126
+ if (typeof value === "symbol") return value.toString();
127
+ try {
128
+ return typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
129
+ } catch {
130
+ return Object.prototype.toString.call(value);
131
+ }
132
+ }
133
+ function describe(context) {
134
+ return context.name ? `${context.phase} "${context.name}"` : context.phase;
135
+ }
136
+ var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
137
+ function untrackedRun(fn) {
138
+ const api = globalThis[REACTIVE_REGISTRY_KEY];
139
+ return api?.untracked ? api.untracked(fn) : fn();
140
+ }
141
+ var PROPAGATE_EVENT = "sibu:error-propagate";
142
+ function claimedByBoundary(error, context) {
143
+ const raw = context.node;
144
+ if (!raw || typeof CustomEvent === "undefined") return false;
145
+ const target = resolveEventTarget(raw);
146
+ if (!target) return false;
147
+ try {
148
+ const event = new CustomEvent(PROPAGATE_EVENT, {
149
+ bubbles: true,
150
+ cancelable: true,
151
+ detail: { error, context }
152
+ });
153
+ return target.dispatchEvent(event) === false;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+ function resolveEventTarget(node) {
159
+ const ELEMENT_NODE = 1;
160
+ let current = node;
161
+ while (current) {
162
+ if (current.nodeType === ELEMENT_NODE && typeof current.dispatchEvent === "function") {
163
+ return current;
164
+ }
165
+ current = current.parentNode;
166
+ }
167
+ const self = node;
168
+ return typeof self.dispatchEvent === "function" ? self : null;
169
+ }
170
+ function defaultReport(error, context) {
171
+ if (typeof console === "undefined") return;
172
+ console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
173
+ }
174
+ function reportError(error, context) {
175
+ const err = normalizeError(error);
176
+ untrackedRun(() => {
177
+ if (claimedByBoundary(err, context)) return;
178
+ const s = state();
179
+ const handler = s.handler;
180
+ if (handler && s.handlerDepth === 0) {
181
+ s.handlerDepth++;
182
+ try {
183
+ handler(error instanceof Error ? error : err, context);
184
+ return;
185
+ } catch (handlerError) {
186
+ if (typeof console !== "undefined") {
187
+ const detail = handlerError instanceof Error ? handlerError.message : String(handlerError);
188
+ console.error(`[SibuJS] runtime error handler itself threw: ${detail}`, handlerError);
189
+ }
190
+ } finally {
191
+ s.handlerDepth--;
192
+ }
193
+ }
194
+ defaultReport(err, context);
195
+ });
196
+ }
197
+
110
198
  // src/core/rendering/dispose.ts
111
199
  var elementDisposers = /* @__PURE__ */ new WeakMap();
200
+ var MAX_DRAIN_TEARDOWNS = 1e4;
201
+ function reportDrainRunaway(label, executed, remaining) {
202
+ if (typeof console === "undefined") return;
203
+ console.error(
204
+ `[SibuJS ${label}] runaway cleanup: stopped after running ${executed} teardowns with ${remaining} still queued. Cleanup production did not stabilize before the teardown safety ceiling was reached \u2014 the remaining work was NOT run.`,
205
+ { executed, remaining }
206
+ );
207
+ }
112
208
  var _isDev2 = isDev();
113
209
  var activeBindingCount = 0;
114
210
  function registerDisposer(node, teardown) {
@@ -133,43 +229,49 @@ function dispose(node) {
133
229
  }
134
230
  for (let i = order.length - 1; i >= 0; i--) {
135
231
  const current = order[i];
136
- const disposers = elementDisposers.get(current);
137
- if (disposers) {
138
- const snapshot = disposers.slice();
139
- elementDisposers.delete(current);
140
- if (_isDev2) activeBindingCount -= snapshot.length;
141
- for (const d of snapshot) {
142
- try {
143
- d();
144
- } catch (err) {
145
- if (_isDev2 && typeof console !== "undefined") {
146
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
147
- }
148
- }
149
- }
150
- let extraPasses = 0;
151
- while (extraPasses++ < 8) {
152
- const added = elementDisposers.get(current);
153
- if (!added || added.length === 0) break;
154
- const moreSnapshot = added.slice();
232
+ if (elementDisposers.has(current)) {
233
+ let executed = 0;
234
+ let runaway = false;
235
+ while (!runaway) {
236
+ const pending = elementDisposers.get(current);
237
+ if (!pending || pending.length === 0) break;
238
+ const snapshot = pending.slice();
155
239
  elementDisposers.delete(current);
156
- if (_isDev2) activeBindingCount -= moreSnapshot.length;
157
- for (const d of moreSnapshot) {
240
+ if (_isDev2) activeBindingCount -= snapshot.length;
241
+ for (let i2 = 0; i2 < snapshot.length; i2++) {
242
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
243
+ const rest = snapshot.slice(i2);
244
+ const added = elementDisposers.get(current);
245
+ elementDisposers.set(current, added ? rest.concat(added) : rest);
246
+ if (_isDev2) activeBindingCount += rest.length;
247
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
248
+ runaway = true;
249
+ break;
250
+ }
251
+ executed++;
158
252
  try {
159
- d();
253
+ snapshot[i2]();
160
254
  } catch (err) {
161
- if (_isDev2 && typeof console !== "undefined") {
162
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
163
- }
255
+ reportError(err, { phase: "cleanup", name: "disposer" });
164
256
  }
165
257
  }
166
258
  }
167
259
  }
168
260
  }
169
261
  }
262
+ function replaceChildrenSafely(parent, ...next) {
263
+ for (let i = 0; i < next.length; i++) {
264
+ const node = next[i];
265
+ node.parentNode?.removeChild(node);
266
+ }
267
+ const current = Array.from(parent.childNodes);
268
+ for (let i = 0; i < current.length; i++) {
269
+ dispose(current[i]);
270
+ }
271
+ parent.replaceChildren(...next);
272
+ }
170
273
 
171
274
  // src/reactivity/track-core.ts
172
- var _isDev3 = isDev();
173
275
  var POOL_MAX = 4096;
174
276
  var nodePool = [];
175
277
  function createNode() {
@@ -177,6 +279,7 @@ function createNode() {
177
279
  sig: null,
178
280
  sub: null,
179
281
  epoch: 0,
282
+ depVersion: 0,
180
283
  sigPrev: null,
181
284
  sigNext: null,
182
285
  subPrev: null,
@@ -261,8 +364,27 @@ function safeInvoke(sub) {
261
364
  try {
262
365
  sub();
263
366
  } catch (err) {
264
- if (_isDev3) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
367
+ const s = sub;
368
+ reportError(err, { phase: s._errorPhase ?? "effect", name: s.__name, node: s._errorNode });
369
+ }
370
+ }
371
+ function depsChanged(sub) {
372
+ let node = sub.depsHead ?? null;
373
+ if (node === null) return true;
374
+ while (node !== null) {
375
+ const sig = node.sig;
376
+ if (sig === null) return true;
377
+ if (sig._d === true && sig._validate !== void 0) {
378
+ try {
379
+ sig._validate();
380
+ } catch {
381
+ return true;
382
+ }
383
+ }
384
+ if (node.depVersion !== sig.__v) return true;
385
+ node = node.subNext;
265
386
  }
387
+ return false;
266
388
  }
267
389
  var suspendDepth = 0;
268
390
  var trackingSuspended = false;
@@ -344,7 +466,7 @@ function track(effectFn, subscriber) {
344
466
  const sub = subscriber;
345
467
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
346
468
  }
347
- function reactiveBinding(commit) {
469
+ function reactiveBinding(commit, ownerNode) {
348
470
  const run = () => {
349
471
  const s = subscriber;
350
472
  if (s._disposed || s._reentrant) return;
@@ -360,13 +482,17 @@ function reactiveBinding(commit) {
360
482
  subscriber.depsTail = null;
361
483
  subscriber._epoch = 0;
362
484
  subscriber._structDirty = false;
485
+ subscriber._hasComputedDep = false;
363
486
  subscriber._runEpoch = 0;
364
487
  subscriber._runs = 0;
365
488
  subscriber._reentrant = false;
366
489
  subscriber._disposed = false;
490
+ subscriber._errorPhase = "binding";
491
+ subscriber._errorNode = ownerNode;
367
492
  run();
368
493
  return subscriber._dispose ?? (subscriber._dispose = () => {
369
494
  subscriber._disposed = true;
495
+ subscriber._errorNode = void 0;
370
496
  cleanup(subscriber);
371
497
  });
372
498
  }
@@ -378,9 +504,12 @@ function recordDependency(signal2) {
378
504
  const active = sig.__activeNode ?? null;
379
505
  if (active !== null && active.sub === sub) {
380
506
  active.epoch = epoch;
507
+ active.depVersion = sig.__v ?? Number.NaN;
381
508
  return;
382
509
  }
510
+ if (sig._validate !== void 0) sub._hasComputedDep = true;
383
511
  const node = allocNode(signal2, sub, epoch);
512
+ node.depVersion = sig.__v ?? Number.NaN;
384
513
  node.prevActive = active;
385
514
  sig.__activeNode = node;
386
515
  linkSub(sub, node);
@@ -399,9 +528,10 @@ function cleanup(subscriber) {
399
528
  node = next;
400
529
  }
401
530
  }
402
- var maxSubscriberRepeats = 50;
531
+ var maxSubscriberRepeats = 1e3;
403
532
  var maxDrainIterations = 1e6;
404
533
  var drainEpoch = 0;
534
+ var quarantined = /* @__PURE__ */ new Set();
405
535
  function setMaxSubscriberRepeats(n) {
406
536
  const prev = maxSubscriberRepeats;
407
537
  if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
@@ -423,19 +553,21 @@ function tickRepeat(sub) {
423
553
  return s._runs > maxSubscriberRepeats;
424
554
  }
425
555
  function cycleError(sub) {
426
- if (typeof console !== "undefined") {
427
- const name = sub.__name ?? "<unnamed>";
428
- console.error(
429
- `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
430
- );
431
- }
556
+ const name = sub.__name ?? "<unnamed>";
557
+ reportError(
558
+ new Error(
559
+ `subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. This subscriber is quarantined for the rest of this update; other pending work still runs.`
560
+ ),
561
+ { phase: "scheduler", name }
562
+ );
432
563
  }
433
564
  function absoluteDrainError() {
434
- if (typeof console !== "undefined") {
435
- console.error(
436
- `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
437
- );
438
- }
565
+ reportError(
566
+ new Error(
567
+ `Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
568
+ ),
569
+ { phase: "scheduler" }
570
+ );
439
571
  }
440
572
  function drainQueue() {
441
573
  let i = 0;
@@ -445,11 +577,14 @@ function drainQueue() {
445
577
  break;
446
578
  }
447
579
  const sub = pendingQueue[i++];
580
+ pendingSet.delete(sub);
581
+ if (quarantined.size > 0 && quarantined.has(sub)) continue;
582
+ if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
448
583
  if (tickRepeat(sub)) {
449
584
  cycleError(sub);
450
- break;
585
+ quarantined.add(sub);
586
+ continue;
451
587
  }
452
- pendingSet.delete(sub);
453
588
  safeInvoke(sub);
454
589
  }
455
590
  }
@@ -464,6 +599,7 @@ function drainNotificationQueue() {
464
599
  if (notifyDepth === 0) {
465
600
  pendingQueue.length = 0;
466
601
  pendingSet.clear();
602
+ if (quarantined.size > 0) quarantined.clear();
467
603
  }
468
604
  }
469
605
  }
@@ -557,6 +693,7 @@ function notifySubscribers(signal2) {
557
693
  if (notifyDepth === 0) {
558
694
  pendingQueue.length = 0;
559
695
  pendingSet.clear();
696
+ if (quarantined.size > 0) quarantined.clear();
560
697
  }
561
698
  }
562
699
  }
@@ -583,14 +720,14 @@ function forEachSubscriber(signal2, visit) {
583
720
  }
584
721
 
585
722
  // src/reactivity/track.ts
586
- var _isDev4 = isDev();
587
- var _runtimeVersion = true ? "3.4.0" : "dev";
723
+ var _isDev3 = isDev();
724
+ var _runtimeVersion = true ? "4.0.0" : "dev";
588
725
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
589
726
  function resolveReactiveApi() {
590
727
  const g = globalThis;
591
728
  const existing = g[REGISTRY_KEY];
592
729
  if (existing) {
593
- if (_isDev4 && !existing.__dupWarned) {
730
+ if (_isDev3 && !existing.__dupWarned) {
594
731
  existing.__dupWarned = true;
595
732
  devWarn(
596
733
  `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']).`
@@ -675,6 +812,21 @@ function derived(getter, options) {
675
812
  }, markDirty);
676
813
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
677
814
  let evaluating = false;
815
+ const validate = () => {
816
+ if (!cs._d) return;
817
+ const oldValue = cs._v;
818
+ evaluating = true;
819
+ try {
820
+ retrack2(recompute, markDirty);
821
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
822
+ } finally {
823
+ evaluating = false;
824
+ }
825
+ if (hook && !Object.is(oldValue, cs._v)) {
826
+ hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
827
+ }
828
+ };
829
+ cs._validate = validate;
678
830
  function computedGetter() {
679
831
  if (evaluating) {
680
832
  throw new Error(
@@ -682,32 +834,11 @@ function derived(getter, options) {
682
834
  );
683
835
  }
684
836
  if (isTrackingSuspended2()) {
685
- if (cs._d) {
686
- const prev = cs._v;
687
- evaluating = true;
688
- try {
689
- retrack2(recompute, markDirty);
690
- if (!Object.is(prev, cs._v)) cs.__v++;
691
- } finally {
692
- evaluating = false;
693
- }
694
- }
837
+ if (cs._d) validate();
695
838
  return cs._v;
696
839
  }
840
+ if (cs._d) validate();
697
841
  recordDependency2(cs);
698
- if (cs._d) {
699
- const oldValue = cs._v;
700
- evaluating = true;
701
- try {
702
- retrack2(recompute, markDirty);
703
- if (!Object.is(oldValue, cs._v)) cs.__v++;
704
- } finally {
705
- evaluating = false;
706
- }
707
- if (hook && oldValue !== cs._v) {
708
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
709
- }
710
- }
711
842
  return cs._v;
712
843
  }
713
844
  if (debugName) {
@@ -729,20 +860,24 @@ function detectSSRShared() {
729
860
  const getBuiltin = process.getBuiltinModule;
730
861
  if (typeof getBuiltin === "function") {
731
862
  mod = getBuiltin("node:async_hooks");
732
- } else {
733
- const req = Function("return typeof require==='function'?require:null")();
734
- if (req) mod = req("node:async_hooks");
863
+ } else if (typeof require === "function") {
864
+ mod = require("async_hooks");
735
865
  }
736
866
  if (mod) detected = new mod.AsyncLocalStorage();
737
867
  }
738
868
  } catch {
739
869
  detected = null;
740
870
  }
741
- return { als: detected, fallbackStore: { ssr: false, suspenseIdCounter: 0 } };
871
+ return {
872
+ als: detected,
873
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
874
+ fallbackDepth: 0
875
+ };
742
876
  }
743
877
  var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
744
878
  var als = _shared.als;
745
879
  var fallbackStore = _shared.fallbackStore;
880
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
746
881
  function getSSRStore() {
747
882
  if (als) {
748
883
  const s = als.getStore();
@@ -765,7 +900,7 @@ function flushUserCleanups(ctx) {
765
900
  try {
766
901
  list[i]();
767
902
  } catch (err) {
768
- if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
903
+ reportError(err, { phase: "cleanup" });
769
904
  }
770
905
  }
771
906
  }
@@ -778,11 +913,12 @@ function drainReruns(ctx) {
778
913
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
779
914
  if (ctx.rerunPending) {
780
915
  ctx.rerunPending = false;
781
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
782
- console.error(
783
- `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
784
- );
785
- }
916
+ reportError(
917
+ new Error(
918
+ `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
919
+ ),
920
+ { phase: "effect" }
921
+ );
786
922
  }
787
923
  }
788
924
  function disposeEffect(ctx) {
@@ -859,6 +995,8 @@ function effect(effectFn, options) {
859
995
  sub.depsTail = null;
860
996
  sub._epoch = 0;
861
997
  sub._structDirty = false;
998
+ sub._hasComputedDep = false;
999
+ sub._errorPhase = "effect";
862
1000
  sub._runEpoch = 0;
863
1001
  sub._runs = 0;
864
1002
  ctx.subscriber = sub;
@@ -927,70 +1065,72 @@ var isBatching = API2.isBatching;
927
1065
 
928
1066
  // src/core/signals/signal.ts
929
1067
  var _g2 = globalThis;
930
- var _isDev5 = isDev();
1068
+ var _isDev4 = isDev();
931
1069
  function signal(initial, options) {
932
- const state = {
1070
+ const state2 = {
933
1071
  value: initial,
934
1072
  __v: 0,
935
1073
  __sc: 0,
936
1074
  subsHead: null,
937
1075
  subsTail: null,
938
1076
  __activeNode: null,
939
- __name: void 0
1077
+ __name: void 0,
1078
+ _d: false,
1079
+ _validate: void 0
940
1080
  };
941
- const debugName = _isDev5 ? options?.name : void 0;
1081
+ const debugName = _isDev4 ? options?.name : void 0;
942
1082
  const equalsFn = options?.equals;
943
- if (debugName) state.__name = debugName;
1083
+ if (debugName) state2.__name = debugName;
944
1084
  function get() {
945
- recordDependency2(state);
946
- return state.value;
1085
+ recordDependency2(state2);
1086
+ return state2.value;
947
1087
  }
948
- get.__signal = state;
1088
+ get.__signal = state2;
949
1089
  if (debugName) get.__name = debugName;
950
1090
  let set;
951
1091
  if (equalsFn) {
952
1092
  set = (next) => {
953
- const prev = state.value;
1093
+ const prev = state2.value;
954
1094
  const newValue = typeof next === "function" ? next(prev) : next;
955
1095
  if (equalsFn(prev, newValue)) return;
956
- state.value = newValue;
957
- state.__v++;
958
- if (_isDev5) {
1096
+ state2.value = newValue;
1097
+ state2.__v++;
1098
+ if (_isDev4) {
959
1099
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
960
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
1100
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
961
1101
  }
962
- if (!enqueueBatchedSignal(state)) {
963
- notifySubscribers2(state);
1102
+ if (!enqueueBatchedSignal(state2)) {
1103
+ notifySubscribers2(state2);
964
1104
  }
965
1105
  };
966
- } else if (_isDev5) {
1106
+ } else if (_isDev4) {
967
1107
  set = (next) => {
968
- const prev = state.value;
1108
+ const prev = state2.value;
969
1109
  const newValue = typeof next === "function" ? next(prev) : next;
970
1110
  if (Object.is(newValue, prev)) return;
971
- state.value = newValue;
972
- state.__v++;
1111
+ state2.value = newValue;
1112
+ state2.__v++;
973
1113
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
974
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
975
- if (!enqueueBatchedSignal(state)) {
976
- notifySubscribers2(state);
1114
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1115
+ if (!enqueueBatchedSignal(state2)) {
1116
+ notifySubscribers2(state2);
977
1117
  }
978
1118
  };
979
1119
  } else {
980
1120
  set = (next) => {
981
- const prev = state.value;
1121
+ const prev = state2.value;
982
1122
  const newValue = typeof next === "function" ? next(prev) : next;
983
1123
  if (Object.is(newValue, prev)) return;
984
- state.value = newValue;
985
- state.__v++;
986
- if (!enqueueBatchedSignal(state)) {
987
- notifySubscribers2(state);
1124
+ state2.value = newValue;
1125
+ state2.__v++;
1126
+ if (!enqueueBatchedSignal(state2)) {
1127
+ notifySubscribers2(state2);
988
1128
  }
989
1129
  };
990
1130
  }
991
- if (_isDev5) {
1131
+ if (_isDev4) {
992
1132
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
993
- if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
1133
+ if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
994
1134
  }
995
1135
  return [get, set];
996
1136
  }
@@ -1088,6 +1228,8 @@ function bindField(field, extras) {
1088
1228
  }
1089
1229
  };
1090
1230
  return {
1231
+ // The field's own getter, no longer widened to `unknown` — that widening is
1232
+ // what forced every consumer to cast. (TYPE-010)
1091
1233
  value: field.value,
1092
1234
  on: mergedOn,
1093
1235
  onElement,
@@ -1891,21 +2033,169 @@ function sanitizeCSS(css) {
1891
2033
  sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
1892
2034
  return sanitized;
1893
2035
  }
2036
+ function firstCompoundEnd(selector) {
2037
+ let parens = 0;
2038
+ let brackets = 0;
2039
+ let quote = null;
2040
+ for (let i = 0; i < selector.length; i++) {
2041
+ const ch = selector[i];
2042
+ if (quote) {
2043
+ if (ch === "\\") i++;
2044
+ else if (ch === quote) quote = null;
2045
+ continue;
2046
+ }
2047
+ if (ch === '"' || ch === "'") {
2048
+ quote = ch;
2049
+ continue;
2050
+ }
2051
+ if (ch === "\\") {
2052
+ i++;
2053
+ continue;
2054
+ }
2055
+ if (ch === "(") parens++;
2056
+ else if (ch === ")") parens--;
2057
+ else if (ch === "[") brackets++;
2058
+ else if (ch === "]") brackets--;
2059
+ if (parens > 0 || brackets > 0) continue;
2060
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f") return i;
2061
+ if (ch === ">" || ch === "+" || ch === "~") return i;
2062
+ if (ch === ":" && selector[i + 1] === ":") return i;
2063
+ }
2064
+ return selector.length;
2065
+ }
2066
+ function scopeSelector(selector, attr) {
2067
+ const end = firstCompoundEnd(selector);
2068
+ const selfForm = `${selector.slice(0, end)}[${attr}]${selector.slice(end)}`;
2069
+ const descendantForm = `[${attr}] ${selector}`;
2070
+ return `${descendantForm}, ${selfForm}`;
2071
+ }
2072
+ function skipNonCode(text, i) {
2073
+ const ch = text[i];
2074
+ if (ch === "\\") return i + 2;
2075
+ if (ch === '"' || ch === "'") {
2076
+ let j = i + 1;
2077
+ while (j < text.length) {
2078
+ if (text[j] === "\\") {
2079
+ j += 2;
2080
+ continue;
2081
+ }
2082
+ if (text[j] === ch) return j + 1;
2083
+ j++;
2084
+ }
2085
+ return text.length;
2086
+ }
2087
+ if (ch === "/" && text[i + 1] === "*") {
2088
+ const end = text.indexOf("*/", i + 2);
2089
+ return end === -1 ? text.length : end + 2;
2090
+ }
2091
+ return -1;
2092
+ }
2093
+ function splitSelectorList(prelude) {
2094
+ const parts = [];
2095
+ let parens = 0;
2096
+ let brackets = 0;
2097
+ let start = 0;
2098
+ for (let i = 0; i < prelude.length; i++) {
2099
+ const skip = skipNonCode(prelude, i);
2100
+ if (skip !== -1) {
2101
+ i = skip - 1;
2102
+ continue;
2103
+ }
2104
+ const ch = prelude[i];
2105
+ if (ch === "(") parens++;
2106
+ else if (ch === ")") parens--;
2107
+ else if (ch === "[") brackets++;
2108
+ else if (ch === "]") brackets--;
2109
+ else if (ch === "," && parens === 0 && brackets === 0) {
2110
+ parts.push(prelude.slice(start, i));
2111
+ start = i + 1;
2112
+ }
2113
+ }
2114
+ parts.push(prelude.slice(start));
2115
+ return parts;
2116
+ }
2117
+ function findBlockEnd(css, openBrace) {
2118
+ let depth = 0;
2119
+ for (let i = openBrace; i < css.length; i++) {
2120
+ const skip = skipNonCode(css, i);
2121
+ if (skip !== -1) {
2122
+ i = skip - 1;
2123
+ continue;
2124
+ }
2125
+ const ch = css[i];
2126
+ if (ch === "{") depth++;
2127
+ else if (ch === "}") {
2128
+ depth--;
2129
+ if (depth === 0) return i;
2130
+ }
2131
+ }
2132
+ return css.length;
2133
+ }
2134
+ var NESTED_AT_RULE = /^@(media|supports|layer|container|scope|document)\b/i;
2135
+ function scopeStylesheet(css, attr) {
2136
+ let out = "";
2137
+ let preludeStart = 0;
2138
+ let parens = 0;
2139
+ let brackets = 0;
2140
+ let i = 0;
2141
+ while (i < css.length) {
2142
+ const skip = skipNonCode(css, i);
2143
+ if (skip !== -1) {
2144
+ i = skip;
2145
+ continue;
2146
+ }
2147
+ const ch = css[i];
2148
+ if (ch === "(") {
2149
+ parens++;
2150
+ i++;
2151
+ continue;
2152
+ }
2153
+ if (ch === ")") {
2154
+ parens--;
2155
+ i++;
2156
+ continue;
2157
+ }
2158
+ if (ch === "[") {
2159
+ brackets++;
2160
+ i++;
2161
+ continue;
2162
+ }
2163
+ if (ch === "]") {
2164
+ brackets--;
2165
+ i++;
2166
+ continue;
2167
+ }
2168
+ if (ch === "{" && parens === 0 && brackets === 0) {
2169
+ const prelude = css.slice(preludeStart, i);
2170
+ const bodyEnd = findBlockEnd(css, i);
2171
+ const body = css.slice(i + 1, bodyEnd);
2172
+ const trimmed = prelude.trim();
2173
+ const lead = prelude.slice(0, prelude.length - prelude.trimStart().length);
2174
+ if (trimmed === "") {
2175
+ out += `${prelude}{${body}}`;
2176
+ } else if (trimmed.startsWith("@")) {
2177
+ out += NESTED_AT_RULE.test(trimmed) ? `${prelude}{${scopeStylesheet(body, attr)}}` : `${prelude}{${body}}`;
2178
+ } else {
2179
+ const scoped = splitSelectorList(trimmed).map((part) => {
2180
+ const selector = part.trim();
2181
+ return selector ? scopeSelector(selector, attr) : part;
2182
+ }).join(", ");
2183
+ out += `${lead}${scoped}{${body}}`;
2184
+ }
2185
+ i = bodyEnd + 1;
2186
+ preludeStart = i;
2187
+ continue;
2188
+ }
2189
+ i++;
2190
+ }
2191
+ out += css.slice(preludeStart);
2192
+ return out;
2193
+ }
1894
2194
  function scopedStyle(css) {
1895
2195
  const id = `sibu-s${_scope.n++}`;
1896
2196
  const attr = `data-${id}`;
1897
2197
  const safeCss = sanitizeCSS(css);
1898
- const scopedCSS = safeCss.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, (match, selector, delimiter) => {
1899
- const trimmed = selector.trim();
1900
- if (trimmed.startsWith("@") || trimmed.startsWith("from") || trimmed.startsWith("to") || /^\d+%$/.test(trimmed)) {
1901
- return match;
1902
- }
1903
- const pseudoIdx = trimmed.indexOf("::");
1904
- if (pseudoIdx >= 0) {
1905
- return `${trimmed.slice(0, pseudoIdx)}[${attr}]${trimmed.slice(pseudoIdx)}${delimiter}`;
1906
- }
1907
- return `${trimmed}[${attr}]${delimiter}`;
1908
- });
2198
+ const scopedCSS = scopeStylesheet(safeCss, attr);
1909
2199
  if (typeof document !== "undefined") {
1910
2200
  const styleEl = document.createElement("style");
1911
2201
  styleEl.setAttribute("data-sibu-scope", id);
@@ -1921,18 +2211,10 @@ function withScopedStyle(css, component) {
1921
2211
  style = scopedStyle(css);
1922
2212
  }
1923
2213
  const el = component(props);
1924
- applyScopeRecursive(el, style.attr);
2214
+ el.setAttribute(style.attr, "");
1925
2215
  return el;
1926
2216
  };
1927
2217
  }
1928
- function applyScopeRecursive(element, attr) {
1929
- element.setAttribute(attr, "");
1930
- for (const child of Array.from(element.children)) {
1931
- if (child instanceof HTMLElement) {
1932
- applyScopeRecursive(child, attr);
1933
- }
1934
- }
1935
- }
1936
2218
  function removeScopedStyle(scopeId) {
1937
2219
  const el = document.head.querySelector(`style[data-sibu-scope="${scopeId}"]`);
1938
2220
  if (el) el.remove();
@@ -1948,10 +2230,12 @@ function isEventHandlerAttr(name) {
1948
2230
  return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
1949
2231
  }
1950
2232
  var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2233
+ var OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
1951
2234
  function sanitizeUrl(url) {
1952
- const trimmed = stripControlChars(url).trim();
1953
- if (!trimmed) return "";
1954
- const lower = trimmed.toLowerCase();
2235
+ const probe = stripControlChars(url).trim();
2236
+ if (!probe) return "";
2237
+ const output = url.replace(OUTER_TRIM, "");
2238
+ const lower = probe.toLowerCase();
1955
2239
  let schemeEnd = -1;
1956
2240
  for (let i = 0; i < lower.length; i++) {
1957
2241
  const ch = lower.charCodeAt(i);
@@ -1961,12 +2245,13 @@ function sanitizeUrl(url) {
1961
2245
  }
1962
2246
  if (ch === 47 || ch === 63 || ch === 35) break;
1963
2247
  }
1964
- if (schemeEnd === -1) return trimmed;
2248
+ if (schemeEnd === -1) return output;
1965
2249
  const scheme = lower.slice(0, schemeEnd + 1);
1966
- if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
2250
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return output;
1967
2251
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1968
- return trimmed;
2252
+ return output;
1969
2253
  }
2254
+ var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
1970
2255
  function sanitizeSrcset(value) {
1971
2256
  const parts = value.split(",");
1972
2257
  const out = [];
@@ -1975,12 +2260,91 @@ function sanitizeSrcset(value) {
1975
2260
  if (!part) continue;
1976
2261
  const m = part.match(/^(\S+)(\s+.+)?$/);
1977
2262
  if (!m) continue;
2263
+ if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
1978
2264
  const safe = sanitizeUrl(m[1]);
1979
2265
  if (!safe) continue;
1980
2266
  out.push(m[2] ? `${safe}${m[2]}` : safe);
1981
2267
  }
1982
2268
  return out.join(", ");
1983
2269
  }
2270
+ var CSS_DANGER_GATE = /[(:@\\]/;
2271
+ function isCssWhitespace(ch) {
2272
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
2273
+ }
2274
+ function isHexDigit(ch) {
2275
+ return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
2276
+ }
2277
+ function decodeCssEscapes2(value) {
2278
+ let out = "";
2279
+ for (let i = 0; i < value.length; i++) {
2280
+ const ch = value[i];
2281
+ if (ch !== "\\") {
2282
+ out += ch;
2283
+ continue;
2284
+ }
2285
+ if (i + 1 >= value.length) break;
2286
+ const next = value[i + 1];
2287
+ if (next === "\n" || next === "\f") {
2288
+ i += 1;
2289
+ continue;
2290
+ }
2291
+ if (next === "\r") {
2292
+ i += value[i + 2] === "\n" ? 2 : 1;
2293
+ continue;
2294
+ }
2295
+ if (isHexDigit(next)) {
2296
+ let hex = "";
2297
+ let j = i + 1;
2298
+ while (j < value.length && hex.length < 6 && isHexDigit(value[j])) {
2299
+ hex += value[j];
2300
+ j++;
2301
+ }
2302
+ if (j < value.length && isCssWhitespace(value[j])) {
2303
+ j += value[j] === "\r" && value[j + 1] === "\n" ? 2 : 1;
2304
+ }
2305
+ const code = Number.parseInt(hex, 16);
2306
+ const invalid = code === 0 || code > 1114111 || code >= 55296 && code <= 57343;
2307
+ out += invalid ? "\uFFFD" : String.fromCodePoint(code);
2308
+ i = j - 1;
2309
+ continue;
2310
+ }
2311
+ out += next;
2312
+ i += 1;
2313
+ }
2314
+ return out;
2315
+ }
2316
+ function sanitizeCSSValue(value) {
2317
+ if (!CSS_DANGER_GATE.test(value)) return value;
2318
+ const normalized = value.includes("\\") ? decodeCssEscapes2(value) : value;
2319
+ const lower = normalized.toLowerCase().replace(/\s+/g, "");
2320
+ if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
2321
+ return "";
2322
+ }
2323
+ return value;
2324
+ }
2325
+ function sanitizeStyleAttribute(cssText) {
2326
+ const input = String(cssText);
2327
+ if (input.trim() === "") return "";
2328
+ if (typeof document === "undefined") {
2329
+ return sanitizeCSSValue(input) === "" ? "" : input;
2330
+ }
2331
+ const probe = document.createElement("div");
2332
+ try {
2333
+ probe.style.cssText = input;
2334
+ } catch {
2335
+ return "";
2336
+ }
2337
+ const declarations = [];
2338
+ for (let i = 0; i < probe.style.length; i++) {
2339
+ const property = probe.style[i];
2340
+ const value = probe.style.getPropertyValue(property);
2341
+ if (sanitizeCSSValue(value) === "") continue;
2342
+ if (sanitizeCSSValue(`${property}:${value}`) === "") continue;
2343
+ const priority = probe.style.getPropertyPriority(property);
2344
+ declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
2345
+ }
2346
+ return declarations.join("; ");
2347
+ }
1984
2348
  var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1985
2349
  "href",
1986
2350
  "xlink:href",
@@ -1993,26 +2357,104 @@ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1993
2357
  "background",
1994
2358
  "srcset",
1995
2359
  "ping",
1996
- "data"
2360
+ "data",
2361
+ // `<html manifest>` is a URL sink too. It was carried only by the SSR
2362
+ // serializers' private copy of this set; folding that copy into this one would
2363
+ // otherwise have silently dropped its coverage.
2364
+ "manifest"
1997
2365
  ]);
2366
+ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
2367
+ function isHtmlContentAttribute(name) {
2368
+ return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
2369
+ }
1998
2370
  function sanitizeAttributeString(attr, value) {
1999
2371
  const lower = attr.toLowerCase();
2000
2372
  if (lower === "srcset") return sanitizeSrcset(value);
2001
2373
  if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
2374
+ if (lower === "style") return sanitizeStyleAttribute(value);
2002
2375
  return value;
2003
2376
  }
2004
2377
 
2005
- // src/reactivity/bindAttribute.ts
2006
- var _isDev6 = isDev();
2378
+ // src/utils/setSafeAttribute.ts
2379
+ var _isDev5 = isDev();
2380
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
2381
+ var SVG_NS = "http://www.w3.org/2000/svg";
2382
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
2383
+ var XML_NS = "http://www.w3.org/XML/1998/namespace";
2384
+ var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
2385
+ function isHtmlElement(el) {
2386
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS;
2387
+ }
2007
2388
  function setProp(el, key, val) {
2008
2389
  el[key] = val;
2009
2390
  }
2010
- function bindAttribute(el, attr, getter) {
2011
- if (isEventHandlerAttr(attr)) {
2012
- if (_isDev6)
2391
+ function namespaceFor(el, name) {
2392
+ if (el.namespaceURI !== SVG_NS) return null;
2393
+ const lower = name.toLowerCase();
2394
+ if (lower.startsWith("xlink:")) return XLINK_NS;
2395
+ if (lower.startsWith("xml:")) return XML_NS;
2396
+ return null;
2397
+ }
2398
+ function setSafeAttribute(el, name, value, options = {}) {
2399
+ const ns = namespaceFor(el, name);
2400
+ const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2401
+ if (isEventHandlerAttr(name)) {
2402
+ if (_isDev5) {
2013
2403
  devWarn(
2014
- `bindAttribute: refusing to bind event-handler attribute "${attr}". Use on:{ ${attr.slice(2)}: fn } instead.`
2404
+ `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
2015
2405
  );
2406
+ }
2407
+ if (ns) el.removeAttributeNS(ns, localName);
2408
+ else el.removeAttribute(name);
2409
+ return false;
2410
+ }
2411
+ if (value == null) {
2412
+ if (ns) el.removeAttributeNS(ns, localName);
2413
+ else el.removeAttribute(name);
2414
+ return true;
2415
+ }
2416
+ if (isHtmlContentAttribute(name)) {
2417
+ if (_isDev5) {
2418
+ devWarn(
2419
+ `${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.`
2420
+ );
2421
+ }
2422
+ if (ns) el.removeAttributeNS(ns, localName);
2423
+ else el.removeAttribute(name);
2424
+ return false;
2425
+ }
2426
+ const html = isHtmlElement(el);
2427
+ const idlName = html ? name.toLowerCase() : name;
2428
+ if (typeof value === "boolean") {
2429
+ if (html && BOOLEAN_IDL_ATTRS.has(idlName) && idlName in el) {
2430
+ setProp(el, idlName, value);
2431
+ } else if (value) {
2432
+ if (ns) el.setAttributeNS(ns, name, "");
2433
+ else el.setAttribute(name, "");
2434
+ } else if (ns) {
2435
+ el.removeAttributeNS(ns, localName);
2436
+ } else {
2437
+ el.removeAttribute(name);
2438
+ }
2439
+ return true;
2440
+ }
2441
+ const str = String(value);
2442
+ if (options.syncValueProperty !== false && html && (idlName === "value" || idlName === "checked") && idlName in el) {
2443
+ setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
2444
+ return true;
2445
+ }
2446
+ const safe = sanitizeAttributeString(name, str);
2447
+ const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
2448
+ if (current === safe) return true;
2449
+ if (ns) el.setAttributeNS(ns, name, safe);
2450
+ else el.setAttribute(name, safe);
2451
+ return true;
2452
+ }
2453
+
2454
+ // src/reactivity/bindAttribute.ts
2455
+ function bindAttribute(el, attr, getter) {
2456
+ if (isEventHandlerAttr(attr)) {
2457
+ setSafeAttribute(el, attr, "", { label: `bindAttribute("${attr}")` });
2016
2458
  return () => {
2017
2459
  };
2018
2460
  }
@@ -2021,28 +2463,12 @@ function bindAttribute(el, attr, getter) {
2021
2463
  try {
2022
2464
  value = getter();
2023
2465
  } catch (err) {
2024
- if (_isDev6)
2025
- devWarn(`bindAttribute: getter for "${attr}" threw: ${err instanceof Error ? err.message : String(err)}`);
2026
- return;
2027
- }
2028
- if (typeof value === "boolean") {
2029
- if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
2030
- setProp(el, attr, value);
2031
- } else if (value) {
2032
- el.setAttribute(attr, "");
2033
- } else {
2034
- el.removeAttribute(attr);
2035
- }
2466
+ reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
2036
2467
  return;
2037
2468
  }
2038
- const str = String(value);
2039
- if ((attr === "value" || attr === "checked") && attr in el) {
2040
- setProp(el, attr, attr === "checked" ? Boolean(value) : str);
2041
- } else {
2042
- el.setAttribute(attr, sanitizeAttributeString(attr, str));
2043
- }
2469
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
2044
2470
  }
2045
- return reactiveBinding2(commit);
2471
+ return reactiveBinding2(commit, el);
2046
2472
  }
2047
2473
 
2048
2474
  // src/ui/reactiveAttr.ts
@@ -2052,14 +2478,8 @@ function bindAttrs(el, attrs) {
2052
2478
  if (typeof value === "function") {
2053
2479
  const teardown = bindAttribute(el, attr, value);
2054
2480
  teardowns.push(teardown);
2055
- } else if (typeof value === "boolean") {
2056
- if (value) {
2057
- el.setAttribute(attr, "");
2058
- } else {
2059
- el.removeAttribute(attr);
2060
- }
2061
2481
  } else {
2062
- el.setAttribute(attr, String(value));
2482
+ setSafeAttribute(el, attr, value, { label: "bindAttrs" });
2063
2483
  }
2064
2484
  }
2065
2485
  return () => {
@@ -2070,11 +2490,7 @@ function bindAttrs(el, attrs) {
2070
2490
  }
2071
2491
  function bindBoolAttr(el, attr, getter) {
2072
2492
  if (typeof getter !== "function") {
2073
- if (getter) {
2074
- el.setAttribute(attr, "");
2075
- } else {
2076
- el.removeAttribute(attr);
2077
- }
2493
+ setSafeAttribute(el, attr, getter, { label: "bindBoolAttr" });
2078
2494
  return () => {
2079
2495
  };
2080
2496
  }
@@ -2086,11 +2502,7 @@ function bindBoolAttr(el, attr, getter) {
2086
2502
  } catch {
2087
2503
  return;
2088
2504
  }
2089
- if (value) {
2090
- el.setAttribute(attr, "");
2091
- } else {
2092
- el.removeAttribute(attr);
2093
- }
2505
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
2094
2506
  }
2095
2507
  const teardown = track2(commit);
2096
2508
  return teardown;
@@ -2098,7 +2510,7 @@ function bindBoolAttr(el, attr, getter) {
2098
2510
  function bindData(el, key, getter) {
2099
2511
  const dataAttr = `data-${key}`;
2100
2512
  if (typeof getter !== "function") {
2101
- el.setAttribute(dataAttr, String(getter));
2513
+ setSafeAttribute(el, dataAttr, getter, { label: "bindData" });
2102
2514
  return () => {
2103
2515
  };
2104
2516
  }
@@ -2238,31 +2650,58 @@ function infiniteScroll(options) {
2238
2650
  const sentinelRef = { current: null };
2239
2651
  let observer = null;
2240
2652
  let disposed = false;
2653
+ let generation = 0;
2654
+ function safeHasMore() {
2655
+ try {
2656
+ return hasMore();
2657
+ } catch (err) {
2658
+ reportError(err, { phase: "async", name: "infiniteScroll(hasMore)" });
2659
+ return false;
2660
+ }
2661
+ }
2662
+ function ownsAttachment(owner) {
2663
+ return !disposed && generation === owner.generation && observer === owner.observer && _current === owner.sentinel;
2664
+ }
2241
2665
  function createObserver() {
2242
2666
  if (typeof IntersectionObserver === "undefined") return;
2243
- observer = new IntersectionObserver(
2667
+ let createdObserver;
2668
+ createdObserver = new IntersectionObserver(
2244
2669
  (entries) => {
2245
2670
  const entry = entries[0];
2246
- if (entry?.isIntersecting && !loading() && hasMore() && !disposed) {
2247
- loadMore();
2248
- }
2671
+ if (!entry?.isIntersecting) return;
2672
+ const sentinel = _current;
2673
+ if (!sentinel) return;
2674
+ const owner = { generation, observer: createdObserver, sentinel };
2675
+ if (!ownsAttachment(owner) || loading()) return;
2676
+ const shouldLoad = safeHasMore();
2677
+ if (!shouldLoad || !ownsAttachment(owner)) return;
2678
+ void loadMore(owner);
2249
2679
  },
2250
2680
  { threshold }
2251
2681
  );
2682
+ observer = createdObserver;
2252
2683
  if (sentinelRef.current) {
2253
- observer.observe(sentinelRef.current);
2684
+ createdObserver.observe(sentinelRef.current);
2254
2685
  }
2255
2686
  }
2256
- async function loadMore() {
2257
- setLoading(true);
2687
+ async function loadMore(owner) {
2258
2688
  try {
2259
- await onLoadMore();
2260
- } finally {
2261
- setLoading(false);
2262
- if (!disposed && observer && _current && hasMore()) {
2263
- observer.unobserve(_current);
2264
- observer.observe(_current);
2689
+ setLoading(true);
2690
+ if (!ownsAttachment(owner)) return;
2691
+ try {
2692
+ await onLoadMore();
2693
+ } catch (err) {
2694
+ reportError(err, { phase: "async", name: "infiniteScroll(onLoadMore)" });
2265
2695
  }
2696
+ if (!ownsAttachment(owner)) return;
2697
+ setLoading(false);
2698
+ if (!ownsAttachment(owner)) return;
2699
+ const shouldContinue = safeHasMore();
2700
+ if (!shouldContinue || !ownsAttachment(owner)) return;
2701
+ owner.observer.unobserve(owner.sentinel);
2702
+ owner.observer.observe(owner.sentinel);
2703
+ } catch (err) {
2704
+ reportError(err, { phase: "async", name: "infiniteScroll(loadMore)" });
2266
2705
  }
2267
2706
  }
2268
2707
  const originalRef = sentinelRef;
@@ -2272,7 +2711,10 @@ function infiniteScroll(options) {
2272
2711
  return _current;
2273
2712
  },
2274
2713
  set(el) {
2714
+ if (el === _current) return;
2275
2715
  _current = el;
2716
+ generation++;
2717
+ setLoading(false);
2276
2718
  if (observer) {
2277
2719
  observer.disconnect();
2278
2720
  observer = null;
@@ -2285,10 +2727,12 @@ function infiniteScroll(options) {
2285
2727
  });
2286
2728
  function dispose2() {
2287
2729
  disposed = true;
2730
+ generation++;
2288
2731
  if (observer) {
2289
2732
  observer.disconnect();
2290
2733
  observer = null;
2291
2734
  }
2735
+ setLoading(false);
2292
2736
  }
2293
2737
  return { sentinelRef: originalRef, loading, dispose: dispose2 };
2294
2738
  }
@@ -2493,6 +2937,7 @@ function scrollLock() {
2493
2937
  }
2494
2938
 
2495
2939
  // src/platform/customElement.ts
2940
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2496
2941
  function defineElement(name, component, options = {}) {
2497
2942
  if (customElements.get(name)) return;
2498
2943
  const observed = options.observedAttributes || [];
@@ -2521,11 +2966,8 @@ function defineElement(name, component, options = {}) {
2521
2966
  }
2522
2967
  }
2523
2968
  _teardown() {
2524
- if (this._rendered) {
2525
- dispose(this._rendered);
2526
- this._rendered = null;
2527
- }
2528
- this._root.replaceChildren();
2969
+ this._rendered = null;
2970
+ replaceChildrenSafely(this._root);
2529
2971
  }
2530
2972
  _render() {
2531
2973
  this._teardown();
@@ -2550,14 +2992,13 @@ function defineElement(name, component, options = {}) {
2550
2992
  customElements.define(name, SibuElement);
2551
2993
  }
2552
2994
  function svgElement(tag, props = {}, ...nodes) {
2553
- const SVG_NS = "http://www.w3.org/2000/svg";
2554
- const el = document.createElementNS(SVG_NS, tag);
2995
+ const el = document.createElementNS(SVG_NS2, tag);
2555
2996
  for (const [key, value] of Object.entries(props)) {
2556
2997
  if (key === "nodes") continue;
2557
- if (typeof value === "function" && key.startsWith("on")) {
2998
+ if (typeof value === "function" && isEventHandlerAttr(key)) {
2558
2999
  el.addEventListener(key.slice(2).toLowerCase(), value);
2559
3000
  } else if (value != null) {
2560
- el.setAttribute(key, String(value));
3001
+ setSafeAttribute(el, key, value, { label: "svgElement" });
2561
3002
  }
2562
3003
  }
2563
3004
  for (const child of nodes) {