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.
- package/dist/signals/lib/active-element.mjs +2 -10
- package/dist/signals/lib/audio.d.mts +5 -10
- package/dist/signals/lib/audio.mjs +18 -43
- package/dist/signals/lib/audio.test.mjs +18 -15
- package/dist/signals/lib/event-driven.d.mts +47 -0
- package/dist/signals/lib/event-driven.mjs +38 -0
- package/dist/signals/lib/fullscreen.mjs +6 -8
- package/dist/signals/lib/fullscreen.test.mjs +3 -11
- package/dist/signals/lib/hash.mjs +5 -11
- package/dist/signals/lib/hash.test.mjs +3 -4
- package/dist/signals/lib/is-document-visible.mjs +2 -8
- package/dist/signals/lib/media.mjs +3 -10
- package/dist/signals/lib/orientation.mjs +11 -12
- package/dist/signals/lib/video.d.mts +4 -8
- package/dist/signals/lib/video.mjs +16 -40
- package/dist/signals/lib/video.test.mjs +7 -7
- package/dist/signals/lib/window-size.mjs +11 -12
- package/package.json +1 -1
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
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:
|
|
8
|
-
volume:
|
|
7
|
+
muted: Signal<boolean>;
|
|
8
|
+
volume: Signal<number>;
|
|
9
9
|
duration: Computed<number>;
|
|
10
|
-
time:
|
|
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(
|
|
19
|
+
declare function createAudio(element: HTMLAudioElement): AudioResult;
|
|
25
20
|
//#endregion
|
|
26
21
|
export { createAudio };
|
|
@@ -1,37 +1,23 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
9
|
-
const el =
|
|
10
|
-
const playing =
|
|
11
|
-
const muted =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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("
|
|
10
|
+
it("accepts an HTMLAudioElement", () => {
|
|
11
|
+
const el = new Audio();
|
|
11
12
|
let a;
|
|
12
13
|
effectScope(() => {
|
|
13
|
-
a = createAudio(
|
|
14
|
+
a = createAudio(el);
|
|
14
15
|
});
|
|
15
|
-
globalExpect(a.element).
|
|
16
|
+
globalExpect(a.element).toBe(el);
|
|
16
17
|
});
|
|
17
18
|
it("starts as paused", () => {
|
|
18
19
|
let a;
|
|
19
20
|
effectScope(() => {
|
|
20
|
-
a = createAudio(
|
|
21
|
+
a = createAudio(new Audio());
|
|
21
22
|
});
|
|
22
23
|
globalExpect(a.playing()).toBe(false);
|
|
23
24
|
});
|
|
24
|
-
it("
|
|
25
|
+
it("muted(true) sets muted to true", () => {
|
|
25
26
|
let a;
|
|
26
27
|
effectScope(() => {
|
|
27
|
-
a = createAudio(
|
|
28
|
+
a = createAudio(new Audio());
|
|
28
29
|
});
|
|
29
|
-
a.
|
|
30
|
+
a.muted(true);
|
|
30
31
|
globalExpect(a.element.muted).toBe(true);
|
|
31
32
|
});
|
|
32
|
-
it("
|
|
33
|
+
it("volume() clamps to [0, 1]", () => {
|
|
33
34
|
let a;
|
|
34
35
|
effectScope(() => {
|
|
35
|
-
a = createAudio(
|
|
36
|
+
a = createAudio(new Audio());
|
|
36
37
|
});
|
|
37
|
-
a.
|
|
38
|
+
a.volume(2);
|
|
38
39
|
globalExpect(a.element.volume).toBe(1);
|
|
39
|
-
a.
|
|
40
|
+
a.volume(-1);
|
|
40
41
|
globalExpect(a.element.volume).toBe(0);
|
|
41
42
|
});
|
|
42
|
-
it("removes event listeners
|
|
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(
|
|
48
|
+
dispose = effectScope(() => {
|
|
49
|
+
a = createAudio(el);
|
|
47
50
|
});
|
|
48
51
|
vi.spyOn(a.element, "removeEventListener").mockImplementation(removeSpy);
|
|
49
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
15
|
+
return {
|
|
19
16
|
isFullscreen,
|
|
20
17
|
enter,
|
|
21
18
|
exit,
|
|
22
|
-
toggle
|
|
23
|
-
|
|
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("
|
|
38
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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("
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
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
|
-
|
|
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:
|
|
8
|
-
volume:
|
|
7
|
+
muted: Signal<boolean>;
|
|
8
|
+
volume: Signal<number>;
|
|
9
9
|
duration: Computed<number>;
|
|
10
|
-
time:
|
|
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 {
|
|
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 =
|
|
10
|
-
const muted =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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("
|
|
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.
|
|
35
|
+
v.time(30);
|
|
36
36
|
globalExpect(el.currentTime).toBe(30);
|
|
37
37
|
});
|
|
38
|
-
it("removes event listeners
|
|
38
|
+
it("removes event listeners when scope is disposed", () => {
|
|
39
39
|
const el = document.createElement("video");
|
|
40
40
|
document.body.appendChild(el);
|
|
41
|
-
let
|
|
42
|
-
effectScope(() => {
|
|
43
|
-
|
|
41
|
+
let dispose;
|
|
42
|
+
dispose = effectScope(() => {
|
|
43
|
+
createVideo(el);
|
|
44
44
|
});
|
|
45
45
|
const removeSpy = vi.spyOn(el, "removeEventListener");
|
|
46
|
-
|
|
46
|
+
dispose();
|
|
47
47
|
globalExpect(removeSpy).toHaveBeenCalled();
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
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
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|