personal web client for Bluesky
typescript
solidjs
bluesky
atcute
1import { createMemo, untrack } from 'solid-js';
2
3import { replaceEqualDeep } from '@mary/solid-query';
4
5const _on = <T, R>(accessor: () => T, callback: (value: T) => R): (() => R) => {
6 return () => {
7 const value = accessor();
8 return untrack(() => callback(value));
9 };
10};
11
12export const on = <T, R>(accessor: () => T, callback: (value: T) => R): (() => R) => {
13 return createMemo(_on(accessor, callback));
14};
15
16type ReconcilableProperties<T> = { [K in keyof T]: T[K] extends string | number ? K : never }[keyof T];
17export const reconcile = <T = any>(
18 prev: NoInfer<T>[] | undefined,
19 next: T[],
20 key: ReconcilableProperties<T> | ((item: T) => string | number),
21): T[] => {
22 if (prev === undefined) {
23 return next;
24 }
25
26 let equalItems = 0;
27
28 const map = new Map<string | number, T>();
29 const prevLen = prev.length;
30 const nextLen = next.length;
31
32 for (let idx = 0; idx < prevLen; idx++) {
33 const item = prev[idx];
34
35 // @ts-expect-error
36 map.set(typeof key === 'function' ? key(item) : item[key], item);
37 }
38
39 const array: T[] = Array.from({ length: next.length });
40 for (let idx = 0; idx < nextLen; idx++) {
41 const nextItem = next[idx];
42 // @ts-expect-error
43 const prevItem = map.get(typeof key === 'function' ? key(nextItem) : nextItem[key]);
44
45 if (prevItem !== undefined) {
46 const replaced = replaceEqualDeep(prevItem, nextItem);
47 if (replaced === prevItem) {
48 equalItems++;
49 }
50
51 array[idx] = replaced;
52 } else {
53 array[idx] = nextItem;
54 }
55 }
56
57 return equalItems === 0 ? next : array;
58};
59
60export const requestIdle = typeof requestIdleCallback === 'function' ? requestIdleCallback : setTimeout;
61
62export const isSetEqual = <T>(a: Set<T>, b: Set<T>): boolean => {
63 if (a.size !== b.size) {
64 return false;
65 }
66
67 if (a.size !== 0) {
68 for (const val of a) {
69 if (!b.has(val)) {
70 return false;
71 }
72 }
73 }
74
75 return true;
76};
77
78export const omit = <T extends Record<string, any>, K extends keyof T>(
79 obj: T,
80 keys: readonly K[],
81): Omit<T, K> => {
82 const result = { ...obj };
83
84 for (let i = 0; i < keys.length; i++) {
85 const key = keys[i];
86 delete result[key];
87 }
88
89 return result as Omit<T, K>;
90};
91
92export const throttleLeading = <T extends (...args: any[]) => void>(
93 fn: T,
94 wait: number,
95): ((...args: Parameters<T>) => void) => {
96 let lastCallTime: number | undefined;
97
98 return (...args: Parameters<T>) => {
99 const now = performance.now();
100
101 if (lastCallTime === undefined || now - lastCallTime >= wait) {
102 lastCallTime = now;
103 fn(...args);
104 }
105 };
106};
107
108export const throttleTrailing = <T extends (...args: any[]) => void>(
109 fn: T,
110 wait: number,
111): ((...args: Parameters<T>) => void) => {
112 let timeoutId: ReturnType<typeof setTimeout> | undefined;
113 let lastArgs: Parameters<T> | undefined;
114
115 return (...args: Parameters<T>) => {
116 lastArgs = args;
117
118 if (timeoutId === undefined) {
119 timeoutId = setTimeout(() => {
120 timeoutId = undefined;
121 fn(...lastArgs!);
122 }, wait);
123 }
124 };
125};
126
127export const throttle = <T extends (...args: any[]) => void>(
128 fn: T,
129 wait: number,
130): ((...args: Parameters<T>) => void) => {
131 let timeoutId: ReturnType<typeof setTimeout> | undefined;
132 let lastArgs: Parameters<T> | undefined;
133 let lastCallTime: number | undefined;
134
135 return (...args: Parameters<T>) => {
136 const now = performance.now();
137 const elapsed = lastCallTime !== undefined ? now - lastCallTime : wait;
138
139 if (elapsed >= wait) {
140 if (timeoutId !== undefined) {
141 clearTimeout(timeoutId);
142 timeoutId = undefined;
143 }
144
145 lastCallTime = now;
146 fn(...args);
147 } else {
148 lastArgs = args;
149
150 if (timeoutId === undefined) {
151 timeoutId = setTimeout(() => {
152 timeoutId = undefined;
153 lastCallTime = performance.now();
154 fn(...lastArgs!);
155 }, wait - elapsed);
156 }
157 }
158 };
159};