elements-kit 0.0.13 → 0.0.15

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.
package/README.md CHANGED
@@ -250,6 +250,44 @@ The same `cart` store drives custom elements, React trees, and plain scripts —
250
250
  Pre-built signal factories for common browser APIs:
251
251
 
252
252
  ```ts
253
+ import { signal, effect, untracked, onCleanup } from "elements-kit/signals";
254
+ import { createMediaQuery } from "elements-kit/utilities/media-query";
255
+ import { async } from "elements-kit/utilities/async";
256
+ import { retry } from "elements-kit/utilities/retry";
257
+ import { online } from "elements-kit/utilities/network";
258
+ import { windowFocused } from "elements-kit/utilities/window-focus";
259
+ import { storage } from "elements-kit/utilities/storage";
260
+
261
+ const id = signal(1);
262
+ const cache = storage("todos"); // persists across reloads, used as initial value
263
+
264
+ // Query — retries on failure, refetches when back online or tab regains focus
265
+ const fetchTodo = async(() => {
266
+ if (!online()) return untracked(cache); // pause while offline, return stale value
267
+ windowFocused(); // refetch on tab focus
268
+ return retry(() => {
269
+ const controller = new AbortController();
270
+ onCleanup(() => controller.abort()); // abort before each retry
271
+ return fetch(`/api/todos/${id()}`, { signal: controller.signal })
272
+ .then((r) => r.json())
273
+ .then(cache); // update cache on success
274
+ }, 3, (n) => n * 500)(); // 0 ms, 500 ms, 1000 ms backoff
275
+ }).start();
276
+
277
+ effect(() => console.log(fetchTodo.state, fetchTodo.value));
278
+
279
+ // Mutation — run once, no reactive tracking
280
+ const deleteTodo = async((todoId: number) =>
281
+ fetch(`/api/todos/${todoId}`, { method: "DELETE" }).then((r) => r.json()),
282
+ );
283
+
284
+ const result = await deleteTodo.run(42);
285
+ ```
286
+
287
+ `createMediaQuery` wraps `window.matchMedia` into a reactive signal — reads inside effects or computeds re-run automatically when the media query result changes.
288
+
289
+ ```tsx
290
+ import { effect } from "elements-kit/signals";
253
291
  import { createMediaQuery } from "elements-kit/utilities/media-query";
254
292
 
255
293
  const isDark = createMediaQuery("(prefers-color-scheme: dark)");
@@ -216,13 +216,8 @@ function setEvent(el, key, handler) {
216
216
  return () => el.removeEventListener(event, handler);
217
217
  }
218
218
  //#endregion
219
- //#region src/jsx-runtime/ref.ts
220
- const $ref = Symbol("ref");
221
- //#endregion
222
219
  //#region src/jsx-runtime/element.ts
223
- function createElement(type, rawProps = {}) {
224
- const ref = rawProps[$ref];
225
- const props = Object.fromEntries(Object.entries(rawProps));
220
+ function createElement(type, { ref, ...props } = {}) {
226
221
  if (typeof type === "function" && !type.prototype?.render) return createFunctionElement(type, props, ref);
227
222
  return createNodeElement(type, props, ref);
228
223
  }
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as disposeElement } from "./element-C_4VbkvQ.mjs";
1
+ import { n as disposeElement } from "./element-ChF24-2z.mjs";
2
2
  import { _ as signal, g as onCleanup, u as effect, v as trigger, y as untracked } from "./signals-BHmWX6ox.mjs";
3
3
  //#region src/jsx-runtime/for.ts
4
4
  /**
@@ -15,10 +15,12 @@ type Child = PrimitiveNodeType | AnyFn | Child[];
15
15
  type AnyFn = (...args: any[]) => any;
16
16
  //#endregion
17
17
  //#region src/jsx-runtime/element.d.ts
18
- declare function createElement(type: string | Element | DocumentFragment | ComponentClass | ComponentFn, rawProps?: Record<string | symbol, unknown>): Element | DocumentFragment | null;
19
- //#endregion
20
- //#region src/jsx-runtime/ref.d.ts
21
- declare const $ref: unique symbol;
18
+ declare function createElement(type: string | Element | DocumentFragment | ComponentClass | ComponentFn, {
19
+ ref,
20
+ ...props
21
+ }?: Record<string | symbol, unknown> & {
22
+ ref?: (el: Element) => void;
23
+ }): Element | DocumentFragment | null;
22
24
  //#endregion
23
25
  //#region src/jsx-runtime/fragment.d.ts
24
26
  /**
@@ -59,7 +61,7 @@ type SlotProps<K extends string> = { [P in K as `slot:${P}`]?: Child };
59
61
  type Attrs<K extends keyof JSX.IntrinsicElements> = JSX.IntrinsicElements[K];
60
62
  /** Extra props injected into every intrinsic element beyond dom-expressions defaults. */
61
63
  type OurProps = {
62
- [$ref]?: (el: Element) => void;
64
+ ref?: (el: Element) => void;
63
65
  [slot: `slot:${string}`]: Child;
64
66
  [cls: `class:${string}`]: FunctionMaybe<boolean>;
65
67
  [sty: `style:${string}`]: FunctionMaybe<string | null>;
@@ -1,4 +1,4 @@
1
- import { t as createElement } from "../element-C_4VbkvQ.mjs";
1
+ import { t as createElement } from "../element-ChF24-2z.mjs";
2
2
  import { g as onCleanup } from "../signals-BHmWX6ox.mjs";
3
3
  //#region src/jsx-runtime/fragment.ts
4
4
  /**
@@ -0,0 +1,37 @@
1
+ import { n as MaybeReactive } from "../index-DUshSQ_6.mjs";
2
+ import { ComputedPromise } from "./promise.mjs";
3
+
4
+ //#region src/utilities/async.d.ts
5
+ type Fn<TInput, TOutput> = (input: TInput) => Promise<TOutput>;
6
+ declare class Async<TInput = undefined, TOutput = unknown> {
7
+ #private;
8
+ get fn(): Fn<TInput, TOutput>;
9
+ set fn(fn: MaybeReactive<Fn<TInput, TOutput>>);
10
+ get raw(): ComputedPromise<TOutput | undefined>;
11
+ get pending(): boolean;
12
+ get state(): "pending" | "fulfilled" | "rejected";
13
+ get value(): TOutput | undefined;
14
+ get reason(): unknown;
15
+ get result(): unknown;
16
+ then<TResult1 = TOutput, TResult2 = never>(onfulfilled?: ((value: TOutput | undefined) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
17
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<TOutput | undefined | TResult>;
18
+ finally(onfinally?: (() => void) | null): Promise<TOutput | undefined>;
19
+ constructor(fn: MaybeReactive<Fn<TInput, TOutput>>);
20
+ /**
21
+ * Runs the async function once with the given input, stopping any currently active
22
+ * and register cleanup effects.
23
+ */
24
+ run(...args: TInput extends undefined ? [] : [input: TInput]): this;
25
+ /**
26
+ * Stops the current async operation and run cleanup effects.
27
+ */
28
+ stop(): this;
29
+ [Symbol.dispose](): void;
30
+ /**
31
+ * Starts a new reactive async operation, stopping any currently active one.
32
+ */
33
+ start(...args: TInput extends undefined ? [] : [input: TInput]): this;
34
+ }
35
+ declare function async<TInput = any, TOutput = undefined>(fn: MaybeReactive<(input: TInput) => Promise<TOutput>>): Async<TInput, TOutput> & ((...args: any[]) => TOutput | undefined);
36
+ //#endregion
37
+ export { Async, Fn, async };
@@ -0,0 +1,119 @@
1
+ import { _ as signal, r as resolve, u as effect, y as untracked } from "../signals-BHmWX6ox.mjs";
2
+ import { promise } from "./promise.mjs";
3
+ //#region src/utilities/async.ts
4
+ var Async = class {
5
+ #fn = signal(async () => Promise.resolve(void 0));
6
+ #cleanup = () => {};
7
+ get fn() {
8
+ return this.#fn();
9
+ }
10
+ set fn(fn) {
11
+ this.#fn(resolve(fn));
12
+ }
13
+ #current = signal(promise(() => {}));
14
+ get raw() {
15
+ return this.#current();
16
+ }
17
+ get pending() {
18
+ return this.raw.state === "pending";
19
+ }
20
+ get state() {
21
+ return this.raw.state;
22
+ }
23
+ get value() {
24
+ return this.raw.value;
25
+ }
26
+ get reason() {
27
+ return this.raw.reason;
28
+ }
29
+ get result() {
30
+ return this.raw.result;
31
+ }
32
+ then(onfulfilled, onrejected) {
33
+ return this.raw.then(onfulfilled, onrejected);
34
+ }
35
+ catch(onrejected) {
36
+ return this.raw.catch(onrejected);
37
+ }
38
+ finally(onfinally) {
39
+ return this.raw.finally(onfinally);
40
+ }
41
+ constructor(fn) {
42
+ this.#fn(resolve(fn));
43
+ }
44
+ #execute(...args) {
45
+ const input = args[0];
46
+ const p = promise(this.fn(input));
47
+ this.#current(p);
48
+ return p;
49
+ }
50
+ /**
51
+ * Runs the async function once with the given input, stopping any currently active
52
+ * and register cleanup effects.
53
+ */
54
+ run(...args) {
55
+ this.stop();
56
+ this.#cleanup = effect(() => {
57
+ untracked(() => this.#execute(...args));
58
+ });
59
+ return this;
60
+ }
61
+ /**
62
+ * Stops the current async operation and run cleanup effects.
63
+ */
64
+ stop() {
65
+ this.#cleanup();
66
+ this.#cleanup = () => {};
67
+ return this;
68
+ }
69
+ [Symbol.dispose]() {
70
+ this.stop();
71
+ }
72
+ /**
73
+ * Starts a new reactive async operation, stopping any currently active one.
74
+ */
75
+ start(...args) {
76
+ this.stop();
77
+ this.#cleanup = effect(() => {
78
+ this.#execute(...args);
79
+ });
80
+ return this;
81
+ }
82
+ };
83
+ const ASYNC_KEYS = new Set([
84
+ "then",
85
+ "catch",
86
+ "finally",
87
+ "state",
88
+ "value",
89
+ "reason",
90
+ "result",
91
+ "pending",
92
+ "start",
93
+ "stop",
94
+ "run",
95
+ "fn",
96
+ "raw",
97
+ Symbol.dispose
98
+ ]);
99
+ function async(fn) {
100
+ const inst = new Async(fn);
101
+ const signal = () => inst.result;
102
+ return new Proxy(signal, {
103
+ apply(_target, _thisArg, args) {
104
+ return inst.result;
105
+ },
106
+ get(_target, prop, receiver) {
107
+ if (ASYNC_KEYS.has(prop)) {
108
+ const val = inst[prop];
109
+ return typeof val === "function" ? val.bind(inst) : val;
110
+ }
111
+ return Reflect.get(signal, prop, receiver);
112
+ },
113
+ getPrototypeOf() {
114
+ return Async.prototype;
115
+ }
116
+ });
117
+ }
118
+ //#endregion
119
+ export { Async, async };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,273 @@
1
+ import { _ as signal, g as onCleanup, u as effect } from "../signals-BHmWX6ox.mjs";
2
+ import { n as vi, o as describe, s as it, t as globalExpect } from "../test.BmQO5GaM-ANkhHvbr.mjs";
3
+ import { Async, async } from "./async.mjs";
4
+ import { createInterval } from "./interval.mjs";
5
+ //#region src/utilities/async.test.ts
6
+ function deferred() {
7
+ let resolve;
8
+ let reject;
9
+ return {
10
+ promise: new Promise((res, rej) => {
11
+ resolve = res;
12
+ reject = rej;
13
+ }),
14
+ resolve,
15
+ reject
16
+ };
17
+ }
18
+ describe("Async", () => {
19
+ it("is reactive as a computed signal (effect)", async () => {
20
+ const op = async((x) => Promise.resolve(x * 2));
21
+ const values = [];
22
+ const stop = effect(() => {
23
+ values.push(op());
24
+ });
25
+ op.run(2);
26
+ await op;
27
+ op.run(3);
28
+ await op;
29
+ stop();
30
+ globalExpect(values).toContain(void 0);
31
+ globalExpect(values).toContain(4);
32
+ globalExpect(values).toContain(6);
33
+ globalExpect(values.at(-1)).toBe(6);
34
+ });
35
+ it("is callable as a signal and returns the current result", async () => {
36
+ const op = async(() => Promise.resolve(123));
37
+ globalExpect(op()).toBeUndefined();
38
+ op.start();
39
+ await op;
40
+ globalExpect(op()).toBe(123);
41
+ const err = /* @__PURE__ */ new Error("fail");
42
+ const op2 = async(() => Promise.reject(err));
43
+ op2.start();
44
+ await op2.catch(() => {});
45
+ globalExpect(op2()).toBe(err);
46
+ });
47
+ it("starts in pending state with no value or reason", () => {
48
+ const op = async(() => Promise.resolve(1));
49
+ globalExpect(op.state).toBe("pending");
50
+ globalExpect(op.pending).toBe(true);
51
+ globalExpect(op.value).toBeUndefined();
52
+ globalExpect(op.reason).toBeUndefined();
53
+ globalExpect(op.result).toBeUndefined();
54
+ globalExpect(op.pending).toBe(op.state === "pending");
55
+ });
56
+ it("transitions to fulfilled after start()", async () => {
57
+ const op = async(() => Promise.resolve(42));
58
+ op.start();
59
+ await op;
60
+ globalExpect(op.state).toBe("fulfilled");
61
+ globalExpect(op.pending).toBe(false);
62
+ globalExpect(op.value).toBe(42);
63
+ globalExpect(op.reason).toBeUndefined();
64
+ globalExpect(op.result).toBe(42);
65
+ globalExpect(op.result).toBe(42);
66
+ });
67
+ it("transitions to rejected after start()", async () => {
68
+ const err = /* @__PURE__ */ new Error("fail");
69
+ const op = async(() => Promise.reject(err));
70
+ op.start();
71
+ await op.catch(() => {});
72
+ globalExpect(op.state).toBe("rejected");
73
+ globalExpect(op.pending).toBe(false);
74
+ globalExpect(op.reason).toBe(err);
75
+ globalExpect(op.value).toBeUndefined();
76
+ globalExpect(op.result).toBe(err);
77
+ globalExpect(op.reason).toBe(err);
78
+ });
79
+ it("await op.run(args) resolves to the fulfilled value", async () => {
80
+ const op = async((x) => Promise.resolve(x + 100));
81
+ globalExpect(await op.run(23)).toBe(123);
82
+ globalExpect(op.value).toBe(123);
83
+ globalExpect(op.state).toBe("fulfilled");
84
+ });
85
+ it("run() resolves to the fulfilled value", async () => {
86
+ globalExpect(await async((x) => Promise.resolve(x * 2)).run(5)).toBe(10);
87
+ });
88
+ it("await op resolves to the fulfilled value", async () => {
89
+ const op = async(() => Promise.resolve(7));
90
+ op.start();
91
+ globalExpect(await op).toBe(7);
92
+ });
93
+ it("await op.catch() resolves on rejection", async () => {
94
+ const err = /* @__PURE__ */ new Error("fail");
95
+ const op = async(() => Promise.reject(err));
96
+ op.start();
97
+ globalExpect(await op.catch((e) => e)).toBe(err);
98
+ });
99
+ it("delegates .then()", async () => {
100
+ const op = async(() => Promise.resolve(3));
101
+ op.start();
102
+ globalExpect(await op.then((v) => (v ?? 0) * 2)).toBe(6);
103
+ });
104
+ it("instanceof Async", () => {
105
+ globalExpect(async(() => Promise.resolve()) instanceof Async).toBe(true);
106
+ });
107
+ it("is reactive — effect reruns when state transitions to fulfilled", async () => {
108
+ const op = async(() => Promise.resolve(10));
109
+ const snapshots = [];
110
+ const stop = effect(() => {
111
+ snapshots.push(op.state);
112
+ });
113
+ op.start();
114
+ await op;
115
+ stop();
116
+ globalExpect(snapshots).toContain("pending");
117
+ globalExpect(snapshots.at(-1)).toBe("fulfilled");
118
+ });
119
+ it("is reactive — effect sees value after fulfillment", async () => {
120
+ const op = async(() => Promise.resolve(99));
121
+ const values = [];
122
+ const stop = effect(() => {
123
+ values.push(op.value);
124
+ });
125
+ op.start();
126
+ await op;
127
+ stop();
128
+ globalExpect(values).toEqual([
129
+ void 0,
130
+ void 0,
131
+ 99
132
+ ]);
133
+ });
134
+ it("is reactive — effect reruns on rejection", async () => {
135
+ const err = /* @__PURE__ */ new Error("x");
136
+ const op = async(() => Promise.reject(err));
137
+ const reasons = [];
138
+ const stop = effect(() => {
139
+ reasons.push(op.reason);
140
+ });
141
+ op.start();
142
+ await op.catch(() => {});
143
+ stop();
144
+ globalExpect(reasons).toEqual([
145
+ void 0,
146
+ void 0,
147
+ err
148
+ ]);
149
+ });
150
+ it("start() re-runs when a signal read inside fn changes", async () => {
151
+ const id = signal(1);
152
+ const calls = [];
153
+ const op = async(() => {
154
+ const current = id();
155
+ calls.push(current);
156
+ return Promise.resolve(current);
157
+ });
158
+ op.start();
159
+ await op;
160
+ id(2);
161
+ await op;
162
+ op.stop();
163
+ globalExpect(calls).toEqual([1, 2]);
164
+ });
165
+ it("run() is not reactive — signal change does not re-run", async () => {
166
+ const id = signal(1);
167
+ const calls = [];
168
+ const op = async(() => {
169
+ const current = id();
170
+ calls.push(current);
171
+ return Promise.resolve(current);
172
+ });
173
+ op.run();
174
+ await op;
175
+ id(2);
176
+ await Promise.resolve();
177
+ op.stop();
178
+ globalExpect(calls).toEqual([1]);
179
+ });
180
+ it("start() resets to pending on re-run", async () => {
181
+ const d = deferred();
182
+ const id = signal(1);
183
+ const op = async(() => {
184
+ id();
185
+ return d.promise;
186
+ });
187
+ op.start();
188
+ globalExpect(op.pending).toBe(true);
189
+ id(2);
190
+ globalExpect(op.pending).toBe(true);
191
+ d.resolve(5);
192
+ op.stop();
193
+ });
194
+ it("stop() prevents further reactive reruns", async () => {
195
+ const id = signal(1);
196
+ const calls = [];
197
+ const op = async(() => {
198
+ calls.push(id());
199
+ return Promise.resolve(id());
200
+ });
201
+ op.start();
202
+ await op;
203
+ op.stop();
204
+ id(2);
205
+ await Promise.resolve();
206
+ globalExpect(calls).toEqual([1]);
207
+ });
208
+ it("onCleanup inside run() fires when stop() is called", () => {
209
+ const cleaned = [];
210
+ const op = async(() => {
211
+ onCleanup(() => cleaned.push(1));
212
+ return Promise.resolve();
213
+ });
214
+ op.run();
215
+ globalExpect(cleaned).toEqual([]);
216
+ op.stop();
217
+ globalExpect(cleaned).toEqual([1]);
218
+ });
219
+ it("onCleanup inside start() fires on each re-run", async () => {
220
+ const id = signal(1);
221
+ const cleaned = [];
222
+ const op = async(() => {
223
+ const current = id();
224
+ onCleanup(() => cleaned.push(current));
225
+ return Promise.resolve();
226
+ });
227
+ op.start();
228
+ await op;
229
+ id(2);
230
+ await op;
231
+ op.stop();
232
+ globalExpect(cleaned[0]).toBe(1);
233
+ });
234
+ it("re-runs when createInterval ticks", async () => {
235
+ vi.useFakeTimers();
236
+ const timer = createInterval(1e3);
237
+ const calls = [];
238
+ const op = async(() => {
239
+ timer.timestamp();
240
+ calls.push(calls.length);
241
+ return Promise.resolve(calls.length);
242
+ });
243
+ op.start();
244
+ await op;
245
+ globalExpect(calls.length).toBe(1);
246
+ vi.advanceTimersByTime(1e3);
247
+ await op;
248
+ globalExpect(calls.length).toBe(2);
249
+ op.stop();
250
+ timer.stop();
251
+ vi.useRealTimers();
252
+ });
253
+ it("state and value update atomically — no inconsistent intermediate state", async () => {
254
+ const op = async(() => Promise.resolve(7));
255
+ const snapshots = [];
256
+ const stop = effect(() => {
257
+ snapshots.push({
258
+ state: op.state,
259
+ value: op.value
260
+ });
261
+ });
262
+ op.start();
263
+ await op;
264
+ stop();
265
+ globalExpect(snapshots.find((s) => s.state === "fulfilled" && s.value === void 0)).toBeUndefined();
266
+ globalExpect(snapshots.at(-1)).toEqual({
267
+ state: "fulfilled",
268
+ value: 7
269
+ });
270
+ });
271
+ });
272
+ //#endregion
273
+ export {};
@@ -2,17 +2,18 @@ import { t as Computed } from "../index-DUshSQ_6.mjs";
2
2
 
3
3
  //#region src/utilities/interval.d.ts
4
4
  type IntervalResult = {
5
- isRunning: Computed<boolean>;
5
+ timestamp: Computed<number>;
6
+ pending: Computed<boolean>;
6
7
  start(): void;
7
8
  stop(): void;
8
9
  reset(): void;
9
10
  } & Disposable;
11
+ type Fn = () => void;
12
+ type Delay = number | (() => number);
10
13
  /**
11
14
  * Pausable `setInterval` wrapper. Starts running immediately on creation.
12
- *
13
- * @param callback - Called on each tick.
14
- * @param delay - Interval delay in ms (or a reactive getter).
15
15
  */
16
- declare function createInterval(callback: () => void, delay: number | (() => number)): IntervalResult;
16
+ declare function createInterval(delay: Delay): IntervalResult;
17
+ declare function createInterval(callback: Fn, delay: Delay): IntervalResult;
17
18
  //#endregion
18
19
  export { createInterval };
@@ -1,24 +1,23 @@
1
1
  import { _ as signal, g as onCleanup } from "../signals-BHmWX6ox.mjs";
2
2
  //#region src/utilities/interval.ts
3
- /**
4
- * Pausable `setInterval` wrapper. Starts running immediately on creation.
5
- *
6
- * @param callback - Called on each tick.
7
- * @param delay - Interval delay in ms (or a reactive getter).
8
- */
9
- function createInterval(callback, delay) {
10
- const isRunning = signal(true);
3
+ function createInterval(arg1, arg2) {
4
+ const [callback, delay] = resolveArgs(arg1, arg2);
5
+ const pending = signal(true);
11
6
  let id;
7
+ const timestamp = signal(Date.now());
12
8
  const getDelay = typeof delay === "function" ? delay : () => delay;
13
9
  const start = () => {
14
10
  if (id !== void 0) return;
15
- isRunning(true);
16
- id = setInterval(() => callback(), getDelay());
11
+ pending(true);
12
+ id = setInterval(() => {
13
+ callback?.();
14
+ timestamp(Date.now());
15
+ }, getDelay());
17
16
  };
18
17
  const stop = () => {
19
18
  clearInterval(id);
20
19
  id = void 0;
21
- isRunning(false);
20
+ pending(false);
22
21
  };
23
22
  const reset = () => {
24
23
  stop();
@@ -28,12 +27,17 @@ function createInterval(callback, delay) {
28
27
  const cleanup = () => stop();
29
28
  onCleanup(cleanup);
30
29
  return {
31
- isRunning,
30
+ timestamp,
31
+ pending,
32
32
  start,
33
33
  stop,
34
34
  reset,
35
35
  [Symbol.dispose]: cleanup
36
36
  };
37
37
  }
38
+ function resolveArgs(arg1, arg2) {
39
+ if (arg2 === void 0) return [void 0, arg1];
40
+ return [arg1, arg2];
41
+ }
38
42
  //#endregion
39
43
  export { createInterval };
@@ -1,5 +1,6 @@
1
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
+ import { async } from "./async.mjs";
3
4
  import { createInterval } from "./interval.mjs";
4
5
  //#region src/utilities/interval.test.ts
5
6
  afterEach(() => {
@@ -26,7 +27,7 @@ describe("createInterval", () => {
26
27
  iv.stop();
27
28
  vi.advanceTimersByTime(500);
28
29
  globalExpect(cb).toHaveBeenCalledTimes(0);
29
- globalExpect(iv.isRunning()).toBe(false);
30
+ globalExpect(iv.pending()).toBe(false);
30
31
  });
31
32
  it("start() resumes after stop", () => {
32
33
  vi.useFakeTimers();
@@ -63,6 +64,50 @@ describe("createInterval", () => {
63
64
  vi.advanceTimersByTime(500);
64
65
  globalExpect(cb).toHaveBeenCalledTimes(0);
65
66
  });
67
+ it("no-callback form updates timestamp on each tick", () => {
68
+ vi.useFakeTimers();
69
+ let iv;
70
+ effectScope(() => {
71
+ iv = createInterval(100);
72
+ });
73
+ const before = iv.timestamp();
74
+ vi.advanceTimersByTime(100);
75
+ globalExpect(iv.timestamp()).toBeGreaterThan(before);
76
+ });
77
+ it("dynamic delay form is called each tick", () => {
78
+ vi.useFakeTimers();
79
+ const cb = vi.fn();
80
+ const getDelay = vi.fn().mockReturnValue(100);
81
+ effectScope(() => {
82
+ createInterval(cb, getDelay);
83
+ });
84
+ vi.advanceTimersByTime(300);
85
+ globalExpect(cb).toHaveBeenCalledTimes(3);
86
+ });
87
+ it("stops when scope is disposed", () => {
88
+ vi.useFakeTimers();
89
+ const cb = vi.fn();
90
+ effectScope(() => {
91
+ createInterval(cb, 100);
92
+ })();
93
+ vi.advanceTimersByTime(500);
94
+ globalExpect(cb).toHaveBeenCalledTimes(0);
95
+ });
96
+ it("composes with async() — reruns on each tick", async () => {
97
+ vi.useFakeTimers();
98
+ const asyncOp = async;
99
+ let runCount = 0;
100
+ const timer = createInterval(100);
101
+ const op = asyncOp(() => {
102
+ timer.timestamp();
103
+ runCount++;
104
+ return Promise.resolve(runCount);
105
+ }).start();
106
+ await vi.advanceTimersByTimeAsync(350);
107
+ globalExpect(runCount).toBeGreaterThan(1);
108
+ timer[Symbol.dispose]();
109
+ op.stop();
110
+ });
66
111
  });
67
112
  //#endregion
68
113
  export {};
@@ -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 };