sibujs 4.6.0 → 4.7.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 (79) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +18 -5
  3. package/dist/browser.js +4 -4
  4. package/dist/build.cjs +1956 -1007
  5. package/dist/build.d.cts +133 -44
  6. package/dist/build.d.ts +133 -44
  7. package/dist/build.js +1368 -569
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +11 -11
  10. package/dist/cdn.full.global.js +10 -10
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-SXXVZMKZ.js → chunk-2BPG2XDA.js} +5 -5
  13. package/dist/{chunk-BAAG6ZTI.js → chunk-3QWBSL5R.js} +4 -4
  14. package/dist/{chunk-5MT6SJ3P.js → chunk-4AWA2PVD.js} +2 -2
  15. package/dist/{chunk-JSXPZCET.js → chunk-7LUJQAOJ.js} +1 -1
  16. package/dist/{chunk-3ISI6ACU.js → chunk-BC2SECJD.js} +1 -1
  17. package/dist/{chunk-DKGBBKOF.js → chunk-BSY63EM6.js} +2 -2
  18. package/dist/{chunk-7NCARGJW.js → chunk-DKXRACVN.js} +3 -3
  19. package/dist/{chunk-47M47FOM.js → chunk-FNJXNGYZ.js} +1 -1
  20. package/dist/{chunk-D33YTSX5.js → chunk-FQRUXCEE.js} +5 -4
  21. package/dist/{chunk-5Q4R7HCL.js → chunk-JWKYU5GV.js} +11 -2
  22. package/dist/{chunk-QE4TTDU3.js → chunk-RUSSKG6G.js} +12 -11
  23. package/dist/{chunk-UZQ6ALFS.js → chunk-SLM3IA34.js} +1 -1
  24. package/dist/{chunk-XRRZKZYX.js → chunk-UNWRJRKC.js} +1 -1
  25. package/dist/{chunk-VVWPJ543.js → chunk-UOL2ECCS.js} +40 -11
  26. package/dist/{chunk-ORMZXBKQ.js → chunk-VZSG24LS.js} +3 -3
  27. package/dist/{chunk-XC4MEKGA.js → chunk-WEQ3DMVL.js} +8 -2
  28. package/dist/{chunk-PK6FK2G2.js → chunk-WOLJZUFQ.js} +176 -27
  29. package/dist/{chunk-7GQCWFOE.js → chunk-XYV3EDB7.js} +143 -74
  30. package/dist/{chunk-AIF3Z2T7.js → chunk-XZR4PXRE.js} +3 -3
  31. package/dist/{chunk-HYXDKS4N.js → chunk-Z3OHK6QT.js} +4 -4
  32. package/dist/{chunk-OXUY2A6L.js → chunk-ZVL7TY4K.js} +2 -2
  33. package/dist/{contracts-DRIuclVT.d.ts → contracts-CLqzJnOV.d.ts} +28 -17
  34. package/dist/{contracts-DRIuclVT.d.cts → contracts-CTOJXu-x.d.cts} +28 -17
  35. package/dist/data.cjs +198 -39
  36. package/dist/data.d.cts +119 -3
  37. package/dist/data.d.ts +119 -3
  38. package/dist/data.js +5 -5
  39. package/dist/devtools.cjs +18 -5
  40. package/dist/devtools.js +4 -4
  41. package/dist/ecosystem.cjs +64 -23
  42. package/dist/ecosystem.d.cts +2 -1
  43. package/dist/ecosystem.d.ts +2 -1
  44. package/dist/ecosystem.js +7 -7
  45. package/dist/extras.cjs +236 -49
  46. package/dist/extras.d.cts +3 -2
  47. package/dist/extras.d.ts +3 -2
  48. package/dist/extras.js +20 -20
  49. package/dist/index.cjs +259 -108
  50. package/dist/index.d.cts +193 -168
  51. package/dist/index.d.ts +193 -168
  52. package/dist/index.js +10 -10
  53. package/dist/motion.cjs +12 -4
  54. package/dist/motion.js +3 -3
  55. package/dist/patterns.cjs +28 -15
  56. package/dist/patterns.d.cts +2 -1
  57. package/dist/patterns.d.ts +2 -1
  58. package/dist/patterns.js +5 -5
  59. package/dist/performance.cjs +18 -5
  60. package/dist/performance.js +4 -4
  61. package/dist/plugins.cjs +118 -34
  62. package/dist/plugins.d.cts +124 -11
  63. package/dist/plugins.d.ts +124 -11
  64. package/dist/plugins.js +68 -26
  65. package/dist/ssr.cjs +54 -13
  66. package/dist/ssr.js +7 -7
  67. package/dist/{tagFactory-DFstCLQV.d.cts → tagFactory-8qL9LCIx.d.cts} +58 -19
  68. package/dist/{tagFactory-DkaNVUNV.d.ts → tagFactory-BL2fymez.d.ts} +58 -19
  69. package/dist/testing.cjs +15 -2
  70. package/dist/testing.js +2 -2
  71. package/dist/types-CJFViL6Q.d.cts +26 -0
  72. package/dist/types-CJFViL6Q.d.ts +26 -0
  73. package/dist/ui.cjs +28 -15
  74. package/dist/ui.d.cts +2 -1
  75. package/dist/ui.d.ts +2 -1
  76. package/dist/ui.js +7 -7
  77. package/dist/widgets.cjs +22 -14
  78. package/dist/widgets.js +6 -6
  79. package/package.json +4 -2
package/dist/index.cjs CHANGED
@@ -411,6 +411,49 @@ function registerDisposer(node, teardown) {
411
411
  const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
412
412
  if (capture) capture.entries.push([node, teardown]);
413
413
  }
414
+ function withDisposerRollback(build) {
415
+ const frame = { entries: [], closed: false };
416
+ const captured = frame.entries;
417
+ registrationCaptures.push(frame);
418
+ let result;
419
+ try {
420
+ result = build();
421
+ } catch (err) {
422
+ let executed = 0;
423
+ try {
424
+ while (captured.length > 0) {
425
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
426
+ reportDrainRunaway("rollback", executed, captured.length);
427
+ break;
428
+ }
429
+ const [node, teardown] = captured.pop();
430
+ if (!unregisterDisposer(node, teardown)) continue;
431
+ executed++;
432
+ try {
433
+ teardown();
434
+ } catch (cleanupErr) {
435
+ reportError(cleanupErr, { phase: "cleanup", name: "disposer" });
436
+ }
437
+ }
438
+ } finally {
439
+ frame.closed = true;
440
+ captured.length = 0;
441
+ registrationCaptures.pop();
442
+ }
443
+ throw err;
444
+ }
445
+ frame.closed = true;
446
+ registrationCaptures.pop();
447
+ const parent = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
448
+ if (parent) {
449
+ for (const entry of captured) {
450
+ if (elementDisposers.get(entry[0])?.includes(entry[1])) parent.entries.push(entry);
451
+ }
452
+ frame.forwardTo = parent;
453
+ }
454
+ captured.length = 0;
455
+ return result;
456
+ }
414
457
  function unregisterDisposer(node, teardown) {
415
458
  const disposers = elementDisposers.get(node);
416
459
  if (!disposers) return false;
@@ -1274,7 +1317,7 @@ function forEachSubscriber(signal2, visit) {
1274
1317
  }
1275
1318
 
1276
1319
  // src/reactivity/track.ts
1277
- var _runtimeVersion = true ? "4.6.0" : "dev";
1320
+ var _runtimeVersion = true ? "4.7.0" : "dev";
1278
1321
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1279
1322
  function resolveReactiveApi() {
1280
1323
  const g2 = globalThis;
@@ -1582,8 +1625,34 @@ function toKebab(prop) {
1582
1625
  }
1583
1626
  function applyStyle(el, style2) {
1584
1627
  if (typeof style2 === "function") {
1628
+ const getter = style2;
1629
+ let written = [];
1630
+ let wroteString = false;
1585
1631
  const teardown = reactiveBinding2(() => {
1586
- el.setAttribute("style", sanitizeStyleAttribute(String(style2()), { element: el }));
1632
+ const value = getter();
1633
+ if (value === null || typeof value !== "object") {
1634
+ written = [];
1635
+ wroteString = true;
1636
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
1637
+ return;
1638
+ }
1639
+ const decl = el.style;
1640
+ if (wroteString) {
1641
+ el.removeAttribute("style");
1642
+ wroteString = false;
1643
+ }
1644
+ const next = [];
1645
+ for (const prop in value) {
1646
+ const val = value[prop];
1647
+ if (val == null) continue;
1648
+ const name = toKebab(prop);
1649
+ next.push(name);
1650
+ decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
1651
+ }
1652
+ for (let i2 = 0; i2 < written.length; i2++) {
1653
+ if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
1654
+ }
1655
+ written = next;
1587
1656
  }, el);
1588
1657
  registerDisposer(el, teardown);
1589
1658
  return;
@@ -1614,7 +1683,9 @@ function applyClass(el, cls) {
1614
1683
  }
1615
1684
  if (typeof cls === "function") {
1616
1685
  const teardown = reactiveBinding2(() => {
1617
- el.setAttribute("class", cls());
1686
+ const value = cls();
1687
+ if (value == null || value === false) el.removeAttribute("class");
1688
+ else el.setAttribute("class", value);
1618
1689
  }, el);
1619
1690
  registerDisposer(el, teardown);
1620
1691
  return;
@@ -1696,7 +1767,7 @@ function appendChildren(el, nodes) {
1696
1767
  }
1697
1768
  }
1698
1769
  }
1699
- var tagFactory = (tag, ns) => {
1770
+ function tagFactory(tag, ns) {
1700
1771
  const blocked = isBlockedTag(tag);
1701
1772
  return (first, second) => {
1702
1773
  if (blocked) {
@@ -1745,7 +1816,9 @@ var tagFactory = (tag, ns) => {
1745
1816
  const pClass = props.class;
1746
1817
  if (pClass != null) applyClass(el, pClass);
1747
1818
  const pId = props.id;
1748
- if (pId != null) {
1819
+ if (typeof pId === "function") {
1820
+ registerDisposer(el, bindAttribute(el, "id", pId));
1821
+ } else if (pId != null) {
1749
1822
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1750
1823
  devWarn(
1751
1824
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -1795,12 +1868,10 @@ var tagFactory = (tag, ns) => {
1795
1868
  }
1796
1869
  }
1797
1870
  }
1798
- if (props.onElement && typeof props.onElement === "function") {
1799
- props.onElement(el);
1800
- }
1871
+ if (typeof props.onElement === "function") props.onElement(el);
1801
1872
  return el;
1802
1873
  };
1803
- };
1874
+ }
1804
1875
 
1805
1876
  // src/core/rendering/html.ts
1806
1877
  var html = tagFactory("html");
@@ -2049,10 +2120,13 @@ function safeCall(cb, hookName) {
2049
2120
  return void 0;
2050
2121
  }
2051
2122
  }
2052
- function runMountCallback(callback, hookName, element) {
2123
+ function runMountCallback(callback, hookName, element, isDisposed) {
2053
2124
  const cleanup3 = safeCall(callback, hookName);
2054
- if (typeof cleanup3 === "function" && element) {
2055
- registerDisposer(element, cleanup3);
2125
+ if (typeof cleanup3 !== "function") return;
2126
+ if (isDisposed() || !element.isConnected) {
2127
+ safeCall(cleanup3, "onUnmount");
2128
+ } else {
2129
+ onUnmount(cleanup3, element);
2056
2130
  }
2057
2131
  }
2058
2132
  var mountWatchers = /* @__PURE__ */ new WeakMap();
@@ -2214,25 +2288,26 @@ function onMount(callback, element) {
2214
2288
  if (typeof document === "undefined") return;
2215
2289
  if (element) {
2216
2290
  let disposed = false;
2291
+ const isDisposed = () => disposed;
2217
2292
  registerDisposer(element, () => {
2218
2293
  disposed = true;
2219
2294
  });
2220
2295
  if (element.isConnected) {
2221
2296
  queueMicrotask(() => {
2222
2297
  if (disposed) return;
2223
- runMountCallback(callback, "onMount", element);
2298
+ runMountCallback(callback, "onMount", element, isDisposed);
2224
2299
  });
2225
2300
  return;
2226
2301
  }
2227
2302
  queueMicrotask(() => {
2228
2303
  if (disposed) return;
2229
2304
  if (element.isConnected) {
2230
- runMountCallback(callback, "onMount", element);
2305
+ runMountCallback(callback, "onMount", element, isDisposed);
2231
2306
  return;
2232
2307
  }
2233
2308
  const unregister = registerMountWatcher(element, () => {
2234
2309
  if (disposed) return;
2235
- runMountCallback(callback, "onMount", element);
2310
+ runMountCallback(callback, "onMount", element, isDisposed);
2236
2311
  });
2237
2312
  registerDisposer(element, unregister);
2238
2313
  });
@@ -2269,6 +2344,14 @@ function onCleanup(callback, element) {
2269
2344
  registerDisposer(element, callback);
2270
2345
  }
2271
2346
 
2347
+ // src/core/devtoolsHook.ts
2348
+ function emitDevtools(hook, event, payload) {
2349
+ try {
2350
+ hook.emit(event, payload);
2351
+ } catch {
2352
+ }
2353
+ }
2354
+
2272
2355
  // src/core/ssr-context.ts
2273
2356
  var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
2274
2357
  function detectSSRShared() {
@@ -2506,12 +2589,17 @@ function effect(effectFn, options) {
2506
2589
  try {
2507
2590
  retrack2(ctx.bodyFn, ctx.subscriber);
2508
2591
  if (ctx.rerunPending) drainReruns(ctx);
2592
+ } catch (err) {
2593
+ ctx.disposed = true;
2594
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
2595
+ cleanup2(ctx.subscriber);
2596
+ throw err;
2509
2597
  } finally {
2510
2598
  ctx.running = false;
2511
2599
  ctx.rerunPending = false;
2512
2600
  }
2513
2601
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2514
- if (hook) hook.emit("effect:create", { effectFn });
2602
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
2515
2603
  return () => disposeEffect(ctx);
2516
2604
  }
2517
2605
 
@@ -2598,7 +2686,7 @@ function signal(initial, options) {
2598
2686
  state2.__v++;
2599
2687
  if (DEV) {
2600
2688
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2601
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2689
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2602
2690
  }
2603
2691
  if (!enqueueBatchedSignal(state2)) {
2604
2692
  notifySubscribers2(state2);
@@ -2612,7 +2700,7 @@ function signal(initial, options) {
2612
2700
  state2.value = newValue;
2613
2701
  state2.__v++;
2614
2702
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2615
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2703
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2616
2704
  if (!enqueueBatchedSignal(state2)) {
2617
2705
  notifySubscribers2(state2);
2618
2706
  }
@@ -2631,7 +2719,7 @@ function signal(initial, options) {
2631
2719
  }
2632
2720
  if (DEV) {
2633
2721
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2634
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
2722
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
2635
2723
  }
2636
2724
  return [get, set];
2637
2725
  }
@@ -3444,36 +3532,34 @@ function Loading(props = {}) {
3444
3532
  const { text: text2, variant = "spinner", size = "md" } = props;
3445
3533
  const sizeClass = size !== "md" ? ` sibu-loading-${size}` : "";
3446
3534
  if (variant === "dots") {
3447
- return div({
3535
+ return div(
3536
+ {
3537
+ class: `sibu-loading${sizeClass}`,
3538
+ role: "status",
3539
+ "aria-live": "polite",
3540
+ // When there's no visible text, give the live region an accessible name
3541
+ // so it isn't announced as an empty status.
3542
+ "aria-label": text2 ? void 0 : "Loading"
3543
+ },
3544
+ [
3545
+ div({ class: "sibu-loading-dots" }, [
3546
+ span({ class: "sibu-loading-dot" }),
3547
+ span({ class: "sibu-loading-dot" }),
3548
+ span({ class: "sibu-loading-dot" })
3549
+ ]),
3550
+ text2 ? span({ class: "sibu-loading-text" }, text2) : null
3551
+ ]
3552
+ );
3553
+ }
3554
+ return div(
3555
+ {
3448
3556
  class: `sibu-loading${sizeClass}`,
3449
3557
  role: "status",
3450
3558
  "aria-live": "polite",
3451
- // When there's no visible text, give the live region an accessible name
3452
- // so it isn't announced as an empty status.
3453
- "aria-label": text2 ? void 0 : "Loading",
3454
- nodes: [
3455
- div({
3456
- class: "sibu-loading-dots",
3457
- nodes: [
3458
- span({ class: "sibu-loading-dot" }),
3459
- span({ class: "sibu-loading-dot" }),
3460
- span({ class: "sibu-loading-dot" })
3461
- ]
3462
- }),
3463
- text2 ? span({ class: "sibu-loading-text", nodes: text2 }) : null
3464
- ].filter(Boolean)
3465
- });
3466
- }
3467
- return div({
3468
- class: `sibu-loading${sizeClass}`,
3469
- role: "status",
3470
- "aria-live": "polite",
3471
- "aria-label": text2 ? void 0 : "Loading",
3472
- nodes: [
3473
- div({ class: "sibu-loading-spinner" }),
3474
- text2 ? span({ class: "sibu-loading-text", nodes: text2 }) : null
3475
- ].filter(Boolean)
3476
- });
3559
+ "aria-label": text2 ? void 0 : "Loading"
3560
+ },
3561
+ [div({ class: "sibu-loading-spinner" }), text2 ? span({ class: "sibu-loading-text" }, text2) : null]
3562
+ );
3477
3563
  }
3478
3564
 
3479
3565
  // src/core/rendering/action.ts
@@ -3579,13 +3665,35 @@ registerAction("copyOnClick", copyOnClick);
3579
3665
  registerAction("autoResize", autoResize);
3580
3666
  registerAction("trapFocus", trapFocus);
3581
3667
 
3668
+ // src/utils/adoptThenable.ts
3669
+ function adoptThenable(value) {
3670
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
3671
+ let then;
3672
+ try {
3673
+ then = value.then;
3674
+ } catch (error) {
3675
+ return Promise.reject(error);
3676
+ }
3677
+ if (typeof then !== "function") return null;
3678
+ return new Promise((resolve, reject) => {
3679
+ queueMicrotask(() => {
3680
+ try {
3681
+ Reflect.apply(then, value, [resolve, reject]);
3682
+ } catch (error) {
3683
+ reject(error);
3684
+ }
3685
+ });
3686
+ });
3687
+ }
3688
+
3582
3689
  // src/core/rendering/catch.ts
3583
3690
  var globalErrorHandler = null;
3584
3691
  function catchError(fn, onError) {
3585
3692
  try {
3586
3693
  const result = fn();
3587
- if (result && typeof result.then === "function") {
3588
- result.catch((err) => {
3694
+ const adopted = adoptThenable(result);
3695
+ if (adopted) {
3696
+ adopted.then(void 0, (err) => {
3589
3697
  if (onError) {
3590
3698
  onError(err, "async");
3591
3699
  } else if (globalErrorHandler) {
@@ -3725,7 +3833,7 @@ function when(condition, thenBranch, elseBranch) {
3725
3833
  const show2 = condition();
3726
3834
  const parent = anchor.parentNode;
3727
3835
  if (!parent) return;
3728
- if (initialized && show2 === lastCondition) return;
3836
+ if (initialized && Object.is(show2, lastCondition)) return;
3729
3837
  lastCondition = show2;
3730
3838
  const focused = currentNode ? captureFocusWithin([currentNode]) : null;
3731
3839
  if (currentNode?.parentNode) {
@@ -3734,7 +3842,7 @@ function when(condition, thenBranch, elseBranch) {
3734
3842
  currentNode = null;
3735
3843
  }
3736
3844
  const branch = show2 ? thenBranch : elseBranch !== void 0 ? elseBranch : null;
3737
- const attached = attachBranch(parent, anchor, branch, attachedOnce, "when");
3845
+ const attached = untracked2(() => attachBranch(parent, anchor, branch, attachedOnce, "when"));
3738
3846
  currentNode = attached.node;
3739
3847
  currentNodeOwned = attached.owned;
3740
3848
  restoreFocusWithin(focused, currentNode ? [currentNode] : [], "when");
@@ -3774,7 +3882,7 @@ function match(value, cases, fallback) {
3774
3882
  currentNode = null;
3775
3883
  }
3776
3884
  const branch = Object.hasOwn(cases, key) ? cases[key] : fallback;
3777
- const attached = attachBranch(parent, anchor, branch ?? null, attachedOnce, "match");
3885
+ const attached = untracked2(() => attachBranch(parent, anchor, branch ?? null, attachedOnce, "match"));
3778
3886
  currentNode = attached.node;
3779
3887
  currentNodeOwned = attached.owned;
3780
3888
  restoreFocusWithin(focused, currentNode ? [currentNode] : [], "match");
@@ -3804,11 +3912,17 @@ var componentRegistry = globalSingleton(
3804
3912
  /* @__PURE__ */ Symbol.for("sibujs.components.registry.v1"),
3805
3913
  () => /* @__PURE__ */ new Map()
3806
3914
  );
3915
+ var registryVersion = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.components.registryVersion.v1"), () => signal(0));
3916
+ function bumpRegistry() {
3917
+ const [read, write] = registryVersion;
3918
+ write(untracked2(read) + 1);
3919
+ }
3807
3920
  function registerComponent(name, component) {
3808
3921
  componentRegistry.set(name, component);
3922
+ bumpRegistry();
3809
3923
  }
3810
3924
  function unregisterComponent(name) {
3811
- componentRegistry.delete(name);
3925
+ if (componentRegistry.delete(name)) bumpRegistry();
3812
3926
  }
3813
3927
  function resolveComponent(name) {
3814
3928
  const component = componentRegistry.get(name);
@@ -3819,14 +3933,21 @@ function resolveComponent(name) {
3819
3933
  }
3820
3934
  function DynamicComponent(is) {
3821
3935
  const container = div({ class: "sibu-dynamic" });
3936
+ const NONE = {};
3937
+ let shown = NONE;
3822
3938
  function render() {
3823
3939
  const target = is();
3824
- let el;
3940
+ let component;
3825
3941
  if (typeof target === "function") {
3826
- el = target();
3942
+ component = target;
3827
3943
  } else {
3828
- el = resolveComponent(target);
3944
+ registryVersion[0]();
3945
+ component = componentRegistry.get(target);
3829
3946
  }
3947
+ const next = component ?? `missing:${target}`;
3948
+ if (next === shown) return;
3949
+ const el = untracked2(() => component ? component() : div(`[Component "${target}" not found]`));
3950
+ shown = next;
3830
3951
  replaceChildrenSafely(container, el);
3831
3952
  }
3832
3953
  const untrack = track2(render);
@@ -4090,27 +4211,26 @@ function Fragment(nodes) {
4090
4211
  if (Array.isArray(child)) {
4091
4212
  for (const nested of child) {
4092
4213
  if (nested == null || typeof nested === "boolean") continue;
4093
- frag.appendChild(resolveChild(nested));
4214
+ appendChild(frag, nested);
4094
4215
  }
4095
4216
  } else {
4096
- frag.appendChild(resolveChild(child));
4217
+ appendChild(frag, child);
4097
4218
  }
4098
4219
  }
4099
4220
  return frag;
4100
4221
  }
4101
- function resolveChild(child) {
4102
- if (child == null) {
4103
- return document.createTextNode("");
4104
- }
4222
+ function appendChild(frag, child) {
4105
4223
  if (child instanceof Node) {
4106
- return child;
4224
+ frag.appendChild(child);
4225
+ return;
4107
4226
  }
4108
4227
  if (typeof child === "function") {
4109
- const result = child();
4110
- if (result instanceof Node) return result;
4111
- return document.createTextNode(String(result ?? ""));
4228
+ const placeholder = document.createComment("");
4229
+ frag.appendChild(placeholder);
4230
+ registerDisposer(placeholder, bindChildNode(placeholder, child));
4231
+ return;
4112
4232
  }
4113
- return document.createTextNode(String(child));
4233
+ frag.appendChild(document.createTextNode(String(child)));
4114
4234
  }
4115
4235
 
4116
4236
  // src/core/rendering/htm.ts
@@ -4580,25 +4700,77 @@ function mount(component, container) {
4580
4700
  typeof component === "function" || component instanceof Node,
4581
4701
  "mount: first argument must be a component function or a DOM Node."
4582
4702
  );
4583
- const startTime = typeof performance !== "undefined" ? performance.now() : 0;
4584
- const node = typeof component === "function" ? component() : component;
4585
- const duration = typeof performance !== "undefined" ? performance.now() - startTime : 0;
4586
- container.appendChild(node);
4587
- const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
4588
- if (hook) {
4589
- hook.emit("app:init", { rootElement: node, container, duration });
4703
+ const now = () => typeof performance !== "undefined" ? performance.now() : 0;
4704
+ const startTime = now();
4705
+ let range = null;
4706
+ const attach = (built) => {
4707
+ if (built instanceof DocumentFragment) {
4708
+ const start = document.createComment("");
4709
+ const end = document.createComment("");
4710
+ built.insertBefore(start, built.firstChild);
4711
+ built.appendChild(end);
4712
+ range = { start, end };
4713
+ }
4714
+ container.appendChild(built);
4715
+ };
4716
+ let node;
4717
+ if (typeof component === "function") {
4718
+ node = withDisposerRollback(() => {
4719
+ const built = component();
4720
+ attach(built);
4721
+ return built;
4722
+ });
4723
+ } else {
4724
+ node = component;
4725
+ attach(node);
4590
4726
  }
4727
+ const duration = now() - startTime;
4728
+ const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
4729
+ if (hook) emitDevtools(hook, "app:init", { rootElement: node, container, duration });
4730
+ const mounted = range;
4591
4731
  return {
4592
4732
  node,
4593
4733
  unmount() {
4594
- if (hook) hook.emit("app:unmount", { rootElement: node });
4595
- dispose(node);
4596
- if (node.parentNode) {
4597
- node.parentNode.removeChild(node);
4734
+ if (hook) emitDevtools(hook, "app:unmount", { rootElement: node });
4735
+ if (mounted) {
4736
+ unmountRange(mounted.start, mounted.end);
4737
+ return;
4598
4738
  }
4739
+ dispose(node);
4740
+ node.parentNode?.removeChild(node);
4599
4741
  }
4600
4742
  };
4601
4743
  }
4744
+ function unmountRange(start, end) {
4745
+ const parent = start.parentNode;
4746
+ if (!parent && !end.parentNode) return;
4747
+ let budget = MAX_DRAIN_TEARDOWNS;
4748
+ for (let n = start.nextSibling; n && n !== end; n = n.nextSibling) budget++;
4749
+ let before = start;
4750
+ let after = null;
4751
+ let intact = true;
4752
+ for (; ; ) {
4753
+ if (start.parentNode !== parent || end.parentNode !== parent) intact = false;
4754
+ else if (start.previousSibling !== before || end.nextSibling !== after) {
4755
+ before = start.previousSibling;
4756
+ after = end.nextSibling;
4757
+ intact = !!(start.compareDocumentPosition(end) & 4);
4758
+ }
4759
+ const n = start.nextSibling;
4760
+ if (!intact || n === end) break;
4761
+ if (budget-- <= 0) {
4762
+ reportDrainRunaway("mount", MAX_DRAIN_TEARDOWNS, 1);
4763
+ return;
4764
+ }
4765
+ dispose(n);
4766
+ if (n.parentNode === parent) parent.removeChild(n);
4767
+ }
4768
+ if (!intact) {
4769
+ reportError(new Error("[SibuJS mount] fragment markers lost"), { phase: "cleanup", name: "mount" });
4770
+ }
4771
+ start.parentNode?.removeChild(start);
4772
+ end.parentNode?.removeChild(end);
4773
+ }
4602
4774
 
4603
4775
  // src/core/rendering/portal.ts
4604
4776
  function Portal(nodes, target) {
@@ -5034,17 +5206,17 @@ function derived(getter, options) {
5034
5206
  cs._d = false;
5035
5207
  cs._init = true;
5036
5208
  };
5037
- track2(() => {
5038
- let threw = true;
5039
- try {
5209
+ try {
5210
+ track2(() => {
5040
5211
  cs._v = getter();
5041
5212
  cs._d = false;
5042
5213
  cs._init = true;
5043
- threw = false;
5044
- } finally {
5045
- if (threw) cs._d = true;
5046
- }
5047
- }, markDirty);
5214
+ }, markDirty);
5215
+ } catch (err) {
5216
+ disposed = true;
5217
+ cleanup2(markDirty);
5218
+ throw err;
5219
+ }
5048
5220
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
5049
5221
  const validate = () => {
5050
5222
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -5063,7 +5235,7 @@ function derived(getter, options) {
5063
5235
  if (disposed) cleanup2(markDirty);
5064
5236
  }
5065
5237
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
5066
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
5238
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
5067
5239
  }
5068
5240
  };
5069
5241
  cs._validate = validate;
@@ -5114,7 +5286,7 @@ function derived(getter, options) {
5114
5286
  }
5115
5287
  }
5116
5288
  };
5117
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
5289
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
5118
5290
  return computedGetter;
5119
5291
  }
5120
5292
 
@@ -5754,27 +5926,6 @@ function trustHTML(html3) {
5754
5926
  }
5755
5927
  var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
5756
5928
 
5757
- // src/utils/adoptThenable.ts
5758
- function adoptThenable(value) {
5759
- if (value === null || typeof value !== "object" && typeof value !== "function") return null;
5760
- let then;
5761
- try {
5762
- then = value.then;
5763
- } catch (error) {
5764
- return Promise.reject(error);
5765
- }
5766
- if (typeof then !== "function") return null;
5767
- return new Promise((resolve, reject) => {
5768
- queueMicrotask(() => {
5769
- try {
5770
- Reflect.apply(then, value, [resolve, reject]);
5771
- } catch (error) {
5772
- reject(error);
5773
- }
5774
- });
5775
- });
5776
- }
5777
-
5778
5929
  // src/reactivity/concurrent.ts
5779
5930
  function defer(getter) {
5780
5931
  const [value, setValue] = signal(getter());