elements-kit 0.0.9 → 0.0.10

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.
@@ -1,19 +1,11 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/active-element.ts
4
3
  /**
5
4
  * Returns a `Computed` that tracks `document.activeElement`.
6
5
  * Updates on every `focusin` / `focusout` event bubbling through the document.
7
6
  */
8
7
  function createActiveElement() {
9
- const active = signal(typeof document !== "undefined" ? document.activeElement : null);
10
- const update = () => active(document.activeElement);
11
- const r1 = createEventListener(document, "focusin", update);
12
- const r2 = createEventListener(document, "focusout", update);
13
- const cleanup = () => {
14
- r1();
15
- r2();
16
- };
8
+ const [active, cleanup] = sync(fromEvent(document, ["focusin", "focusout"]), () => typeof document !== "undefined" ? document.activeElement : null);
17
9
  return Object.assign(active, { [Symbol.dispose]: cleanup });
18
10
  }
19
11
  //#endregion
@@ -1,26 +1,21 @@
1
- import { t as Computed } from "../../index-BtqiEEfc.mjs";
1
+ import { n as Signal, t as Computed } from "../../index-BtqiEEfc.mjs";
2
2
 
3
3
  //#region src/signals/lib/audio.d.ts
4
4
  type AudioResult = {
5
5
  element: HTMLAudioElement;
6
6
  playing: Computed<boolean>;
7
- muted: Computed<boolean>;
8
- volume: Computed<number>;
7
+ muted: Signal<boolean>;
8
+ volume: Signal<number>;
9
9
  duration: Computed<number>;
10
- time: Computed<number>;
10
+ time: Signal<number>;
11
11
  ended: Computed<boolean>;
12
12
  play(): void;
13
13
  pause(): void;
14
14
  toggle(): void;
15
- setVolume(v: number): void;
16
- setTime(t: number): void;
17
- mute(): void;
18
- unmute(): void;
19
15
  } & Disposable;
20
16
  /**
21
17
  * Wraps an `HTMLAudioElement` with reactive state and playback controls.
22
- * Pass a `src` string to create a new element, or pass an existing element.
23
18
  */
24
- declare function createAudio(src: string | HTMLAudioElement): AudioResult;
19
+ declare function createAudio(element: HTMLAudioElement): AudioResult;
25
20
  //#endregion
26
21
  export { createAudio };
@@ -1,37 +1,23 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/audio.ts
4
3
  /**
5
4
  * Wraps an `HTMLAudioElement` with reactive state and playback controls.
6
- * Pass a `src` string to create a new element, or pass an existing element.
7
5
  */
8
- function createAudio(src) {
9
- const el = typeof src === "string" ? new Audio(src) : src;
10
- const playing = signal(!el.paused);
11
- const muted = signal(el.muted);
12
- const volume = signal(el.volume);
13
- const duration = signal(el.duration || 0);
14
- const time = signal(el.currentTime);
15
- const ended = signal(el.ended);
16
- const onPlay = () => playing(true);
17
- const onPause = () => playing(false);
18
- const onVolumeChange = () => {
19
- muted(el.muted);
20
- volume(el.volume);
21
- };
22
- const onDurationChange = () => duration(el.duration);
23
- const onTimeUpdate = () => time(el.currentTime);
24
- const onEnded = () => ended(true);
25
- const cleanups = [
26
- createEventListener(el, "play", onPlay),
27
- createEventListener(el, "pause", onPause),
28
- createEventListener(el, "volumechange", onVolumeChange),
29
- createEventListener(el, "durationchange", onDurationChange),
30
- createEventListener(el, "timeupdate", onTimeUpdate),
31
- createEventListener(el, "ended", onEnded)
32
- ];
33
- const cleanup = () => cleanups.forEach((fn) => fn());
34
- return Object.assign({
6
+ function createAudio(element) {
7
+ const el = element;
8
+ const [playing] = sync(fromEvent(el, ["play", "pause"]), () => !el.paused);
9
+ const [muted] = sync(fromEvent(el, "volumechange"), () => el.muted, (v) => {
10
+ el.muted = v;
11
+ });
12
+ const [volume] = sync(fromEvent(el, "volumechange"), () => el.volume, (v) => {
13
+ el.volume = Math.min(1, Math.max(0, v));
14
+ });
15
+ const [duration] = sync(fromEvent(el, "durationchange"), () => el.duration || 0);
16
+ const [time] = sync(fromEvent(el, "timeupdate"), () => el.currentTime, (v) => {
17
+ el.currentTime = v;
18
+ });
19
+ const [ended] = sync(fromEvent(el, "ended"), () => el.ended);
20
+ return {
35
21
  element: el,
36
22
  playing,
37
23
  muted,
@@ -42,19 +28,8 @@ function createAudio(src) {
42
28
  play: () => el.play(),
43
29
  pause: () => el.pause(),
44
30
  toggle: () => el.paused ? el.play() : el.pause(),
45
- setVolume: (v) => {
46
- el.volume = Math.min(1, Math.max(0, v));
47
- },
48
- setTime: (t) => {
49
- el.currentTime = t;
50
- },
51
- mute: () => {
52
- el.muted = true;
53
- },
54
- unmute: () => {
55
- el.muted = false;
56
- }
57
- }, { [Symbol.dispose]: cleanup });
31
+ [Symbol.dispose]: () => {}
32
+ };
58
33
  }
59
34
  //#endregion
60
35
  export { createAudio };
@@ -7,46 +7,49 @@ afterEach(() => {
7
7
  vi.restoreAllMocks();
8
8
  });
9
9
  describe("createAudio", () => {
10
- it("creates an Audio element from a src string", () => {
10
+ it("accepts an HTMLAudioElement", () => {
11
+ const el = new Audio();
11
12
  let a;
12
13
  effectScope(() => {
13
- a = createAudio("test.mp3");
14
+ a = createAudio(el);
14
15
  });
15
- globalExpect(a.element).toBeInstanceOf(HTMLAudioElement);
16
+ globalExpect(a.element).toBe(el);
16
17
  });
17
18
  it("starts as paused", () => {
18
19
  let a;
19
20
  effectScope(() => {
20
- a = createAudio("test.mp3");
21
+ a = createAudio(new Audio());
21
22
  });
22
23
  globalExpect(a.playing()).toBe(false);
23
24
  });
24
- it("mute() sets muted to true", () => {
25
+ it("muted(true) sets muted to true", () => {
25
26
  let a;
26
27
  effectScope(() => {
27
- a = createAudio("test.mp3");
28
+ a = createAudio(new Audio());
28
29
  });
29
- a.mute();
30
+ a.muted(true);
30
31
  globalExpect(a.element.muted).toBe(true);
31
32
  });
32
- it("setVolume clamps to [0, 1]", () => {
33
+ it("volume() clamps to [0, 1]", () => {
33
34
  let a;
34
35
  effectScope(() => {
35
- a = createAudio("test.mp3");
36
+ a = createAudio(new Audio());
36
37
  });
37
- a.setVolume(2);
38
+ a.volume(2);
38
39
  globalExpect(a.element.volume).toBe(1);
39
- a.setVolume(-1);
40
+ a.volume(-1);
40
41
  globalExpect(a.element.volume).toBe(0);
41
42
  });
42
- it("removes event listeners on Symbol.dispose", () => {
43
+ it("removes event listeners when scope is disposed", () => {
43
44
  const removeSpy = vi.fn();
45
+ const el = new Audio();
46
+ let dispose;
44
47
  let a;
45
- effectScope(() => {
46
- a = createAudio("test.mp3");
48
+ dispose = effectScope(() => {
49
+ a = createAudio(el);
47
50
  });
48
51
  vi.spyOn(a.element, "removeEventListener").mockImplementation(removeSpy);
49
- a[Symbol.dispose]();
52
+ dispose();
50
53
  globalExpect(removeSpy).toHaveBeenCalled();
51
54
  });
52
55
  });
@@ -0,0 +1,47 @@
1
+ import { n as Signal, t as Computed } from "../../index-BtqiEEfc.mjs";
2
+
3
+ //#region src/signals/lib/event-driven.d.ts
4
+ /**
5
+ * A subscribe function: registers a `notify` callback and returns an
6
+ * unsubscribe cleanup. Same contract as `useSyncExternalStore`.
7
+ */
8
+ type Subscribe = (notify: () => void) => () => void;
9
+ /**
10
+ * Returns a `Subscribe` for one or more DOM events on a target.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const [playing, stop] = sync(fromEvent(el, ["play", "pause"]), () => !el.paused);
15
+ * ```
16
+ */
17
+ declare function fromEvent(target: EventTarget, events: string | string[]): Subscribe;
18
+ /**
19
+ * Keeps a reactive value in sync with an external source by re-reading
20
+ * `getter` whenever `subscribe` notifies of a change.
21
+ *
22
+ * Returns a `[value, cleanup]` tuple.
23
+ *
24
+ * Without `setter` → value is `Computed<T>` (read-only).
25
+ * With `setter` → value is `Signal<T>` (writable).
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const [playing, stop] = sync(fromEvent(el, ["play", "pause"]), () => !el.paused);
30
+ *
31
+ * const [volume, stopVolume] = sync(
32
+ * fromEvent(el, "volumechange"),
33
+ * () => el.volume,
34
+ * (v) => { el.volume = v; },
35
+ * );
36
+ *
37
+ * // Any external store
38
+ * const [data, unsub] = sync(
39
+ * (notify) => { store.on("change", notify); return () => store.off("change", notify); },
40
+ * () => store.getSnapshot(),
41
+ * );
42
+ * ```
43
+ */
44
+ declare function sync<T>(subscribe: Subscribe, getter: () => T): [Computed<T>, () => void];
45
+ declare function sync<T>(subscribe: Subscribe, getter: () => T, setter: (value: T) => void): [Signal<T>, () => void];
46
+ //#endregion
47
+ export { Subscribe, fromEvent, sync };
@@ -0,0 +1,38 @@
1
+ import { d as onCleanup, f as signal, i as computed, p as trigger } from "../../signals-CLAPw8kk.mjs";
2
+ //#region src/signals/lib/event-driven.ts
3
+ /**
4
+ * Returns a `Subscribe` for one or more DOM events on a target.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const [playing, stop] = sync(fromEvent(el, ["play", "pause"]), () => !el.paused);
9
+ * ```
10
+ */
11
+ function fromEvent(target, events) {
12
+ return (notify) => {
13
+ const evts = Array.isArray(events) ? events : [events];
14
+ evts.forEach((ev) => target.addEventListener(ev, notify));
15
+ return () => evts.forEach((ev) => target.removeEventListener(ev, notify));
16
+ };
17
+ }
18
+ function sync(subscribe, getter, setter) {
19
+ const tick = signal(void 0);
20
+ const value = computed(() => {
21
+ tick();
22
+ return getter();
23
+ });
24
+ value();
25
+ const cleanup = subscribe(() => trigger(tick));
26
+ onCleanup(cleanup);
27
+ if (setter) {
28
+ const proxy = (v) => {
29
+ if (v === void 0) return value();
30
+ setter(v);
31
+ return v;
32
+ };
33
+ return [proxy, cleanup];
34
+ }
35
+ return [value, cleanup];
36
+ }
37
+ //#endregion
38
+ export { fromEvent, sync };
@@ -1,5 +1,4 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/fullscreen.ts
4
3
  /**
5
4
  * Wraps the Fullscreen API. `target` defaults to `document.documentElement`.
@@ -9,18 +8,17 @@ function createFullscreen(target) {
9
8
  if (!target) return document.documentElement;
10
9
  return typeof target === "function" ? target() ?? document.documentElement : target;
11
10
  };
12
- const isFullscreen = signal(!!document.fullscreenElement);
13
- const onChange = () => isFullscreen(!!document.fullscreenElement);
14
- const cleanup = createEventListener(document, "fullscreenchange", onChange);
11
+ const [isFullscreen, cleanup] = sync(fromEvent(document, "fullscreenchange"), () => !!document.fullscreenElement);
15
12
  const enter = () => getTarget().requestFullscreen();
16
13
  const exit = () => document.exitFullscreen();
17
14
  const toggle = () => isFullscreen() ? exit() : enter();
18
- return Object.assign({
15
+ return {
19
16
  isFullscreen,
20
17
  enter,
21
18
  exit,
22
- toggle
23
- }, { [Symbol.dispose]: cleanup });
19
+ toggle,
20
+ [Symbol.dispose]: cleanup
21
+ };
24
22
  }
25
23
  //#endregion
26
24
  export { createFullscreen };
@@ -34,22 +34,14 @@ describe("createFullscreen", () => {
34
34
  document.dispatchEvent(new Event("fullscreenchange"));
35
35
  globalExpect(f.isFullscreen()).toBe(true);
36
36
  });
37
- it("stops reacting after Symbol.dispose", () => {
38
- Object.defineProperty(document, "fullscreenElement", {
39
- configurable: true,
40
- get: () => null
41
- });
37
+ it("removes event listener on Symbol.dispose", () => {
38
+ const removeSpy = vi.spyOn(document, "removeEventListener");
42
39
  let f;
43
40
  effectScope(() => {
44
41
  f = createFullscreen();
45
42
  });
46
43
  f[Symbol.dispose]();
47
- Object.defineProperty(document, "fullscreenElement", {
48
- configurable: true,
49
- get: () => document.documentElement
50
- });
51
- document.dispatchEvent(new Event("fullscreenchange"));
52
- globalExpect(f.isFullscreen()).toBe(false);
44
+ globalExpect(removeSpy).toHaveBeenCalledWith("fullscreenchange", globalExpect.any(Function));
53
45
  });
54
46
  });
55
47
  //#endregion
@@ -1,20 +1,14 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/hash.ts
4
3
  /**
5
4
  * Returns a writable `Signal<string>` that stays in sync with `location.hash`
6
5
  * (including the leading `#`). Writing the signal updates `location.hash`.
7
6
  */
8
7
  function createHash() {
9
- const hash = signal(typeof location !== "undefined" ? location.hash : "");
10
- const onHashChange = () => hash(location.hash);
11
- const cleanup = createEventListener(window, "hashchange", onHashChange);
12
- const proxy = (value) => {
13
- if (value === void 0) return hash();
14
- location.hash = value;
15
- hash(value);
16
- };
17
- return Object.assign(proxy, { [Symbol.dispose]: cleanup });
8
+ const [hash, cleanup] = sync(fromEvent(window, "hashchange"), () => typeof location !== "undefined" ? location.hash : "", (v) => {
9
+ location.hash = v;
10
+ });
11
+ return Object.assign(hash, { [Symbol.dispose]: cleanup });
18
12
  }
19
13
  //#endregion
20
14
  export { createHash };
@@ -32,15 +32,14 @@ describe("createHash", () => {
32
32
  h("#new");
33
33
  globalExpect(location.hash).toBe("#new");
34
34
  });
35
- it("stops updating after Symbol.dispose", () => {
35
+ it("removes event listener on Symbol.dispose", () => {
36
+ const removeSpy = vi.spyOn(window, "removeEventListener");
36
37
  let h;
37
38
  effectScope(() => {
38
39
  h = createHash();
39
40
  });
40
41
  h[Symbol.dispose]();
41
- location.hash = "#other";
42
- window.dispatchEvent(new HashChangeEvent("hashchange"));
43
- globalExpect(h()).not.toBe("#other");
42
+ globalExpect(removeSpy).toHaveBeenCalledWith("hashchange", globalExpect.any(Function));
44
43
  });
45
44
  });
46
45
  //#endregion
@@ -1,4 +1,4 @@
1
- import { d as onCleanup, f as signal } from "../../signals-CLAPw8kk.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
2
2
  //#region src/signals/lib/is-document-visible.ts
3
3
  /**
4
4
  * Returns a `Computed<boolean>` that tracks the Page Visibility API.
@@ -6,13 +6,7 @@ import { d as onCleanup, f as signal } from "../../signals-CLAPw8kk.mjs";
6
6
  * background tab, etc.).
7
7
  */
8
8
  function createIsDocumentVisible() {
9
- const visible = signal(typeof document !== "undefined" ? document.visibilityState === "visible" : true);
10
- const handler = () => {
11
- visible(document.visibilityState === "visible");
12
- };
13
- document.addEventListener("visibilitychange", handler);
14
- const cleanup = () => document.removeEventListener("visibilitychange", handler);
15
- onCleanup(cleanup);
9
+ const [visible, cleanup] = sync(fromEvent(document, "visibilitychange"), () => typeof document !== "undefined" ? document.visibilityState === "visible" : true);
16
10
  return Object.assign(visible, { [Symbol.dispose]: cleanup });
17
11
  }
18
12
  //#endregion
@@ -1,4 +1,5 @@
1
- import { d as onCleanup, f as signal } from "../../signals-CLAPw8kk.mjs";
1
+ import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
+ import { fromEvent, sync } from "./event-driven.mjs";
2
3
  //#region src/signals/lib/media.ts
3
4
  const isBrowser = typeof window !== "undefined";
4
5
  /**
@@ -11,15 +12,7 @@ const isBrowser = typeof window !== "undefined";
11
12
  function createMediaSignal(query, defaultState) {
12
13
  if (!isBrowser) return signal(defaultState ?? false);
13
14
  const mql = window.matchMedia(query);
14
- const state = signal(mql.matches);
15
- const handler = () => {
16
- state(mql.matches);
17
- };
18
- mql.addEventListener("change", handler);
19
- const cleanup = () => {
20
- mql.removeEventListener("change", handler);
21
- };
22
- onCleanup(cleanup);
15
+ const [state, cleanup] = sync(fromEvent(mql, "change"), () => mql.matches);
23
16
  return Object.assign(state, { [Symbol.dispose]: cleanup });
24
17
  }
25
18
  //#endregion
@@ -1,21 +1,20 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/orientation.ts
4
3
  /**
5
4
  * Returns reactive signals for the screen orientation.
6
5
  */
7
6
  function createOrientation() {
8
- const angle = signal(screen.orientation?.angle ?? 0);
9
- const type = signal(screen.orientation?.type ?? "portrait-primary");
10
- const onChange = () => {
11
- angle(screen.orientation.angle);
12
- type(screen.orientation.type);
13
- };
14
- const cleanup = createEventListener(screen.orientation, "change", onChange);
15
- return Object.assign({
7
+ const subscribe = fromEvent(screen.orientation, "change");
8
+ const [angle, stopAngle] = sync(subscribe, () => screen.orientation?.angle ?? 0);
9
+ const [type, stopType] = sync(subscribe, () => screen.orientation?.type ?? "portrait-primary");
10
+ return {
16
11
  angle,
17
- type
18
- }, { [Symbol.dispose]: cleanup });
12
+ type,
13
+ [Symbol.dispose]: () => {
14
+ stopAngle();
15
+ stopType();
16
+ }
17
+ };
19
18
  }
20
19
  //#endregion
21
20
  export { createOrientation };
@@ -1,21 +1,17 @@
1
- import { t as Computed } from "../../index-BtqiEEfc.mjs";
1
+ import { n as Signal, t as Computed } from "../../index-BtqiEEfc.mjs";
2
2
 
3
3
  //#region src/signals/lib/video.d.ts
4
4
  type VideoResult = {
5
5
  element: HTMLVideoElement;
6
6
  playing: Computed<boolean>;
7
- muted: Computed<boolean>;
8
- volume: Computed<number>;
7
+ muted: Signal<boolean>;
8
+ volume: Signal<number>;
9
9
  duration: Computed<number>;
10
- time: Computed<number>;
10
+ time: Signal<number>;
11
11
  ended: Computed<boolean>;
12
12
  play(): void;
13
13
  pause(): void;
14
14
  toggle(): void;
15
- setVolume(v: number): void;
16
- setTime(t: number): void;
17
- mute(): void;
18
- unmute(): void;
19
15
  } & Disposable;
20
16
  /**
21
17
  * Wraps an `HTMLVideoElement` with reactive state and playback controls.
@@ -1,36 +1,23 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/video.ts
4
3
  /**
5
4
  * Wraps an `HTMLVideoElement` with reactive state and playback controls.
6
5
  */
7
6
  function createVideo(element) {
8
7
  const el = element;
9
- const playing = signal(!el.paused);
10
- const muted = signal(el.muted);
11
- const volume = signal(el.volume);
12
- const duration = signal(el.duration || 0);
13
- const time = signal(el.currentTime);
14
- const ended = signal(el.ended);
15
- const onPlay = () => playing(true);
16
- const onPause = () => playing(false);
17
- const onVolumeChange = () => {
18
- muted(el.muted);
19
- volume(el.volume);
20
- };
21
- const onDurationChange = () => duration(el.duration);
22
- const onTimeUpdate = () => time(el.currentTime);
23
- const onEnded = () => ended(true);
24
- const cleanups = [
25
- createEventListener(el, "play", onPlay),
26
- createEventListener(el, "pause", onPause),
27
- createEventListener(el, "volumechange", onVolumeChange),
28
- createEventListener(el, "durationchange", onDurationChange),
29
- createEventListener(el, "timeupdate", onTimeUpdate),
30
- createEventListener(el, "ended", onEnded)
31
- ];
32
- const cleanup = () => cleanups.forEach((fn) => fn());
33
- return Object.assign({
8
+ const [playing] = sync(fromEvent(el, ["play", "pause"]), () => !el.paused);
9
+ const [muted] = sync(fromEvent(el, "volumechange"), () => el.muted, (v) => {
10
+ el.muted = v;
11
+ });
12
+ const [volume] = sync(fromEvent(el, "volumechange"), () => el.volume, (v) => {
13
+ el.volume = Math.min(1, Math.max(0, v));
14
+ });
15
+ const [duration] = sync(fromEvent(el, "durationchange"), () => el.duration || 0);
16
+ const [time] = sync(fromEvent(el, "timeupdate"), () => el.currentTime, (v) => {
17
+ el.currentTime = v;
18
+ });
19
+ const [ended] = sync(fromEvent(el, "ended"), () => el.ended);
20
+ return {
34
21
  element: el,
35
22
  playing,
36
23
  muted,
@@ -41,19 +28,8 @@ function createVideo(element) {
41
28
  play: () => el.play(),
42
29
  pause: () => el.pause(),
43
30
  toggle: () => el.paused ? el.play() : el.pause(),
44
- setVolume: (v) => {
45
- el.volume = Math.min(1, Math.max(0, v));
46
- },
47
- setTime: (t) => {
48
- el.currentTime = t;
49
- },
50
- mute: () => {
51
- el.muted = true;
52
- },
53
- unmute: () => {
54
- el.muted = false;
55
- }
56
- }, { [Symbol.dispose]: cleanup });
31
+ [Symbol.dispose]: () => {}
32
+ };
57
33
  }
58
34
  //#endregion
59
35
  export { createVideo };
@@ -25,25 +25,25 @@ describe("createVideo", () => {
25
25
  });
26
26
  globalExpect(v.playing()).toBe(false);
27
27
  });
28
- it("setTime updates currentTime", () => {
28
+ it("time(v) updates currentTime", () => {
29
29
  const el = document.createElement("video");
30
30
  document.body.appendChild(el);
31
31
  let v;
32
32
  effectScope(() => {
33
33
  v = createVideo(el);
34
34
  });
35
- v.setTime(30);
35
+ v.time(30);
36
36
  globalExpect(el.currentTime).toBe(30);
37
37
  });
38
- it("removes event listeners on Symbol.dispose", () => {
38
+ it("removes event listeners when scope is disposed", () => {
39
39
  const el = document.createElement("video");
40
40
  document.body.appendChild(el);
41
- let v;
42
- effectScope(() => {
43
- v = createVideo(el);
41
+ let dispose;
42
+ dispose = effectScope(() => {
43
+ createVideo(el);
44
44
  });
45
45
  const removeSpy = vi.spyOn(el, "removeEventListener");
46
- v[Symbol.dispose]();
46
+ dispose();
47
47
  globalExpect(removeSpy).toHaveBeenCalled();
48
48
  });
49
49
  });
@@ -1,22 +1,21 @@
1
- import { f as signal } from "../../signals-CLAPw8kk.mjs";
2
- import { createEventListener } from "./event-listener.mjs";
1
+ import { fromEvent, sync } from "./event-driven.mjs";
3
2
  //#region src/signals/lib/window-size.ts
4
3
  /**
5
4
  * Returns reactive `width` and `height` signals tracking the browser window
6
5
  * inner dimensions.
7
6
  */
8
7
  function createWindowSize() {
9
- const width = signal(typeof window !== "undefined" ? window.innerWidth : 0);
10
- const height = signal(typeof window !== "undefined" ? window.innerHeight : 0);
11
- const onResize = () => {
12
- width(window.innerWidth);
13
- height(window.innerHeight);
14
- };
15
- const cleanup = createEventListener(window, "resize", onResize);
16
- return Object.assign({
8
+ const subscribe = fromEvent(window, "resize");
9
+ const [width, stopWidth] = sync(subscribe, () => typeof window !== "undefined" ? window.innerWidth : 0);
10
+ const [height, stopHeight] = sync(subscribe, () => typeof window !== "undefined" ? window.innerHeight : 0);
11
+ return {
17
12
  width,
18
- height
19
- }, { [Symbol.dispose]: cleanup });
13
+ height,
14
+ [Symbol.dispose]: () => {
15
+ stopWidth();
16
+ stopHeight();
17
+ }
18
+ };
20
19
  }
21
20
  //#endregion
22
21
  export { createWindowSize };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elements-kit",
3
3
  "type": "module",
4
- "version": "0.0.9",
4
+ "version": "0.0.10",
5
5
  "description": "A lightweight reactive UI library that transforms native HTMLElements into reactive components with signals. Ideal for framework-agnostic applications and web components.",
6
6
  "keywords": [
7
7
  "webcomponents",