sibujs 3.4.1 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +784 -392
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-PBZWJQNY.js → chunk-52ERIVFD.js} +8 -8
  11. package/dist/chunk-52XFPGSN.js +123 -0
  12. package/dist/{chunk-Y22QAKJX.js → chunk-5BVKQX2F.js} +4 -4
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-4I3M326N.js → chunk-AD5YSZAN.js} +19 -25
  15. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  16. package/dist/chunk-DHDZ7IVN.js +109 -0
  17. package/dist/{chunk-VQZDHAEK.js → chunk-E72G5JSK.js} +1 -1
  18. package/dist/{chunk-MPE75KOA.js → chunk-HPXTUPJD.js} +259 -124
  19. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  20. package/dist/{chunk-FWFEBYQ6.js → chunk-IKVUUE7D.js} +115 -41
  21. package/dist/{chunk-5MFF5EPT.js → chunk-J4WZMCIW.js} +5 -5
  22. package/dist/{chunk-BQLVTAUZ.js → chunk-JYXQCELN.js} +57 -22
  23. package/dist/{chunk-WEZ5SP7L.js → chunk-PHB3MCK4.js} +73 -34
  24. package/dist/chunk-PHRH66Y7.js +147 -0
  25. package/dist/{chunk-S2VW4NBA.js → chunk-PJDSI2K7.js} +295 -198
  26. package/dist/{chunk-QHPUFNHZ.js → chunk-PTLJ7NQX.js} +3 -3
  27. package/dist/{chunk-NXR2YFZB.js → chunk-QD4I7ZGZ.js} +24 -34
  28. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  29. package/dist/{chunk-7PNKW3FJ.js → chunk-TNEHT3NG.js} +232 -67
  30. package/dist/{chunk-335KJEN2.js → chunk-TVURNWDK.js} +5 -3
  31. package/dist/{chunk-CV7FAQ2R.js → chunk-UNAYBND2.js} +9 -3
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-X4JS367G.js → chunk-XDOMTQHM.js} +14 -10
  34. package/dist/chunk-XET2RUJF.js +61 -0
  35. package/dist/{chunk-3FFCUIGV.js → chunk-XZMCVZW6.js} +309 -188
  36. package/dist/{chunk-YDWP3ZOU.js → chunk-YLV26GAJ.js} +228 -86
  37. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.ts → 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 +426 -182
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1988 -772
  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 +791 -392
  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 +1378 -583
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +610 -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 +1111 -450
  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-6CAJU5TP.js +0 -38
  96. package/dist/chunk-COY6PUD2.js +0 -34
  97. package/dist/chunk-DLBFEAIS.js +0 -96
  98. package/dist/chunk-L3GAGWCC.js +0 -111
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  devWarn,
3
- isDev
4
- } from "./chunk-COY6PUD2.js";
3
+ isDev,
4
+ reportError
5
+ } from "./chunk-VPP2FONR.js";
5
6
 
6
7
  // src/reactivity/track-core.ts
7
- var _isDev = isDev();
8
8
  var POOL_MAX = 4096;
9
9
  var nodePool = [];
10
10
  function createNode() {
@@ -12,6 +12,7 @@ function createNode() {
12
12
  sig: null,
13
13
  sub: null,
14
14
  epoch: 0,
15
+ depVersion: 0,
15
16
  sigPrev: null,
16
17
  sigNext: null,
17
18
  subPrev: null,
@@ -96,8 +97,27 @@ function safeInvoke(sub) {
96
97
  try {
97
98
  sub();
98
99
  } catch (err) {
99
- if (_isDev) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
100
+ const s = sub;
101
+ reportError(err, { phase: s._errorPhase ?? "effect", name: s.__name, node: s._errorNode });
102
+ }
103
+ }
104
+ function depsChanged(sub) {
105
+ let node = sub.depsHead ?? null;
106
+ if (node === null) return true;
107
+ while (node !== null) {
108
+ const sig = node.sig;
109
+ if (sig === null) return true;
110
+ if (sig._d === true && sig._validate !== void 0) {
111
+ try {
112
+ sig._validate();
113
+ } catch {
114
+ return true;
115
+ }
116
+ }
117
+ if (node.depVersion !== sig.__v) return true;
118
+ node = node.subNext;
100
119
  }
120
+ return false;
101
121
  }
102
122
  var suspendDepth = 0;
103
123
  var trackingSuspended = false;
@@ -179,7 +199,7 @@ function track(effectFn, subscriber) {
179
199
  const sub = subscriber;
180
200
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
181
201
  }
182
- function reactiveBinding(commit) {
202
+ function reactiveBinding(commit, ownerNode) {
183
203
  const run = () => {
184
204
  const s = subscriber;
185
205
  if (s._disposed || s._reentrant) return;
@@ -195,13 +215,17 @@ function reactiveBinding(commit) {
195
215
  subscriber.depsTail = null;
196
216
  subscriber._epoch = 0;
197
217
  subscriber._structDirty = false;
218
+ subscriber._hasComputedDep = false;
198
219
  subscriber._runEpoch = 0;
199
220
  subscriber._runs = 0;
200
221
  subscriber._reentrant = false;
201
222
  subscriber._disposed = false;
223
+ subscriber._errorPhase = "binding";
224
+ subscriber._errorNode = ownerNode;
202
225
  run();
203
226
  return subscriber._dispose ?? (subscriber._dispose = () => {
204
227
  subscriber._disposed = true;
228
+ subscriber._errorNode = void 0;
205
229
  cleanup(subscriber);
206
230
  });
207
231
  }
@@ -213,9 +237,12 @@ function recordDependency(signal) {
213
237
  const active = sig.__activeNode ?? null;
214
238
  if (active !== null && active.sub === sub) {
215
239
  active.epoch = epoch;
240
+ active.depVersion = sig.__v ?? Number.NaN;
216
241
  return;
217
242
  }
243
+ if (sig._validate !== void 0) sub._hasComputedDep = true;
218
244
  const node = allocNode(signal, sub, epoch);
245
+ node.depVersion = sig.__v ?? Number.NaN;
219
246
  node.prevActive = active;
220
247
  sig.__activeNode = node;
221
248
  linkSub(sub, node);
@@ -234,9 +261,10 @@ function cleanup(subscriber) {
234
261
  node = next;
235
262
  }
236
263
  }
237
- var maxSubscriberRepeats = 50;
264
+ var maxSubscriberRepeats = 1e3;
238
265
  var maxDrainIterations = 1e6;
239
266
  var drainEpoch = 0;
267
+ var quarantined = /* @__PURE__ */ new Set();
240
268
  function setMaxSubscriberRepeats(n) {
241
269
  const prev = maxSubscriberRepeats;
242
270
  if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
@@ -258,19 +286,21 @@ function tickRepeat(sub) {
258
286
  return s._runs > maxSubscriberRepeats;
259
287
  }
260
288
  function cycleError(sub) {
261
- if (typeof console !== "undefined") {
262
- const name = sub.__name ?? "<unnamed>";
263
- console.error(
264
- `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
265
- );
266
- }
289
+ const name = sub.__name ?? "<unnamed>";
290
+ reportError(
291
+ new Error(
292
+ `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.`
293
+ ),
294
+ { phase: "scheduler", name }
295
+ );
267
296
  }
268
297
  function absoluteDrainError() {
269
- if (typeof console !== "undefined") {
270
- console.error(
271
- `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
272
- );
273
- }
298
+ reportError(
299
+ new Error(
300
+ `Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
301
+ ),
302
+ { phase: "scheduler" }
303
+ );
274
304
  }
275
305
  function drainQueue() {
276
306
  let i = 0;
@@ -280,11 +310,14 @@ function drainQueue() {
280
310
  break;
281
311
  }
282
312
  const sub = pendingQueue[i++];
313
+ pendingSet.delete(sub);
314
+ if (quarantined.size > 0 && quarantined.has(sub)) continue;
315
+ if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
283
316
  if (tickRepeat(sub)) {
284
317
  cycleError(sub);
285
- break;
318
+ quarantined.add(sub);
319
+ continue;
286
320
  }
287
- pendingSet.delete(sub);
288
321
  safeInvoke(sub);
289
322
  }
290
323
  }
@@ -299,6 +332,7 @@ function drainNotificationQueue() {
299
332
  if (notifyDepth === 0) {
300
333
  pendingQueue.length = 0;
301
334
  pendingSet.clear();
335
+ if (quarantined.size > 0) quarantined.clear();
302
336
  }
303
337
  }
304
338
  }
@@ -392,6 +426,7 @@ function notifySubscribers(signal) {
392
426
  if (notifyDepth === 0) {
393
427
  pendingQueue.length = 0;
394
428
  pendingSet.clear();
429
+ if (quarantined.size > 0) quarantined.clear();
395
430
  }
396
431
  }
397
432
  }
@@ -418,14 +453,14 @@ function forEachSubscriber(signal, visit) {
418
453
  }
419
454
 
420
455
  // src/reactivity/track.ts
421
- var _isDev2 = isDev();
422
- var _runtimeVersion = true ? "3.4.1" : "dev";
456
+ var _isDev = isDev();
457
+ var _runtimeVersion = true ? "4.0.1" : "dev";
423
458
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
424
459
  function resolveReactiveApi() {
425
460
  const g = globalThis;
426
461
  const existing = g[REGISTRY_KEY];
427
462
  if (existing) {
428
- if (_isDev2 && !existing.__dupWarned) {
463
+ if (_isDev && !existing.__dupWarned) {
429
464
  existing.__dupWarned = true;
430
465
  devWarn(
431
466
  `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']).`
@@ -1,22 +1,24 @@
1
+ import {
2
+ sanitizeUrl
3
+ } from "./chunk-5INI7D2L.js";
1
4
  import {
2
5
  globalSingleton
3
6
  } from "./chunk-7ZHH77QA.js";
4
- import {
5
- sanitizeUrl
6
- } from "./chunk-L3GAGWCC.js";
7
7
  import {
8
8
  effect
9
- } from "./chunk-X4JS367G.js";
9
+ } from "./chunk-XDOMTQHM.js";
10
10
  import {
11
- dispose
12
- } from "./chunk-5VGSK6D2.js";
11
+ dispose,
12
+ replaceChildrenSafely
13
+ } from "./chunk-DHDZ7IVN.js";
13
14
  import {
14
15
  signal
15
- } from "./chunk-335KJEN2.js";
16
+ } from "./chunk-TVURNWDK.js";
16
17
  import {
17
18
  devWarn,
18
- isDev
19
- } from "./chunk-COY6PUD2.js";
19
+ isDev,
20
+ reportError
21
+ } from "./chunk-VPP2FONR.js";
20
22
 
21
23
  // src/performance/scheduler.ts
22
24
  var Priority = {
@@ -265,7 +267,7 @@ var DOMPool = class {
265
267
  this.pools.set(tag, pool);
266
268
  }
267
269
  if (pool.length >= this.maxSize) return;
268
- element.innerHTML = "";
270
+ replaceChildrenSafely(element);
269
271
  element.className = "";
270
272
  element.removeAttribute("style");
271
273
  element.removeAttribute("id");
@@ -396,8 +398,9 @@ function normalizedStore(schema) {
396
398
  ids: [],
397
399
  entities: {}
398
400
  });
401
+ const idOf = (entity) => String(entity[idKey]);
399
402
  function add(entity) {
400
- const id = String(entity[idKey]);
403
+ const id = idOf(entity);
401
404
  setState((prev) => {
402
405
  const ids = prev.ids.includes(id) ? prev.ids : [...prev.ids, id];
403
406
  return {
@@ -411,7 +414,7 @@ function normalizedStore(schema) {
411
414
  const nextIds = [...prev.ids];
412
415
  const nextEntities = { ...prev.entities };
413
416
  for (const entity of entities) {
414
- const id = String(entity[idKey]);
417
+ const id = idOf(entity);
415
418
  if (!nextIds.includes(id)) {
416
419
  nextIds.push(id);
417
420
  }
@@ -533,7 +536,14 @@ function createChunkRegistry(config = {}) {
533
536
  } = config;
534
537
  const cache = /* @__PURE__ */ new Map();
535
538
  const pending = /* @__PURE__ */ new Map();
536
- const preloaded = /* @__PURE__ */ new Set();
539
+ const preloaded = /* @__PURE__ */ new Map();
540
+ function notify(name, run) {
541
+ try {
542
+ run();
543
+ } catch (err) {
544
+ reportError(err, { phase: "async", name: `chunkRegistry(${name})` });
545
+ }
546
+ }
537
547
  function evict() {
538
548
  while (cache.size >= maxCacheSize) {
539
549
  let lru = null;
@@ -588,30 +598,47 @@ function createChunkRegistry(config = {}) {
588
598
  cached.lastAccess = Date.now();
589
599
  return cached.value;
590
600
  }
591
- const pendingLoad = pending.get(id);
592
- if (pendingLoad) return pendingLoad;
593
- onLoadStart?.(id);
594
- const loadPromise = loadWithRetry(id, loader).then((value) => {
595
- evict();
596
- const now = Date.now();
597
- cache.set(id, { value, timestamp: now, lastAccess: now, accessCount: 1 });
598
- pending.delete(id);
599
- onLoadEnd?.(id);
600
- return value;
601
- }).catch((err) => {
602
- pending.delete(id);
603
- const error = err instanceof Error ? err : new Error(String(err));
604
- onLoadError?.(id, error);
605
- throw error;
601
+ const existing = pending.get(id);
602
+ if (existing) return existing.promise;
603
+ let resolveOuter;
604
+ let rejectOuter;
605
+ const outcome = new Promise((resolve, reject) => {
606
+ resolveOuter = resolve;
607
+ rejectOuter = reject;
608
+ });
609
+ const entry = { promise: outcome };
610
+ pending.set(id, entry);
611
+ const owns = () => pending.get(id) === entry;
612
+ notify("onLoadStart", () => onLoadStart?.(id));
613
+ void loadWithRetry(id, loader).then(
614
+ (value) => {
615
+ if (owns()) {
616
+ pending.delete(id);
617
+ evict();
618
+ const now = Date.now();
619
+ cache.set(id, { value, timestamp: now, lastAccess: now, accessCount: 1 });
620
+ }
621
+ notify("onLoadEnd", () => onLoadEnd?.(id));
622
+ resolveOuter(value);
623
+ },
624
+ (err) => {
625
+ if (owns()) pending.delete(id);
626
+ const error = err instanceof Error ? err : new Error(String(err));
627
+ notify("onLoadError", () => onLoadError?.(id, error));
628
+ rejectOuter(error);
629
+ }
630
+ ).catch((internal) => {
631
+ reportError(internal, { phase: "async", name: "chunkRegistry(settlement)" });
632
+ rejectOuter(internal);
606
633
  });
607
- pending.set(id, loadPromise);
608
- return loadPromise;
634
+ return outcome;
609
635
  }
610
636
  function preloadFn(id, loader) {
611
637
  if (cache.has(id) || pending.has(id) || preloaded.has(id)) return;
612
- preloaded.add(id);
638
+ const marker = {};
639
+ preloaded.set(id, marker);
613
640
  loadFn(id, loader).catch(() => {
614
- preloaded.delete(id);
641
+ if (preloaded.get(id) === marker) preloaded.delete(id);
615
642
  });
616
643
  }
617
644
  return {
@@ -645,21 +672,33 @@ function createChunkRegistry(config = {}) {
645
672
  return void 0;
646
673
  },
647
674
  /**
648
- * Invalidate a cached chunk.
675
+ * Invalidate a cached chunk, and supersede any load still in flight for it.
676
+ *
677
+ * In-flight work is not cancelled — the loader API takes no abort signal, so
678
+ * claiming cancellation would be a lie. It is *superseded*: it still settles
679
+ * for whoever called `load()`, but it can no longer write to the cache, and
680
+ * a subsequent `load(id, …)` starts fresh work rather than adopting it.
649
681
  */
650
682
  invalidate(id) {
651
683
  cache.delete(id);
652
684
  preloaded.delete(id);
685
+ pending.delete(id);
653
686
  },
654
687
  /**
655
- * Clear all cached chunks.
688
+ * Clear all cached chunks, and supersede every load still in flight.
689
+ *
690
+ * Same barrier as `invalidate`, applied to every key at once.
656
691
  */
657
692
  clear() {
658
693
  cache.clear();
659
694
  preloaded.clear();
695
+ pending.clear();
660
696
  },
661
697
  /**
662
698
  * Get cache statistics.
699
+ *
700
+ * Describes currently OWNED state: superseded loads are already absent from
701
+ * `pending`, so the counts never include work whose result will be discarded.
663
702
  */
664
703
  stats() {
665
704
  return {
@@ -0,0 +1,147 @@
1
+ import {
2
+ isEventHandlerAttr,
3
+ isHtmlContentAttribute,
4
+ sanitizeAttributeString
5
+ } from "./chunk-5INI7D2L.js";
6
+ import {
7
+ reactiveBinding
8
+ } from "./chunk-JYXQCELN.js";
9
+ import {
10
+ devWarn,
11
+ isDev,
12
+ reportError
13
+ } from "./chunk-VPP2FONR.js";
14
+
15
+ // src/utils/setSafeAttribute.ts
16
+ var _isDev = isDev();
17
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
18
+ var SVG_NS = "http://www.w3.org/2000/svg";
19
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
20
+ var XML_NS = "http://www.w3.org/XML/1998/namespace";
21
+ var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
22
+ function isHtmlElement(el) {
23
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS;
24
+ }
25
+ function setProp(el, key, val) {
26
+ el[key] = val;
27
+ }
28
+ function namespaceFor(el, name) {
29
+ if (el.namespaceURI !== SVG_NS) return null;
30
+ const lower = name.toLowerCase();
31
+ if (lower.startsWith("xlink:")) return XLINK_NS;
32
+ if (lower.startsWith("xml:")) return XML_NS;
33
+ return null;
34
+ }
35
+ function setSafeAttribute(el, name, value, options = {}) {
36
+ const ns = namespaceFor(el, name);
37
+ const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
38
+ if (isEventHandlerAttr(name)) {
39
+ if (_isDev) {
40
+ devWarn(
41
+ `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
42
+ );
43
+ }
44
+ if (ns) el.removeAttributeNS(ns, localName);
45
+ else el.removeAttribute(name);
46
+ return false;
47
+ }
48
+ if (value == null) {
49
+ if (ns) el.removeAttributeNS(ns, localName);
50
+ else el.removeAttribute(name);
51
+ return true;
52
+ }
53
+ if (isHtmlContentAttribute(name)) {
54
+ if (_isDev) {
55
+ devWarn(
56
+ `${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.`
57
+ );
58
+ }
59
+ if (ns) el.removeAttributeNS(ns, localName);
60
+ else el.removeAttribute(name);
61
+ return false;
62
+ }
63
+ const html = isHtmlElement(el);
64
+ const idlName = html ? name.toLowerCase() : name;
65
+ if (typeof value === "boolean") {
66
+ if (html && BOOLEAN_IDL_ATTRS.has(idlName) && idlName in el) {
67
+ setProp(el, idlName, value);
68
+ } else if (value) {
69
+ if (ns) el.setAttributeNS(ns, name, "");
70
+ else el.setAttribute(name, "");
71
+ } else if (ns) {
72
+ el.removeAttributeNS(ns, localName);
73
+ } else {
74
+ el.removeAttribute(name);
75
+ }
76
+ return true;
77
+ }
78
+ const str = String(value);
79
+ if (options.syncValueProperty !== false && html && (idlName === "value" || idlName === "checked") && idlName in el) {
80
+ setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
81
+ return true;
82
+ }
83
+ const safe = sanitizeAttributeString(name, str);
84
+ const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
85
+ if (current === safe) return true;
86
+ if (ns) el.setAttributeNS(ns, name, safe);
87
+ else el.setAttribute(name, safe);
88
+ return true;
89
+ }
90
+
91
+ // src/reactivity/bindAttribute.ts
92
+ function bindAttribute(el, attr, getter) {
93
+ if (isEventHandlerAttr(attr)) {
94
+ setSafeAttribute(el, attr, "", { label: `bindAttribute("${attr}")` });
95
+ return () => {
96
+ };
97
+ }
98
+ function commit() {
99
+ let value;
100
+ try {
101
+ value = getter();
102
+ } catch (err) {
103
+ reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
104
+ return;
105
+ }
106
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
107
+ }
108
+ return reactiveBinding(commit, el);
109
+ }
110
+ function bindDynamic(el, nameGetter, valueGetter) {
111
+ let prevName = null;
112
+ function commit() {
113
+ let name;
114
+ try {
115
+ name = typeof nameGetter === "function" ? nameGetter() : nameGetter;
116
+ } catch (err) {
117
+ reportError(err, { phase: "binding", name: "bindDynamic (name getter)", node: el });
118
+ return;
119
+ }
120
+ let value;
121
+ try {
122
+ value = typeof valueGetter === "function" ? valueGetter() : valueGetter;
123
+ } catch (err) {
124
+ reportError(err, { phase: "binding", name: "bindDynamic (value getter)", node: el });
125
+ return;
126
+ }
127
+ if (prevName !== null && prevName !== name) {
128
+ el.removeAttribute(prevName);
129
+ prevName = null;
130
+ }
131
+ if (!setSafeAttribute(el, name, value, { label: "bindDynamic" })) return;
132
+ prevName = name;
133
+ }
134
+ const teardown = reactiveBinding(commit, el);
135
+ return () => {
136
+ teardown();
137
+ if (prevName !== null) {
138
+ el.removeAttribute(prevName);
139
+ }
140
+ };
141
+ }
142
+
143
+ export {
144
+ setSafeAttribute,
145
+ bindAttribute,
146
+ bindDynamic
147
+ };