elements-kit 0.0.12 → 0.0.14

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 (94) hide show
  1. package/README.md +38 -0
  2. package/dist/{element-MXzFk4G2.mjs → element-C_4VbkvQ.mjs} +1 -1
  3. package/dist/{index-Cvdhuy6Y.d.mts → index-DUshSQ_6.d.mts} +11 -7
  4. package/dist/index.mjs +2 -2
  5. package/dist/integrations/react.d.mts +1 -1
  6. package/dist/integrations/react.mjs +1 -1
  7. package/dist/jsx-runtime/index.d.mts +1 -1
  8. package/dist/jsx-runtime/index.mjs +2 -2
  9. package/dist/signals/index.d.mts +2 -2
  10. package/dist/signals/index.mjs +2 -2
  11. package/dist/{signals-DcgXhkU2.mjs → signals-BHmWX6ox.mjs} +86 -76
  12. package/dist/slot.d.mts +2 -1
  13. package/dist/utilities/active-element.d.mts +1 -1
  14. package/dist/utilities/async.d.mts +37 -0
  15. package/dist/utilities/async.mjs +119 -0
  16. package/dist/utilities/async.test.d.mts +1 -0
  17. package/dist/utilities/async.test.mjs +273 -0
  18. package/dist/utilities/debounced.d.mts +1 -1
  19. package/dist/utilities/debounced.mjs +1 -1
  20. package/dist/utilities/debounced.test.mjs +1 -1
  21. package/dist/utilities/element-rect.d.mts +1 -1
  22. package/dist/utilities/element-rect.mjs +1 -1
  23. package/dist/utilities/element-rect.test.mjs +1 -1
  24. package/dist/utilities/element-scroll.d.mts +1 -1
  25. package/dist/utilities/element-scroll.mjs +1 -1
  26. package/dist/utilities/element-scroll.test.mjs +1 -1
  27. package/dist/utilities/event-driven.d.mts +1 -1
  28. package/dist/utilities/event-driven.mjs +10 -8
  29. package/dist/utilities/event-listener.d.mts +1 -1
  30. package/dist/utilities/event-listener.mjs +1 -1
  31. package/dist/utilities/event-listener.test.mjs +1 -1
  32. package/dist/utilities/focus-within.d.mts +1 -1
  33. package/dist/utilities/focus-within.mjs +1 -1
  34. package/dist/utilities/focus-within.test.mjs +1 -1
  35. package/dist/utilities/hover.d.mts +1 -1
  36. package/dist/utilities/hover.mjs +1 -1
  37. package/dist/utilities/hover.test.mjs +1 -1
  38. package/dist/utilities/intersection-observer.mjs +1 -1
  39. package/dist/utilities/intersection-observer.test.mjs +1 -1
  40. package/dist/utilities/interval.d.mts +7 -6
  41. package/dist/utilities/interval.mjs +17 -13
  42. package/dist/utilities/interval.test.mjs +31 -2
  43. package/dist/utilities/location.d.mts +1 -1
  44. package/dist/utilities/location.mjs +1 -1
  45. package/dist/utilities/long-press.test.mjs +1 -1
  46. package/dist/utilities/media-devices.d.mts +1 -1
  47. package/dist/utilities/media-devices.mjs +1 -1
  48. package/dist/utilities/media-devices.test.mjs +1 -1
  49. package/dist/utilities/media-player.d.mts +1 -1
  50. package/dist/utilities/media-player.test.mjs +1 -1
  51. package/dist/utilities/media-query.d.mts +1 -1
  52. package/dist/utilities/media-query.mjs +1 -1
  53. package/dist/utilities/mutation-observer.mjs +1 -1
  54. package/dist/utilities/mutation-observer.test.mjs +1 -1
  55. package/dist/utilities/network.d.mts +10 -0
  56. package/dist/utilities/network.mjs +17 -0
  57. package/dist/utilities/network.test.d.mts +1 -0
  58. package/dist/utilities/network.test.mjs +29 -0
  59. package/dist/utilities/on-click-outside.test.mjs +1 -1
  60. package/dist/utilities/orientation.d.mts +1 -1
  61. package/dist/utilities/previous.d.mts +1 -1
  62. package/dist/utilities/previous.mjs +1 -1
  63. package/dist/utilities/previous.test.mjs +1 -1
  64. package/dist/utilities/promise.d.mts +56 -0
  65. package/dist/utilities/promise.mjs +101 -0
  66. package/dist/utilities/promise.test.d.mts +1 -0
  67. package/dist/utilities/promise.test.mjs +156 -0
  68. package/dist/utilities/resize-observer.mjs +1 -1
  69. package/dist/utilities/retry.d.mts +10 -0
  70. package/dist/utilities/retry.mjs +32 -0
  71. package/dist/utilities/retry.test.d.mts +1 -0
  72. package/dist/utilities/retry.test.mjs +83 -0
  73. package/dist/utilities/routing.d.mts +1 -1
  74. package/dist/utilities/routing.mjs +1 -1
  75. package/dist/utilities/search-params.d.mts +1 -1
  76. package/dist/utilities/search-params.test.mjs +1 -1
  77. package/dist/utilities/storage.d.mts +1 -1
  78. package/dist/utilities/storage.test.mjs +1 -1
  79. package/dist/utilities/throttled.d.mts +1 -1
  80. package/dist/utilities/throttled.mjs +1 -1
  81. package/dist/utilities/throttled.test.mjs +1 -1
  82. package/dist/utilities/timeout.d.mts +2 -2
  83. package/dist/utilities/timeout.mjs +7 -7
  84. package/dist/utilities/timeout.test.mjs +4 -4
  85. package/dist/utilities/window-focus.d.mts +10 -0
  86. package/dist/utilities/window-focus.mjs +16 -0
  87. package/dist/utilities/window-size.d.mts +1 -1
  88. package/dist/utilities/window-size.mjs +1 -1
  89. package/package.json +3 -13
  90. package/dist/builder/dom.d.mts +0 -212
  91. package/dist/builder/dom.mjs +0 -205
  92. package/dist/builder/index.d.mts +0 -88
  93. package/dist/builder/index.mjs +0 -114
  94. /package/dist/{lib-BYJ5jPTG.d.mts → lib-JA05lzCN.d.mts} +0 -0
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createInterval } from "./interval.mjs";
4
4
  //#region src/utilities/interval.test.ts
@@ -26,7 +26,7 @@ describe("createInterval", () => {
26
26
  iv.stop();
27
27
  vi.advanceTimersByTime(500);
28
28
  globalExpect(cb).toHaveBeenCalledTimes(0);
29
- globalExpect(iv.isRunning()).toBe(false);
29
+ globalExpect(iv.pending()).toBe(false);
30
30
  });
31
31
  it("start() resumes after stop", () => {
32
32
  vi.useFakeTimers();
@@ -63,6 +63,35 @@ describe("createInterval", () => {
63
63
  vi.advanceTimersByTime(500);
64
64
  globalExpect(cb).toHaveBeenCalledTimes(0);
65
65
  });
66
+ it("no-callback form updates timestamp on each tick", () => {
67
+ vi.useFakeTimers();
68
+ let iv;
69
+ effectScope(() => {
70
+ iv = createInterval(100);
71
+ });
72
+ const before = iv.timestamp();
73
+ vi.advanceTimersByTime(100);
74
+ globalExpect(iv.timestamp()).toBeGreaterThan(before);
75
+ });
76
+ it("dynamic delay form is called each tick", () => {
77
+ vi.useFakeTimers();
78
+ const cb = vi.fn();
79
+ const getDelay = vi.fn().mockReturnValue(100);
80
+ effectScope(() => {
81
+ createInterval(cb, getDelay);
82
+ });
83
+ vi.advanceTimersByTime(300);
84
+ globalExpect(cb).toHaveBeenCalledTimes(3);
85
+ });
86
+ it("stops when scope is disposed", () => {
87
+ vi.useFakeTimers();
88
+ const cb = vi.fn();
89
+ effectScope(() => {
90
+ createInterval(cb, 100);
91
+ })();
92
+ vi.advanceTimersByTime(500);
93
+ globalExpect(cb).toHaveBeenCalledTimes(0);
94
+ });
66
95
  });
67
96
  //#endregion
68
97
  export {};
@@ -1,4 +1,4 @@
1
- import { t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/location.d.ts
4
4
  type LocationResult = {
@@ -1,4 +1,4 @@
1
- import { a as computed, f as onCleanup, m as trigger, p as signal } from "../signals-DcgXhkU2.mjs";
1
+ import { _ as signal, g as onCleanup, l as computed, v as trigger } from "../signals-BHmWX6ox.mjs";
2
2
  import { fromEvent } from "./event-driven.mjs";
3
3
  //#region src/utilities/location.ts
4
4
  const EVENTS = [
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createLongPress } from "./long-press.mjs";
4
4
  //#region src/utilities/long-press.test.ts
@@ -1,4 +1,4 @@
1
- import { t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/media-devices.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { p as signal } from "../signals-DcgXhkU2.mjs";
1
+ import { _ as signal } from "../signals-BHmWX6ox.mjs";
2
2
  import { on } from "./event-listener.mjs";
3
3
  //#region src/utilities/media-devices.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createMediaDevices } from "./media-devices.mjs";
4
4
  //#region src/utilities/media-devices.test.ts
@@ -1,4 +1,4 @@
1
- import { r as Signal, t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { r as Signal, t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/media-player.d.ts
4
4
  type MediaPlayerResult<T extends HTMLMediaElement> = {
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createMediaPlayer } from "./media-player.mjs";
4
4
  //#region src/utilities/media-player.test.ts
@@ -1,4 +1,4 @@
1
- import { t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/media-query.d.ts
4
4
  declare const isBrowser: boolean;
@@ -1,4 +1,4 @@
1
- import { p as signal } from "../signals-DcgXhkU2.mjs";
1
+ import { _ as signal } from "../signals-BHmWX6ox.mjs";
2
2
  import { fromEvent, sync } from "./event-driven.mjs";
3
3
  //#region src/utilities/media-query.ts
4
4
  const isBrowser = typeof window !== "undefined";
@@ -1,4 +1,4 @@
1
- import { f as onCleanup } from "../signals-DcgXhkU2.mjs";
1
+ import { g as onCleanup } from "../signals-BHmWX6ox.mjs";
2
2
  //#region src/utilities/mutation-observer.ts
3
3
  /**
4
4
  * Watches `target` for DOM mutations and calls `callback` with each batch of
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createMutationObserver } from "./mutation-observer.mjs";
4
4
  //#region src/utilities/mutation-observer.test.ts
@@ -0,0 +1,10 @@
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
+
3
+ //#region src/utilities/network.d.ts
4
+ /**
5
+ * Singleton `Computed<boolean>` — `true` when `navigator.onLine` is true.
6
+ * Reacts to `online` / `offline` window events.
7
+ */
8
+ declare const online: Computed<boolean>;
9
+ //#endregion
10
+ export { online };
@@ -0,0 +1,17 @@
1
+ import { _ as signal } from "../signals-BHmWX6ox.mjs";
2
+ import { on } from "./event-listener.mjs";
3
+ //#region src/utilities/network.ts
4
+ function createOnline() {
5
+ const value = signal(typeof navigator !== "undefined" ? navigator.onLine : true);
6
+ const update = () => value(navigator.onLine);
7
+ on(window, "online", update);
8
+ on(window, "offline", update);
9
+ return value;
10
+ }
11
+ /**
12
+ * Singleton `Computed<boolean>` — `true` when `navigator.onLine` is true.
13
+ * Reacts to `online` / `offline` window events.
14
+ */
15
+ const online = createOnline();
16
+ //#endregion
17
+ export { online };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,29 @@
1
+ import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
2
+ import { online } from "./network.mjs";
3
+ //#region src/utilities/network.test.ts
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ });
7
+ describe("online", () => {
8
+ it("reflects navigator.onLine", () => {
9
+ globalExpect(online()).toBe(navigator.onLine);
10
+ });
11
+ it("becomes false on offline event", () => {
12
+ Object.defineProperty(navigator, "onLine", {
13
+ configurable: true,
14
+ get: () => false
15
+ });
16
+ window.dispatchEvent(new Event("offline"));
17
+ globalExpect(online()).toBe(false);
18
+ });
19
+ it("becomes true on online event", () => {
20
+ Object.defineProperty(navigator, "onLine", {
21
+ configurable: true,
22
+ get: () => true
23
+ });
24
+ window.dispatchEvent(new Event("online"));
25
+ globalExpect(online()).toBe(true);
26
+ });
27
+ });
28
+ //#endregion
29
+ export {};
@@ -1,4 +1,4 @@
1
- import { s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { n as vi, o as describe, r as afterEach, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { onClickOutside } from "./on-click-outside.mjs";
4
4
  //#region src/utilities/on-click-outside.test.ts
@@ -1,4 +1,4 @@
1
- import { t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/orientation.d.ts
4
4
  type OrientationResult = {
@@ -1,4 +1,4 @@
1
- import { t as Computed } from "../index-Cvdhuy6Y.mjs";
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/previous.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { o as effect, p as signal } from "../signals-DcgXhkU2.mjs";
1
+ import { _ as signal, u as effect } from "../signals-BHmWX6ox.mjs";
2
2
  //#region src/utilities/previous.ts
3
3
  /**
4
4
  * Returns a `Computed` that always holds the *previous* value of `source`.
@@ -1,4 +1,4 @@
1
- import { p as signal, s as effectScope } from "../signals-DcgXhkU2.mjs";
1
+ import { _ as signal, d as effectScope } from "../signals-BHmWX6ox.mjs";
2
2
  import { o as describe, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
3
  import { createPrevious } from "./previous.mjs";
4
4
  //#region src/utilities/previous.test.ts
@@ -0,0 +1,56 @@
1
+ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
+
3
+ //#region src/utilities/promise.d.ts
4
+ /**
5
+ * A `Promise` subclass that exposes its state as reactive signals.
6
+ *
7
+ * Prefer the {@link promise} factory for most use cases — it returns a
8
+ * `ComputedPromise` that is both awaitable and callable as a signal.
9
+ * Use `ReactivePromise` directly when you need the lower-level class —
10
+ * for example, to wrap a promise and expose `.state`, `.value`, `.reason`,
11
+ * and `.result` without the `Computed` callable interface.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const rp = ReactivePromise.from(fetch("/api/data"));
16
+ *
17
+ * effect(() => {
18
+ * if (rp.state === "fulfilled") console.log(rp.value);
19
+ * });
20
+ * ```
21
+ */
22
+ declare class ReactivePromise<T, E = unknown> extends Promise<T> {
23
+ #private;
24
+ get state(): "pending" | "fulfilled" | "rejected";
25
+ get value(): T | undefined;
26
+ get reason(): E | undefined;
27
+ get result(): T | E | undefined;
28
+ constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void);
29
+ static from<T, E = unknown>(p: Promise<T>): ReactivePromise<T, E>;
30
+ }
31
+ type ComputedPromise<T, E = unknown> = ReactivePromise<T, E> & Computed<T | E | undefined>;
32
+ type Executor<T, E = unknown> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: E) => void) => void;
33
+ /**
34
+ * Wraps a promise, executor, or `ReactivePromise` into a `ComputedPromise` —
35
+ * an object that is both awaitable like a regular Promise and reactive like a
36
+ * `Computed` signal.
37
+ *
38
+ * **Awaitable:** `await promise(fetch(...))` resolves to the fulfilled value,
39
+ * or rejects with the rejection reason, just like a native Promise.
40
+ *
41
+ * **Reactive:** calling the returned value as a function (`cp()`) reads the
42
+ * current result inside an `effect` or `computed`, tracking it as a dependency.
43
+ * Equivalent to `.result` — returns `undefined` while pending, the fulfilled
44
+ * value when resolved, or the rejection reason when rejected.
45
+ *
46
+ * Reactive state is also accessible via:
47
+ * - `.state` — `"pending" | "fulfilled" | "rejected"`
48
+ * - `.value` — the resolved value (or `undefined` while pending)
49
+ * - `.reason` — the rejection reason (or `undefined` while pending/fulfilled)
50
+ * - `.result` — `T | E | undefined`; the resolved value, rejection reason, or `undefined` while pending
51
+ */
52
+ declare function promise<T, E = unknown>(p: ReactivePromise<T>): ComputedPromise<T, E>;
53
+ declare function promise<T, E = unknown>(p: Promise<T>): ComputedPromise<T, E>;
54
+ declare function promise<T, E = unknown>(executor: Executor<T, E>): ComputedPromise<T, E>;
55
+ //#endregion
56
+ export { ComputedPromise, ReactivePromise, promise };
@@ -0,0 +1,101 @@
1
+ import { _ as signal, c as batch, l as computed } from "../signals-BHmWX6ox.mjs";
2
+ //#region src/utilities/promise.ts
3
+ /**
4
+ * A `Promise` subclass that exposes its state as reactive signals.
5
+ *
6
+ * Prefer the {@link promise} factory for most use cases — it returns a
7
+ * `ComputedPromise` that is both awaitable and callable as a signal.
8
+ * Use `ReactivePromise` directly when you need the lower-level class —
9
+ * for example, to wrap a promise and expose `.state`, `.value`, `.reason`,
10
+ * and `.result` without the `Computed` callable interface.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const rp = ReactivePromise.from(fetch("/api/data"));
15
+ *
16
+ * effect(() => {
17
+ * if (rp.state === "fulfilled") console.log(rp.value);
18
+ * });
19
+ * ```
20
+ */
21
+ var ReactivePromise = class ReactivePromise extends Promise {
22
+ #state = signal("pending");
23
+ #value = signal(void 0);
24
+ #reason = signal(void 0);
25
+ #result = computed(() => {
26
+ const state = this.#state();
27
+ if (state === "fulfilled") return this.#value();
28
+ if (state === "rejected") return this.#reason();
29
+ });
30
+ get state() {
31
+ return this.#state();
32
+ }
33
+ get value() {
34
+ return this.#value();
35
+ }
36
+ get reason() {
37
+ return this.#reason();
38
+ }
39
+ get result() {
40
+ return this.#result();
41
+ }
42
+ constructor(executor) {
43
+ super((res, rej) => {
44
+ executor(async (value) => {
45
+ const resolved = await value;
46
+ batch(() => {
47
+ this.#state("fulfilled");
48
+ this.#value(resolved);
49
+ });
50
+ res(value);
51
+ }, async (_reason) => {
52
+ const reason = await _reason;
53
+ batch(() => {
54
+ this.#state("rejected");
55
+ this.#reason(reason);
56
+ });
57
+ rej(reason);
58
+ });
59
+ });
60
+ }
61
+ static from(p) {
62
+ return new ReactivePromise((resolve, reject) => {
63
+ p.then(resolve).catch(reject);
64
+ });
65
+ }
66
+ };
67
+ const PROMISE_KEYS = new Set([
68
+ "then",
69
+ "catch",
70
+ "finally",
71
+ "state",
72
+ "value",
73
+ "reason",
74
+ "result"
75
+ ]);
76
+ function resolvePromise(from) {
77
+ if (from instanceof ReactivePromise) return from;
78
+ if (from instanceof Promise) return ReactivePromise.from(from);
79
+ return new ReactivePromise(from);
80
+ }
81
+ function promise(from) {
82
+ const p = resolvePromise(from);
83
+ const $value = computed(() => p.result);
84
+ return new Proxy($value, {
85
+ apply() {
86
+ return $value();
87
+ },
88
+ get(target, prop, receiver) {
89
+ if (PROMISE_KEYS.has(prop)) {
90
+ const val = p[prop];
91
+ return typeof val === "function" ? val.bind(p) : val;
92
+ }
93
+ return Reflect.get(target, prop, receiver);
94
+ },
95
+ getPrototypeOf() {
96
+ return ReactivePromise.prototype;
97
+ }
98
+ });
99
+ }
100
+ //#endregion
101
+ export { ReactivePromise, promise };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,156 @@
1
+ import { u as effect } from "../signals-BHmWX6ox.mjs";
2
+ import { o as describe, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
+ import { ReactivePromise, promise } from "./promise.mjs";
4
+ //#region src/utilities/promise.test.ts
5
+ describe("ReactivePromise", () => {
6
+ it("starts in pending state", () => {
7
+ const rp = new ReactivePromise(() => {});
8
+ globalExpect(rp.state).toBe("pending");
9
+ globalExpect(rp.value).toBeUndefined();
10
+ globalExpect(rp.reason).toBeUndefined();
11
+ globalExpect(rp.result).toBeUndefined();
12
+ });
13
+ it("transitions to fulfilled", async () => {
14
+ const rp = new ReactivePromise((resolve) => resolve(42));
15
+ await rp;
16
+ globalExpect(rp.state).toBe("fulfilled");
17
+ globalExpect(rp.value).toBe(42);
18
+ globalExpect(rp.reason).toBeUndefined();
19
+ globalExpect(rp.result).toBe(42);
20
+ });
21
+ it("transitions to rejected", async () => {
22
+ const err = /* @__PURE__ */ new Error("fail");
23
+ const rp = new ReactivePromise((_, reject) => reject(err));
24
+ await rp.catch(() => {});
25
+ globalExpect(rp.state).toBe("rejected");
26
+ globalExpect(rp.reason).toBe(err);
27
+ globalExpect(rp.value).toBeUndefined();
28
+ globalExpect(rp.result).toBe(err);
29
+ });
30
+ it("unwraps a PromiseLike value", async () => {
31
+ const rp = new ReactivePromise((resolve) => resolve(Promise.resolve(99)));
32
+ await rp;
33
+ globalExpect(rp.value).toBe(99);
34
+ });
35
+ it("state and value update atomically — no inconsistent intermediate state", async () => {
36
+ const rp = new ReactivePromise((resolve) => resolve(7));
37
+ const snapshots = [];
38
+ const stop = effect(() => {
39
+ snapshots.push({
40
+ state: rp.state,
41
+ value: rp.value
42
+ });
43
+ });
44
+ await rp;
45
+ stop();
46
+ globalExpect(snapshots.find((s) => s.state === "fulfilled" && s.value === void 0)).toBeUndefined();
47
+ globalExpect(snapshots.at(-1)).toEqual({
48
+ state: "fulfilled",
49
+ value: 7
50
+ });
51
+ });
52
+ it("state and reason update atomically on rejection", async () => {
53
+ const err = /* @__PURE__ */ new Error("oops");
54
+ const rp = new ReactivePromise((_, reject) => reject(err));
55
+ const snapshots = [];
56
+ const stop = effect(() => {
57
+ snapshots.push({
58
+ state: rp.state,
59
+ reason: rp.reason
60
+ });
61
+ });
62
+ await rp.catch(() => {});
63
+ stop();
64
+ globalExpect(snapshots.find((s) => s.state === "rejected" && s.reason === void 0)).toBeUndefined();
65
+ globalExpect(snapshots.at(-1)).toEqual({
66
+ state: "rejected",
67
+ reason: err
68
+ });
69
+ });
70
+ it("ReactivePromise.from() wraps an existing Promise", async () => {
71
+ const rp = ReactivePromise.from(Promise.resolve("hello"));
72
+ await rp;
73
+ globalExpect(rp.state).toBe("fulfilled");
74
+ globalExpect(rp.value).toBe("hello");
75
+ });
76
+ });
77
+ describe("promise()", () => {
78
+ it("is instanceof ReactivePromise", () => {
79
+ globalExpect(promise(() => {}) instanceof ReactivePromise).toBe(true);
80
+ });
81
+ it("returns undefined while pending", () => {
82
+ const cp = promise(() => {});
83
+ globalExpect(cp()).toBeUndefined();
84
+ globalExpect(cp.state).toBe("pending");
85
+ });
86
+ it("returns the resolved value when fulfilled", async () => {
87
+ const cp = promise((resolve) => resolve(42));
88
+ await cp;
89
+ globalExpect(cp()).toBe(42);
90
+ });
91
+ it("returns the rejection reason when rejected", async () => {
92
+ const err = /* @__PURE__ */ new Error("fail");
93
+ const cp = promise((_, reject) => reject(err));
94
+ await cp.catch(() => {});
95
+ globalExpect(cp()).toBe(err);
96
+ });
97
+ it("wraps an existing Promise", async () => {
98
+ const cp = promise(Promise.resolve("hi"));
99
+ await cp;
100
+ globalExpect(cp()).toBe("hi");
101
+ });
102
+ it("passes through a ReactivePromise", async () => {
103
+ const cp = promise(new ReactivePromise((resolve) => resolve(5)));
104
+ await cp;
105
+ globalExpect(cp()).toBe(5);
106
+ });
107
+ it("is awaitable — resolves to the fulfilled value", async () => {
108
+ globalExpect(await promise((resolve) => resolve(100))).toBe(100);
109
+ });
110
+ it("is awaitable — rejects on failure", async () => {
111
+ const err = /* @__PURE__ */ new Error("bad");
112
+ const cp = promise((_, reject) => reject(err));
113
+ let caught;
114
+ await cp.catch((e) => {
115
+ caught = e;
116
+ });
117
+ globalExpect(caught).toBe(err);
118
+ });
119
+ it("delegates .then()", async () => {
120
+ globalExpect(await promise((resolve) => resolve(3)).then((v) => v * 2)).toBe(6);
121
+ });
122
+ it("delegates .catch()", async () => {
123
+ const err = /* @__PURE__ */ new Error("caught");
124
+ globalExpect(await promise((_, reject) => reject(err)).catch((e) => e)).toBe(err);
125
+ });
126
+ it("cp.catch handler runs on rejection", async () => {
127
+ const err = /* @__PURE__ */ new Error("caught");
128
+ const cp = promise((_, reject) => reject(err));
129
+ const caught = [];
130
+ await cp.catch((e) => caught.push(e));
131
+ globalExpect(caught).toEqual([err]);
132
+ });
133
+ it("is reactive — effect reruns on fulfillment", async () => {
134
+ const cp = promise((resolve) => resolve(1));
135
+ const values = [];
136
+ const stop = effect(() => {
137
+ values.push(cp.value);
138
+ });
139
+ await cp;
140
+ stop();
141
+ globalExpect(values).toEqual([void 0, 1]);
142
+ });
143
+ it("is reactive — effect reruns on rejection", async () => {
144
+ const err = /* @__PURE__ */ new Error("x");
145
+ const cp = promise((_, reject) => reject(err));
146
+ const results = [];
147
+ const stop = effect(() => {
148
+ results.push(cp());
149
+ });
150
+ await cp.catch(() => {});
151
+ stop();
152
+ globalExpect(results).toEqual([void 0, err]);
153
+ });
154
+ });
155
+ //#endregion
156
+ export {};
@@ -1,4 +1,4 @@
1
- import { f as onCleanup } from "../signals-DcgXhkU2.mjs";
1
+ import { g as onCleanup } from "../signals-BHmWX6ox.mjs";
2
2
  //#region src/utilities/resize-observer.ts
3
3
  /**
4
4
  * Raw `ResizeObserver` wrapper with automatic cleanup.
@@ -0,0 +1,10 @@
1
+ //#region src/utilities/retry.d.ts
2
+ type RetryDelay = number | ((attempt: number) => number);
3
+ /**
4
+ * Wraps `fn` to retry up to `attempts` times on failure.
5
+ * Delay (if provided) is inserted between failures only — not after the last.
6
+ * Each attempt runs in an effect scope so `onCleanup` inside `fn` fires before each retry.
7
+ */
8
+ declare function retry<T>(fn: () => Promise<T>, attempts: number, delay?: RetryDelay): () => Promise<T>;
9
+ //#endregion
10
+ export { retry };
@@ -0,0 +1,32 @@
1
+ import { u as effect, y as untracked } from "../signals-BHmWX6ox.mjs";
2
+ //#region src/utilities/retry.ts
3
+ /**
4
+ * Wraps `fn` to retry up to `attempts` times on failure.
5
+ * Delay (if provided) is inserted between failures only — not after the last.
6
+ * Each attempt runs in an effect scope so `onCleanup` inside `fn` fires before each retry.
7
+ */
8
+ function retry(fn, attempts, delay) {
9
+ return async () => {
10
+ let last;
11
+ for (let i = 0; i < attempts; i++) {
12
+ let stop = () => {};
13
+ try {
14
+ const result = await new Promise((res, rej) => {
15
+ stop = effect(() => untracked(() => fn().then(res, rej)));
16
+ });
17
+ stop();
18
+ return result;
19
+ } catch (err) {
20
+ stop();
21
+ last = err;
22
+ if (i < attempts - 1 && delay !== void 0) {
23
+ const ms = typeof delay === "function" ? delay(i) : delay;
24
+ await new Promise((r) => setTimeout(r, ms));
25
+ }
26
+ }
27
+ }
28
+ throw last;
29
+ };
30
+ }
31
+ //#endregion
32
+ export { retry };
@@ -0,0 +1 @@
1
+ export { };