sibujs 1.3.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 (87) 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 +125 -135
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +11 -91
  10. package/dist/cdn.global.js +6 -6
  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-CNZ35WI2.js +178 -0
  17. package/dist/chunk-GJPXRJ45.js +37 -0
  18. package/dist/chunk-JCDUJN2F.js +2779 -0
  19. package/dist/chunk-K4G4ZQNR.js +286 -0
  20. package/dist/chunk-M4NLBH4I.js +725 -0
  21. package/dist/chunk-MB6QFH3I.js +2776 -0
  22. package/dist/chunk-MYRV7VDM.js +742 -0
  23. package/dist/chunk-NZIIMDWI.js +84 -0
  24. package/dist/chunk-P3XWXJZU.js +282 -0
  25. package/dist/chunk-PDZQY43A.js +616 -0
  26. package/dist/chunk-RJ46C3CS.js +1293 -0
  27. package/dist/chunk-SFKNRVCU.js +292 -0
  28. package/dist/chunk-TDGZL5CU.js +365 -0
  29. package/dist/chunk-UHNL42EF.js +2730 -0
  30. package/dist/chunk-VAPYJN4X.js +368 -0
  31. package/dist/chunk-VQDZK23A.js +1023 -0
  32. package/dist/chunk-VQNQZCWJ.js +61 -0
  33. package/dist/chunk-XHK6BDAJ.js +76 -0
  34. package/dist/chunk-XUEEGU5O.js +409 -0
  35. package/dist/chunk-ZWKZCBO6.js +317 -0
  36. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  37. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  38. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  39. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  40. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  41. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  42. package/dist/data.cjs +52 -11
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +22 -24
  45. package/dist/devtools.d.cts +1 -1
  46. package/dist/devtools.d.ts +1 -1
  47. package/dist/devtools.js +26 -28
  48. package/dist/ecosystem.cjs +31 -6
  49. package/dist/ecosystem.d.cts +4 -4
  50. package/dist/ecosystem.d.ts +4 -4
  51. package/dist/ecosystem.js +7 -7
  52. package/dist/extras.cjs +305 -131
  53. package/dist/extras.d.cts +3 -3
  54. package/dist/extras.d.ts +3 -3
  55. package/dist/extras.js +21 -29
  56. package/dist/index.cjs +124 -56
  57. package/dist/index.d.cts +60 -72
  58. package/dist/index.d.ts +60 -72
  59. package/dist/index.js +10 -14
  60. package/dist/motion.cjs +13 -2
  61. package/dist/motion.d.cts +1 -1
  62. package/dist/motion.d.ts +1 -1
  63. package/dist/motion.js +3 -3
  64. package/dist/patterns.cjs +91 -46
  65. package/dist/patterns.d.cts +46 -60
  66. package/dist/patterns.d.ts +46 -60
  67. package/dist/patterns.js +6 -14
  68. package/dist/performance.cjs +97 -12
  69. package/dist/performance.d.cts +6 -1
  70. package/dist/performance.d.ts +6 -1
  71. package/dist/performance.js +5 -3
  72. package/dist/plugins.cjs +20 -14
  73. package/dist/plugins.d.cts +3 -3
  74. package/dist/plugins.d.ts +3 -3
  75. package/dist/plugins.js +17 -19
  76. package/dist/ssr.cjs +9 -0
  77. package/dist/ssr.d.cts +1 -1
  78. package/dist/ssr.d.ts +1 -1
  79. package/dist/ssr.js +7 -7
  80. package/dist/testing.js +2 -2
  81. package/dist/ui.cjs +130 -53
  82. package/dist/ui.d.cts +13 -16
  83. package/dist/ui.d.ts +13 -16
  84. package/dist/ui.js +7 -9
  85. package/dist/widgets.cjs +31 -6
  86. package/dist/widgets.js +5 -5
  87. package/package.json +1 -1
@@ -0,0 +1,318 @@
1
+ type ValidatorFn<T = unknown> = (value: T) => string | null;
2
+ interface FieldConfig<T = unknown> {
3
+ initial: T;
4
+ validators?: ValidatorFn<T>[];
5
+ }
6
+ type FormConfig<T extends Record<string, unknown>> = {
7
+ [K in keyof T]: FieldConfig<T[K]>;
8
+ };
9
+ interface FormField<T = unknown> {
10
+ value: () => T;
11
+ set: (v: T) => void;
12
+ error: () => string | null;
13
+ touched: () => boolean;
14
+ touch: () => void;
15
+ reset: () => void;
16
+ }
17
+ interface FormReturn<T extends Record<string, unknown>> {
18
+ fields: {
19
+ [K in keyof T]: FormField<T[K]>;
20
+ };
21
+ errors: () => Partial<Record<keyof T, string | null>>;
22
+ isValid: () => boolean;
23
+ isDirty: () => boolean;
24
+ /** True while an async handleSubmit callback is in flight. Prevents double-submit. */
25
+ submitting: () => boolean;
26
+ touched: () => Partial<Record<keyof T, boolean>>;
27
+ values: () => T;
28
+ handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: Event) => void;
29
+ reset: () => void;
30
+ setError: (field: keyof T, message: string) => void;
31
+ }
32
+ declare function required(message?: string): ValidatorFn<unknown>;
33
+ declare function minLength(min: number, message?: string): ValidatorFn<string>;
34
+ declare function maxLength(max: number, message?: string): ValidatorFn<string>;
35
+ declare function matchesPattern(regex: RegExp, message?: string): ValidatorFn<string>;
36
+ declare function email(message?: string): ValidatorFn<string>;
37
+ declare function min(minVal: number, message?: string): ValidatorFn<number>;
38
+ declare function max(maxVal: number, message?: string): ValidatorFn<number>;
39
+ declare function custom<T>(fn: (value: T) => boolean, message: string): ValidatorFn<T>;
40
+ /**
41
+ * Props returned by bindField, ready to spread into an input tag factory.
42
+ */
43
+ interface BoundFieldProps {
44
+ value: () => unknown;
45
+ on: {
46
+ input: (e: Event) => void;
47
+ change: (e: Event) => void;
48
+ blur: () => void;
49
+ };
50
+ [attr: string]: unknown;
51
+ }
52
+ /**
53
+ * Bind a FormField to an input element, eliminating the value/input/blur boilerplate.
54
+ *
55
+ * Works with text inputs (`input` event) and selects/checkboxes (`change` event).
56
+ *
57
+ * @param field A FormField from form().fields
58
+ * @param extras Additional props to merge (placeholder, class, disabled, etc.)
59
+ * @returns Props object ready to pass directly to a tag factory
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const f = form({ email: { initial: "", validators: [required(), email()] } });
64
+ *
65
+ * // Before — verbose
66
+ * input({ value: f.fields.email.value(), on: { input: e => f.fields.email.set(e.target.value), blur: () => f.fields.email.touch() } })
67
+ *
68
+ * // After — one-liner
69
+ * input(bindField(f.fields.email, { type: "email", placeholder: "Email" }))
70
+ * ```
71
+ */
72
+ declare function bindField<T>(field: FormField<T>, extras?: Record<string, unknown>): BoundFieldProps;
73
+ declare function form<T extends Record<string, unknown>>(config: FormConfig<T>): FormReturn<T>;
74
+
75
+ interface VirtualListProps<T> {
76
+ items: () => T[];
77
+ itemHeight: number;
78
+ containerHeight: number;
79
+ overscan?: number;
80
+ renderItem: (item: T, index: number) => HTMLElement;
81
+ class?: string;
82
+ }
83
+ /**
84
+ * VirtualList renders only visible items for efficient large-list rendering.
85
+ */
86
+ declare function VirtualList<T>(props: VirtualListProps<T>): HTMLElement;
87
+
88
+ interface IntersectionResult {
89
+ isIntersecting: () => boolean;
90
+ intersectionRatio: () => number;
91
+ observe: (element: HTMLElement) => void;
92
+ unobserve: () => void;
93
+ }
94
+ /**
95
+ * intersection provides reactive intersection observer state.
96
+ */
97
+ declare function intersection(options?: IntersectionObserverInit): IntersectionResult;
98
+ /**
99
+ * Lazy-load utility using IntersectionObserver.
100
+ * Calls the loader function when element becomes visible.
101
+ */
102
+ declare function lazyLoad(element: HTMLElement, loader: () => void, options?: IntersectionObserverInit): () => void;
103
+
104
+ interface MaskOptions {
105
+ /** Pattern: 9 = digit, A = letter, * = any, other chars are literals */
106
+ pattern: string;
107
+ /** Placeholder character for unfilled positions */
108
+ placeholder?: string;
109
+ }
110
+ /**
111
+ * inputMask applies a mask to an input element.
112
+ * Returns reactive value and ref binding.
113
+ */
114
+ declare function inputMask(options: MaskOptions): {
115
+ value: () => string;
116
+ rawValue: () => string;
117
+ bind: (input: HTMLInputElement) => void;
118
+ };
119
+ /** Phone number mask: (999) 999-9999 */
120
+ declare function phoneMask(): MaskOptions;
121
+ /** Date mask: 99/99/9999 */
122
+ declare function dateMask(): MaskOptions;
123
+ /** Credit card mask: 9999 9999 9999 9999 */
124
+ declare function creditCardMask(): MaskOptions;
125
+ /** Time mask: 99:99 */
126
+ declare function timeMask(): MaskOptions;
127
+ /** SSN mask: 999-99-9999 */
128
+ declare function ssnMask(): MaskOptions;
129
+ /** ZIP code mask: 99999 */
130
+ declare function zipMask(): MaskOptions;
131
+
132
+ /**
133
+ * aria applies reactive ARIA attributes to an element.
134
+ */
135
+ declare function aria(element: HTMLElement, attrs: Record<string, string | boolean | (() => string | boolean)>): void;
136
+ /**
137
+ * focus manages focus state for an element.
138
+ */
139
+ declare function focus(): {
140
+ isFocused: () => boolean;
141
+ focus: () => void;
142
+ blur: () => void;
143
+ bind: (element: HTMLElement) => void;
144
+ };
145
+ /**
146
+ * FocusTrap traps focus within a container element.
147
+ * Tab cycling stays inside the container.
148
+ */
149
+ declare function FocusTrap(nodes: HTMLElement, options?: {
150
+ autoFocus?: boolean;
151
+ restoreFocus?: boolean;
152
+ }): HTMLElement;
153
+ /**
154
+ * hotkey registers a keyboard shortcut handler.
155
+ * Returns a cleanup function.
156
+ *
157
+ * Supports two calling styles:
158
+ * - String combo: hotkey("ctrl+shift+z", handler)
159
+ * - Explicit flags: hotkey("z", handler, { ctrl: true, shift: true })
160
+ */
161
+ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, options?: {
162
+ ctrl?: boolean;
163
+ shift?: boolean;
164
+ alt?: boolean;
165
+ meta?: boolean;
166
+ preventDefault?: boolean;
167
+ }): () => void;
168
+ /**
169
+ * announce creates a screen reader announcement using ARIA live regions.
170
+ */
171
+ declare function announce(message: string, priority?: "polite" | "assertive"): void;
172
+
173
+ /**
174
+ * scopedStyle creates component-scoped CSS by generating a unique scope ID
175
+ * and prefixing all selectors.
176
+ * Returns the scope attribute name and injects the CSS into the document.
177
+ *
178
+ * CSS is sanitized to remove dangerous patterns (`url()`, `@import`,
179
+ * `expression()`, `-moz-binding`, `behavior`). If you need `url()` for
180
+ * background images, use inline styles via the `style` prop instead.
181
+ */
182
+ declare function scopedStyle(css: string): {
183
+ scope: string;
184
+ attr: string;
185
+ };
186
+ /**
187
+ * withScopedStyle wraps a component function to auto-apply scoped styles.
188
+ * The component and all its children get the scope attribute.
189
+ */
190
+ declare function withScopedStyle<P>(css: string, component: (props: P) => HTMLElement): (props: P) => HTMLElement;
191
+ /**
192
+ * Removes a scoped style by its scope ID.
193
+ */
194
+ declare function removeScopedStyle(scopeId: string): void;
195
+
196
+ /**
197
+ * Bind multiple reactive attributes to an element.
198
+ * Each attribute value can be a static value or a reactive getter.
199
+ * Returns a single teardown function that stops all bindings.
200
+ */
201
+ declare function bindAttrs(el: HTMLElement, attrs: Record<string, string | number | boolean | (() => string | number | boolean)>): () => void;
202
+ /**
203
+ * Reactively toggle a boolean attribute (like disabled, readonly, hidden).
204
+ * When the value is truthy the attribute is present (set to ""),
205
+ * when falsy the attribute is removed entirely.
206
+ * Returns a teardown function to stop reactive tracking.
207
+ */
208
+ declare function bindBoolAttr(el: HTMLElement, attr: string, getter: boolean | (() => boolean)): () => void;
209
+ /**
210
+ * Bind a data-* attribute reactively.
211
+ * Shorthand for `bindAttribute(el, "data-<key>", getter)`.
212
+ * Returns a teardown function to stop reactive tracking.
213
+ */
214
+ declare function bindData(el: HTMLElement, key: string, getter: string | (() => string)): () => void;
215
+
216
+ /**
217
+ * dialog provides reactive dialog state management with escape-to-close support.
218
+ *
219
+ * Call `dispose()` when the owning component unmounts to ensure the global
220
+ * keydown listener is removed even if the dialog is still open.
221
+ */
222
+ declare function dialog(): {
223
+ open: () => void;
224
+ close: () => void;
225
+ isOpen: () => boolean;
226
+ toggle: () => void;
227
+ dispose: () => void;
228
+ };
229
+
230
+ /**
231
+ * Toast notification system with auto-dismiss and max toast limits.
232
+ */
233
+ interface Toast {
234
+ id: string;
235
+ message: string;
236
+ type?: "info" | "success" | "error" | "warning";
237
+ }
238
+ interface ToastInstance {
239
+ toasts: () => Toast[];
240
+ show: (message: string, type?: Toast["type"]) => string;
241
+ /** Show an info toast. */
242
+ info: (message: string) => string;
243
+ /** Show a success toast. */
244
+ success: (message: string) => string;
245
+ /** Show an error toast. */
246
+ error: (message: string) => string;
247
+ /** Show a warning toast. */
248
+ warning: (message: string) => string;
249
+ dismiss: (id: string) => void;
250
+ dismissAll: () => void;
251
+ }
252
+ declare function toast(options?: {
253
+ duration?: number;
254
+ maxToasts?: number;
255
+ }): ToastInstance;
256
+
257
+ /**
258
+ * infiniteScroll combines IntersectionObserver with a data-fetching trigger
259
+ * to implement infinite scroll behavior.
260
+ */
261
+ declare function infiniteScroll(options: {
262
+ onLoadMore: () => Promise<void>;
263
+ hasMore: () => boolean;
264
+ threshold?: number;
265
+ }): {
266
+ sentinelRef: {
267
+ current: HTMLElement | null;
268
+ };
269
+ loading: () => boolean;
270
+ dispose: () => void;
271
+ };
272
+
273
+ /**
274
+ * pagination provides reactive pagination state and controls.
275
+ */
276
+ declare function pagination(options: {
277
+ totalItems: () => number;
278
+ pageSize?: number;
279
+ initialPage?: number;
280
+ }): {
281
+ page: () => number;
282
+ pageSize: () => number;
283
+ totalPages: () => number;
284
+ next: () => void;
285
+ prev: () => void;
286
+ goTo: (page: number) => void;
287
+ startIndex: () => number;
288
+ endIndex: () => number;
289
+ };
290
+
291
+ /**
292
+ * eventBus creates a typed publish/subscribe event system.
293
+ * No reactive state needed -- pure event dispatching.
294
+ */
295
+ declare function eventBus<T extends Record<string, unknown>>(): {
296
+ on: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => () => void;
297
+ emit: <K extends keyof T>(event: K, data: T[K]) => void;
298
+ off: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => void;
299
+ clear: () => void;
300
+ };
301
+
302
+ interface CustomElementOptions {
303
+ shadow?: boolean;
304
+ mode?: "open" | "closed";
305
+ styles?: string;
306
+ observedAttributes?: string[];
307
+ extends?: string;
308
+ }
309
+ /**
310
+ * defineElement creates a Web Component wrapping a SibuJS component function.
311
+ */
312
+ declare function defineElement(name: string, component: (props: Record<string, unknown>, element: HTMLElement) => HTMLElement, options?: CustomElementOptions): void;
313
+ /**
314
+ * Creates an SVG element with proper namespace.
315
+ */
316
+ declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
317
+
318
+ export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
package/dist/data.cjs CHANGED
@@ -138,12 +138,21 @@ function queueSignalNotification(signal2) {
138
138
  }
139
139
  }
140
140
  }
141
+ var MAX_DRAIN_ITERATIONS = 1e3;
141
142
  function drainNotificationQueue() {
142
143
  if (notifyDepth > 0) return;
143
144
  notifyDepth++;
144
145
  try {
145
146
  let i = 0;
146
147
  while (i < pendingQueue.length) {
148
+ if (i >= MAX_DRAIN_ITERATIONS) {
149
+ if (typeof console !== "undefined") {
150
+ console.error(
151
+ `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
152
+ );
153
+ }
154
+ break;
155
+ }
147
156
  safeInvoke(pendingQueue[i]);
148
157
  i++;
149
158
  }
@@ -310,21 +319,37 @@ function derived(getter, options) {
310
319
  cs._v = getter();
311
320
  }, markDirty);
312
321
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
322
+ let evaluating = false;
313
323
  function computedGetter() {
324
+ if (evaluating) {
325
+ throw new Error(
326
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
327
+ );
328
+ }
314
329
  if (trackingSuspended) {
315
330
  if (cs._d) {
316
- cs._d = false;
317
- cs._v = getter();
331
+ evaluating = true;
332
+ try {
333
+ cs._d = false;
334
+ cs._v = getter();
335
+ } finally {
336
+ evaluating = false;
337
+ }
318
338
  }
319
339
  return cs._v;
320
340
  }
321
341
  recordDependency(cs);
322
342
  if (cs._d) {
323
343
  const oldValue = cs._v;
324
- track(() => {
325
- cs._d = false;
326
- cs._v = getter();
327
- }, markDirty);
344
+ evaluating = true;
345
+ try {
346
+ track(() => {
347
+ cs._d = false;
348
+ cs._v = getter();
349
+ }, markDirty);
350
+ } finally {
351
+ evaluating = false;
352
+ }
328
353
  if (hook && oldValue !== cs._v) {
329
354
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
330
355
  }
@@ -480,9 +505,13 @@ async function withRetry(fn, options, onRetry, signal2) {
480
505
  const delay = calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter);
481
506
  onRetry?.(error, attempt, delay);
482
507
  await new Promise((resolve, reject) => {
483
- const timer = setTimeout(resolve, delay);
508
+ let onAbort = null;
509
+ const timer = setTimeout(() => {
510
+ if (onAbort && signal2) signal2.removeEventListener("abort", onAbort);
511
+ resolve();
512
+ }, delay);
484
513
  if (signal2) {
485
- const onAbort = () => {
514
+ onAbort = () => {
486
515
  clearTimeout(timer);
487
516
  reject(new DOMException("Aborted", "AbortError"));
488
517
  };
@@ -633,9 +662,10 @@ function query(key, fetcher, options = {}) {
633
662
  }
634
663
  }
635
664
  }
665
+ const keyChanged = currentKey !== key2;
636
666
  currentKey = key2;
637
667
  const entry = getOrCreateEntry(key2, initialData);
638
- entry.subscribers++;
668
+ if (keyChanged || entry.subscribers === 0) entry.subscribers++;
639
669
  if (entry.gcTimer !== null) {
640
670
  clearTimeout(entry.gcTimer);
641
671
  entry.gcTimer = null;
@@ -749,7 +779,9 @@ function mutation(mutationFn, options = {}) {
749
779
  const [status, setStatus] = signal("idle");
750
780
  const isSuccess = derived(() => status() === "success");
751
781
  const isIdle = derived(() => status() === "idle");
782
+ let runId = 0;
752
783
  async function execute(variables) {
784
+ const myRun = ++runId;
753
785
  let context2;
754
786
  batch(() => {
755
787
  setLoading(true);
@@ -761,6 +793,7 @@ function mutation(mutationFn, options = {}) {
761
793
  context2 = await options.onMutate(variables);
762
794
  }
763
795
  const result = await withRetry(() => mutationFn(variables), options.retry);
796
+ if (myRun !== runId) return result;
764
797
  batch(() => {
765
798
  setData(result);
766
799
  setLoading(false);
@@ -771,6 +804,7 @@ function mutation(mutationFn, options = {}) {
771
804
  return result;
772
805
  } catch (err) {
773
806
  const errorObj = err instanceof Error ? err : new Error(String(err));
807
+ if (myRun !== runId) throw errorObj;
774
808
  batch(() => {
775
809
  setError(errorObj);
776
810
  setLoading(false);
@@ -782,6 +816,7 @@ function mutation(mutationFn, options = {}) {
782
816
  }
783
817
  }
784
818
  function reset() {
819
+ runId++;
785
820
  batch(() => {
786
821
  setData(void 0);
787
822
  setError(void 0);
@@ -1031,7 +1066,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1031
1066
  options.onSuccess?.(result);
1032
1067
  } catch (err) {
1033
1068
  if (version !== fetchVersion || disposed) return;
1034
- if (err instanceof DOMException && err.name === "AbortError") return;
1069
+ if (err instanceof DOMException && err.name === "AbortError") {
1070
+ if (version === fetchVersion) setLoading(false);
1071
+ return;
1072
+ }
1035
1073
  const errorObj = err instanceof Error ? err : new Error(String(err));
1036
1074
  batch(() => {
1037
1075
  setError(errorObj);
@@ -1285,6 +1323,7 @@ function socket(url, options) {
1285
1323
  let reconnectTimer = null;
1286
1324
  let heartbeatTimer = null;
1287
1325
  let disposed = false;
1326
+ let manuallyClosed = false;
1288
1327
  function getUrl() {
1289
1328
  return typeof url === "function" ? url() : url;
1290
1329
  }
@@ -1308,12 +1347,13 @@ function socket(url, options) {
1308
1347
  ws.onclose = () => {
1309
1348
  setStatus("closed");
1310
1349
  stopHeartbeat();
1311
- if (autoReconnect && !disposed && reconnectCount < maxReconnects) {
1350
+ if (autoReconnect && !disposed && !manuallyClosed && reconnectCount < maxReconnects) {
1312
1351
  reconnectCount++;
1313
1352
  reconnectTimer = setTimeout(() => {
1314
1353
  connect();
1315
1354
  }, reconnectDelay);
1316
1355
  }
1356
+ manuallyClosed = false;
1317
1357
  };
1318
1358
  ws.onerror = () => {
1319
1359
  };
@@ -1339,6 +1379,7 @@ function socket(url, options) {
1339
1379
  }
1340
1380
  }
1341
1381
  function close() {
1382
+ manuallyClosed = true;
1342
1383
  if (reconnectTimer !== null) {
1343
1384
  clearTimeout(reconnectTimer);
1344
1385
  reconnectTimer = null;
package/dist/data.js CHANGED
@@ -19,14 +19,14 @@ import {
19
19
  syncAdapter,
20
20
  throttle,
21
21
  withRetry
22
- } from "./chunk-JAKHTMQU.js";
23
- import "./chunk-BGN5ZMP4.js";
24
- import "./chunk-NEKUBFPT.js";
22
+ } from "./chunk-BGTHZHJ5.js";
23
+ import "./chunk-6BMPXPUW.js";
24
+ import "./chunk-XHK6BDAJ.js";
25
25
  import "./chunk-CMBFNA7L.js";
26
- import "./chunk-CHF5OHIA.js";
26
+ import "./chunk-VQNQZCWJ.js";
27
27
  import "./chunk-EUZND3CB.js";
28
- import "./chunk-WZSPOOER.js";
29
- import "./chunk-ZD6OAMTH.js";
28
+ import "./chunk-NZIIMDWI.js";
29
+ import "./chunk-K4G4ZQNR.js";
30
30
  import "./chunk-5X6PP2UK.js";
31
31
  export {
32
32
  calculateDelay,
package/dist/devtools.cjs CHANGED
@@ -1473,55 +1473,53 @@ function createTraceProfiler() {
1473
1473
  }
1474
1474
  const start = typeof performance !== "undefined" ? performance.now() : Date.now();
1475
1475
  let recording = true;
1476
- const onEffectStart = (payload) => {
1476
+ function now() {
1477
+ return (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
1478
+ }
1479
+ const onEffectCreate = (payload) => {
1477
1480
  if (!recording) return;
1478
- const now = (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
1479
- const label = payload.name ?? "effect";
1480
1481
  events.push({
1481
- name: label,
1482
+ name: payload.name ?? "effect",
1482
1483
  cat: "effect",
1483
- ph: "B",
1484
- ts: Math.floor(now * 1e3),
1484
+ ph: "I",
1485
+ ts: Math.floor(now() * 1e3),
1485
1486
  tid: 0,
1486
1487
  pid: 0
1487
1488
  });
1488
1489
  };
1489
- const onEffectEnd = (payload) => {
1490
+ const onEffectDestroy = (payload) => {
1490
1491
  if (!recording) return;
1491
- const now = (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
1492
- const label = payload.name ?? "effect";
1493
1492
  events.push({
1494
- name: label,
1493
+ name: payload.name ?? "effect:destroy",
1495
1494
  cat: "effect",
1496
- ph: "E",
1497
- ts: Math.floor(now * 1e3),
1495
+ ph: "I",
1496
+ ts: Math.floor(now() * 1e3),
1498
1497
  tid: 0,
1499
1498
  pid: 0
1500
1499
  });
1501
1500
  };
1502
- const onSignalSet = (payload) => {
1501
+ const onSignalUpdate = (payload) => {
1503
1502
  if (!recording) return;
1504
- const now = (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
1505
- const label = payload.name ?? "signal";
1503
+ const p = payload;
1506
1504
  events.push({
1507
- name: label,
1505
+ name: p.name ?? "signal",
1508
1506
  cat: "signal",
1509
1507
  ph: "I",
1510
- ts: Math.floor(now * 1e3),
1508
+ ts: Math.floor(now() * 1e3),
1511
1509
  tid: 0,
1512
1510
  pid: 0,
1513
- args: payload.args
1511
+ args: p.oldValue !== void 0 ? { oldValue: String(p.oldValue), newValue: String(p.newValue) } : void 0
1514
1512
  });
1515
1513
  };
1516
- const offStart = hook.on("effect:start", onEffectStart);
1517
- const offEnd = hook.on("effect:end", onEffectEnd);
1518
- const offSet = hook.on("signal:set", onSignalSet);
1514
+ const offCreate = hook.on("effect:create", onEffectCreate);
1515
+ const offDestroy = hook.on("effect:destroy", onEffectDestroy);
1516
+ const offUpdate = hook.on("signal:update", onSignalUpdate);
1519
1517
  function stop() {
1520
1518
  if (!recording) return events;
1521
1519
  recording = false;
1522
- offStart();
1523
- offEnd();
1524
- offSet();
1520
+ offCreate();
1521
+ offDestroy();
1522
+ offUpdate();
1525
1523
  return events;
1526
1524
  }
1527
1525
  function stopTrace() {
@@ -6,7 +6,7 @@ interface SignalNodeSnapshot {
6
6
  id: string;
7
7
  /** Debug name, if the caller tagged the signal. */
8
8
  name: string | null;
9
- /** Runtime type tag: `"signal"`, `"derived"`, `"memo"`, `"effect"`. */
9
+ /** Runtime type tag: `"signal"`, `"derived"`, `"effect"`. */
10
10
  kind: string;
11
11
  /** Best-effort preview of the current value. */
12
12
  value: string;
@@ -6,7 +6,7 @@ interface SignalNodeSnapshot {
6
6
  id: string;
7
7
  /** Debug name, if the caller tagged the signal. */
8
8
  name: string | null;
9
- /** Runtime type tag: `"signal"`, `"derived"`, `"memo"`, `"effect"`. */
9
+ /** Runtime type tag: `"signal"`, `"derived"`, `"effect"`. */
10
10
  kind: string;
11
11
  /** Best-effort preview of the current value. */
12
12
  value: string;