sibujs 4.0.0 → 4.1.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 (55) hide show
  1. package/README.md +5 -1
  2. package/dist/browser.cjs +9 -2
  3. package/dist/browser.js +4 -4
  4. package/dist/build.cjs +93 -14
  5. package/dist/build.js +10 -10
  6. package/dist/cdn.global.js +10 -10
  7. package/dist/{chunk-F64ZPCFE.js → chunk-4RH6D7GR.js} +3 -3
  8. package/dist/{chunk-WOOMUJFT.js → chunk-4U7KD3RY.js} +3 -3
  9. package/dist/{chunk-O3QQ3XOL.js → chunk-5GJ5RTHA.js} +1 -1
  10. package/dist/{chunk-GCSGGA3Q.js → chunk-ANGSYNPX.js} +77 -14
  11. package/dist/{chunk-VFHIVEPF.js → chunk-BMSN6NWQ.js} +3 -3
  12. package/dist/{chunk-ACBT6UZS.js → chunk-D3SZ4LVS.js} +9 -2
  13. package/dist/{chunk-EWEE7NBF.js → chunk-DAKLOMKS.js} +2 -2
  14. package/dist/{chunk-Z2CE42DB.js → chunk-DCDS2PQ6.js} +1 -1
  15. package/dist/{chunk-WN6WDFCB.js → chunk-FB7KQXZI.js} +4 -4
  16. package/dist/{chunk-6LG2T7LO.js → chunk-FMHIQNMP.js} +4 -4
  17. package/dist/{chunk-DQVUBVNX.js → chunk-I3VI34DP.js} +10 -11
  18. package/dist/{chunk-E62UADKL.js → chunk-IM7N5WR4.js} +1 -1
  19. package/dist/{chunk-RRIDJQ5C.js → chunk-KSOEJGFO.js} +1 -1
  20. package/dist/{chunk-3EKJK5FZ.js → chunk-LOFZHP6V.js} +2 -2
  21. package/dist/{chunk-ES2GERY2.js → chunk-MRAMKDHQ.js} +1 -1
  22. package/dist/{chunk-PBQEW5VW.js → chunk-PFTDGEPZ.js} +1 -1
  23. package/dist/{chunk-4MFZMLUU.js → chunk-SHDVWATN.js} +1 -1
  24. package/dist/{chunk-W55YHPEP.js → chunk-UFSQEO7P.js} +14 -2
  25. package/dist/{chunk-DR7H6UGM.js → chunk-XIHMS2Y6.js} +5 -5
  26. package/dist/{chunk-NF3LYCQH.js → chunk-XSZRJW76.js} +4 -4
  27. package/dist/data.cjs +9 -2
  28. package/dist/data.js +6 -6
  29. package/dist/devtools.cjs +9 -2
  30. package/dist/devtools.js +4 -4
  31. package/dist/ecosystem.cjs +16 -9
  32. package/dist/ecosystem.js +7 -7
  33. package/dist/extras.cjs +16 -9
  34. package/dist/extras.js +19 -19
  35. package/dist/index.cjs +94 -14
  36. package/dist/index.d.cts +133 -1
  37. package/dist/index.d.ts +133 -1
  38. package/dist/index.js +12 -10
  39. package/dist/motion.cjs +9 -2
  40. package/dist/motion.js +3 -3
  41. package/dist/patterns.cjs +9 -2
  42. package/dist/patterns.js +5 -5
  43. package/dist/performance.cjs +9 -2
  44. package/dist/performance.js +4 -4
  45. package/dist/plugins.cjs +16 -9
  46. package/dist/plugins.js +6 -6
  47. package/dist/ssr.cjs +16 -9
  48. package/dist/ssr.js +7 -7
  49. package/dist/testing.cjs +9 -2
  50. package/dist/testing.js +2 -2
  51. package/dist/ui.cjs +9 -2
  52. package/dist/ui.js +6 -6
  53. package/dist/widgets.cjs +9 -2
  54. package/dist/widgets.js +6 -6
  55. package/package.json +7 -1
@@ -359,7 +359,14 @@ function reactiveBinding(commit, ownerNode) {
359
359
  subscriber._disposed = false;
360
360
  subscriber._errorPhase = "binding";
361
361
  subscriber._errorNode = ownerNode;
362
- run();
362
+ try {
363
+ run();
364
+ } catch (err) {
365
+ subscriber._disposed = true;
366
+ subscriber._errorNode = void 0;
367
+ cleanup(subscriber);
368
+ throw err;
369
+ }
363
370
  return subscriber._dispose ?? (subscriber._dispose = () => {
364
371
  subscriber._disposed = true;
365
372
  subscriber._errorNode = void 0;
@@ -591,7 +598,7 @@ function forEachSubscriber(signal2, visit) {
591
598
 
592
599
  // src/reactivity/track.ts
593
600
  var _isDev2 = isDev();
594
- var _runtimeVersion = true ? "4.0.0" : "dev";
601
+ var _runtimeVersion = true ? "4.1.0" : "dev";
595
602
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
596
603
  function resolveReactiveApi() {
597
604
  const g = globalThis;
@@ -1623,9 +1630,9 @@ function toKebab(prop) {
1623
1630
  }
1624
1631
  function applyStyle(el, style) {
1625
1632
  if (typeof style === "function") {
1626
- const teardown = track2(() => {
1633
+ const teardown = reactiveBinding2(() => {
1627
1634
  el.setAttribute("style", sanitizeStyleAttribute(String(style())));
1628
- });
1635
+ }, el);
1629
1636
  registerDisposer(el, teardown);
1630
1637
  return;
1631
1638
  }
@@ -1639,9 +1646,9 @@ function applyStyle(el, style) {
1639
1646
  const name = toKebab(prop);
1640
1647
  if (typeof val === "function") {
1641
1648
  const getter = val;
1642
- const teardown = track2(() => {
1649
+ const teardown = reactiveBinding2(() => {
1643
1650
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter())));
1644
- });
1651
+ }, el);
1645
1652
  registerDisposer(el, teardown);
1646
1653
  } else {
1647
1654
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(val)));
@@ -1654,9 +1661,9 @@ function applyClass(el, cls) {
1654
1661
  return;
1655
1662
  }
1656
1663
  if (typeof cls === "function") {
1657
- const teardown = track2(() => {
1664
+ const teardown = reactiveBinding2(() => {
1658
1665
  el.setAttribute("class", cls());
1659
- });
1666
+ }, el);
1660
1667
  registerDisposer(el, teardown);
1661
1668
  return;
1662
1669
  }
@@ -1681,7 +1688,7 @@ function applyClass(el, cls) {
1681
1688
  }
1682
1689
  el.setAttribute("class", r);
1683
1690
  };
1684
- const teardown = track2(update);
1691
+ const teardown = reactiveBinding2(update, el);
1685
1692
  registerDisposer(el, teardown);
1686
1693
  } else {
1687
1694
  el.setAttribute("class", result);
package/dist/ecosystem.js CHANGED
@@ -7,18 +7,18 @@ import {
7
7
  mobXAdapter,
8
8
  reduxAdapter,
9
9
  zustandAdapter
10
- } from "./chunk-WN6WDFCB.js";
10
+ } from "./chunk-FB7KQXZI.js";
11
11
  import "./chunk-WWV3SJ3L.js";
12
- import "./chunk-DQVUBVNX.js";
13
- import "./chunk-4MFZMLUU.js";
14
- import "./chunk-Z2CE42DB.js";
12
+ import "./chunk-I3VI34DP.js";
13
+ import "./chunk-SHDVWATN.js";
14
+ import "./chunk-DCDS2PQ6.js";
15
15
  import "./chunk-5INI7D2L.js";
16
16
  import "./chunk-7ZHH77QA.js";
17
- import "./chunk-PBQEW5VW.js";
17
+ import "./chunk-PFTDGEPZ.js";
18
18
  import "./chunk-TIRZCERI.js";
19
19
  import "./chunk-DHDZ7IVN.js";
20
- import "./chunk-W55YHPEP.js";
21
- import "./chunk-ACBT6UZS.js";
20
+ import "./chunk-UFSQEO7P.js";
21
+ import "./chunk-D3SZ4LVS.js";
22
22
  import "./chunk-VPP2FONR.js";
23
23
  export {
24
24
  antdAdapter,
package/dist/extras.cjs CHANGED
@@ -645,7 +645,14 @@ function reactiveBinding(commit, ownerNode) {
645
645
  subscriber._disposed = false;
646
646
  subscriber._errorPhase = "binding";
647
647
  subscriber._errorNode = ownerNode;
648
- run();
648
+ try {
649
+ run();
650
+ } catch (err) {
651
+ subscriber._disposed = true;
652
+ subscriber._errorNode = void 0;
653
+ cleanup(subscriber);
654
+ throw err;
655
+ }
649
656
  return subscriber._dispose ?? (subscriber._dispose = () => {
650
657
  subscriber._disposed = true;
651
658
  subscriber._errorNode = void 0;
@@ -877,7 +884,7 @@ function forEachSubscriber(signal2, visit) {
877
884
 
878
885
  // src/reactivity/track.ts
879
886
  var _isDev2 = isDev();
880
- var _runtimeVersion = true ? "4.0.0" : "dev";
887
+ var _runtimeVersion = true ? "4.1.0" : "dev";
881
888
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
882
889
  function resolveReactiveApi() {
883
890
  const g2 = globalThis;
@@ -9021,9 +9028,9 @@ function toKebab(prop) {
9021
9028
  }
9022
9029
  function applyStyle(el, style2) {
9023
9030
  if (typeof style2 === "function") {
9024
- const teardown = track2(() => {
9031
+ const teardown = reactiveBinding2(() => {
9025
9032
  el.setAttribute("style", sanitizeStyleAttribute(String(style2())));
9026
- });
9033
+ }, el);
9027
9034
  registerDisposer(el, teardown);
9028
9035
  return;
9029
9036
  }
@@ -9037,9 +9044,9 @@ function applyStyle(el, style2) {
9037
9044
  const name = toKebab(prop);
9038
9045
  if (typeof val === "function") {
9039
9046
  const getter = val;
9040
- const teardown = track2(() => {
9047
+ const teardown = reactiveBinding2(() => {
9041
9048
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter())));
9042
- });
9049
+ }, el);
9043
9050
  registerDisposer(el, teardown);
9044
9051
  } else {
9045
9052
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(val)));
@@ -9052,9 +9059,9 @@ function applyClass(el, cls) {
9052
9059
  return;
9053
9060
  }
9054
9061
  if (typeof cls === "function") {
9055
- const teardown = track2(() => {
9062
+ const teardown = reactiveBinding2(() => {
9056
9063
  el.setAttribute("class", cls());
9057
- });
9064
+ }, el);
9058
9065
  registerDisposer(el, teardown);
9059
9066
  return;
9060
9067
  }
@@ -9079,7 +9086,7 @@ function applyClass(el, cls) {
9079
9086
  }
9080
9087
  el.setAttribute("class", r);
9081
9088
  };
9082
- const teardown = track2(update);
9089
+ const teardown = reactiveBinding2(update, el);
9083
9090
  registerDisposer(el, teardown);
9084
9091
  } else {
9085
9092
  el.setAttribute("class", result);
package/dist/extras.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  optimisticList,
6
6
  persisted,
7
7
  timeline
8
- } from "./chunk-WOOMUJFT.js";
8
+ } from "./chunk-4U7KD3RY.js";
9
9
  import {
10
10
  DOMPool,
11
11
  Features,
@@ -41,7 +41,7 @@ import {
41
41
  transitionState,
42
42
  uniqueId,
43
43
  yieldToMain
44
- } from "./chunk-3EKJK5FZ.js";
44
+ } from "./chunk-LOFZHP6V.js";
45
45
  import {
46
46
  VERSION,
47
47
  bundlerMetadata,
@@ -88,7 +88,7 @@ import {
88
88
  wasm,
89
89
  worker,
90
90
  workerFn
91
- } from "./chunk-VFHIVEPF.js";
91
+ } from "./chunk-BMSN6NWQ.js";
92
92
  import {
93
93
  FocusTrap,
94
94
  VirtualList,
@@ -129,7 +129,7 @@ import {
129
129
  toast,
130
130
  withScopedStyle,
131
131
  zipMask
132
- } from "./chunk-DR7H6UGM.js";
132
+ } from "./chunk-XIHMS2Y6.js";
133
133
  import {
134
134
  RenderProp,
135
135
  assertType,
@@ -156,7 +156,7 @@ import {
156
156
  select,
157
157
  tabs,
158
158
  tooltip
159
- } from "./chunk-6LG2T7LO.js";
159
+ } from "./chunk-FMHIQNMP.js";
160
160
  import {
161
161
  animationFrame,
162
162
  battery,
@@ -194,7 +194,7 @@ import {
194
194
  visibility,
195
195
  wakeLock,
196
196
  windowSize
197
- } from "./chunk-EWEE7NBF.js";
197
+ } from "./chunk-DAKLOMKS.js";
198
198
  import "./chunk-H3SRKIYX.js";
199
199
  import "./chunk-52XFPGSN.js";
200
200
  import {
@@ -219,7 +219,7 @@ import {
219
219
  syncAdapter,
220
220
  throttle,
221
221
  withRetry
222
- } from "./chunk-NF3LYCQH.js";
222
+ } from "./chunk-XSZRJW76.js";
223
223
  import {
224
224
  SibuError,
225
225
  checkLeaks,
@@ -257,7 +257,7 @@ import {
257
257
  trackCleanup,
258
258
  walkDependencyGraph,
259
259
  withErrorTracking
260
- } from "./chunk-F64ZPCFE.js";
260
+ } from "./chunk-4RH6D7GR.js";
261
261
  import {
262
262
  antdAdapter,
263
263
  chakraAdapter,
@@ -267,7 +267,7 @@ import {
267
267
  mobXAdapter,
268
268
  reduxAdapter,
269
269
  zustandAdapter
270
- } from "./chunk-WN6WDFCB.js";
270
+ } from "./chunk-FB7KQXZI.js";
271
271
  import {
272
272
  createPlugin,
273
273
  createPluginRegistry,
@@ -279,7 +279,7 @@ import {
279
279
  triggerPluginMount,
280
280
  triggerPluginUnmount
281
281
  } from "./chunk-WWV3SJ3L.js";
282
- import "./chunk-ES2GERY2.js";
282
+ import "./chunk-MRAMKDHQ.js";
283
283
  import {
284
284
  collectStream,
285
285
  deserializeState,
@@ -300,15 +300,15 @@ import {
300
300
  suspenseSwapScript,
301
301
  trustHTML
302
302
  } from "./chunk-HYCCIYNS.js";
303
- import "./chunk-RRIDJQ5C.js";
303
+ import "./chunk-KSOEJGFO.js";
304
304
  import "./chunk-CCSJMTRN.js";
305
- import "./chunk-O3QQ3XOL.js";
306
- import "./chunk-DQVUBVNX.js";
307
- import "./chunk-4MFZMLUU.js";
308
- import "./chunk-Z2CE42DB.js";
305
+ import "./chunk-5GJ5RTHA.js";
306
+ import "./chunk-I3VI34DP.js";
307
+ import "./chunk-SHDVWATN.js";
308
+ import "./chunk-DCDS2PQ6.js";
309
309
  import "./chunk-5INI7D2L.js";
310
310
  import "./chunk-7ZHH77QA.js";
311
- import "./chunk-PBQEW5VW.js";
311
+ import "./chunk-PFTDGEPZ.js";
312
312
  import "./chunk-TIRZCERI.js";
313
313
  import {
314
314
  TransitionGroup,
@@ -332,10 +332,10 @@ import {
332
332
  stagger,
333
333
  transition,
334
334
  viewTransition
335
- } from "./chunk-E62UADKL.js";
335
+ } from "./chunk-IM7N5WR4.js";
336
336
  import "./chunk-DHDZ7IVN.js";
337
- import "./chunk-W55YHPEP.js";
338
- import "./chunk-ACBT6UZS.js";
337
+ import "./chunk-UFSQEO7P.js";
338
+ import "./chunk-D3SZ4LVS.js";
339
339
  import "./chunk-VPP2FONR.js";
340
340
  export {
341
341
  DOMPool,
package/dist/index.cjs CHANGED
@@ -95,6 +95,7 @@ __export(index_exports, {
95
95
  enhance: () => enhance,
96
96
  enhanceAll: () => enhanceAll,
97
97
  enqueueBatchedSignal: () => enqueueBatchedSignal,
98
+ external: () => external,
98
99
  fieldset: () => fieldset,
99
100
  figcaption: () => figcaption,
100
101
  figure: () => figure,
@@ -909,7 +910,14 @@ function reactiveBinding(commit, ownerNode) {
909
910
  subscriber._disposed = false;
910
911
  subscriber._errorPhase = "binding";
911
912
  subscriber._errorNode = ownerNode;
912
- run();
913
+ try {
914
+ run();
915
+ } catch (err) {
916
+ subscriber._disposed = true;
917
+ subscriber._errorNode = void 0;
918
+ cleanup(subscriber);
919
+ throw err;
920
+ }
913
921
  return subscriber._dispose ?? (subscriber._dispose = () => {
914
922
  subscriber._disposed = true;
915
923
  subscriber._errorNode = void 0;
@@ -1141,7 +1149,7 @@ function forEachSubscriber(signal2, visit) {
1141
1149
 
1142
1150
  // src/reactivity/track.ts
1143
1151
  var _isDev4 = isDev();
1144
- var _runtimeVersion = true ? "4.0.0" : "dev";
1152
+ var _runtimeVersion = true ? "4.1.0" : "dev";
1145
1153
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1146
1154
  function resolveReactiveApi() {
1147
1155
  const g2 = globalThis;
@@ -1364,9 +1372,9 @@ function toKebab(prop) {
1364
1372
  }
1365
1373
  function applyStyle(el, style2) {
1366
1374
  if (typeof style2 === "function") {
1367
- const teardown = track2(() => {
1375
+ const teardown = reactiveBinding2(() => {
1368
1376
  el.setAttribute("style", sanitizeStyleAttribute(String(style2())));
1369
- });
1377
+ }, el);
1370
1378
  registerDisposer(el, teardown);
1371
1379
  return;
1372
1380
  }
@@ -1380,9 +1388,9 @@ function applyStyle(el, style2) {
1380
1388
  const name = toKebab(prop);
1381
1389
  if (typeof val === "function") {
1382
1390
  const getter = val;
1383
- const teardown = track2(() => {
1391
+ const teardown = reactiveBinding2(() => {
1384
1392
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter())));
1385
- });
1393
+ }, el);
1386
1394
  registerDisposer(el, teardown);
1387
1395
  } else {
1388
1396
  htmlEl.style.setProperty(name, sanitizeCSSValue(String(val)));
@@ -1395,9 +1403,9 @@ function applyClass(el, cls) {
1395
1403
  return;
1396
1404
  }
1397
1405
  if (typeof cls === "function") {
1398
- const teardown = track2(() => {
1406
+ const teardown = reactiveBinding2(() => {
1399
1407
  el.setAttribute("class", cls());
1400
- });
1408
+ }, el);
1401
1409
  registerDisposer(el, teardown);
1402
1410
  return;
1403
1411
  }
@@ -1422,7 +1430,7 @@ function applyClass(el, cls) {
1422
1430
  }
1423
1431
  el.setAttribute("class", r);
1424
1432
  };
1425
- const teardown = track2(update);
1433
+ const teardown = reactiveBinding2(update, el);
1426
1434
  registerDisposer(el, teardown);
1427
1435
  } else {
1428
1436
  el.setAttribute("class", result);
@@ -2396,6 +2404,17 @@ function signal(initial, options) {
2396
2404
  }
2397
2405
  return [get, set];
2398
2406
  }
2407
+ function external(options) {
2408
+ const [version, bump] = signal(0, options?.name ? { name: options.name } : void 0);
2409
+ return {
2410
+ track() {
2411
+ version();
2412
+ },
2413
+ invalidate() {
2414
+ bump((n) => n + 1);
2415
+ }
2416
+ };
2417
+ }
2399
2418
 
2400
2419
  // src/components/ErrorDisplay.ts
2401
2420
  var STYLES = `
@@ -4960,6 +4979,7 @@ function drainTeardowns(teardowns, label2) {
4960
4979
  }
4961
4980
  }
4962
4981
  }
4982
+ var EACH_KEYS = ["text", "attr", "class", "show", "on", "cleanup"];
4963
4983
  function resolveTarget(root, target) {
4964
4984
  if (target == null) return root;
4965
4985
  if (typeof target !== "string") return target;
@@ -4970,6 +4990,11 @@ function resolveTarget(root, target) {
4970
4990
  return null;
4971
4991
  }
4972
4992
  }
4993
+ function bindNode(el, commit) {
4994
+ if (isSSR()) return () => {
4995
+ };
4996
+ return reactiveBinding2(commit, el);
4997
+ }
4973
4998
  function readControlValue(el) {
4974
4999
  if (el instanceof HTMLInputElement) {
4975
5000
  if (el.type === "checkbox") return el.checked;
@@ -4993,6 +5018,50 @@ function writeControlValue(el, value) {
4993
5018
  const next = value == null ? "" : String(value);
4994
5019
  if (el.value !== next) el.value = next;
4995
5020
  }
5021
+ function applyEachBindings(ctx, el, spec, index) {
5022
+ const where = `ctx.each[${index}]`;
5023
+ if (isDev()) {
5024
+ for (const key of Object.keys(spec)) {
5025
+ devAssert(
5026
+ EACH_KEYS.includes(key),
5027
+ `${where}: unknown binding "${key}". Expected one of: ${EACH_KEYS.join(", ")}.`
5028
+ );
5029
+ }
5030
+ }
5031
+ if (spec.text !== void 0) {
5032
+ devAssert(typeof spec.text === "function", `${where}: "text" must be a function returning the value.`);
5033
+ ctx.text(el, spec.text);
5034
+ }
5035
+ if (spec.attr !== void 0) {
5036
+ for (const name of Object.keys(spec.attr)) {
5037
+ const value = spec.attr[name];
5038
+ devAssert(typeof value === "function", `${where}: attr["${name}"] must be a function returning the value.`);
5039
+ ctx.attr(el, name, value);
5040
+ }
5041
+ }
5042
+ if (spec.class !== void 0) {
5043
+ for (const name of Object.keys(spec.class)) {
5044
+ const on2 = spec.class[name];
5045
+ devAssert(typeof on2 === "function", `${where}: class["${name}"] must be a function returning a boolean.`);
5046
+ ctx.classed(el, name, on2);
5047
+ }
5048
+ }
5049
+ if (spec.show !== void 0) {
5050
+ devAssert(typeof spec.show === "function", `${where}: "show" must be a function returning a boolean.`);
5051
+ ctx.show(el, spec.show);
5052
+ }
5053
+ if (spec.on !== void 0) {
5054
+ for (const event of Object.keys(spec.on)) {
5055
+ const handler = spec.on[event];
5056
+ devAssert(typeof handler === "function", `${where}: on["${String(event)}"] must be a function.`);
5057
+ ctx.on(el, event, handler);
5058
+ }
5059
+ }
5060
+ if (spec.cleanup !== void 0) {
5061
+ devAssert(typeof spec.cleanup === "function", `${where}: "cleanup" must be a function.`);
5062
+ ctx.cleanup(spec.cleanup);
5063
+ }
5064
+ }
4996
5065
  function enhance(target, setup) {
4997
5066
  const root = typeof target === "string" ? typeof document !== "undefined" ? document.querySelector(target) : null : target;
4998
5067
  if (!root) {
@@ -5043,7 +5112,7 @@ function enhance(target, setup) {
5043
5112
  text: (t, value) => {
5044
5113
  bind(t, (el) => {
5045
5114
  teardowns.push(
5046
- effect(() => {
5115
+ bindNode(el, () => {
5047
5116
  const v = value();
5048
5117
  const next = v == null ? "" : String(v);
5049
5118
  if (el.textContent !== next) el.textContent = next;
@@ -5054,7 +5123,7 @@ function enhance(target, setup) {
5054
5123
  attr: (t, name, value) => {
5055
5124
  bind(t, (el) => {
5056
5125
  teardowns.push(
5057
- effect(() => {
5126
+ bindNode(el, () => {
5058
5127
  const v = value();
5059
5128
  const next = v == null ? null : String(v);
5060
5129
  setSafeAttribute(el, name, next, { syncValueProperty: false, label: "enhance attr()" });
@@ -5065,7 +5134,7 @@ function enhance(target, setup) {
5065
5134
  classed: (t, name, on2) => {
5066
5135
  bind(t, (el) => {
5067
5136
  teardowns.push(
5068
- effect(() => {
5137
+ bindNode(el, () => {
5069
5138
  el.classList.toggle(name, Boolean(on2()));
5070
5139
  })
5071
5140
  );
@@ -5075,7 +5144,7 @@ function enhance(target, setup) {
5075
5144
  bind(t, (el) => {
5076
5145
  const prevHidden = el.hidden;
5077
5146
  teardowns.push(
5078
- effect(() => {
5147
+ bindNode(el, () => {
5079
5148
  el.hidden = !when2();
5080
5149
  })
5081
5150
  );
@@ -5090,7 +5159,7 @@ function enhance(target, setup) {
5090
5159
  const [get, set] = state2;
5091
5160
  const evt = options?.event ?? (control instanceof HTMLSelectElement || control instanceof HTMLInputElement && (control.type === "checkbox" || control.type === "radio") ? "change" : "input");
5092
5161
  teardowns.push(
5093
- effect(() => {
5162
+ bindNode(el, () => {
5094
5163
  writeControlValue(control, get());
5095
5164
  })
5096
5165
  );
@@ -5099,6 +5168,16 @@ function enhance(target, setup) {
5099
5168
  teardowns.push(() => control.removeEventListener(evt, onInput));
5100
5169
  });
5101
5170
  },
5171
+ each: (target_, describe2) => {
5172
+ devAssert(typeof describe2 === "function", "ctx.each: second argument must be a function.");
5173
+ const elements = typeof target_ === "string" ? ctx.refs(target_) : Array.from(target_);
5174
+ for (let i2 = 0; i2 < elements.length; i2++) {
5175
+ const el = elements[i2];
5176
+ const spec = describe2(el, i2);
5177
+ if (spec == null) continue;
5178
+ applyEachBindings(ctx, el, spec, i2);
5179
+ }
5180
+ },
5102
5181
  cleanup: (fn) => {
5103
5182
  teardowns.push(fn);
5104
5183
  }
@@ -5474,6 +5553,7 @@ function nextTick() {
5474
5553
  enhance,
5475
5554
  enhanceAll,
5476
5555
  enqueueBatchedSignal,
5556
+ external,
5477
5557
  fieldset,
5478
5558
  figcaption,
5479
5559
  figure,
package/dist/index.d.cts CHANGED
@@ -1186,6 +1186,70 @@ interface SignalOptions<T = unknown> {
1186
1186
  * @param options Optional config: `{ name: "count" }` for devtools labeling
1187
1187
  */
1188
1188
  declare function signal<T>(initial: T, options?: SignalOptions<T>): StateTuple<T>;
1189
+ /**
1190
+ * A valueless reactive token standing in for state SibuJS does not own.
1191
+ *
1192
+ * See {@link external}.
1193
+ */
1194
+ interface ExternalSource {
1195
+ /**
1196
+ * Declare, from inside a reactive computation, that it reads the external
1197
+ * state this source represents. Call it in the same places you would read a
1198
+ * signal — the top of a binding getter, a `derived()` body, an `effect()`.
1199
+ *
1200
+ * Outside a tracking context it is a no-op, exactly like reading a signal.
1201
+ */
1202
+ track(): void;
1203
+ /**
1204
+ * Declare that the external state changed. Every consumer that called
1205
+ * {@link ExternalSource.track} is invalidated.
1206
+ *
1207
+ * Participates in `batch()` like any signal write: inside a batch, consumers
1208
+ * are notified once when the outermost batch flushes.
1209
+ */
1210
+ invalidate(): void;
1211
+ }
1212
+ /**
1213
+ * Create a reactive source for state that lives outside SibuJS — a domain
1214
+ * engine, a media element, a canvas scene, an editor document, a cache a
1215
+ * socket writes into.
1216
+ *
1217
+ * The pattern is two lines: `track()` where you read, `invalidate()` after you
1218
+ * mutate.
1219
+ *
1220
+ * ```ts
1221
+ * import { Chess } from "chess.js";
1222
+ * import { external } from "sibujs";
1223
+ *
1224
+ * const game = new Chess(); // owns the rules and the mutable state
1225
+ * const moved = external(); // owns "something changed"
1226
+ *
1227
+ * ctx.text("@status", () => {
1228
+ * moved.track(); // this binding reads the engine
1229
+ * return game.isCheckmate() ? "Checkmate" : `${game.turn()} to move`;
1230
+ * });
1231
+ *
1232
+ * game.move({ from: "e2", to: "e4" });
1233
+ * moved.invalidate(); // every consumer above re-reads
1234
+ * ```
1235
+ *
1236
+ * **One source is one invalidation domain.** Every consumer of a source
1237
+ * re-runs on every `invalidate()`, so the granularity of your updates is
1238
+ * exactly the granularity of your sources: one for a whole engine is the
1239
+ * cheapest to write, several (`board`, `clock`, `history`) let an update touch
1240
+ * only what it affects. See `docs/architecture/external-state.md` for the
1241
+ * trade-offs and when subdividing is worth it.
1242
+ *
1243
+ * Ownership, disposal and error routing are the consumer's, not the source's:
1244
+ * a disposed binding or effect is never invalidated, and a consumer that
1245
+ * throws is reported through the normal runtime error pipeline with its own
1246
+ * phase and node.
1247
+ *
1248
+ * @param options `name` labels the source in devtools (development only).
1249
+ */
1250
+ declare function external(options?: {
1251
+ name?: string;
1252
+ }): ExternalSource;
1189
1253
 
1190
1254
  /**
1191
1255
  * Reactive array hook. Provides common array operations that
@@ -1683,6 +1747,37 @@ declare function strict<T>(fn: () => T): T;
1683
1747
  */
1684
1748
  declare function strictEffect(fn: () => void): () => void;
1685
1749
 
1750
+ /** Event handlers for {@link EachBindings}, typed per event name. */
1751
+ type EachEventBindings = {
1752
+ [K in keyof HTMLElementEventMap]?: (event: HTMLElementEventMap[K], el: HTMLElement) => void;
1753
+ };
1754
+ /**
1755
+ * What {@link EnhanceContext.each} attaches to one element.
1756
+ *
1757
+ * Every field maps one-to-one onto an existing `ctx.*` helper and is committed
1758
+ * through it — this is a shorthand for calls you could write by hand, not a
1759
+ * template language and not a second binding engine. There is no expression
1760
+ * parsing, no string interpolation and no `eval`: every value is a plain
1761
+ * function you wrote, so it stays CSP-safe and fully type-checked.
1762
+ *
1763
+ * Anything not covered here (two-way `model()`, listener options, a nested
1764
+ * `enhance`) is written imperatively in the same callback — it receives the
1765
+ * element, so `ctx.model(el, …)` beside a returned descriptor is normal.
1766
+ */
1767
+ interface EachBindings {
1768
+ /** Reactive `textContent` — same as `ctx.text(el, value)`. */
1769
+ text?: () => unknown;
1770
+ /** Reactive attributes by name — same as `ctx.attr(el, name, value)`. */
1771
+ attr?: Record<string, () => unknown>;
1772
+ /** Reactive class toggles by class name — same as `ctx.classed(el, name, on)`. */
1773
+ class?: Record<string, () => boolean>;
1774
+ /** Reactive visibility — same as `ctx.show(el, when)`. */
1775
+ show?: () => boolean;
1776
+ /** Event listeners by event name — same as `ctx.on(el, event, handler)`. */
1777
+ on?: EachEventBindings;
1778
+ /** Per-element teardown, run with the rest of the enhancement's cleanups. */
1779
+ cleanup?: () => void;
1780
+ }
1686
1781
  /**
1687
1782
  * Helpers handed to an `enhance` setup. Every binding is fine-grained (its own
1688
1783
  * effect) and auto-disposed when the root element (or the returned dispose) is
@@ -1715,6 +1810,43 @@ interface EnhanceContext {
1715
1810
  model<T>(target: string | Element, state: readonly [() => T, (value: T) => void], options?: {
1716
1811
  event?: string;
1717
1812
  }): void;
1813
+ /**
1814
+ * Bind a set of elements the server already rendered — a board, a table, a
1815
+ * keyboard, a timeline, a legend — one descriptor at a time.
1816
+ *
1817
+ * The callback receives each element and its index and returns what to
1818
+ * attach; every field is committed through the matching `ctx.*` helper, so
1819
+ * ownership, disposal, write elision, attribute sanitization and error
1820
+ * routing are byte-for-byte the same as writing the calls out by hand. No
1821
+ * element is created, replaced, moved or re-parented — node identity is
1822
+ * preserved, which is the entire point of enhancing existing markup.
1823
+ *
1824
+ * ```ts
1825
+ * ctx.each<HTMLButtonElement>("@square", (el) => {
1826
+ * const square = el.dataset.square as Square;
1827
+ * return {
1828
+ * text: () => pieceAt(square),
1829
+ * class: { selected: () => selected() === square },
1830
+ * attr: { "aria-label": () => describe(square) },
1831
+ * on: { click: () => choose(square) },
1832
+ * };
1833
+ * });
1834
+ * ```
1835
+ *
1836
+ * The callback may also return nothing and wire the element imperatively —
1837
+ * `ctx.model(el, …)`, `ctx.on(el, "click", h, { passive: true })` — for the
1838
+ * cases the descriptor deliberately does not cover.
1839
+ *
1840
+ * Zero matches is a silent no-op. Calling `each` twice over the same
1841
+ * elements creates two independent sets of bindings, exactly as calling
1842
+ * `ctx.text()` twice on one node does; the helper is sugar over those calls
1843
+ * and does not track what a previous call attached.
1844
+ *
1845
+ * @param target A `@ref`/CSS selector resolved with {@link EnhanceContext.refs},
1846
+ * or any iterable of elements (an array, a `NodeList`, an `HTMLCollection`).
1847
+ * @param describe Called once per element, in document order.
1848
+ */
1849
+ each<T extends Element = HTMLElement>(target: string | Iterable<Element>, describe: (element: T, index: number) => EachBindings | void): void;
1718
1850
  /** Register arbitrary teardown to run on disposal. */
1719
1851
  cleanup(fn: () => void): void;
1720
1852
  }
@@ -2021,4 +2153,4 @@ declare const untracked: ReactiveApi["untracked"];
2021
2153
  declare const retrack: ReactiveApi["retrack"];
2022
2154
  declare const setMaxDrainIterations: ReactiveApi["setMaxDrainIterations"];
2023
2155
 
2024
- export { type Accessor, type ActionFn, type AnchorProps, type ArrayActions, type AsyncDerivedContext, type AsyncDerivedState, type AudioProps, type ButtonProps, type Context, DynamicComponent, type EffectBody, type EffectOptions, type EnhanceContext, type EnhanceSetup, ErrorBoundary, type ErrorBoundaryOptions, type ErrorBoundaryProps, ErrorDisplay, type ErrorDisplayProps, type ErrorSeverity, type FormProps, Fragment, type ImgProps, type InputProps, type InputType, type IslandLoader, type IslandRegistration, type IslandStrategy, KeepAlive, type KeepAliveOptions, type LabelProps, Loading, type LoadingProps, type LongPressOptions, MAX_DRAIN_TEARDOWNS, type MediaProps, type MountIslandsOptions, NodeChild, NodeChildren, type OnCleanup, type OptionProps, Portal, type Ref, type RuntimeErrorContext, type RuntimeErrorHandler, type RuntimeErrorPhase, type SSRStore, type SelectProps, type SignalOptions, type SlotFn, type Slots, type StoreActions, Suspense, type SuspenseProps, TagProps, type TextareaProps, type TypedTagFunction, type VideoProps, __resetIdCounter, a, abbr, action, address, area, array, article, aside, asyncDerived, audio, autoResize, b, base, batch, bdi, bdo, bindDynamic, blockquote, body, br, button, canvas, caption, catchError, catchErrorAsync, center, checkLeaks, circle, cite, clickOutside, clipPath, code, col, colgroup, context, copyOnClick, createId, customElement, data, datalist, dd, deepEqual, deepSignal, defer, defs, del, derived, details, dfn, dialog, disableSSR, dispose, div, dl, dt, each, effect, ellipse, em, embed, enableSSR, enhance, enhanceAll, enqueueBatchedSignal, fieldset, figcaption, figure, font, footer, form, g, getAction, getRequestScopedCache, getRuntimeErrorHandler, getSSRStore, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, lazyIsland, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, menu, meta, meter, mount, mountIslands, nav, nextTick, noscript, object, ol, on, onCleanup, onMount, onUnmount, optgroup, option, output, p, param, path, pattern, picture, polygon, polyline, portal, pre, progress, q, radialGradient, reactiveArray, rect, ref, registerAction, registerComponent, registerDisposer, registerIsland, replaceChildrenSafely, reportDrainRunaway, reportError, resolveComponent, retrack, rp, rt, ruby, runInSSRContext, s, samp, script, section, select, setGlobalErrorHandler, setMaxDrainIterations, setRuntimeErrorHandler, show, signal, slot, small, source, span, stop, store, strict, strictEffect, strong, style, sub, summary, sup, svg, symbol, table, takePendingError, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track$1 as track, transition, trapFocus, tspan, u, ul, unregisterComponent, unregisterDisposer, unregisterIsland, untracked, use, var_, video, watch, when, withSSR, writable };
2156
+ export { type Accessor, type ActionFn, type AnchorProps, type ArrayActions, type AsyncDerivedContext, type AsyncDerivedState, type AudioProps, type ButtonProps, type Context, DynamicComponent, type EachBindings, type EachEventBindings, type EffectBody, type EffectOptions, type EnhanceContext, type EnhanceSetup, ErrorBoundary, type ErrorBoundaryOptions, type ErrorBoundaryProps, ErrorDisplay, type ErrorDisplayProps, type ErrorSeverity, type ExternalSource, type FormProps, Fragment, type ImgProps, type InputProps, type InputType, type IslandLoader, type IslandRegistration, type IslandStrategy, KeepAlive, type KeepAliveOptions, type LabelProps, Loading, type LoadingProps, type LongPressOptions, MAX_DRAIN_TEARDOWNS, type MediaProps, type MountIslandsOptions, NodeChild, NodeChildren, type OnCleanup, type OptionProps, Portal, type Ref, type RuntimeErrorContext, type RuntimeErrorHandler, type RuntimeErrorPhase, type SSRStore, type SelectProps, type SignalOptions, type SlotFn, type Slots, type StoreActions, Suspense, type SuspenseProps, TagProps, type TextareaProps, type TypedTagFunction, type VideoProps, __resetIdCounter, a, abbr, action, address, area, array, article, aside, asyncDerived, audio, autoResize, b, base, batch, bdi, bdo, bindDynamic, blockquote, body, br, button, canvas, caption, catchError, catchErrorAsync, center, checkLeaks, circle, cite, clickOutside, clipPath, code, col, colgroup, context, copyOnClick, createId, customElement, data, datalist, dd, deepEqual, deepSignal, defer, defs, del, derived, details, dfn, dialog, disableSSR, dispose, div, dl, dt, each, effect, ellipse, em, embed, enableSSR, enhance, enhanceAll, enqueueBatchedSignal, external, fieldset, figcaption, figure, font, footer, form, g, getAction, getRequestScopedCache, getRuntimeErrorHandler, getSSRStore, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, lazyIsland, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, menu, meta, meter, mount, mountIslands, nav, nextTick, noscript, object, ol, on, onCleanup, onMount, onUnmount, optgroup, option, output, p, param, path, pattern, picture, polygon, polyline, portal, pre, progress, q, radialGradient, reactiveArray, rect, ref, registerAction, registerComponent, registerDisposer, registerIsland, replaceChildrenSafely, reportDrainRunaway, reportError, resolveComponent, retrack, rp, rt, ruby, runInSSRContext, s, samp, script, section, select, setGlobalErrorHandler, setMaxDrainIterations, setRuntimeErrorHandler, show, signal, slot, small, source, span, stop, store, strict, strictEffect, strong, style, sub, summary, sup, svg, symbol, table, takePendingError, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track$1 as track, transition, trapFocus, tspan, u, ul, unregisterComponent, unregisterDisposer, unregisterIsland, untracked, use, var_, video, watch, when, withSSR, writable };