sibujs 1.2.0 → 1.4.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 (95) hide show
  1. package/README.md +29 -25
  2. package/dist/browser.cjs +804 -2
  3. package/dist/browser.d.cts +591 -1
  4. package/dist/browser.d.ts +591 -1
  5. package/dist/browser.js +50 -8
  6. package/dist/build.cjs +655 -237
  7. package/dist/build.js +15 -93
  8. package/dist/cdn.global.js +188 -7
  9. package/dist/chunk-2BYQDGN3.js +742 -0
  10. package/dist/chunk-32DY64NT.js +282 -0
  11. package/dist/chunk-3AIRKM3B.js +1263 -0
  12. package/dist/chunk-3X2YG6YM.js +505 -0
  13. package/dist/chunk-5X6PP2UK.js +28 -0
  14. package/dist/chunk-77L6NL3X.js +1097 -0
  15. package/dist/chunk-BGN5ZMP4.js +26 -0
  16. package/dist/chunk-BTU3TJDS.js +365 -0
  17. package/dist/chunk-CHF5OHIA.js +61 -0
  18. package/dist/chunk-CMBFNA7L.js +27 -0
  19. package/dist/chunk-CNZ35WI2.js +178 -0
  20. package/dist/chunk-DAHRH4ON.js +331 -0
  21. package/dist/chunk-EBGIRKQY.js +616 -0
  22. package/dist/chunk-EUZND3CB.js +27 -0
  23. package/dist/chunk-F3FA4F32.js +292 -0
  24. package/dist/chunk-JAKHTMQU.js +1000 -0
  25. package/dist/chunk-JCI5M6U6.js +956 -0
  26. package/dist/chunk-KQPDEVVS.js +398 -0
  27. package/dist/chunk-M4NLBH4I.js +725 -0
  28. package/dist/chunk-NEKUBFPT.js +60 -0
  29. package/dist/chunk-NYVAC6P5.js +37 -0
  30. package/dist/chunk-PTQJDMRT.js +146 -0
  31. package/dist/chunk-QWZG56ET.js +2744 -0
  32. package/dist/chunk-TSOKIX5Z.js +654 -0
  33. package/dist/chunk-UHNL42EF.js +2730 -0
  34. package/dist/chunk-VRW3FULF.js +725 -0
  35. package/dist/chunk-WZSPOOER.js +84 -0
  36. package/dist/chunk-YT6HQ6AM.js +14 -0
  37. package/dist/chunk-ZD6OAMTH.js +277 -0
  38. package/dist/chunk-ZWKZCBO6.js +317 -0
  39. package/dist/contracts-DDrwxvJ-.d.cts +245 -0
  40. package/dist/contracts-DDrwxvJ-.d.ts +245 -0
  41. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  42. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  43. package/dist/data.cjs +35 -2
  44. package/dist/data.d.cts +7 -0
  45. package/dist/data.d.ts +7 -0
  46. package/dist/data.js +9 -8
  47. package/dist/devtools.cjs +122 -0
  48. package/dist/devtools.d.cts +69 -461
  49. package/dist/devtools.d.ts +69 -461
  50. package/dist/devtools.js +127 -6
  51. package/dist/ecosystem.cjs +23 -6
  52. package/dist/ecosystem.d.cts +1 -1
  53. package/dist/ecosystem.d.ts +1 -1
  54. package/dist/ecosystem.js +10 -9
  55. package/dist/extras.cjs +1208 -88
  56. package/dist/extras.d.cts +6 -6
  57. package/dist/extras.d.ts +6 -6
  58. package/dist/extras.js +70 -33
  59. package/dist/index.cjs +663 -158
  60. package/dist/index.d.cts +398 -40
  61. package/dist/index.d.ts +398 -40
  62. package/dist/index.js +39 -21
  63. package/dist/introspect-BumjnBKr.d.cts +477 -0
  64. package/dist/introspect-CZrlcaYy.d.ts +477 -0
  65. package/dist/introspect-Cb0zgpi2.d.cts +477 -0
  66. package/dist/introspect-Y2xNXGSf.d.ts +477 -0
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +51 -24
  69. package/dist/patterns.d.cts +19 -57
  70. package/dist/patterns.d.ts +19 -57
  71. package/dist/patterns.js +8 -16
  72. package/dist/performance.js +4 -4
  73. package/dist/plugins.cjs +429 -82
  74. package/dist/plugins.d.cts +27 -4
  75. package/dist/plugins.d.ts +27 -4
  76. package/dist/plugins.js +156 -37
  77. package/dist/ssr-4PBXAOO3.js +40 -0
  78. package/dist/ssr-Do_SiVoL.d.cts +201 -0
  79. package/dist/ssr-Do_SiVoL.d.ts +201 -0
  80. package/dist/ssr.cjs +312 -60
  81. package/dist/ssr.d.cts +10 -1
  82. package/dist/ssr.d.ts +10 -1
  83. package/dist/ssr.js +13 -10
  84. package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
  85. package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
  86. package/dist/testing.cjs +233 -2
  87. package/dist/testing.d.cts +42 -1
  88. package/dist/testing.d.ts +42 -1
  89. package/dist/testing.js +129 -2
  90. package/dist/ui.cjs +374 -8
  91. package/dist/ui.d.cts +252 -2
  92. package/dist/ui.d.ts +252 -2
  93. package/dist/ui.js +329 -11
  94. package/dist/widgets.js +7 -7
  95. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -1,7 +1,134 @@
1
- import { T as TagProps, N as NodeChildren, a as NodeChild } from './tagFactory-Dl8QCLga.cjs';
2
- export { S as SVG_NS, t as tagFactory } from './tagFactory-Dl8QCLga.cjs';
1
+ import { T as TagProps, N as NodeChildren, a as NodeChild } from './tagFactory-DaJ0YWX6.cjs';
2
+ export { S as SVG_NS, t as tagFactory } from './tagFactory-DaJ0YWX6.cjs';
3
3
  import { R as ReactiveSignal } from './signal-BnWpq6WB.cjs';
4
4
 
5
+ type reactive<T> = T | (() => T);
6
+ interface AnchorProps extends TagProps {
7
+ href?: reactive<string>;
8
+ target?: "_self" | "_blank" | "_parent" | "_top" | (string & {});
9
+ rel?: reactive<string>;
10
+ download?: reactive<string | boolean>;
11
+ hreflang?: reactive<string>;
12
+ referrerpolicy?: reactive<"" | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url">;
13
+ ping?: reactive<string>;
14
+ }
15
+ type InputType = "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week";
16
+ interface InputProps extends TagProps {
17
+ type?: InputType | (string & {});
18
+ name?: reactive<string>;
19
+ value?: reactive<string | number>;
20
+ placeholder?: reactive<string>;
21
+ required?: reactive<boolean>;
22
+ disabled?: reactive<boolean>;
23
+ readonly?: reactive<boolean>;
24
+ checked?: reactive<boolean>;
25
+ min?: reactive<string | number>;
26
+ max?: reactive<string | number>;
27
+ step?: reactive<string | number>;
28
+ minlength?: reactive<number>;
29
+ maxlength?: reactive<number>;
30
+ pattern?: reactive<string>;
31
+ autocomplete?: reactive<string>;
32
+ autofocus?: reactive<boolean>;
33
+ multiple?: reactive<boolean>;
34
+ accept?: reactive<string>;
35
+ size?: reactive<number>;
36
+ form?: reactive<string>;
37
+ list?: reactive<string>;
38
+ inputmode?: reactive<"none" | "text" | "decimal" | "numeric" | "tel" | "search" | "email" | "url">;
39
+ }
40
+ interface ImgProps extends TagProps {
41
+ src?: reactive<string>;
42
+ alt?: reactive<string>;
43
+ width?: reactive<number | string>;
44
+ height?: reactive<number | string>;
45
+ loading?: reactive<"lazy" | "eager">;
46
+ decoding?: reactive<"sync" | "async" | "auto">;
47
+ srcset?: reactive<string>;
48
+ sizes?: reactive<string>;
49
+ crossorigin?: reactive<"anonymous" | "use-credentials">;
50
+ referrerpolicy?: reactive<string>;
51
+ }
52
+ interface ButtonProps extends TagProps {
53
+ type?: reactive<"button" | "submit" | "reset">;
54
+ name?: reactive<string>;
55
+ value?: reactive<string>;
56
+ disabled?: reactive<boolean>;
57
+ form?: reactive<string>;
58
+ formaction?: reactive<string>;
59
+ formenctype?: reactive<string>;
60
+ formmethod?: reactive<"get" | "post" | "dialog">;
61
+ formnovalidate?: reactive<boolean>;
62
+ formtarget?: reactive<string>;
63
+ autofocus?: reactive<boolean>;
64
+ }
65
+ interface FormProps extends TagProps {
66
+ action?: reactive<string>;
67
+ method?: reactive<"get" | "post" | "dialog">;
68
+ enctype?: reactive<"application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain">;
69
+ name?: reactive<string>;
70
+ novalidate?: reactive<boolean>;
71
+ target?: reactive<string>;
72
+ autocomplete?: reactive<"on" | "off">;
73
+ acceptcharset?: reactive<string>;
74
+ }
75
+ interface SelectProps extends TagProps {
76
+ name?: reactive<string>;
77
+ value?: reactive<string>;
78
+ disabled?: reactive<boolean>;
79
+ multiple?: reactive<boolean>;
80
+ required?: reactive<boolean>;
81
+ size?: reactive<number>;
82
+ autocomplete?: reactive<string>;
83
+ autofocus?: reactive<boolean>;
84
+ form?: reactive<string>;
85
+ }
86
+ interface TextareaProps extends TagProps {
87
+ name?: reactive<string>;
88
+ value?: reactive<string>;
89
+ placeholder?: reactive<string>;
90
+ disabled?: reactive<boolean>;
91
+ readonly?: reactive<boolean>;
92
+ required?: reactive<boolean>;
93
+ rows?: reactive<number>;
94
+ cols?: reactive<number>;
95
+ minlength?: reactive<number>;
96
+ maxlength?: reactive<number>;
97
+ wrap?: reactive<"hard" | "soft" | "off">;
98
+ autocomplete?: reactive<string>;
99
+ autofocus?: reactive<boolean>;
100
+ form?: reactive<string>;
101
+ spellcheck?: reactive<boolean>;
102
+ }
103
+ interface LabelProps extends TagProps {
104
+ for?: reactive<string>;
105
+ form?: reactive<string>;
106
+ }
107
+ interface OptionProps extends TagProps {
108
+ value?: reactive<string | number>;
109
+ selected?: reactive<boolean>;
110
+ disabled?: reactive<boolean>;
111
+ label?: reactive<string>;
112
+ }
113
+ interface MediaProps extends TagProps {
114
+ src?: reactive<string>;
115
+ autoplay?: reactive<boolean>;
116
+ controls?: reactive<boolean>;
117
+ loop?: reactive<boolean>;
118
+ muted?: reactive<boolean>;
119
+ preload?: reactive<"none" | "metadata" | "auto">;
120
+ crossorigin?: reactive<"anonymous" | "use-credentials">;
121
+ }
122
+ interface VideoProps extends MediaProps {
123
+ poster?: reactive<string>;
124
+ width?: reactive<number | string>;
125
+ height?: reactive<number | string>;
126
+ playsinline?: reactive<boolean>;
127
+ }
128
+ type AudioProps = MediaProps;
129
+
130
+ type TypedTagFunction<Props extends TagProps, El extends Element> = (first?: Props | NodeChildren, second?: NodeChildren) => El;
131
+
5
132
  declare const head: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
6
133
  declare const body: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
7
134
  declare const title: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
@@ -33,7 +160,7 @@ declare const li: (first?: TagProps | NodeChildren, second?: NodeChildren) => El
33
160
  declare const ol: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
34
161
  declare const ul: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
35
162
  declare const pre: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
36
- declare const a: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
163
+ declare const a: TypedTagFunction<AnchorProps, HTMLAnchorElement>;
37
164
  declare const abbr: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
38
165
  declare const b: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
39
166
  declare const bdi: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
@@ -61,11 +188,11 @@ declare const time: (first?: TagProps | NodeChildren, second?: NodeChildren) =>
61
188
  declare const u: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
62
189
  declare const var_: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
63
190
  declare const area: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
64
- declare const audio: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
65
- declare const img: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
191
+ declare const audio: TypedTagFunction<AudioProps, HTMLAudioElement>;
192
+ declare const img: TypedTagFunction<ImgProps, HTMLImageElement>;
66
193
  declare const map: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
67
194
  declare const track: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
68
- declare const video: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
195
+ declare const video: TypedTagFunction<VideoProps, HTMLVideoElement>;
69
196
  declare const embed: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
70
197
  declare const iframe: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
71
198
  declare const object: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
@@ -90,20 +217,20 @@ declare const tfoot: (first?: TagProps | NodeChildren, second?: NodeChildren) =>
90
217
  declare const th: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
91
218
  declare const thead: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
92
219
  declare const tr: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
93
- declare const button: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
220
+ declare const button: TypedTagFunction<ButtonProps, HTMLButtonElement>;
94
221
  declare const datalist: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
95
222
  declare const fieldset: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
96
- declare const form: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
97
- declare const input: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
98
- declare const label: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
223
+ declare const form: TypedTagFunction<FormProps, HTMLFormElement>;
224
+ declare const input: TypedTagFunction<InputProps, HTMLInputElement>;
225
+ declare const label: TypedTagFunction<LabelProps, HTMLLabelElement>;
99
226
  declare const legend: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
100
227
  declare const meter: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
101
228
  declare const optgroup: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
102
- declare const option: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
229
+ declare const option: TypedTagFunction<OptionProps, HTMLOptionElement>;
103
230
  declare const output: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
104
231
  declare const progress: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
105
- declare const select: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
106
- declare const textarea: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
232
+ declare const select: TypedTagFunction<SelectProps, HTMLSelectElement>;
233
+ declare const textarea: TypedTagFunction<TextareaProps, HTMLTextAreaElement>;
107
234
  declare const details: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
108
235
  declare const dialog: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
109
236
  declare const menu: (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
@@ -493,6 +620,38 @@ declare function catchErrorAsync<T>(fn: () => Promise<T>, onError?: ErrorHandler
493
620
  */
494
621
  declare function setGlobalErrorHandler(handler: ErrorHandler): void;
495
622
 
623
+ /**
624
+ * Generate a stable, framework-unique ID string suitable for a11y pairing
625
+ * (`aria-labelledby`, `htmlFor` + `id`, etc.).
626
+ *
627
+ * Each call returns a fresh incrementing id. Optionally accepts a prefix.
628
+ *
629
+ * IDs are plain strings (not reactive) — call once per component instance
630
+ * and reuse the returned value for both sides of the association.
631
+ *
632
+ * @param prefix Optional prefix, default "sibu"
633
+ * @returns A unique id like `"sibu-1"` or `"my-input-2"`
634
+ *
635
+ * @example
636
+ * ```ts
637
+ * function Field(labelText: string) {
638
+ * const id = createId("field");
639
+ * return div({ nodes: [
640
+ * label({ for: id, nodes: labelText }),
641
+ * input({ id }),
642
+ * ]});
643
+ * }
644
+ * ```
645
+ */
646
+ declare function createId(prefix?: string): string;
647
+ /**
648
+ * Reset the id counter. Intended for tests and SSR setups that want
649
+ * deterministic ids across runs.
650
+ *
651
+ * @internal
652
+ */
653
+ declare function __resetIdCounter(): void;
654
+
496
655
  /**
497
656
  * Register a teardown function for a DOM node.
498
657
  * When dispose(node) is called, all registered teardowns run.
@@ -516,7 +675,7 @@ declare function checkLeaks(warnThreshold?: number): number;
516
675
 
517
676
  declare const __accessor: unique symbol;
518
677
  /**
519
- * A reactive signal getter returned by signal(), derived(), memo(), and similar primitives.
678
+ * A reactive signal getter returned by signal(), derived(), and similar primitives.
520
679
  *
521
680
  * Pass an Accessor directly into reactive prop positions — never call it there:
522
681
  * ```ts
@@ -669,28 +828,6 @@ interface Ref<T> {
669
828
  declare function ref<T>(initial: T): Ref<T>;
670
829
  declare function ref<T = undefined>(): Ref<T | undefined>;
671
830
 
672
- /**
673
- * memo returns a memoized value that only recomputes when its
674
- * reactive dependencies change. This is semantically identical to
675
- * derived but named for convenience.
676
- *
677
- * Use this to avoid expensive computations on every render cycle.
678
- *
679
- * @param factory Function that computes the memoized value
680
- * @returns Getter function that returns the memoized value
681
- */
682
- declare function memo<T>(factory: () => T): Accessor<T>;
683
-
684
- /**
685
- * memoFn returns a memoized callback function that only updates
686
- * when its reactive dependencies change. This prevents unnecessary
687
- * re-creations of callback functions passed to child components.
688
- *
689
- * @param callback The callback function to memoize
690
- * @returns Getter that returns the current memoized callback
691
- */
692
- declare function memoFn<T extends (...args: any[]) => any>(callback: () => T): Accessor<T>;
693
-
694
831
  /**
695
832
  * Reactive array hook. Provides common array operations that
696
833
  * automatically trigger reactive updates.
@@ -823,6 +960,46 @@ declare function writable<T>(get: () => T, set: (value: T) => void, options?: {
823
960
  name?: string;
824
961
  }): [Accessor<T>, (value: T) => void];
825
962
 
963
+ interface AsyncDerivedState<T> {
964
+ /** Resolved value, or `initial` while loading. */
965
+ value: () => T;
966
+ /** True while the underlying promise is in-flight. */
967
+ loading: () => boolean;
968
+ /** The last caught error, or `null`. */
969
+ error: () => unknown | null;
970
+ /** Manually re-run the async computation. */
971
+ refresh: () => void;
972
+ }
973
+ /**
974
+ * `asyncDerived` is the async counterpart of `derived`: it takes a factory
975
+ * that returns a Promise and re-runs whenever its reactive dependencies
976
+ * change. The returned object exposes reactive `value`, `loading`, and
977
+ * `error` getters, plus a `refresh()` trigger.
978
+ *
979
+ * Stale responses are dropped: if a new run starts before an older one
980
+ * resolves, the older one's result is ignored. This prevents flicker when
981
+ * dependencies change rapidly (e.g. typing in a search box).
982
+ *
983
+ * Unlike `query()` or `resource()`, `asyncDerived` has no caching or retry
984
+ * logic — it's a minimal async-reactivity primitive suited for ad-hoc
985
+ * derivations (parsing, formatting, validation against a server).
986
+ *
987
+ * @param factory Async function returning the derived value
988
+ * @param initial Value used while the first computation is pending
989
+ *
990
+ * @example
991
+ * ```ts
992
+ * const [query, setQuery] = signal("");
993
+ * const results = asyncDerived(async () => {
994
+ * const q = query();
995
+ * if (!q) return [];
996
+ * const r = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
997
+ * return r.json();
998
+ * }, []);
999
+ * ```
1000
+ */
1001
+ declare function asyncDerived<T>(factory: () => Promise<T>, initial: T): AsyncDerivedState<T>;
1002
+
826
1003
  /**
827
1004
  * Lifecycle hooks for SibuJS components.
828
1005
  *
@@ -889,8 +1066,6 @@ declare function onCleanup(callback: CleanupFn, element: Node): void;
889
1066
  * Context API for SibuJS — provides dependency injection across
890
1067
  * component trees without prop drilling.
891
1068
  *
892
- * Inspired by React's Context and Vue's provide/inject patterns.
893
- *
894
1069
  * @example
895
1070
  * ```ts
896
1071
  * // Create a context with a default value
@@ -927,6 +1102,46 @@ interface Context<T> {
927
1102
  */
928
1103
  declare function context<T>(defaultValue: T): Context<T>;
929
1104
 
1105
+ /**
1106
+ * Dev-only wrapper that runs `fn` twice — once now, once on the next
1107
+ * microtask. Any hidden side effect (duplicate event listener, missing
1108
+ * cleanup, stale closure) shows up immediately.
1109
+ *
1110
+ * In production builds the helper inlines to a single call.
1111
+ *
1112
+ * @example
1113
+ * ```ts
1114
+ * strict(() => {
1115
+ * // Any effect here is invoked twice in dev. If the second run
1116
+ * // causes duplicated listeners or DOM nodes, you have a cleanup bug.
1117
+ * effect(() => {
1118
+ * document.addEventListener("keydown", onKey);
1119
+ * // Forgot to return a disposer — strict() makes this obvious.
1120
+ * });
1121
+ * });
1122
+ * ```
1123
+ */
1124
+ declare function strict<T>(fn: () => T): T;
1125
+ /**
1126
+ * Dev-only wrapper that re-runs an effect twice the first time to catch
1127
+ * accidental side-effect bleeding. The returned teardown disposes both
1128
+ * invocations.
1129
+ *
1130
+ * This is a thin wrapper around `effect()` — use it instead of `effect()`
1131
+ * in any place where you suspect a cleanup bug.
1132
+ *
1133
+ * @example
1134
+ * ```ts
1135
+ * const dispose = strictEffect(() => {
1136
+ * const handler = () => { ... };
1137
+ * window.addEventListener("resize", handler);
1138
+ * // Missing `return () => removeEventListener(...)` — strictEffect
1139
+ * // will double-attach in dev and you'll see two log lines.
1140
+ * });
1141
+ * ```
1142
+ */
1143
+ declare function strictEffect(fn: () => void): () => void;
1144
+
930
1145
  /**
931
1146
  * SSR context for SibuJS.
932
1147
  *
@@ -950,6 +1165,11 @@ declare function disableSSR(): void;
950
1165
  /**
951
1166
  * Run a function in SSR mode. Automatically enables/disables SSR around the callback.
952
1167
  * Returns whatever the callback returns.
1168
+ *
1169
+ * Nesting-safe: saves the prior SSR flag and restores it in the `finally`
1170
+ * block. A nested `withSSR(...)` call cannot prematurely flip the outer
1171
+ * scope's SSR flag back to `false`, and an exception thrown inside `fn`
1172
+ * still leaves the flag in its original state.
953
1173
  */
954
1174
  declare function withSSR<T>(fn: () => T): T;
955
1175
 
@@ -986,6 +1206,77 @@ declare function enqueueBatchedSignal(signal: ReactiveSignal): boolean;
986
1206
  */
987
1207
  declare function isBatching(): boolean;
988
1208
 
1209
+ /**
1210
+ * Wait for the next microtask — after any currently-pending reactive updates
1211
+ * have been flushed. Useful in imperative code that needs to read DOM state
1212
+ * right after changing a signal.
1213
+ *
1214
+ * Under the hood this resolves on a microtask and again on an animation frame
1215
+ * so both synchronous reactive passes and layout side-effects have settled.
1216
+ *
1217
+ * @returns Promise that resolves after the next DOM flush
1218
+ *
1219
+ * @example
1220
+ * ```ts
1221
+ * setMenuOpen(true);
1222
+ * await nextTick();
1223
+ * menuRef.current?.focus(); // DOM has the new menu rendered
1224
+ * ```
1225
+ */
1226
+ declare function nextTick(): Promise<void>;
1227
+
1228
+ /**
1229
+ * Create a deferred mirror of a reactive getter. The returned accessor
1230
+ * eventually converges to the source value, but updates on a microtask
1231
+ * + `requestAnimationFrame` pair — so if the source changes repeatedly
1232
+ * in the same frame, only the latest value is ever surfaced.
1233
+ *
1234
+ * Use this for expensive derived views (filtered lists, rich charts)
1235
+ * that should not block fast state changes (typing, cursor movement).
1236
+ *
1237
+ * @example
1238
+ * ```ts
1239
+ * const [query, setQuery] = signal("");
1240
+ * const deferredQuery = defer(query);
1241
+ *
1242
+ * // input stays instant — it reads query()
1243
+ * input({ on: { input: e => setQuery(e.target.value) } });
1244
+ *
1245
+ * // heavy list reads deferredQuery() and updates one frame later
1246
+ * each(() => heavyFilter(items, deferredQuery()), row => li({ nodes: row.name }));
1247
+ * ```
1248
+ */
1249
+ declare function defer<T>(getter: () => T): () => T;
1250
+ interface TransitionState {
1251
+ pending: () => boolean;
1252
+ start: (fn: () => void | Promise<void>) => void;
1253
+ }
1254
+ /**
1255
+ * Create a transition handle. `start(fn)` runs `fn` on the next idle
1256
+ * callback so expensive reactive updates do not block immediate input
1257
+ * events. `pending()` is a reactive boolean that is `true` while a
1258
+ * transition is in flight.
1259
+ *
1260
+ * There is no "interruption" — the runtime has no concept of partial
1261
+ * renders. The transition is cooperative: its body runs when the browser
1262
+ * reports spare time via `requestIdleCallback`. That is sufficient for
1263
+ * the 90% case (defer a heavy update so a click handler can finish first)
1264
+ * and avoids the complexity of an interruptible reconciler.
1265
+ *
1266
+ * Async callbacks are supported: `pending()` stays `true` until the
1267
+ * returned promise resolves OR rejects.
1268
+ *
1269
+ * @example
1270
+ * ```ts
1271
+ * const t = transition();
1272
+ * button({
1273
+ * disabled: t.pending,
1274
+ * on: { click: () => t.start(() => setFilter(nextFilter)) },
1275
+ * });
1276
+ * ```
1277
+ */
1278
+ declare function transition(): TransitionState;
1279
+
989
1280
  /**
990
1281
  * Execute a function without tracking any signal reads as dependencies.
991
1282
  * Useful for reading signals inside effects without creating subscriptions.
@@ -1067,6 +1358,23 @@ interface ErrorBoundaryProps {
1067
1358
  * Called when an error is caught (sync or async).
1068
1359
  */
1069
1360
  onError?: (error: Error) => void;
1361
+ /**
1362
+ * A list of reactive getters. Whenever any of these values change
1363
+ * after an error has been caught, the boundary automatically resets
1364
+ * (clears the error and re-renders). Useful for recovering from a
1365
+ * failed render after the user navigates, changes filters, or
1366
+ * otherwise picks a new input that might not fail this time.
1367
+ *
1368
+ * @example
1369
+ * ```ts
1370
+ * const [route, setRoute] = signal("/");
1371
+ * ErrorBoundary({
1372
+ * resetKeys: [route],
1373
+ * nodes: () => div({ nodes: riskyPageFor(route()) }),
1374
+ * });
1375
+ * ```
1376
+ */
1377
+ resetKeys?: Array<() => unknown>;
1070
1378
  }
1071
1379
  /**
1072
1380
  * ErrorBoundary component using SibuJS reactive pattern.
@@ -1080,7 +1388,57 @@ interface ErrorBoundaryProps {
1080
1388
  * - onError callback for logging/telemetry
1081
1389
  * - Improved CSS styling
1082
1390
  */
1083
- declare function ErrorBoundary({ nodes, fallback, onError }: ErrorBoundaryProps): Element;
1391
+ declare function ErrorBoundary({ nodes, fallback, onError, resetKeys }: ErrorBoundaryProps): Element;
1392
+
1393
+ type ErrorSeverity = "error" | "warning" | "info";
1394
+ interface ErrorDisplayProps {
1395
+ /** The Error (or error-like value) to show. */
1396
+ error: unknown;
1397
+ /** Severity colour. Default `"error"`. */
1398
+ severity?: ErrorSeverity;
1399
+ /** Optional headline override. By default `error.message` is used. */
1400
+ title?: string;
1401
+ /**
1402
+ * Label for the primary action. Shown next to a Reload button.
1403
+ * Leave unset to hide the retry action.
1404
+ */
1405
+ retryLabel?: string;
1406
+ /** Callback for the retry button. */
1407
+ onRetry?: () => void;
1408
+ /**
1409
+ * If `true`, the Reload button is hidden. Useful for embedded
1410
+ * error panels where a full page reload is inappropriate.
1411
+ */
1412
+ hideReload?: boolean;
1413
+ /**
1414
+ * If `true`, the stack trace and metadata are always shown even in
1415
+ * production builds. Default: only shown in dev.
1416
+ */
1417
+ alwaysShowDetails?: boolean;
1418
+ /**
1419
+ * Extra metadata rendered as a key/value list under the message.
1420
+ * Useful for attaching request IDs, user IDs, etc.
1421
+ */
1422
+ metadata?: Record<string, string | number | boolean | null | undefined>;
1423
+ }
1424
+ /**
1425
+ * Rich error display component. Wire an error-like value in and get
1426
+ * a colored panel back with copy, retry, reload, stack, cause chain,
1427
+ * and metadata — all built from tag factories. Reusable anywhere
1428
+ * (inside `ErrorBoundary`, from a fetch failure, from a form submit).
1429
+ *
1430
+ * @example
1431
+ * ```ts
1432
+ * button({
1433
+ * on: { click: async () => {
1434
+ * try { await save(); }
1435
+ * catch (err) { mount(ErrorDisplay({ error: err, onRetry: save }), errorHost); }
1436
+ * }},
1437
+ * nodes: "Save",
1438
+ * });
1439
+ * ```
1440
+ */
1441
+ declare function ErrorDisplay(props: ErrorDisplayProps): Element;
1084
1442
 
1085
1443
  interface LoadingProps {
1086
1444
  /** Text to show alongside the spinner */
@@ -1103,4 +1461,4 @@ interface LoadingProps {
1103
1461
  */
1104
1462
  declare function Loading(props?: LoadingProps): HTMLElement;
1105
1463
 
1106
- export { type Accessor, type ActionFn, type ArrayActions, type Context, DynamicComponent, type EffectOptions, ErrorBoundary, type ErrorBoundaryProps, Fragment, KeepAlive, type KeepAliveOptions, Loading, type LoadingProps, type LongPressOptions, NodeChild, NodeChildren, Portal, type Ref, type SignalOptions, type SlotFn, type Slots, type StoreActions, Suspense, type SuspenseProps, TagProps, a, abbr, action, address, area, array, article, aside, 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, customElement, data, datalist, dd, deepEqual, deepSignal, defs, del, derived, details, dfn, dialog, disableSSR, dispose, div, dl, dt, each, effect, ellipse, em, embed, enableSSR, enqueueBatchedSignal, fieldset, figcaption, figure, font, footer, form, g, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, memo, memoFn, menu, meta, meter, mount, nav, 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, registerComponent, registerDisposer, resolveComponent, rp, rt, ruby, s, samp, script, section, select, setGlobalErrorHandler, show, signal, slot, small, source, span, stop, store, strong, style, sub, summary, sup, svg, symbol, table, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track, trapFocus, tspan, u, ul, unregisterComponent, untracked, use, var_, video, watch, when, withSSR, writable };
1464
+ export { type Accessor, type ActionFn, type AnchorProps, type ArrayActions, type AsyncDerivedState, type AudioProps, type ButtonProps, type Context, DynamicComponent, type EffectOptions, ErrorBoundary, type ErrorBoundaryProps, ErrorDisplay, type ErrorDisplayProps, type ErrorSeverity, type FormProps, Fragment, type ImgProps, type InputProps, type InputType, KeepAlive, type KeepAliveOptions, type LabelProps, Loading, type LoadingProps, type LongPressOptions, type MediaProps, NodeChild, NodeChildren, type OptionProps, Portal, type Ref, 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, enqueueBatchedSignal, fieldset, figcaption, figure, font, footer, form, g, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, menu, meta, meter, mount, 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, registerComponent, registerDisposer, resolveComponent, rp, rt, ruby, s, samp, script, section, select, setGlobalErrorHandler, show, signal, slot, small, source, span, stop, store, strict, strictEffect, strong, style, sub, summary, sup, svg, symbol, table, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track, transition, trapFocus, tspan, u, ul, unregisterComponent, untracked, use, var_, video, watch, when, withSSR, writable };