sibujs 1.4.0 → 1.5.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 +105 -119
  2. package/dist/browser.cjs +53 -14
  3. package/dist/browser.d.cts +14 -9
  4. package/dist/browser.d.ts +14 -9
  5. package/dist/browser.js +4 -4
  6. package/dist/build.cjs +124 -42
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +10 -10
  10. package/dist/cdn.global.js +7 -7
  11. package/dist/chunk-5ZYQ6KDD.js +154 -0
  12. package/dist/chunk-6BMPXPUW.js +26 -0
  13. package/dist/chunk-7GRNSCFT.js +1097 -0
  14. package/dist/chunk-BGTHZHJ5.js +1016 -0
  15. package/dist/chunk-BMPL52BF.js +654 -0
  16. package/dist/chunk-GJPXRJ45.js +37 -0
  17. package/dist/chunk-JCDUJN2F.js +2779 -0
  18. package/dist/chunk-K4G4ZQNR.js +286 -0
  19. package/dist/chunk-MB6QFH3I.js +2776 -0
  20. package/dist/chunk-MYRV7VDM.js +742 -0
  21. package/dist/chunk-NZIIMDWI.js +84 -0
  22. package/dist/chunk-P3XWXJZU.js +282 -0
  23. package/dist/chunk-PDZQY43A.js +616 -0
  24. package/dist/chunk-RJ46C3CS.js +1293 -0
  25. package/dist/chunk-SFKNRVCU.js +292 -0
  26. package/dist/chunk-TDGZL5CU.js +365 -0
  27. package/dist/chunk-VAPYJN4X.js +368 -0
  28. package/dist/chunk-VQDZK23A.js +1023 -0
  29. package/dist/chunk-VQNQZCWJ.js +61 -0
  30. package/dist/chunk-XHK6BDAJ.js +76 -0
  31. package/dist/chunk-XUEEGU5O.js +409 -0
  32. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  33. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  34. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  35. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  36. package/dist/data.cjs +52 -11
  37. package/dist/data.js +6 -6
  38. package/dist/devtools.cjs +22 -24
  39. package/dist/devtools.js +26 -28
  40. package/dist/ecosystem.cjs +31 -6
  41. package/dist/ecosystem.d.cts +4 -4
  42. package/dist/ecosystem.d.ts +4 -4
  43. package/dist/ecosystem.js +7 -7
  44. package/dist/extras.cjs +304 -108
  45. package/dist/extras.d.cts +3 -3
  46. package/dist/extras.d.ts +3 -3
  47. package/dist/extras.js +19 -19
  48. package/dist/index.cjs +124 -42
  49. package/dist/index.d.cts +58 -48
  50. package/dist/index.d.ts +58 -48
  51. package/dist/index.js +10 -10
  52. package/dist/motion.cjs +13 -2
  53. package/dist/motion.d.cts +1 -1
  54. package/dist/motion.d.ts +1 -1
  55. package/dist/motion.js +3 -3
  56. package/dist/patterns.cjs +91 -24
  57. package/dist/patterns.d.cts +46 -12
  58. package/dist/patterns.d.ts +46 -12
  59. package/dist/patterns.js +5 -5
  60. package/dist/performance.cjs +97 -12
  61. package/dist/performance.d.cts +6 -1
  62. package/dist/performance.d.ts +6 -1
  63. package/dist/performance.js +5 -3
  64. package/dist/plugins.cjs +19 -13
  65. package/dist/plugins.d.cts +3 -3
  66. package/dist/plugins.d.ts +3 -3
  67. package/dist/plugins.js +16 -18
  68. package/dist/ssr.cjs +9 -0
  69. package/dist/ssr.d.cts +1 -1
  70. package/dist/ssr.d.ts +1 -1
  71. package/dist/ssr.js +7 -7
  72. package/dist/testing.js +2 -2
  73. package/dist/ui.cjs +130 -48
  74. package/dist/ui.d.cts +13 -16
  75. package/dist/ui.d.ts +13 -16
  76. package/dist/ui.js +6 -6
  77. package/dist/widgets.cjs +31 -6
  78. package/dist/widgets.js +5 -5
  79. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -319,14 +319,12 @@ declare function each<T>(getArray: () => T[], render: (item: () => T, index: ()
319
319
  *
320
320
  * @example
321
321
  * ```ts
322
- * div({
323
- * nodes: [
324
- * Fragment([
325
- * p({ nodes: "First" }),
326
- * p({ nodes: "Second" }),
327
- * ])
328
- * ]
329
- * });
322
+ * div([
323
+ * Fragment([
324
+ * p("First"),
325
+ * p("Second"),
326
+ * ])
327
+ * ]);
330
328
  * ```
331
329
  *
332
330
  * @param nodes Array of child nodes to include in the fragment
@@ -338,6 +336,10 @@ declare function Fragment(nodes: NodeChildren[]): DocumentFragment;
338
336
  * Portal renders nodes into a DOM node outside the parent component hierarchy.
339
337
  * Useful for modals, tooltips, dropdowns, and overlays.
340
338
  *
339
+ * Cleanup integrates with `dispose()` / `registerDisposer()` so portals
340
+ * are properly torn down when the anchor is disposed by `when()`, `match()`,
341
+ * `each()`, or manual `dispose(anchor)`.
342
+ *
341
343
  * @param nodes Function that returns the content to render
342
344
  * @param target Target DOM element (defaults to document.body)
343
345
  * @returns A Comment anchor node in the original position
@@ -345,11 +347,11 @@ declare function Fragment(nodes: NodeChildren[]): DocumentFragment;
345
347
  * @example
346
348
  * ```ts
347
349
  * // Render modal at document.body
348
- * Portal(() => div({ class: "modal", nodes: "Modal content" }));
350
+ * Portal(() => div("modal", "Modal content"));
349
351
  *
350
352
  * // Render into specific container
351
353
  * const overlay = document.getElementById("overlay-root")!;
352
- * Portal(() => div({ nodes: "Tooltip" }), overlay);
354
+ * Portal(() => div("Tooltip"), overlay);
353
355
  * ```
354
356
  */
355
357
  declare function Portal(nodes: () => HTMLElement, target?: HTMLElement): Comment;
@@ -382,7 +384,7 @@ declare function unregisterComponent(name: string): void;
382
384
  * @example
383
385
  * ```ts
384
386
  * registerComponent("Widget", MyWidget);
385
- * div({ nodes: [resolveComponent("Widget")] });
387
+ * div([resolveComponent("Widget")]);
386
388
  * ```
387
389
  */
388
390
  declare function resolveComponent(name: string): HTMLElement;
@@ -418,7 +420,7 @@ declare function getSlot(slots: Slots | undefined, name?: string): SlotFn | unde
418
420
  * @example
419
421
  * ```ts
420
422
  * const [visible, setVisible] = signal(true);
421
- * div({ nodes: [show(() => visible(), span({ nodes: "I toggle!" }))] });
423
+ * div([show(() => visible(), span("I toggle!"))]);
422
424
  * ```
423
425
  */
424
426
  declare function show<T extends Element>(condition: () => boolean, element: T): T;
@@ -435,8 +437,8 @@ declare function show<T extends Element>(condition: () => boolean, element: T):
435
437
  * ```ts
436
438
  * when(
437
439
  * () => isLoggedIn(),
438
- * () => div({ nodes: "Welcome!" }),
439
- * () => div({ nodes: "Please log in" })
440
+ * () => div("Welcome!"),
441
+ * () => div("Please log in")
440
442
  * );
441
443
  * ```
442
444
  */
@@ -459,7 +461,7 @@ declare function when<T>(condition: () => T, thenBranch: () => NodeChild, elseBr
459
461
  * error: () => ErrorMessage(),
460
462
  * success: () => Content(),
461
463
  * },
462
- * () => div({ nodes: "Unknown status" })
464
+ * () => div("Unknown status")
463
465
  * );
464
466
  * ```
465
467
  */
@@ -527,8 +529,7 @@ type ActionFn<T = void> = (element: HTMLElement, param: T) => (() => void) | und
527
529
  * action(el, clickOutside, () => setOpen(false));
528
530
  * action(el, longPress, { duration: 500, callback: onLongPress });
529
531
  * },
530
- * nodes: "Content",
531
- * });
532
+ * }, "Content");
532
533
  * ```
533
534
  */
534
535
  declare function action<T>(element: HTMLElement, actionFn: ActionFn<T>, param: T): void;
@@ -636,10 +637,10 @@ declare function setGlobalErrorHandler(handler: ErrorHandler): void;
636
637
  * ```ts
637
638
  * function Field(labelText: string) {
638
639
  * const id = createId("field");
639
- * return div({ nodes: [
640
- * label({ for: id, nodes: labelText }),
640
+ * return div([
641
+ * label({ for: id }, labelText),
641
642
  * input({ id }),
642
- * ]});
643
+ * ]);
643
644
  * }
644
645
  * ```
645
646
  */
@@ -681,9 +682,9 @@ declare const __accessor: unique symbol;
681
682
  * ```ts
682
683
  * const [count, setCount] = signal(0);
683
684
  *
684
- * div({ nodes: count }) // ✓ reactive — Accessor passed directly
685
- * div({ nodes: () => count() }) // ✓ reactive — explicit arrow wrapper
686
- * div({ nodes: count() }) // ✗ static — evaluated once, not reactive
685
+ * div(count) // ✓ reactive — Accessor passed directly
686
+ * div(() => count()) // ✓ reactive — explicit arrow wrapper
687
+ * div(count()) // ✗ static — evaluated once, not reactive
687
688
  * ```
688
689
  */
689
690
  type Accessor<T> = (() => T) & {
@@ -903,16 +904,23 @@ declare function reactiveArray<T>(initial?: T[]): [Accessor<readonly T[]>, Array
903
904
  /**
904
905
  * Deep equality comparison for objects and arrays.
905
906
  * Falls back to Object.is for primitives.
906
- * Handles circular references and common built-in types (Date, RegExp).
907
+ * Handles circular references, shared sub-references, and common
908
+ * built-in types (Date, RegExp, Map, Set, ArrayBuffer, TypedArrays).
909
+ *
910
+ * The `seen` parameter tracks `(a, b)` pairs — not just `a` — so that
911
+ * a shared sub-object compared against two different partners is always
912
+ * fully checked, while genuine cycles (same a-with-same-b revisited)
913
+ * still terminate.
907
914
  */
908
- declare function deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean;
915
+ declare function deepEqual(a: unknown, b: unknown, seen?: Map<object, Set<object>>): boolean;
909
916
  /**
910
917
  * Like signal but uses deep equality comparison instead of Object.is.
911
918
  * This prevents unnecessary re-renders when setting an object/array
912
919
  * to a structurally identical value.
913
920
  *
914
921
  * @param initial Initial value
915
- * @returns Tuple [getter, setter]
922
+ * @returns Tuple [getter, setter] — same shape as `signal()`, preserving
923
+ * the `Accessor<T>` brand on the getter.
916
924
  *
917
925
  * @example
918
926
  * ```ts
@@ -921,7 +929,7 @@ declare function deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean
921
929
  * setUser({ name: "Bob", age: 25 }); // Notifies — different value
922
930
  * ```
923
931
  */
924
- declare function deepSignal<T>(initial: T): [() => T, (next: T | ((prev: T) => T)) => void];
932
+ declare function deepSignal<T>(initial: T): [Accessor<T>, (next: T | ((prev: T) => T)) => void];
925
933
 
926
934
  /**
927
935
  * Creates a writable computed value — a derived getter paired with
@@ -1017,7 +1025,7 @@ declare function asyncDerived<T>(factory: () => Promise<T>, initial: T): AsyncDe
1017
1025
  * console.log("Component was removed");
1018
1026
  * });
1019
1027
  *
1020
- * return div({ nodes: "Hello" });
1028
+ * return div("Hello");
1021
1029
  * }
1022
1030
  * ```
1023
1031
  */
@@ -1054,7 +1062,7 @@ declare function onUnmount(callback: CleanupFn, element: HTMLElement): void;
1054
1062
  * ```ts
1055
1063
  * function RealtimeBar(siteId: string) {
1056
1064
  * const ws = new WebSocket(`/ws/sites/${siteId}/realtime`);
1057
- * const root = div({ nodes: "Realtime data..." });
1065
+ * const root = div("Realtime data...");
1058
1066
  * onCleanup(() => ws.close(), root);
1059
1067
  * return root;
1060
1068
  * }
@@ -1063,29 +1071,31 @@ declare function onUnmount(callback: CleanupFn, element: HTMLElement): void;
1063
1071
  declare function onCleanup(callback: CleanupFn, element: Node): void;
1064
1072
 
1065
1073
  /**
1066
- * Context API for SibuJS — provides dependency injection across
1067
- * component trees without prop drilling.
1074
+ * Context API for SibuJS — a reactive global value that any component
1075
+ * can read without prop drilling.
1076
+ *
1077
+ * Note: this is a **global reactive store**, not a subtree-scoped DI
1078
+ * system. Calling `provide()` sets the value for ALL consumers, not
1079
+ * just descendants of the provider. For most apps this is sufficient
1080
+ * — use separate `context()` instances for independent scopes.
1068
1081
  *
1069
1082
  * @example
1070
1083
  * ```ts
1071
1084
  * // Create a context with a default value
1072
1085
  * const ThemeContext = context("light");
1073
1086
  *
1074
- * // Provide a value at a parent level
1075
- * function App() {
1076
- * ThemeContext.provide("dark");
1077
- * return div({ nodes: [Child()] });
1078
- * }
1087
+ * // Set the value (global affects all consumers)
1088
+ * ThemeContext.provide("dark");
1079
1089
  *
1080
- * // Consume the value anywhere below
1090
+ * // Read reactively from any component
1081
1091
  * function Child() {
1082
1092
  * const theme = ThemeContext.use(); // reactive getter
1083
- * return div({ nodes: () => `Theme: ${theme()}` });
1093
+ * return div(() => `Theme: ${theme()}`);
1084
1094
  * }
1085
1095
  * ```
1086
1096
  */
1087
1097
  interface Context<T> {
1088
- /** Provide a value for this context. Overrides any parent provider. */
1098
+ /** Set the context value globally. Affects all consumers. */
1089
1099
  provide(value: T): void;
1090
1100
  /** Get a reactive getter for the current context value. */
1091
1101
  use(): () => T;
@@ -1243,7 +1253,7 @@ declare function nextTick(): Promise<void>;
1243
1253
  * input({ on: { input: e => setQuery(e.target.value) } });
1244
1254
  *
1245
1255
  * // heavy list reads deferredQuery() and updates one frame later
1246
- * each(() => heavyFilter(items, deferredQuery()), row => li({ nodes: row.name }));
1256
+ * each(() => heavyFilter(items, deferredQuery()), row => li(row.name));
1247
1257
  * ```
1248
1258
  */
1249
1259
  declare function defer<T>(getter: () => T): () => T;
@@ -1312,7 +1322,7 @@ type LazyImport = () => Promise<{
1312
1322
  * // Use inside Suspense for custom loading UI
1313
1323
  * Suspense({
1314
1324
  * nodes: () => LazyDashboard(),
1315
- * fallback: () => div({ nodes: "Loading dashboard..." }),
1325
+ * fallback: () => div("Loading dashboard..."),
1316
1326
  * });
1317
1327
  *
1318
1328
  * // Or use standalone — shows default "Loading..." text
@@ -1330,7 +1340,7 @@ declare function lazy(importFn: LazyImport): Component;
1330
1340
  * ```ts
1331
1341
  * Suspense({
1332
1342
  * nodes: () => LazyChart(),
1333
- * fallback: () => div({ nodes: "Loading chart..." }),
1343
+ * fallback: () => div("Loading chart..."),
1334
1344
  * });
1335
1345
  * ```
1336
1346
  *
@@ -1370,7 +1380,7 @@ interface ErrorBoundaryProps {
1370
1380
  * const [route, setRoute] = signal("/");
1371
1381
  * ErrorBoundary({
1372
1382
  * resetKeys: [route],
1373
- * nodes: () => div({ nodes: riskyPageFor(route()) }),
1383
+ * nodes: () => div(riskyPageFor(route())),
1374
1384
  * });
1375
1385
  * ```
1376
1386
  */
@@ -1429,13 +1439,13 @@ interface ErrorDisplayProps {
1429
1439
  *
1430
1440
  * @example
1431
1441
  * ```ts
1432
- * button({
1433
- * on: { click: async () => {
1442
+ * button(
1443
+ * { on: { click: async () => {
1434
1444
  * try { await save(); }
1435
1445
  * catch (err) { mount(ErrorDisplay({ error: err, onRetry: save }), errorHost); }
1436
- * }},
1437
- * nodes: "Save",
1438
- * });
1446
+ * }}},
1447
+ * "Save",
1448
+ * );
1439
1449
  * ```
1440
1450
  */
1441
1451
  declare function ErrorDisplay(props: ErrorDisplayProps): Element;
package/dist/index.d.ts CHANGED
@@ -319,14 +319,12 @@ declare function each<T>(getArray: () => T[], render: (item: () => T, index: ()
319
319
  *
320
320
  * @example
321
321
  * ```ts
322
- * div({
323
- * nodes: [
324
- * Fragment([
325
- * p({ nodes: "First" }),
326
- * p({ nodes: "Second" }),
327
- * ])
328
- * ]
329
- * });
322
+ * div([
323
+ * Fragment([
324
+ * p("First"),
325
+ * p("Second"),
326
+ * ])
327
+ * ]);
330
328
  * ```
331
329
  *
332
330
  * @param nodes Array of child nodes to include in the fragment
@@ -338,6 +336,10 @@ declare function Fragment(nodes: NodeChildren[]): DocumentFragment;
338
336
  * Portal renders nodes into a DOM node outside the parent component hierarchy.
339
337
  * Useful for modals, tooltips, dropdowns, and overlays.
340
338
  *
339
+ * Cleanup integrates with `dispose()` / `registerDisposer()` so portals
340
+ * are properly torn down when the anchor is disposed by `when()`, `match()`,
341
+ * `each()`, or manual `dispose(anchor)`.
342
+ *
341
343
  * @param nodes Function that returns the content to render
342
344
  * @param target Target DOM element (defaults to document.body)
343
345
  * @returns A Comment anchor node in the original position
@@ -345,11 +347,11 @@ declare function Fragment(nodes: NodeChildren[]): DocumentFragment;
345
347
  * @example
346
348
  * ```ts
347
349
  * // Render modal at document.body
348
- * Portal(() => div({ class: "modal", nodes: "Modal content" }));
350
+ * Portal(() => div("modal", "Modal content"));
349
351
  *
350
352
  * // Render into specific container
351
353
  * const overlay = document.getElementById("overlay-root")!;
352
- * Portal(() => div({ nodes: "Tooltip" }), overlay);
354
+ * Portal(() => div("Tooltip"), overlay);
353
355
  * ```
354
356
  */
355
357
  declare function Portal(nodes: () => HTMLElement, target?: HTMLElement): Comment;
@@ -382,7 +384,7 @@ declare function unregisterComponent(name: string): void;
382
384
  * @example
383
385
  * ```ts
384
386
  * registerComponent("Widget", MyWidget);
385
- * div({ nodes: [resolveComponent("Widget")] });
387
+ * div([resolveComponent("Widget")]);
386
388
  * ```
387
389
  */
388
390
  declare function resolveComponent(name: string): HTMLElement;
@@ -418,7 +420,7 @@ declare function getSlot(slots: Slots | undefined, name?: string): SlotFn | unde
418
420
  * @example
419
421
  * ```ts
420
422
  * const [visible, setVisible] = signal(true);
421
- * div({ nodes: [show(() => visible(), span({ nodes: "I toggle!" }))] });
423
+ * div([show(() => visible(), span("I toggle!"))]);
422
424
  * ```
423
425
  */
424
426
  declare function show<T extends Element>(condition: () => boolean, element: T): T;
@@ -435,8 +437,8 @@ declare function show<T extends Element>(condition: () => boolean, element: T):
435
437
  * ```ts
436
438
  * when(
437
439
  * () => isLoggedIn(),
438
- * () => div({ nodes: "Welcome!" }),
439
- * () => div({ nodes: "Please log in" })
440
+ * () => div("Welcome!"),
441
+ * () => div("Please log in")
440
442
  * );
441
443
  * ```
442
444
  */
@@ -459,7 +461,7 @@ declare function when<T>(condition: () => T, thenBranch: () => NodeChild, elseBr
459
461
  * error: () => ErrorMessage(),
460
462
  * success: () => Content(),
461
463
  * },
462
- * () => div({ nodes: "Unknown status" })
464
+ * () => div("Unknown status")
463
465
  * );
464
466
  * ```
465
467
  */
@@ -527,8 +529,7 @@ type ActionFn<T = void> = (element: HTMLElement, param: T) => (() => void) | und
527
529
  * action(el, clickOutside, () => setOpen(false));
528
530
  * action(el, longPress, { duration: 500, callback: onLongPress });
529
531
  * },
530
- * nodes: "Content",
531
- * });
532
+ * }, "Content");
532
533
  * ```
533
534
  */
534
535
  declare function action<T>(element: HTMLElement, actionFn: ActionFn<T>, param: T): void;
@@ -636,10 +637,10 @@ declare function setGlobalErrorHandler(handler: ErrorHandler): void;
636
637
  * ```ts
637
638
  * function Field(labelText: string) {
638
639
  * const id = createId("field");
639
- * return div({ nodes: [
640
- * label({ for: id, nodes: labelText }),
640
+ * return div([
641
+ * label({ for: id }, labelText),
641
642
  * input({ id }),
642
- * ]});
643
+ * ]);
643
644
  * }
644
645
  * ```
645
646
  */
@@ -681,9 +682,9 @@ declare const __accessor: unique symbol;
681
682
  * ```ts
682
683
  * const [count, setCount] = signal(0);
683
684
  *
684
- * div({ nodes: count }) // ✓ reactive — Accessor passed directly
685
- * div({ nodes: () => count() }) // ✓ reactive — explicit arrow wrapper
686
- * div({ nodes: count() }) // ✗ static — evaluated once, not reactive
685
+ * div(count) // ✓ reactive — Accessor passed directly
686
+ * div(() => count()) // ✓ reactive — explicit arrow wrapper
687
+ * div(count()) // ✗ static — evaluated once, not reactive
687
688
  * ```
688
689
  */
689
690
  type Accessor<T> = (() => T) & {
@@ -903,16 +904,23 @@ declare function reactiveArray<T>(initial?: T[]): [Accessor<readonly T[]>, Array
903
904
  /**
904
905
  * Deep equality comparison for objects and arrays.
905
906
  * Falls back to Object.is for primitives.
906
- * Handles circular references and common built-in types (Date, RegExp).
907
+ * Handles circular references, shared sub-references, and common
908
+ * built-in types (Date, RegExp, Map, Set, ArrayBuffer, TypedArrays).
909
+ *
910
+ * The `seen` parameter tracks `(a, b)` pairs — not just `a` — so that
911
+ * a shared sub-object compared against two different partners is always
912
+ * fully checked, while genuine cycles (same a-with-same-b revisited)
913
+ * still terminate.
907
914
  */
908
- declare function deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean;
915
+ declare function deepEqual(a: unknown, b: unknown, seen?: Map<object, Set<object>>): boolean;
909
916
  /**
910
917
  * Like signal but uses deep equality comparison instead of Object.is.
911
918
  * This prevents unnecessary re-renders when setting an object/array
912
919
  * to a structurally identical value.
913
920
  *
914
921
  * @param initial Initial value
915
- * @returns Tuple [getter, setter]
922
+ * @returns Tuple [getter, setter] — same shape as `signal()`, preserving
923
+ * the `Accessor<T>` brand on the getter.
916
924
  *
917
925
  * @example
918
926
  * ```ts
@@ -921,7 +929,7 @@ declare function deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean
921
929
  * setUser({ name: "Bob", age: 25 }); // Notifies — different value
922
930
  * ```
923
931
  */
924
- declare function deepSignal<T>(initial: T): [() => T, (next: T | ((prev: T) => T)) => void];
932
+ declare function deepSignal<T>(initial: T): [Accessor<T>, (next: T | ((prev: T) => T)) => void];
925
933
 
926
934
  /**
927
935
  * Creates a writable computed value — a derived getter paired with
@@ -1017,7 +1025,7 @@ declare function asyncDerived<T>(factory: () => Promise<T>, initial: T): AsyncDe
1017
1025
  * console.log("Component was removed");
1018
1026
  * });
1019
1027
  *
1020
- * return div({ nodes: "Hello" });
1028
+ * return div("Hello");
1021
1029
  * }
1022
1030
  * ```
1023
1031
  */
@@ -1054,7 +1062,7 @@ declare function onUnmount(callback: CleanupFn, element: HTMLElement): void;
1054
1062
  * ```ts
1055
1063
  * function RealtimeBar(siteId: string) {
1056
1064
  * const ws = new WebSocket(`/ws/sites/${siteId}/realtime`);
1057
- * const root = div({ nodes: "Realtime data..." });
1065
+ * const root = div("Realtime data...");
1058
1066
  * onCleanup(() => ws.close(), root);
1059
1067
  * return root;
1060
1068
  * }
@@ -1063,29 +1071,31 @@ declare function onUnmount(callback: CleanupFn, element: HTMLElement): void;
1063
1071
  declare function onCleanup(callback: CleanupFn, element: Node): void;
1064
1072
 
1065
1073
  /**
1066
- * Context API for SibuJS — provides dependency injection across
1067
- * component trees without prop drilling.
1074
+ * Context API for SibuJS — a reactive global value that any component
1075
+ * can read without prop drilling.
1076
+ *
1077
+ * Note: this is a **global reactive store**, not a subtree-scoped DI
1078
+ * system. Calling `provide()` sets the value for ALL consumers, not
1079
+ * just descendants of the provider. For most apps this is sufficient
1080
+ * — use separate `context()` instances for independent scopes.
1068
1081
  *
1069
1082
  * @example
1070
1083
  * ```ts
1071
1084
  * // Create a context with a default value
1072
1085
  * const ThemeContext = context("light");
1073
1086
  *
1074
- * // Provide a value at a parent level
1075
- * function App() {
1076
- * ThemeContext.provide("dark");
1077
- * return div({ nodes: [Child()] });
1078
- * }
1087
+ * // Set the value (global affects all consumers)
1088
+ * ThemeContext.provide("dark");
1079
1089
  *
1080
- * // Consume the value anywhere below
1090
+ * // Read reactively from any component
1081
1091
  * function Child() {
1082
1092
  * const theme = ThemeContext.use(); // reactive getter
1083
- * return div({ nodes: () => `Theme: ${theme()}` });
1093
+ * return div(() => `Theme: ${theme()}`);
1084
1094
  * }
1085
1095
  * ```
1086
1096
  */
1087
1097
  interface Context<T> {
1088
- /** Provide a value for this context. Overrides any parent provider. */
1098
+ /** Set the context value globally. Affects all consumers. */
1089
1099
  provide(value: T): void;
1090
1100
  /** Get a reactive getter for the current context value. */
1091
1101
  use(): () => T;
@@ -1243,7 +1253,7 @@ declare function nextTick(): Promise<void>;
1243
1253
  * input({ on: { input: e => setQuery(e.target.value) } });
1244
1254
  *
1245
1255
  * // heavy list reads deferredQuery() and updates one frame later
1246
- * each(() => heavyFilter(items, deferredQuery()), row => li({ nodes: row.name }));
1256
+ * each(() => heavyFilter(items, deferredQuery()), row => li(row.name));
1247
1257
  * ```
1248
1258
  */
1249
1259
  declare function defer<T>(getter: () => T): () => T;
@@ -1312,7 +1322,7 @@ type LazyImport = () => Promise<{
1312
1322
  * // Use inside Suspense for custom loading UI
1313
1323
  * Suspense({
1314
1324
  * nodes: () => LazyDashboard(),
1315
- * fallback: () => div({ nodes: "Loading dashboard..." }),
1325
+ * fallback: () => div("Loading dashboard..."),
1316
1326
  * });
1317
1327
  *
1318
1328
  * // Or use standalone — shows default "Loading..." text
@@ -1330,7 +1340,7 @@ declare function lazy(importFn: LazyImport): Component;
1330
1340
  * ```ts
1331
1341
  * Suspense({
1332
1342
  * nodes: () => LazyChart(),
1333
- * fallback: () => div({ nodes: "Loading chart..." }),
1343
+ * fallback: () => div("Loading chart..."),
1334
1344
  * });
1335
1345
  * ```
1336
1346
  *
@@ -1370,7 +1380,7 @@ interface ErrorBoundaryProps {
1370
1380
  * const [route, setRoute] = signal("/");
1371
1381
  * ErrorBoundary({
1372
1382
  * resetKeys: [route],
1373
- * nodes: () => div({ nodes: riskyPageFor(route()) }),
1383
+ * nodes: () => div(riskyPageFor(route())),
1374
1384
  * });
1375
1385
  * ```
1376
1386
  */
@@ -1429,13 +1439,13 @@ interface ErrorDisplayProps {
1429
1439
  *
1430
1440
  * @example
1431
1441
  * ```ts
1432
- * button({
1433
- * on: { click: async () => {
1442
+ * button(
1443
+ * { on: { click: async () => {
1434
1444
  * try { await save(); }
1435
1445
  * catch (err) { mount(ErrorDisplay({ error: err, onRetry: save }), errorHost); }
1436
- * }},
1437
- * nodes: "Save",
1438
- * });
1446
+ * }}},
1447
+ * "Save",
1448
+ * );
1439
1449
  * ```
1440
1450
  */
1441
1451
  declare function ErrorDisplay(props: ErrorDisplayProps): Element;
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  unregisterComponent,
44
44
  when,
45
45
  writable
46
- } from "./chunk-UHNL42EF.js";
46
+ } from "./chunk-JCDUJN2F.js";
47
47
  import {
48
48
  __resetIdCounter,
49
49
  createId
@@ -185,31 +185,31 @@ import {
185
185
  use,
186
186
  var_,
187
187
  video
188
- } from "./chunk-32DY64NT.js";
188
+ } from "./chunk-P3XWXJZU.js";
189
189
  import {
190
190
  watch
191
- } from "./chunk-NYVAC6P5.js";
191
+ } from "./chunk-GJPXRJ45.js";
192
192
  import {
193
193
  context
194
- } from "./chunk-BGN5ZMP4.js";
194
+ } from "./chunk-6BMPXPUW.js";
195
195
  import {
196
196
  SVG_NS,
197
197
  tagFactory
198
- } from "./chunk-F3FA4F32.js";
198
+ } from "./chunk-SFKNRVCU.js";
199
199
  import {
200
200
  bindDynamic,
201
201
  checkLeaks,
202
202
  dispose,
203
203
  registerDisposer
204
- } from "./chunk-PTQJDMRT.js";
204
+ } from "./chunk-5ZYQ6KDD.js";
205
205
  import {
206
206
  derived
207
- } from "./chunk-NEKUBFPT.js";
207
+ } from "./chunk-XHK6BDAJ.js";
208
208
  import "./chunk-CMBFNA7L.js";
209
209
  import {
210
210
  effect,
211
211
  on
212
- } from "./chunk-CHF5OHIA.js";
212
+ } from "./chunk-VQNQZCWJ.js";
213
213
  import {
214
214
  disableSSR,
215
215
  enableSSR,
@@ -221,10 +221,10 @@ import {
221
221
  enqueueBatchedSignal,
222
222
  isBatching,
223
223
  signal
224
- } from "./chunk-WZSPOOER.js";
224
+ } from "./chunk-NZIIMDWI.js";
225
225
  import {
226
226
  untracked
227
- } from "./chunk-ZD6OAMTH.js";
227
+ } from "./chunk-K4G4ZQNR.js";
228
228
  import "./chunk-5X6PP2UK.js";
229
229
  export {
230
230
  DynamicComponent,
package/dist/motion.cjs CHANGED
@@ -57,20 +57,29 @@ function transition(element, options = {}) {
57
57
  onLeaveDone
58
58
  } = options;
59
59
  const transitionValue = `${property} ${duration}ms ${easing} ${delay}ms`;
60
+ let activeTimer = null;
61
+ function cancelPending() {
62
+ if (activeTimer !== null) {
63
+ clearTimeout(activeTimer);
64
+ activeTimer = null;
65
+ }
66
+ }
60
67
  function enter() {
61
68
  return new Promise((resolve) => {
69
+ cancelPending();
62
70
  element.style.transition = transitionValue;
63
71
  if (enterClass) element.classList.add(enterClass);
64
72
  if (leaveClass) element.classList.remove(leaveClass);
65
73
  void element.offsetHeight;
66
74
  if (activeClass) element.classList.add(activeClass);
67
75
  const done = () => {
76
+ activeTimer = null;
68
77
  if (enterClass) element.classList.remove(enterClass);
69
78
  onEnterDone?.();
70
79
  resolve();
71
80
  };
72
81
  if (duration > 0) {
73
- setTimeout(done, duration + delay);
82
+ activeTimer = setTimeout(done, duration + delay);
74
83
  } else {
75
84
  done();
76
85
  }
@@ -78,17 +87,19 @@ function transition(element, options = {}) {
78
87
  }
79
88
  function leave() {
80
89
  return new Promise((resolve) => {
90
+ cancelPending();
81
91
  element.style.transition = transitionValue;
82
92
  if (activeClass) element.classList.remove(activeClass);
83
93
  if (leaveClass) element.classList.add(leaveClass);
84
94
  if (enterClass) element.classList.remove(enterClass);
85
95
  const done = () => {
96
+ activeTimer = null;
86
97
  if (leaveClass) element.classList.remove(leaveClass);
87
98
  onLeaveDone?.();
88
99
  resolve();
89
100
  };
90
101
  if (duration > 0) {
91
- setTimeout(done, duration + delay);
102
+ activeTimer = setTimeout(done, duration + delay);
92
103
  } else {
93
104
  done();
94
105
  }
package/dist/motion.d.cts CHANGED
@@ -32,7 +32,7 @@ interface TransitionOptions {
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * const box = div({ class: "box", nodes: "Hello" });
35
+ * const box = div("box", "Hello");
36
36
  * const { enter, leave } = transition(box, {
37
37
  * duration: 300,
38
38
  * enterClass: "fade-in",
package/dist/motion.d.ts CHANGED
@@ -32,7 +32,7 @@ interface TransitionOptions {
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * const box = div({ class: "box", nodes: "Hello" });
35
+ * const box = div("box", "Hello");
36
36
  * const { enter, leave } = transition(box, {
37
37
  * duration: 300,
38
38
  * enterClass: "fade-in",
package/dist/motion.js CHANGED
@@ -19,9 +19,9 @@ import {
19
19
  stagger,
20
20
  transition,
21
21
  viewTransition
22
- } from "./chunk-KQPDEVVS.js";
23
- import "./chunk-WZSPOOER.js";
24
- import "./chunk-ZD6OAMTH.js";
22
+ } from "./chunk-XUEEGU5O.js";
23
+ import "./chunk-NZIIMDWI.js";
24
+ import "./chunk-K4G4ZQNR.js";
25
25
  import "./chunk-5X6PP2UK.js";
26
26
  export {
27
27
  TransitionGroup,