Find the cost of adding an npm package to your app's bundle size
teardown.kelinci.dev
1import { createEffect, createMemo, createSignal, onCleanup, type Accessor, type Signal } from 'solid-js';
2
3/**
4 * creates a signal that derives its initial value from an accessor but can be overwritten.
5 * when the source accessor changes, the signal resets to the new derived value.
6 *
7 * @param accessor the source accessor to derive from
8 * @returns a signal tuple [getter, setter]
9 */
10export function createDerivedSignal<T>(accessor: Accessor<T>): Signal<T> {
11 const computable = createMemo(() => createSignal(accessor()));
12
13 // @ts-expect-error: setter type mismatch is fine
14 // oxlint-disable-next-line typescript/no-unsafe-type-assertion
15 return [() => computable()[0](), (next) => computable()[1](next)] as Signal<T>;
16}
17
18/**
19 * creates an abortable signal pair for cancelling async operations.
20 * calling create() aborts any previous signal and returns a fresh one.
21 * automatically aborts on component cleanup.
22 *
23 * @returns tuple of [create, abort] functions
24 */
25export const makeAbortable = (): [create: () => AbortSignal, abort: () => void] => {
26 let controller: AbortController | undefined;
27
28 const abort = (): void => {
29 controller?.abort();
30 };
31 const create = (): AbortSignal => {
32 abort();
33 controller = new AbortController();
34 return controller.signal;
35 };
36
37 onCleanup(abort);
38
39 return [create, abort];
40};
41
42/**
43 * creates a throttled accessor that emits the source value at most once per interval.
44 * uses trailing edge only: waits for the interval to elapse, then emits the latest value.
45 *
46 * @param source the source accessor to throttle
47 * @param ms throttle interval in milliseconds
48 * @returns throttled accessor
49 */
50export function createTrailingThrottle<T>(source: Accessor<T>, ms: number): Accessor<T> {
51 let lastSource = source();
52 let timeout: number | undefined;
53
54 const [throttled, setThrottled] = createSignal(lastSource);
55
56 createEffect((initialMount: boolean) => {
57 lastSource = source();
58
59 if (initialMount || timeout !== undefined) {
60 return false;
61 }
62
63 timeout = setTimeout(() => {
64 timeout = undefined;
65 return setThrottled(() => lastSource);
66 }, ms);
67
68 return false;
69 }, true);
70
71 return throttled;
72}