UNPKG

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