UNPKG

21.2 kBJavaScriptView Raw
1import { atom, SECRET_INTERNAL_getScopeContext, useAtom, useSetAtom } from 'jotai';
2export { useAtomValue, useSetAtom as useUpdateAtom } from 'jotai';
3import { useContext, useCallback, useMemo } from 'react';
4
5const RESET = Symbol();
6
7function atomWithReset(initialValue) {
8 const anAtom = atom(initialValue, (get, set, update) => {
9 if (update === RESET) {
10 set(anAtom, initialValue);
11 } else {
12 set(anAtom, typeof update === "function" ? update(get(anAtom)) : update);
13 }
14 });
15 return anAtom;
16}
17
18const WRITE_ATOM = "w";
19const RESTORE_ATOMS = "h";
20
21function useResetAtom(anAtom, scope) {
22 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
23 const store = useContext(ScopeContext).s;
24 const setAtom = useCallback(() => store[WRITE_ATOM](anAtom, RESET), [store, anAtom]);
25 return setAtom;
26}
27
28function useReducerAtom(anAtom, reducer, scope) {
29 const [state, setState] = useAtom(anAtom, scope);
30 const dispatch = useCallback((action) => {
31 setState((prev) => reducer(prev, action));
32 }, [setState, reducer]);
33 return [state, dispatch];
34}
35
36function atomWithReducer(initialValue, reducer) {
37 const anAtom = atom(initialValue, (get, set, action) => set(anAtom, reducer(get(anAtom), action)));
38 return anAtom;
39}
40
41function atomFamily(initializeAtom, areEqual) {
42 let shouldRemove = null;
43 const atoms = /* @__PURE__ */ new Map();
44 const createAtom = (param) => {
45 let item;
46 if (areEqual === void 0) {
47 item = atoms.get(param);
48 } else {
49 for (const [key, value] of atoms) {
50 if (areEqual(key, param)) {
51 item = value;
52 break;
53 }
54 }
55 }
56 if (item !== void 0) {
57 if (shouldRemove == null ? void 0 : shouldRemove(item[1], param)) {
58 atoms.delete(param);
59 } else {
60 return item[0];
61 }
62 }
63 const newAtom = initializeAtom(param);
64 atoms.set(param, [newAtom, Date.now()]);
65 return newAtom;
66 };
67 createAtom.remove = (param) => {
68 if (areEqual === void 0) {
69 atoms.delete(param);
70 } else {
71 for (const [key] of atoms) {
72 if (areEqual(key, param)) {
73 atoms.delete(key);
74 break;
75 }
76 }
77 }
78 };
79 createAtom.setShouldRemove = (fn) => {
80 shouldRemove = fn;
81 if (!shouldRemove)
82 return;
83 for (const [key, value] of atoms) {
84 if (shouldRemove(value[1], key)) {
85 atoms.delete(key);
86 }
87 }
88 };
89 return createAtom;
90}
91
92const getWeakCacheItem = (cache, deps) => {
93 do {
94 const [dep, ...rest] = deps;
95 const entry = cache.get(dep);
96 if (!entry) {
97 return;
98 }
99 if (!rest.length) {
100 return entry[1];
101 }
102 cache = entry[0];
103 deps = rest;
104 } while (deps.length);
105};
106const setWeakCacheItem = (cache, deps, item) => {
107 do {
108 const [dep, ...rest] = deps;
109 let entry = cache.get(dep);
110 if (!entry) {
111 entry = [ new WeakMap()];
112 cache.set(dep, entry);
113 }
114 if (!rest.length) {
115 entry[1] = item;
116 return;
117 }
118 cache = entry[0];
119 deps = rest;
120 } while (deps.length);
121};
122const createMemoizeAtom = () => {
123 const cache = /* @__PURE__ */ new WeakMap();
124 const memoizeAtom = (createAtom, deps) => {
125 const cachedAtom = getWeakCacheItem(cache, deps);
126 if (cachedAtom) {
127 return cachedAtom;
128 }
129 const createdAtom = createAtom();
130 setWeakCacheItem(cache, deps, createdAtom);
131 return createdAtom;
132 };
133 return memoizeAtom;
134};
135
136const memoizeAtom$4 = createMemoizeAtom();
137function selectAtom(anAtom, selector, equalityFn = Object.is) {
138 return memoizeAtom$4(() => {
139 const refAtom = atom(() => ({}));
140 const derivedAtom = atom((get) => {
141 const slice = selector(get(anAtom));
142 const ref = get(refAtom);
143 if ("prev" in ref && equalityFn(ref.prev, slice)) {
144 return ref.prev;
145 }
146 ref.prev = slice;
147 return slice;
148 });
149 return derivedAtom;
150 }, [anAtom, selector, equalityFn]);
151}
152
153function useAtomCallback(callback, scope) {
154 const anAtom = useMemo(() => atom(null, (get, set, [arg, resolve, reject]) => {
155 try {
156 resolve(callback(get, set, arg));
157 } catch (e) {
158 reject(e);
159 }
160 }), [callback]);
161 const invoke = useSetAtom(anAtom, scope);
162 return useCallback((arg) => {
163 let isSync = true;
164 let settled = {};
165 const promise = new Promise((resolve, reject) => {
166 invoke([
167 arg,
168 (v) => {
169 if (isSync) {
170 settled = { v };
171 } else {
172 resolve(v);
173 }
174 },
175 (e) => {
176 if (isSync) {
177 settled = { e };
178 } else {
179 reject(e);
180 }
181 }
182 ]);
183 });
184 isSync = false;
185 if ("e" in settled) {
186 throw settled.e;
187 }
188 if ("v" in settled) {
189 return settled.v;
190 }
191 return promise;
192 }, [invoke]);
193}
194
195const memoizeAtom$3 = createMemoizeAtom();
196const deepFreeze = (obj) => {
197 if (typeof obj !== "object" || obj === null)
198 return;
199 Object.freeze(obj);
200 const propNames = Object.getOwnPropertyNames(obj);
201 for (const name of propNames) {
202 const value = obj[name];
203 deepFreeze(value);
204 }
205 return obj;
206};
207function freezeAtom(anAtom) {
208 return memoizeAtom$3(() => {
209 const frozenAtom = atom((get) => deepFreeze(get(anAtom)), (_get, set, arg) => set(anAtom, arg));
210 return frozenAtom;
211 }, [anAtom]);
212}
213function freezeAtomCreator(createAtom) {
214 return (...params) => {
215 const anAtom = createAtom(...params);
216 const origRead = anAtom.read;
217 anAtom.read = (get) => deepFreeze(origRead(get));
218 return anAtom;
219 };
220}
221
222const memoizeAtom$2 = createMemoizeAtom();
223const isWritable = (atom2) => !!atom2.write;
224const isFunction = (x) => typeof x === "function";
225function splitAtom(arrAtom, keyExtractor) {
226 return memoizeAtom$2(() => {
227 const mappingCache = /* @__PURE__ */ new WeakMap();
228 const getMapping = (arr, prev) => {
229 let mapping = mappingCache.get(arr);
230 if (mapping) {
231 return mapping;
232 }
233 const prevMapping = prev && mappingCache.get(prev);
234 const atomList = [];
235 const keyList = [];
236 arr.forEach((item, index) => {
237 const key = keyExtractor ? keyExtractor(item) : index;
238 keyList[index] = key;
239 const cachedAtom = prevMapping && prevMapping.atomList[prevMapping.keyList.indexOf(key)];
240 if (cachedAtom) {
241 atomList[index] = cachedAtom;
242 return;
243 }
244 const read2 = (get) => {
245 const ref = get(refAtom);
246 const currArr = get(arrAtom);
247 const mapping2 = getMapping(currArr, ref.prev);
248 const index2 = mapping2.keyList.indexOf(key);
249 if (index2 < 0 || index2 >= currArr.length) {
250 const prevItem = arr[getMapping(arr).keyList.indexOf(key)];
251 if (prevItem) {
252 return prevItem;
253 }
254 throw new Error("splitAtom: index out of bounds for read");
255 }
256 return currArr[index2];
257 };
258 const write2 = (get, set, update) => {
259 const ref = get(refAtom);
260 const arr2 = get(arrAtom);
261 const mapping2 = getMapping(arr2, ref.prev);
262 const index2 = mapping2.keyList.indexOf(key);
263 if (index2 < 0 || index2 >= arr2.length) {
264 throw new Error("splitAtom: index out of bounds for write");
265 }
266 const nextItem = isFunction(update) ? update(arr2[index2]) : update;
267 set(arrAtom, [
268 ...arr2.slice(0, index2),
269 nextItem,
270 ...arr2.slice(index2 + 1)
271 ]);
272 };
273 atomList[index] = isWritable(arrAtom) ? atom(read2, write2) : atom(read2);
274 });
275 if (prevMapping && prevMapping.keyList.length === keyList.length && prevMapping.keyList.every((x, i) => x === keyList[i])) {
276 mapping = prevMapping;
277 } else {
278 mapping = { atomList, keyList };
279 }
280 mappingCache.set(arr, mapping);
281 return mapping;
282 };
283 const refAtom = atom(() => ({}));
284 const read = (get) => {
285 const ref = get(refAtom);
286 const arr = get(arrAtom);
287 const mapping = getMapping(arr, ref.prev);
288 ref.prev = arr;
289 return mapping.atomList;
290 };
291 const write = (get, set, action) => {
292 if ("read" in action) {
293 console.warn("atomToRemove is deprecated. use action with type");
294 action = { type: "remove", atom: action };
295 }
296 switch (action.type) {
297 case "remove": {
298 const index = get(splittedAtom).indexOf(action.atom);
299 if (index >= 0) {
300 const arr = get(arrAtom);
301 set(arrAtom, [
302 ...arr.slice(0, index),
303 ...arr.slice(index + 1)
304 ]);
305 }
306 break;
307 }
308 case "insert": {
309 const index = action.before ? get(splittedAtom).indexOf(action.before) : get(splittedAtom).length;
310 if (index >= 0) {
311 const arr = get(arrAtom);
312 set(arrAtom, [
313 ...arr.slice(0, index),
314 action.value,
315 ...arr.slice(index)
316 ]);
317 }
318 break;
319 }
320 case "move": {
321 const index1 = get(splittedAtom).indexOf(action.atom);
322 const index2 = action.before ? get(splittedAtom).indexOf(action.before) : get(splittedAtom).length;
323 if (index1 >= 0 && index2 >= 0) {
324 const arr = get(arrAtom);
325 if (index1 < index2) {
326 set(arrAtom, [
327 ...arr.slice(0, index1),
328 ...arr.slice(index1 + 1, index2),
329 arr[index1],
330 ...arr.slice(index2)
331 ]);
332 } else {
333 set(arrAtom, [
334 ...arr.slice(0, index2),
335 arr[index1],
336 ...arr.slice(index2, index1),
337 ...arr.slice(index1 + 1)
338 ]);
339 }
340 }
341 break;
342 }
343 }
344 };
345 const splittedAtom = isWritable(arrAtom) ? atom(read, write) : atom(read);
346 return splittedAtom;
347 }, keyExtractor ? [arrAtom, keyExtractor] : [arrAtom]);
348}
349
350function atomWithDefault(getDefault) {
351 const EMPTY = Symbol();
352 const overwrittenAtom = atom(EMPTY);
353 const anAtom = atom((get) => {
354 const overwritten = get(overwrittenAtom);
355 if (overwritten !== EMPTY) {
356 return overwritten;
357 }
358 return getDefault(get);
359 }, (get, set, update) => {
360 if (update === RESET) {
361 return set(overwrittenAtom, EMPTY);
362 }
363 return set(overwrittenAtom, typeof update === "function" ? update(get(anAtom)) : update);
364 });
365 return anAtom;
366}
367
368const memoizeAtom$1 = createMemoizeAtom();
369const emptyArrayAtom = atom(() => []);
370function waitForAll(atoms) {
371 const createAtom = () => {
372 const unwrappedAtoms = unwrapAtoms(atoms);
373 const derivedAtom = atom((get) => {
374 const promises = [];
375 const values = unwrappedAtoms.map((anAtom, index) => {
376 try {
377 return get(anAtom);
378 } catch (e) {
379 if (e instanceof Promise) {
380 promises[index] = e;
381 } else {
382 throw e;
383 }
384 }
385 });
386 if (promises.length) {
387 throw Promise.all(promises);
388 }
389 return wrapResults(atoms, values);
390 });
391 return derivedAtom;
392 };
393 if (Array.isArray(atoms)) {
394 if (atoms.length) {
395 return memoizeAtom$1(createAtom, atoms);
396 }
397 return emptyArrayAtom;
398 }
399 return createAtom();
400}
401const unwrapAtoms = (atoms) => Array.isArray(atoms) ? atoms : Object.getOwnPropertyNames(atoms).map((key) => atoms[key]);
402const wrapResults = (atoms, results) => Array.isArray(atoms) ? results : Object.getOwnPropertyNames(atoms).reduce((out, key, idx) => ({ ...out, [key]: results[idx] }), {});
403
404function createJSONStorage(getStringStorage) {
405 let lastStr;
406 let lastValue;
407 return {
408 getItem: (key) => {
409 const parse = (str2) => {
410 str2 = str2 || "";
411 if (lastStr !== str2) {
412 lastValue = JSON.parse(str2);
413 lastStr = str2;
414 }
415 return lastValue;
416 };
417 const str = getStringStorage().getItem(key);
418 if (str instanceof Promise) {
419 return str.then(parse);
420 }
421 return parse(str);
422 },
423 setItem: (key, newValue) => getStringStorage().setItem(key, JSON.stringify(newValue)),
424 removeItem: (key) => getStringStorage().removeItem(key)
425 };
426}
427const defaultStorage = createJSONStorage(() => localStorage);
428defaultStorage.subscribe = (key, callback) => {
429 const storageEventCallback = (e) => {
430 if (e.key === key && e.newValue) {
431 callback(JSON.parse(e.newValue));
432 }
433 };
434 window.addEventListener("storage", storageEventCallback);
435 return () => {
436 window.removeEventListener("storage", storageEventCallback);
437 };
438};
439function atomWithStorage(key, initialValue, storage = defaultStorage) {
440 const getInitialValue = () => {
441 try {
442 const value = storage.getItem(key);
443 if (value instanceof Promise) {
444 return value.catch(() => initialValue);
445 }
446 return value;
447 } catch {
448 return initialValue;
449 }
450 };
451 const baseAtom = atom(storage.delayInit ? initialValue : getInitialValue());
452 baseAtom.onMount = (setAtom) => {
453 let unsub;
454 if (storage.subscribe) {
455 unsub = storage.subscribe(key, setAtom);
456 setAtom(getInitialValue());
457 }
458 if (storage.delayInit) {
459 const value = getInitialValue();
460 if (value instanceof Promise) {
461 value.then(setAtom);
462 } else {
463 setAtom(value);
464 }
465 }
466 return unsub;
467 };
468 const anAtom = atom((get) => get(baseAtom), (get, set, update) => {
469 if (update === RESET) {
470 set(baseAtom, initialValue);
471 return storage.removeItem(key);
472 }
473 const newValue = typeof update === "function" ? update(get(baseAtom)) : update;
474 set(baseAtom, newValue);
475 return storage.setItem(key, newValue);
476 });
477 return anAtom;
478}
479function atomWithHash(key, initialValue, options) {
480 const serialize = (options == null ? void 0 : options.serialize) || JSON.stringify;
481 const deserialize = (options == null ? void 0 : options.deserialize) || JSON.parse;
482 const subscribe = (options == null ? void 0 : options.subscribe) || ((callback) => {
483 window.addEventListener("hashchange", callback);
484 return () => {
485 window.removeEventListener("hashchange", callback);
486 };
487 });
488 const hashStorage = {
489 getItem: (key2) => {
490 const searchParams = new URLSearchParams(location.hash.slice(1));
491 const storedValue = searchParams.get(key2);
492 if (storedValue === null) {
493 throw new Error("no value stored");
494 }
495 return deserialize(storedValue);
496 },
497 setItem: (key2, newValue) => {
498 const searchParams = new URLSearchParams(location.hash.slice(1));
499 searchParams.set(key2, serialize(newValue));
500 if (options == null ? void 0 : options.replaceState) {
501 history.replaceState(null, "", "#" + searchParams.toString());
502 } else {
503 location.hash = searchParams.toString();
504 }
505 },
506 removeItem: (key2) => {
507 const searchParams = new URLSearchParams(location.hash.slice(1));
508 searchParams.delete(key2);
509 if (options == null ? void 0 : options.replaceState) {
510 history.replaceState(null, "", "#" + searchParams.toString());
511 } else {
512 location.hash = searchParams.toString();
513 }
514 },
515 ...(options == null ? void 0 : options.delayInit) && { delayInit: true },
516 subscribe: (key2, setValue) => {
517 const callback = () => {
518 const searchParams = new URLSearchParams(location.hash.slice(1));
519 const str = searchParams.get(key2);
520 if (str !== null) {
521 setValue(deserialize(str));
522 } else {
523 setValue(initialValue);
524 }
525 };
526 return subscribe(callback);
527 }
528 };
529 return atomWithStorage(key, initialValue, hashStorage);
530}
531
532function atomWithObservable(createObservable, options) {
533 const observableResultAtom = atom((get) => {
534 var _a;
535 let observable = createObservable(get);
536 const itself = (_a = observable[Symbol.observable]) == null ? void 0 : _a.call(observable);
537 if (itself) {
538 observable = itself;
539 }
540 const EMPTY = Symbol();
541 let resolveEmittedInitialValue = null;
542 let initialEmittedValue = (options == null ? void 0 : options.initialValue) === void 0 ? new Promise((resolve) => {
543 resolveEmittedInitialValue = resolve;
544 }) : void 0;
545 let initialValueWasEmitted = false;
546 let emittedValueBeforeMount = EMPTY;
547 let isSync = true;
548 let setData = (data) => {
549 if ((options == null ? void 0 : options.initialValue) === void 0 && !initialValueWasEmitted) {
550 if (isSync) {
551 initialEmittedValue = data;
552 }
553 resolveEmittedInitialValue == null ? void 0 : resolveEmittedInitialValue(data);
554 initialValueWasEmitted = true;
555 resolveEmittedInitialValue = null;
556 } else {
557 emittedValueBeforeMount = data;
558 }
559 };
560 const dataListener = (data) => {
561 setData(data);
562 };
563 const errorListener = (error) => {
564 setData(Promise.reject(error));
565 };
566 let subscription = null;
567 let initialValue;
568 if ((options == null ? void 0 : options.initialValue) !== void 0) {
569 initialValue = getInitialValue(options);
570 } else {
571 subscription = observable.subscribe(dataListener, errorListener);
572 initialValue = initialEmittedValue;
573 }
574 isSync = false;
575 const dataAtom = atom(initialValue);
576 dataAtom.onMount = (update) => {
577 setData = update;
578 if (emittedValueBeforeMount !== EMPTY) {
579 update(emittedValueBeforeMount);
580 }
581 if (!subscription) {
582 subscription = observable.subscribe(dataListener, errorListener);
583 }
584 return () => {
585 subscription == null ? void 0 : subscription.unsubscribe();
586 subscription = null;
587 };
588 };
589 return { dataAtom, observable };
590 });
591 const observableAtom = atom((get) => {
592 const { dataAtom } = get(observableResultAtom);
593 return get(dataAtom);
594 }, (get, set, data) => {
595 const { dataAtom, observable } = get(observableResultAtom);
596 if ("next" in observable) {
597 let subscription = null;
598 const callback = (data2) => {
599 set(dataAtom, data2);
600 subscription == null ? void 0 : subscription.unsubscribe();
601 };
602 subscription = observable.subscribe(callback);
603 observable.next(data);
604 } else {
605 throw new Error("observable is not subject");
606 }
607 });
608 return observableAtom;
609}
610function getInitialValue(options) {
611 const initialValue = options.initialValue;
612 return initialValue instanceof Function ? initialValue() : initialValue;
613}
614
615const hydratedMap = /* @__PURE__ */ new WeakMap();
616function useHydrateAtoms(values, scope) {
617 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
618 const scopeContainer = useContext(ScopeContext);
619 const store = scopeContainer.s;
620 const hydratedSet = getHydratedSet(scopeContainer);
621 const tuplesToRestore = [];
622 for (const tuple of values) {
623 const atom = tuple[0];
624 if (!hydratedSet.has(atom)) {
625 hydratedSet.add(atom);
626 tuplesToRestore.push(tuple);
627 }
628 }
629 if (tuplesToRestore.length) {
630 store[RESTORE_ATOMS](tuplesToRestore);
631 }
632}
633function getHydratedSet(scopeContainer) {
634 let hydratedSet = hydratedMap.get(scopeContainer);
635 if (!hydratedSet) {
636 hydratedSet = /* @__PURE__ */ new WeakSet();
637 hydratedMap.set(scopeContainer, hydratedSet);
638 }
639 return hydratedSet;
640}
641
642const memoizeAtom = createMemoizeAtom();
643const LOADING = { state: "loading" };
644function loadable(anAtom) {
645 return memoizeAtom(() => {
646 const loadableAtomCache = /* @__PURE__ */ new WeakMap();
647 const catchAtom = atom((get) => {
648 let promise;
649 try {
650 const data = get(anAtom);
651 const loadableAtom2 = atom({ state: "hasData", data });
652 return loadableAtom2;
653 } catch (error) {
654 if (error instanceof Promise) {
655 promise = error;
656 } else {
657 const loadableAtom2 = atom({
658 state: "hasError",
659 error
660 });
661 return loadableAtom2;
662 }
663 }
664 const cached = loadableAtomCache.get(promise);
665 if (cached) {
666 return cached;
667 }
668 const loadableAtom = atom(LOADING, async (get2, set) => {
669 try {
670 const data = await get2(anAtom, { unstable_promise: true });
671 set(loadableAtom, { state: "hasData", data });
672 } catch (error) {
673 set(loadableAtom, { state: "hasError", error });
674 }
675 });
676 loadableAtom.onMount = (init) => {
677 init();
678 };
679 loadableAtomCache.set(promise, loadableAtom);
680 return loadableAtom;
681 });
682 const derivedAtom = atom((get) => {
683 const loadableAtom = get(catchAtom);
684 return get(loadableAtom);
685 });
686 return derivedAtom;
687 }, [anAtom]);
688}
689
690export { RESET, atomFamily, atomWithDefault, atomWithHash, atomWithObservable, atomWithReducer, atomWithReset, atomWithStorage, createJSONStorage, freezeAtom, freezeAtomCreator, loadable, selectAtom, splitAtom, useAtomCallback, useHydrateAtoms, useReducerAtom, useResetAtom, waitForAll };