Skip to content

Commit ca61749

Browse files
committed
Add set of nanostores utils: for writing effect in better way than nanostores allows out of the box, for creating atom which works like intersection observer and other tiny utils
1 parent 3ffcf49 commit ca61749

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { atom, onMount, type ReadableAtom } from "nanostores";
2+
3+
import { effect } from "./effect";
4+
import { type ToAtom, toAtom } from "./toAtom";
5+
6+
interface IntersectionObserverAtomOptions<E extends HTMLElement, Mapped> {
7+
element: ToAtom<E | null>;
8+
initialValue: Mapped;
9+
mapper: (entry: IntersectionObserverEntry) => Mapped;
10+
observerOptions?: ReadableAtom<IntersectionObserverInit> | IntersectionObserverInit;
11+
}
12+
13+
export function createIntersectionObserverAtom<E extends HTMLElement, Mapped>({
14+
element,
15+
initialValue,
16+
mapper,
17+
observerOptions,
18+
}: IntersectionObserverAtomOptions<E, Mapped>): {
19+
$atom: ReadableAtom<Mapped>;
20+
intersectionObserver: IntersectionObserver | null;
21+
} {
22+
const $result = atom(initialValue);
23+
const $element = toAtom(element);
24+
const $observerOptions = toAtom(observerOptions);
25+
let intersectionObserver: IntersectionObserver | null = null;
26+
27+
onMount($result, () => {
28+
return effect([$element, $observerOptions], (element, observerOptions) => {
29+
if (!element) {
30+
return;
31+
}
32+
33+
intersectionObserver = new IntersectionObserver((entries) => {
34+
const entry = entries[0];
35+
36+
if (entry) {
37+
$result.set(mapper(entry));
38+
}
39+
}, observerOptions);
40+
intersectionObserver.observe(element);
41+
42+
return () => {
43+
intersectionObserver?.disconnect();
44+
};
45+
});
46+
});
47+
48+
return { $atom: $result, intersectionObserver };
49+
}

src/lib/nanostores/effect.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* eslint-disable @typescript-eslint/no-invalid-void-type */
2+
import type { AnyStore, Store, StoreValue } from "nanostores";
3+
4+
export type StoreValues<Stores extends AnyStore[]> = {
5+
[Index in keyof Stores]: StoreValue<Stores[Index]>;
6+
};
7+
8+
export function effect<S extends Store>(
9+
store: S,
10+
callback: (value: StoreValue<S>) => VoidFunction | void,
11+
): VoidFunction;
12+
export function effect<Stores extends Store[]>(
13+
stores: [...Stores],
14+
callback: (...values: StoreValues<Stores>) => VoidFunction | void,
15+
): VoidFunction;
16+
export function effect<Stores extends Store[]>(
17+
stores: [...Stores],
18+
callback: (...values: StoreValues<Stores>) => VoidFunction | void,
19+
): VoidFunction {
20+
const storesArr = (Array.isArray(stores) ? stores : [stores]) as [...Stores];
21+
let storesUnsubscribe: VoidFunction[] = [];
22+
let callbackUnsubscribe: VoidFunction | void = undefined;
23+
24+
function runCallback() {
25+
callbackUnsubscribe?.();
26+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
27+
const values = storesArr.map((store) => store.get()) as StoreValues<Stores>;
28+
callbackUnsubscribe = callback(...values);
29+
}
30+
31+
function unsubscribe() {
32+
for (const unsubscribe of storesUnsubscribe) {
33+
unsubscribe();
34+
}
35+
callbackUnsubscribe?.();
36+
}
37+
38+
storesUnsubscribe = storesArr.map((store) => store.listen(runCallback));
39+
runCallback();
40+
41+
return unsubscribe;
42+
}

src/lib/nanostores/isAtom.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Atom } from "nanostores";
2+
3+
export function isAtom(value: unknown): value is Atom<unknown> {
4+
return (
5+
typeof value === "object" &&
6+
value !== null &&
7+
"get" in value &&
8+
"listen" in value &&
9+
"subscribe" in value
10+
);
11+
}

src/lib/nanostores/toAtom.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { type Atom, atom } from "nanostores";
2+
3+
import { isAtom } from "./isAtom";
4+
5+
export type ToAtom<V> = Atom<V> | V;
6+
7+
export function toAtom<V>(value: ToAtom<V>): Atom<V> {
8+
return isAtom(value) ? value : atom(value);
9+
}

0 commit comments

Comments
 (0)