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
@@ -53,8 +53,95 @@ function devWarn(message) {
53
53
  }
54
54
  }
55
55
 
56
+ // src/core/errors.ts
57
+ var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
58
+ function state() {
59
+ const g = globalThis;
60
+ const existing = g[ERROR_STATE_KEY];
61
+ if (existing) return existing;
62
+ const created = { handler: null, handlerDepth: 0 };
63
+ g[ERROR_STATE_KEY] = created;
64
+ return created;
65
+ }
66
+ function normalizeError(value) {
67
+ if (value instanceof Error) return value;
68
+ const message = typeof value === "string" ? value : `Non-Error value thrown: ${safeStringify(value)}`;
69
+ return new Error(message, { cause: value });
70
+ }
71
+ function safeStringify(value) {
72
+ if (typeof value === "symbol") return value.toString();
73
+ try {
74
+ return typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
75
+ } catch {
76
+ return Object.prototype.toString.call(value);
77
+ }
78
+ }
79
+ function describe(context) {
80
+ return context.name ? `${context.phase} "${context.name}"` : context.phase;
81
+ }
82
+ var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
83
+ function untrackedRun(fn) {
84
+ const api = globalThis[REACTIVE_REGISTRY_KEY];
85
+ return api?.untracked ? api.untracked(fn) : fn();
86
+ }
87
+ var PROPAGATE_EVENT = "sibu:error-propagate";
88
+ function claimedByBoundary(error, context) {
89
+ const raw = context.node;
90
+ if (!raw || typeof CustomEvent === "undefined") return false;
91
+ const target = resolveEventTarget(raw);
92
+ if (!target) return false;
93
+ try {
94
+ const event = new CustomEvent(PROPAGATE_EVENT, {
95
+ bubbles: true,
96
+ cancelable: true,
97
+ detail: { error, context }
98
+ });
99
+ return target.dispatchEvent(event) === false;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ function resolveEventTarget(node) {
105
+ const ELEMENT_NODE = 1;
106
+ let current = node;
107
+ while (current) {
108
+ if (current.nodeType === ELEMENT_NODE && typeof current.dispatchEvent === "function") {
109
+ return current;
110
+ }
111
+ current = current.parentNode;
112
+ }
113
+ const self = node;
114
+ return typeof self.dispatchEvent === "function" ? self : null;
115
+ }
116
+ function defaultReport(error, context) {
117
+ if (typeof console === "undefined") return;
118
+ console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
119
+ }
120
+ function reportError(error, context) {
121
+ const err = normalizeError(error);
122
+ untrackedRun(() => {
123
+ if (claimedByBoundary(err, context)) return;
124
+ const s = state();
125
+ const handler = s.handler;
126
+ if (handler && s.handlerDepth === 0) {
127
+ s.handlerDepth++;
128
+ try {
129
+ handler(error instanceof Error ? error : err, context);
130
+ return;
131
+ } catch (handlerError) {
132
+ if (typeof console !== "undefined") {
133
+ const detail = handlerError instanceof Error ? handlerError.message : String(handlerError);
134
+ console.error(`[SibuJS] runtime error handler itself threw: ${detail}`, handlerError);
135
+ }
136
+ } finally {
137
+ s.handlerDepth--;
138
+ }
139
+ }
140
+ defaultReport(err, context);
141
+ });
142
+ }
143
+
56
144
  // src/reactivity/track-core.ts
57
- var _isDev2 = isDev();
58
145
  var POOL_MAX = 4096;
59
146
  var nodePool = [];
60
147
  function createNode() {
@@ -62,6 +149,7 @@ function createNode() {
62
149
  sig: null,
63
150
  sub: null,
64
151
  epoch: 0,
152
+ depVersion: 0,
65
153
  sigPrev: null,
66
154
  sigNext: null,
67
155
  subPrev: null,
@@ -146,8 +234,27 @@ function safeInvoke(sub) {
146
234
  try {
147
235
  sub();
148
236
  } catch (err) {
149
- if (_isDev2) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
237
+ const s = sub;
238
+ reportError(err, { phase: s._errorPhase ?? "effect", name: s.__name, node: s._errorNode });
239
+ }
240
+ }
241
+ function depsChanged(sub) {
242
+ let node = sub.depsHead ?? null;
243
+ if (node === null) return true;
244
+ while (node !== null) {
245
+ const sig = node.sig;
246
+ if (sig === null) return true;
247
+ if (sig._d === true && sig._validate !== void 0) {
248
+ try {
249
+ sig._validate();
250
+ } catch {
251
+ return true;
252
+ }
253
+ }
254
+ if (node.depVersion !== sig.__v) return true;
255
+ node = node.subNext;
150
256
  }
257
+ return false;
151
258
  }
152
259
  var suspendDepth = 0;
153
260
  var trackingSuspended = false;
@@ -229,7 +336,7 @@ function track(effectFn, subscriber) {
229
336
  const sub = subscriber;
230
337
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
231
338
  }
232
- function reactiveBinding(commit) {
339
+ function reactiveBinding(commit, ownerNode) {
233
340
  const run = () => {
234
341
  const s = subscriber;
235
342
  if (s._disposed || s._reentrant) return;
@@ -245,13 +352,17 @@ function reactiveBinding(commit) {
245
352
  subscriber.depsTail = null;
246
353
  subscriber._epoch = 0;
247
354
  subscriber._structDirty = false;
355
+ subscriber._hasComputedDep = false;
248
356
  subscriber._runEpoch = 0;
249
357
  subscriber._runs = 0;
250
358
  subscriber._reentrant = false;
251
359
  subscriber._disposed = false;
360
+ subscriber._errorPhase = "binding";
361
+ subscriber._errorNode = ownerNode;
252
362
  run();
253
363
  return subscriber._dispose ?? (subscriber._dispose = () => {
254
364
  subscriber._disposed = true;
365
+ subscriber._errorNode = void 0;
255
366
  cleanup(subscriber);
256
367
  });
257
368
  }
@@ -263,9 +374,12 @@ function recordDependency(signal2) {
263
374
  const active = sig.__activeNode ?? null;
264
375
  if (active !== null && active.sub === sub) {
265
376
  active.epoch = epoch;
377
+ active.depVersion = sig.__v ?? Number.NaN;
266
378
  return;
267
379
  }
380
+ if (sig._validate !== void 0) sub._hasComputedDep = true;
268
381
  const node = allocNode(signal2, sub, epoch);
382
+ node.depVersion = sig.__v ?? Number.NaN;
269
383
  node.prevActive = active;
270
384
  sig.__activeNode = node;
271
385
  linkSub(sub, node);
@@ -284,9 +398,10 @@ function cleanup(subscriber) {
284
398
  node = next;
285
399
  }
286
400
  }
287
- var maxSubscriberRepeats = 50;
401
+ var maxSubscriberRepeats = 1e3;
288
402
  var maxDrainIterations = 1e6;
289
403
  var drainEpoch = 0;
404
+ var quarantined = /* @__PURE__ */ new Set();
290
405
  function setMaxSubscriberRepeats(n) {
291
406
  const prev = maxSubscriberRepeats;
292
407
  if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
@@ -308,19 +423,21 @@ function tickRepeat(sub) {
308
423
  return s._runs > maxSubscriberRepeats;
309
424
  }
310
425
  function cycleError(sub) {
311
- if (typeof console !== "undefined") {
312
- const name = sub.__name ?? "<unnamed>";
313
- console.error(
314
- `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
315
- );
316
- }
426
+ const name = sub.__name ?? "<unnamed>";
427
+ reportError(
428
+ new Error(
429
+ `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.`
430
+ ),
431
+ { phase: "scheduler", name }
432
+ );
317
433
  }
318
434
  function absoluteDrainError() {
319
- if (typeof console !== "undefined") {
320
- console.error(
321
- `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
322
- );
323
- }
435
+ reportError(
436
+ new Error(
437
+ `Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
438
+ ),
439
+ { phase: "scheduler" }
440
+ );
324
441
  }
325
442
  function drainQueue() {
326
443
  let i = 0;
@@ -330,11 +447,14 @@ function drainQueue() {
330
447
  break;
331
448
  }
332
449
  const sub = pendingQueue[i++];
450
+ pendingSet.delete(sub);
451
+ if (quarantined.size > 0 && quarantined.has(sub)) continue;
452
+ if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
333
453
  if (tickRepeat(sub)) {
334
454
  cycleError(sub);
335
- break;
455
+ quarantined.add(sub);
456
+ continue;
336
457
  }
337
- pendingSet.delete(sub);
338
458
  safeInvoke(sub);
339
459
  }
340
460
  }
@@ -349,6 +469,7 @@ function drainNotificationQueue() {
349
469
  if (notifyDepth === 0) {
350
470
  pendingQueue.length = 0;
351
471
  pendingSet.clear();
472
+ if (quarantined.size > 0) quarantined.clear();
352
473
  }
353
474
  }
354
475
  }
@@ -442,6 +563,7 @@ function notifySubscribers(signal2) {
442
563
  if (notifyDepth === 0) {
443
564
  pendingQueue.length = 0;
444
565
  pendingSet.clear();
566
+ if (quarantined.size > 0) quarantined.clear();
445
567
  }
446
568
  }
447
569
  }
@@ -468,14 +590,14 @@ function forEachSubscriber(signal2, visit) {
468
590
  }
469
591
 
470
592
  // src/reactivity/track.ts
471
- var _isDev3 = isDev();
472
- var _runtimeVersion = true ? "3.4.0" : "dev";
593
+ var _isDev2 = isDev();
594
+ var _runtimeVersion = true ? "4.0.0" : "dev";
473
595
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
474
596
  function resolveReactiveApi() {
475
597
  const g = globalThis;
476
598
  const existing = g[REGISTRY_KEY];
477
599
  if (existing) {
478
- if (_isDev3 && !existing.__dupWarned) {
600
+ if (_isDev2 && !existing.__dupWarned) {
479
601
  existing.__dupWarned = true;
480
602
  devWarn(
481
603
  `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']).`
@@ -535,20 +657,24 @@ function detectSSRShared() {
535
657
  const getBuiltin = process.getBuiltinModule;
536
658
  if (typeof getBuiltin === "function") {
537
659
  mod = getBuiltin("node:async_hooks");
538
- } else {
539
- const req = Function("return typeof require==='function'?require:null")();
540
- if (req) mod = req("node:async_hooks");
660
+ } else if (typeof require === "function") {
661
+ mod = require("async_hooks");
541
662
  }
542
663
  if (mod) detected = new mod.AsyncLocalStorage();
543
664
  }
544
665
  } catch {
545
666
  detected = null;
546
667
  }
547
- return { als: detected, fallbackStore: { ssr: false, suspenseIdCounter: 0 } };
668
+ return {
669
+ als: detected,
670
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
671
+ fallbackDepth: 0
672
+ };
548
673
  }
549
674
  var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
550
675
  var als = _shared.als;
551
676
  var fallbackStore = _shared.fallbackStore;
677
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
552
678
  function getSSRStore() {
553
679
  if (als) {
554
680
  const s = als.getStore();
@@ -571,7 +697,7 @@ function flushUserCleanups(ctx) {
571
697
  try {
572
698
  list[i]();
573
699
  } catch (err) {
574
- if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
700
+ reportError(err, { phase: "cleanup" });
575
701
  }
576
702
  }
577
703
  }
@@ -584,11 +710,12 @@ function drainReruns(ctx) {
584
710
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
585
711
  if (ctx.rerunPending) {
586
712
  ctx.rerunPending = false;
587
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
588
- console.error(
589
- `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
590
- );
591
- }
713
+ reportError(
714
+ new Error(
715
+ `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
716
+ ),
717
+ { phase: "effect" }
718
+ );
592
719
  }
593
720
  }
594
721
  function disposeEffect(ctx) {
@@ -665,6 +792,8 @@ function effect(effectFn, options) {
665
792
  sub.depsTail = null;
666
793
  sub._epoch = 0;
667
794
  sub._structDirty = false;
795
+ sub._hasComputedDep = false;
796
+ sub._errorPhase = "effect";
668
797
  sub._runEpoch = 0;
669
798
  sub._runs = 0;
670
799
  ctx.subscriber = sub;
@@ -733,70 +862,72 @@ var isBatching = API2.isBatching;
733
862
 
734
863
  // src/core/signals/signal.ts
735
864
  var _g2 = globalThis;
736
- var _isDev4 = isDev();
865
+ var _isDev3 = isDev();
737
866
  function signal(initial, options) {
738
- const state = {
867
+ const state2 = {
739
868
  value: initial,
740
869
  __v: 0,
741
870
  __sc: 0,
742
871
  subsHead: null,
743
872
  subsTail: null,
744
873
  __activeNode: null,
745
- __name: void 0
874
+ __name: void 0,
875
+ _d: false,
876
+ _validate: void 0
746
877
  };
747
- const debugName = _isDev4 ? options?.name : void 0;
878
+ const debugName = _isDev3 ? options?.name : void 0;
748
879
  const equalsFn = options?.equals;
749
- if (debugName) state.__name = debugName;
880
+ if (debugName) state2.__name = debugName;
750
881
  function get() {
751
- recordDependency2(state);
752
- return state.value;
882
+ recordDependency2(state2);
883
+ return state2.value;
753
884
  }
754
- get.__signal = state;
885
+ get.__signal = state2;
755
886
  if (debugName) get.__name = debugName;
756
887
  let set;
757
888
  if (equalsFn) {
758
889
  set = (next) => {
759
- const prev = state.value;
890
+ const prev = state2.value;
760
891
  const newValue = typeof next === "function" ? next(prev) : next;
761
892
  if (equalsFn(prev, newValue)) return;
762
- state.value = newValue;
763
- state.__v++;
764
- if (_isDev4) {
893
+ state2.value = newValue;
894
+ state2.__v++;
895
+ if (_isDev3) {
765
896
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
766
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
897
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
767
898
  }
768
- if (!enqueueBatchedSignal(state)) {
769
- notifySubscribers2(state);
899
+ if (!enqueueBatchedSignal(state2)) {
900
+ notifySubscribers2(state2);
770
901
  }
771
902
  };
772
- } else if (_isDev4) {
903
+ } else if (_isDev3) {
773
904
  set = (next) => {
774
- const prev = state.value;
905
+ const prev = state2.value;
775
906
  const newValue = typeof next === "function" ? next(prev) : next;
776
907
  if (Object.is(newValue, prev)) return;
777
- state.value = newValue;
778
- state.__v++;
908
+ state2.value = newValue;
909
+ state2.__v++;
779
910
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
780
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
781
- if (!enqueueBatchedSignal(state)) {
782
- notifySubscribers2(state);
911
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
912
+ if (!enqueueBatchedSignal(state2)) {
913
+ notifySubscribers2(state2);
783
914
  }
784
915
  };
785
916
  } else {
786
917
  set = (next) => {
787
- const prev = state.value;
918
+ const prev = state2.value;
788
919
  const newValue = typeof next === "function" ? next(prev) : next;
789
920
  if (Object.is(newValue, prev)) return;
790
- state.value = newValue;
791
- state.__v++;
792
- if (!enqueueBatchedSignal(state)) {
793
- notifySubscribers2(state);
921
+ state2.value = newValue;
922
+ state2.__v++;
923
+ if (!enqueueBatchedSignal(state2)) {
924
+ notifySubscribers2(state2);
794
925
  }
795
926
  };
796
927
  }
797
- if (_isDev4) {
928
+ if (_isDev3) {
798
929
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
799
- if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
930
+ if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
800
931
  }
801
932
  return [get, set];
802
933
  }
@@ -975,6 +1106,21 @@ function derived(getter, options) {
975
1106
  }, markDirty);
976
1107
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
977
1108
  let evaluating = false;
1109
+ const validate = () => {
1110
+ if (!cs._d) return;
1111
+ const oldValue = cs._v;
1112
+ evaluating = true;
1113
+ try {
1114
+ retrack2(recompute, markDirty);
1115
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
1116
+ } finally {
1117
+ evaluating = false;
1118
+ }
1119
+ if (hook && !Object.is(oldValue, cs._v)) {
1120
+ hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
1121
+ }
1122
+ };
1123
+ cs._validate = validate;
978
1124
  function computedGetter() {
979
1125
  if (evaluating) {
980
1126
  throw new Error(
@@ -982,32 +1128,11 @@ function derived(getter, options) {
982
1128
  );
983
1129
  }
984
1130
  if (isTrackingSuspended2()) {
985
- if (cs._d) {
986
- const prev = cs._v;
987
- evaluating = true;
988
- try {
989
- retrack2(recompute, markDirty);
990
- if (!Object.is(prev, cs._v)) cs.__v++;
991
- } finally {
992
- evaluating = false;
993
- }
994
- }
1131
+ if (cs._d) validate();
995
1132
  return cs._v;
996
1133
  }
1134
+ if (cs._d) validate();
997
1135
  recordDependency2(cs);
998
- if (cs._d) {
999
- const oldValue = cs._v;
1000
- evaluating = true;
1001
- try {
1002
- retrack2(recompute, markDirty);
1003
- if (!Object.is(oldValue, cs._v)) cs.__v++;
1004
- } finally {
1005
- evaluating = false;
1006
- }
1007
- if (hook && oldValue !== cs._v) {
1008
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
1009
- }
1010
- }
1011
1136
  return cs._v;
1012
1137
  }
1013
1138
  if (debugName) {
@@ -1047,9 +1172,9 @@ function zustandAdapter(options) {
1047
1172
  return createPlugin("sibu-zustand", (ctx) => {
1048
1173
  const { store } = options;
1049
1174
  const [getState, setSibuState] = signal(store.getState());
1050
- const unsubscribe = store.subscribe((state) => {
1175
+ const unsubscribe = store.subscribe((state2) => {
1051
1176
  batch(() => {
1052
- setSibuState(state);
1177
+ setSibuState(state2);
1053
1178
  });
1054
1179
  });
1055
1180
  function select(selector) {
@@ -1078,10 +1203,12 @@ function isEventHandlerAttr(name) {
1078
1203
  return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
1079
1204
  }
1080
1205
  var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1206
+ var OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
1081
1207
  function sanitizeUrl(url) {
1082
- const trimmed = stripControlChars(url).trim();
1083
- if (!trimmed) return "";
1084
- const lower = trimmed.toLowerCase();
1208
+ const probe = stripControlChars(url).trim();
1209
+ if (!probe) return "";
1210
+ const output = url.replace(OUTER_TRIM, "");
1211
+ const lower = probe.toLowerCase();
1085
1212
  let schemeEnd = -1;
1086
1213
  for (let i = 0; i < lower.length; i++) {
1087
1214
  const ch = lower.charCodeAt(i);
@@ -1091,12 +1218,13 @@ function sanitizeUrl(url) {
1091
1218
  }
1092
1219
  if (ch === 47 || ch === 63 || ch === 35) break;
1093
1220
  }
1094
- if (schemeEnd === -1) return trimmed;
1221
+ if (schemeEnd === -1) return output;
1095
1222
  const scheme = lower.slice(0, schemeEnd + 1);
1096
- if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1223
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return output;
1097
1224
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1098
- return trimmed;
1225
+ return output;
1099
1226
  }
1227
+ var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
1100
1228
  function sanitizeSrcset(value) {
1101
1229
  const parts = value.split(",");
1102
1230
  const out = [];
@@ -1105,6 +1233,7 @@ function sanitizeSrcset(value) {
1105
1233
  if (!part) continue;
1106
1234
  const m = part.match(/^(\S+)(\s+.+)?$/);
1107
1235
  if (!m) continue;
1236
+ if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
1108
1237
  const safe = sanitizeUrl(m[1]);
1109
1238
  if (!safe) continue;
1110
1239
  out.push(m[2] ? `${safe}${m[2]}` : safe);
@@ -1112,23 +1241,83 @@ function sanitizeSrcset(value) {
1112
1241
  return out.join(", ");
1113
1242
  }
1114
1243
  var CSS_DANGER_GATE = /[(:@\\]/;
1244
+ function isCssWhitespace(ch) {
1245
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
1246
+ }
1247
+ function isHexDigit(ch) {
1248
+ return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
1249
+ }
1250
+ function decodeCssEscapes(value) {
1251
+ let out = "";
1252
+ for (let i = 0; i < value.length; i++) {
1253
+ const ch = value[i];
1254
+ if (ch !== "\\") {
1255
+ out += ch;
1256
+ continue;
1257
+ }
1258
+ if (i + 1 >= value.length) break;
1259
+ const next = value[i + 1];
1260
+ if (next === "\n" || next === "\f") {
1261
+ i += 1;
1262
+ continue;
1263
+ }
1264
+ if (next === "\r") {
1265
+ i += value[i + 2] === "\n" ? 2 : 1;
1266
+ continue;
1267
+ }
1268
+ if (isHexDigit(next)) {
1269
+ let hex = "";
1270
+ let j = i + 1;
1271
+ while (j < value.length && hex.length < 6 && isHexDigit(value[j])) {
1272
+ hex += value[j];
1273
+ j++;
1274
+ }
1275
+ if (j < value.length && isCssWhitespace(value[j])) {
1276
+ j += value[j] === "\r" && value[j + 1] === "\n" ? 2 : 1;
1277
+ }
1278
+ const code = Number.parseInt(hex, 16);
1279
+ const invalid = code === 0 || code > 1114111 || code >= 55296 && code <= 57343;
1280
+ out += invalid ? "\uFFFD" : String.fromCodePoint(code);
1281
+ i = j - 1;
1282
+ continue;
1283
+ }
1284
+ out += next;
1285
+ i += 1;
1286
+ }
1287
+ return out;
1288
+ }
1115
1289
  function sanitizeCSSValue(value) {
1116
1290
  if (!CSS_DANGER_GATE.test(value)) return value;
1117
- const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
1118
- const code = Number.parseInt(hex, 16);
1119
- if (!Number.isFinite(code) || code < 0 || code > 1114111) return "";
1120
- try {
1121
- return String.fromCodePoint(code);
1122
- } catch {
1123
- return "";
1124
- }
1125
- });
1126
- const lower = decoded.toLowerCase().replace(/\s+/g, "");
1291
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
1292
+ const lower = normalized.toLowerCase().replace(/\s+/g, "");
1127
1293
  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")) {
1128
1294
  return "";
1129
1295
  }
1130
1296
  return value;
1131
1297
  }
1298
+ function sanitizeStyleAttribute(cssText) {
1299
+ const input = String(cssText);
1300
+ if (input.trim() === "") return "";
1301
+ if (typeof document === "undefined") {
1302
+ return sanitizeCSSValue(input) === "" ? "" : input;
1303
+ }
1304
+ const probe = document.createElement("div");
1305
+ try {
1306
+ probe.style.cssText = input;
1307
+ } catch {
1308
+ return "";
1309
+ }
1310
+ const declarations = [];
1311
+ for (let i = 0; i < probe.style.length; i++) {
1312
+ const property = probe.style[i];
1313
+ const value = probe.style.getPropertyValue(property);
1314
+ if (sanitizeCSSValue(value) === "") continue;
1315
+ if (sanitizeCSSValue(`${property}:${value}`) === "") continue;
1316
+ const priority = probe.style.getPropertyPriority(property);
1317
+ declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
1318
+ }
1319
+ return declarations.join("; ");
1320
+ }
1132
1321
  var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1133
1322
  "href",
1134
1323
  "xlink:href",
@@ -1141,26 +1330,104 @@ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1141
1330
  "background",
1142
1331
  "srcset",
1143
1332
  "ping",
1144
- "data"
1333
+ "data",
1334
+ // `<html manifest>` is a URL sink too. It was carried only by the SSR
1335
+ // serializers' private copy of this set; folding that copy into this one would
1336
+ // otherwise have silently dropped its coverage.
1337
+ "manifest"
1145
1338
  ]);
1339
+ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
1340
+ function isHtmlContentAttribute(name) {
1341
+ return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
1342
+ }
1146
1343
  function sanitizeAttributeString(attr, value) {
1147
1344
  const lower = attr.toLowerCase();
1148
1345
  if (lower === "srcset") return sanitizeSrcset(value);
1149
1346
  if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
1347
+ if (lower === "style") return sanitizeStyleAttribute(value);
1150
1348
  return value;
1151
1349
  }
1152
1350
 
1153
- // src/reactivity/bindAttribute.ts
1154
- var _isDev5 = isDev();
1351
+ // src/utils/setSafeAttribute.ts
1352
+ var _isDev4 = isDev();
1353
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
1354
+ var SVG_NS = "http://www.w3.org/2000/svg";
1355
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
1356
+ var XML_NS = "http://www.w3.org/XML/1998/namespace";
1357
+ var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
1358
+ function isHtmlElement(el) {
1359
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS;
1360
+ }
1155
1361
  function setProp(el, key, val) {
1156
1362
  el[key] = val;
1157
1363
  }
1158
- function bindAttribute(el, attr, getter) {
1159
- if (isEventHandlerAttr(attr)) {
1160
- if (_isDev5)
1364
+ function namespaceFor(el, name) {
1365
+ if (el.namespaceURI !== SVG_NS) return null;
1366
+ const lower = name.toLowerCase();
1367
+ if (lower.startsWith("xlink:")) return XLINK_NS;
1368
+ if (lower.startsWith("xml:")) return XML_NS;
1369
+ return null;
1370
+ }
1371
+ function setSafeAttribute(el, name, value, options = {}) {
1372
+ const ns = namespaceFor(el, name);
1373
+ const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
1374
+ if (isEventHandlerAttr(name)) {
1375
+ if (_isDev4) {
1376
+ devWarn(
1377
+ `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
1378
+ );
1379
+ }
1380
+ if (ns) el.removeAttributeNS(ns, localName);
1381
+ else el.removeAttribute(name);
1382
+ return false;
1383
+ }
1384
+ if (value == null) {
1385
+ if (ns) el.removeAttributeNS(ns, localName);
1386
+ else el.removeAttribute(name);
1387
+ return true;
1388
+ }
1389
+ if (isHtmlContentAttribute(name)) {
1390
+ if (_isDev4) {
1161
1391
  devWarn(
1162
- `bindAttribute: refusing to bind event-handler attribute "${attr}". Use on:{ ${attr.slice(2)}: fn } instead.`
1392
+ `${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.`
1163
1393
  );
1394
+ }
1395
+ if (ns) el.removeAttributeNS(ns, localName);
1396
+ else el.removeAttribute(name);
1397
+ return false;
1398
+ }
1399
+ const html = isHtmlElement(el);
1400
+ const idlName = html ? name.toLowerCase() : name;
1401
+ if (typeof value === "boolean") {
1402
+ if (html && BOOLEAN_IDL_ATTRS.has(idlName) && idlName in el) {
1403
+ setProp(el, idlName, value);
1404
+ } else if (value) {
1405
+ if (ns) el.setAttributeNS(ns, name, "");
1406
+ else el.setAttribute(name, "");
1407
+ } else if (ns) {
1408
+ el.removeAttributeNS(ns, localName);
1409
+ } else {
1410
+ el.removeAttribute(name);
1411
+ }
1412
+ return true;
1413
+ }
1414
+ const str = String(value);
1415
+ if (options.syncValueProperty !== false && html && (idlName === "value" || idlName === "checked") && idlName in el) {
1416
+ setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
1417
+ return true;
1418
+ }
1419
+ const safe = sanitizeAttributeString(name, str);
1420
+ const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1421
+ if (current === safe) return true;
1422
+ if (ns) el.setAttributeNS(ns, name, safe);
1423
+ else el.setAttribute(name, safe);
1424
+ return true;
1425
+ }
1426
+
1427
+ // src/reactivity/bindAttribute.ts
1428
+ function bindAttribute(el, attr, getter) {
1429
+ if (isEventHandlerAttr(attr)) {
1430
+ setSafeAttribute(el, attr, "", { label: `bindAttribute("${attr}")` });
1164
1431
  return () => {
1165
1432
  };
1166
1433
  }
@@ -1169,33 +1436,25 @@ function bindAttribute(el, attr, getter) {
1169
1436
  try {
1170
1437
  value = getter();
1171
1438
  } catch (err) {
1172
- if (_isDev5)
1173
- devWarn(`bindAttribute: getter for "${attr}" threw: ${err instanceof Error ? err.message : String(err)}`);
1174
- return;
1175
- }
1176
- if (typeof value === "boolean") {
1177
- if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1178
- setProp(el, attr, value);
1179
- } else if (value) {
1180
- el.setAttribute(attr, "");
1181
- } else {
1182
- el.removeAttribute(attr);
1183
- }
1439
+ reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
1184
1440
  return;
1185
1441
  }
1186
- const str = String(value);
1187
- if ((attr === "value" || attr === "checked") && attr in el) {
1188
- setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1189
- } else {
1190
- el.setAttribute(attr, sanitizeAttributeString(attr, str));
1191
- }
1442
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
1192
1443
  }
1193
- return reactiveBinding2(commit);
1444
+ return reactiveBinding2(commit, el);
1194
1445
  }
1195
1446
 
1196
1447
  // src/core/rendering/dispose.ts
1197
1448
  var elementDisposers = /* @__PURE__ */ new WeakMap();
1198
- var _isDev6 = isDev();
1449
+ var MAX_DRAIN_TEARDOWNS = 1e4;
1450
+ function reportDrainRunaway(label, executed, remaining) {
1451
+ if (typeof console === "undefined") return;
1452
+ console.error(
1453
+ `[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.`,
1454
+ { executed, remaining }
1455
+ );
1456
+ }
1457
+ var _isDev5 = isDev();
1199
1458
  var activeBindingCount = 0;
1200
1459
  function registerDisposer(node, teardown) {
1201
1460
  let disposers = elementDisposers.get(node);
@@ -1204,7 +1463,7 @@ function registerDisposer(node, teardown) {
1204
1463
  elementDisposers.set(node, disposers);
1205
1464
  }
1206
1465
  disposers.push(teardown);
1207
- if (_isDev6) activeBindingCount++;
1466
+ if (_isDev5) activeBindingCount++;
1208
1467
  }
1209
1468
  function dispose(node) {
1210
1469
  const stack = [node];
@@ -1219,34 +1478,30 @@ function dispose(node) {
1219
1478
  }
1220
1479
  for (let i = order.length - 1; i >= 0; i--) {
1221
1480
  const current = order[i];
1222
- const disposers = elementDisposers.get(current);
1223
- if (disposers) {
1224
- const snapshot = disposers.slice();
1225
- elementDisposers.delete(current);
1226
- if (_isDev6) activeBindingCount -= snapshot.length;
1227
- for (const d of snapshot) {
1228
- try {
1229
- d();
1230
- } catch (err) {
1231
- if (_isDev6 && typeof console !== "undefined") {
1232
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
1233
- }
1234
- }
1235
- }
1236
- let extraPasses = 0;
1237
- while (extraPasses++ < 8) {
1238
- const added = elementDisposers.get(current);
1239
- if (!added || added.length === 0) break;
1240
- const moreSnapshot = added.slice();
1481
+ if (elementDisposers.has(current)) {
1482
+ let executed = 0;
1483
+ let runaway = false;
1484
+ while (!runaway) {
1485
+ const pending = elementDisposers.get(current);
1486
+ if (!pending || pending.length === 0) break;
1487
+ const snapshot = pending.slice();
1241
1488
  elementDisposers.delete(current);
1242
- if (_isDev6) activeBindingCount -= moreSnapshot.length;
1243
- for (const d of moreSnapshot) {
1489
+ if (_isDev5) activeBindingCount -= snapshot.length;
1490
+ for (let i2 = 0; i2 < snapshot.length; i2++) {
1491
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
1492
+ const rest = snapshot.slice(i2);
1493
+ const added = elementDisposers.get(current);
1494
+ elementDisposers.set(current, added ? rest.concat(added) : rest);
1495
+ if (_isDev5) activeBindingCount += rest.length;
1496
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
1497
+ runaway = true;
1498
+ break;
1499
+ }
1500
+ executed++;
1244
1501
  try {
1245
- d();
1502
+ snapshot[i2]();
1246
1503
  } catch (err) {
1247
- if (_isDev6 && typeof console !== "undefined") {
1248
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
1249
- }
1504
+ reportError(err, { phase: "cleanup", name: "disposer" });
1250
1505
  }
1251
1506
  }
1252
1507
  }
@@ -1255,7 +1510,7 @@ function dispose(node) {
1255
1510
  }
1256
1511
 
1257
1512
  // src/reactivity/bindChildNode.ts
1258
- var _isDev7 = isDev();
1513
+ var _isDev6 = isDev();
1259
1514
  function bindChildNode(placeholder, getter) {
1260
1515
  let lastNodes = [];
1261
1516
  function commit() {
@@ -1263,7 +1518,7 @@ function bindChildNode(placeholder, getter) {
1263
1518
  try {
1264
1519
  result = getter();
1265
1520
  } catch (err) {
1266
- if (_isDev7) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
1521
+ reportError(err, { phase: "binding", name: "bindChildNode", node: placeholder });
1267
1522
  return;
1268
1523
  }
1269
1524
  if (result == null || typeof result === "boolean") {
@@ -1289,7 +1544,7 @@ function bindChildNode(placeholder, getter) {
1289
1544
  if (item == null || typeof item === "boolean") continue;
1290
1545
  const node = item instanceof Node ? item : document.createTextNode(String(item));
1291
1546
  if (seen.has(node)) {
1292
- if (_isDev7)
1547
+ if (_isDev6)
1293
1548
  devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1294
1549
  continue;
1295
1550
  }
@@ -1324,11 +1579,11 @@ function bindChildNode(placeholder, getter) {
1324
1579
  }
1325
1580
  lastNodes = newNodes;
1326
1581
  }
1327
- return reactiveBinding2(commit);
1582
+ return reactiveBinding2(commit, placeholder);
1328
1583
  }
1329
1584
 
1330
1585
  // src/core/rendering/tagFactory.ts
1331
- var _isDev8 = isDev();
1586
+ var _isDev7 = isDev();
1332
1587
  var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1333
1588
  function isBlockedTag(tag) {
1334
1589
  return BLOCKED_TAGS.has(tag.toLowerCase());
@@ -1346,9 +1601,6 @@ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1346
1601
  "self",
1347
1602
  "frames"
1348
1603
  ]);
1349
- function setProp2(el, key, val) {
1350
- el[key] = val;
1351
- }
1352
1604
  function looksLikeClassList(s) {
1353
1605
  const t = s.trim();
1354
1606
  if (!t) return false;
@@ -1372,13 +1624,13 @@ function toKebab(prop) {
1372
1624
  function applyStyle(el, style) {
1373
1625
  if (typeof style === "function") {
1374
1626
  const teardown = track2(() => {
1375
- el.setAttribute("style", style());
1627
+ el.setAttribute("style", sanitizeStyleAttribute(String(style())));
1376
1628
  });
1377
1629
  registerDisposer(el, teardown);
1378
1630
  return;
1379
1631
  }
1380
1632
  if (typeof style === "string") {
1381
- el.setAttribute("style", style);
1633
+ el.setAttribute("style", sanitizeStyleAttribute(style));
1382
1634
  return;
1383
1635
  }
1384
1636
  const htmlEl = el;
@@ -1499,7 +1751,7 @@ var tagFactory = (tag, ns) => {
1499
1751
  appendChildren(el, second);
1500
1752
  return el;
1501
1753
  }
1502
- if (_isDev8 && looksLikeClassList(first)) {
1754
+ if (_isDev7 && looksLikeClassList(first)) {
1503
1755
  devWarn(
1504
1756
  `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`
1505
1757
  );
@@ -1520,7 +1772,7 @@ var tagFactory = (tag, ns) => {
1520
1772
  if (pClass != null) applyClass(el, pClass);
1521
1773
  const pId = props.id;
1522
1774
  if (pId != null) {
1523
- if (_isDev8 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1775
+ if (_isDev7 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1524
1776
  devWarn(
1525
1777
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1526
1778
  );
@@ -1535,7 +1787,7 @@ var tagFactory = (tag, ns) => {
1535
1787
  const handler = pOn[ev];
1536
1788
  if (typeof handler === "function") {
1537
1789
  el.addEventListener(ev, handler);
1538
- } else if (_isDev8) {
1790
+ } else if (_isDev7) {
1539
1791
  devWarn(
1540
1792
  `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1541
1793
  );
@@ -1563,16 +1815,8 @@ var tagFactory = (tag, ns) => {
1563
1815
  if (isEventHandlerAttr(key)) continue;
1564
1816
  if (typeof value === "function") {
1565
1817
  registerDisposer(el, bindAttribute(el, key, value));
1566
- } else if (typeof value === "boolean") {
1567
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1568
- setProp2(el, key, value);
1569
- } else if (value) {
1570
- el.setAttribute(key, "");
1571
- } else {
1572
- el.removeAttribute(key);
1573
- }
1574
1818
  } else {
1575
- el.setAttribute(key, sanitizeAttributeString(key, String(value)));
1819
+ setSafeAttribute(el, key, value, { syncValueProperty: false, label: "tagFactory" });
1576
1820
  }
1577
1821
  }
1578
1822
  }