UNPKG

jotai

Version:

👻 Primitive and flexible state management for React

567 lines (562 loc) • 20.9 kB
System.register([], (function (exports) { 'use strict'; return { execute: (function () { exports('atom', atom); let keyCount = 0; function atom(read, write) { const key = `atom${++keyCount}`; const config = { toString: () => key }; if (typeof read === "function") { config.read = read; } else { config.init = read; config.read = (get) => get(config); config.write = (get, set, arg) => set( config, typeof arg === "function" ? arg(get(config)) : arg ); } if (write) { config.write = write; } return config; } const hasInitialValue = (atom) => "init" in atom; const isActuallyWritableAtom = (atom) => !!atom.write; const cancelPromiseMap = /* @__PURE__ */ new WeakMap(); const registerCancelPromise = (promise, cancel) => { cancelPromiseMap.set(promise, cancel); promise.catch(() => { }).finally(() => cancelPromiseMap.delete(promise)); }; const cancelPromise = (promise, next) => { const cancel = cancelPromiseMap.get(promise); if (cancel) { cancelPromiseMap.delete(promise); cancel(next); } }; const resolvePromise = (promise, value) => { promise.status = "fulfilled"; promise.value = value; }; const rejectPromise = (promise, e) => { promise.status = "rejected"; promise.reason = e; }; const isPromiseLike = (x) => typeof (x == null ? void 0 : x.then) === "function"; const isEqualAtomValue = (a, b) => "v" in a && "v" in b && Object.is(a.v, b.v); const isEqualAtomError = (a, b) => "e" in a && "e" in b && Object.is(a.e, b.e); const hasPromiseAtomValue = (a) => "v" in a && a.v instanceof Promise; const isEqualPromiseAtomValue = (a, b) => "v" in a && "v" in b && a.v.orig && a.v.orig === b.v.orig; const returnAtomValue = (atomState) => { if ("e" in atomState) { throw atomState.e; } return atomState.v; }; const createStore = exports('createStore', () => { const atomStateMap = /* @__PURE__ */ new WeakMap(); const mountedMap = /* @__PURE__ */ new WeakMap(); const pendingMap = /* @__PURE__ */ new Map(); let storeListenersRev1; let storeListenersRev2; let mountedAtoms; { storeListenersRev1 = /* @__PURE__ */ new Set(); storeListenersRev2 = /* @__PURE__ */ new Set(); mountedAtoms = /* @__PURE__ */ new Set(); } const getAtomState = (atom) => atomStateMap.get(atom); const setAtomState = (atom, atomState) => { { Object.freeze(atomState); } const prevAtomState = atomStateMap.get(atom); atomStateMap.set(atom, atomState); if (!pendingMap.has(atom)) { pendingMap.set(atom, prevAtomState); } if (prevAtomState && hasPromiseAtomValue(prevAtomState)) { const next = "v" in atomState ? atomState.v instanceof Promise ? atomState.v : Promise.resolve(atomState.v) : Promise.reject(atomState.e); cancelPromise(prevAtomState.v, next); } }; const updateDependencies = (atom, nextAtomState, nextDependencies) => { const dependencies = /* @__PURE__ */ new Map(); let changed = false; nextDependencies.forEach((aState, a) => { if (!aState && a === atom) { aState = nextAtomState; } if (aState) { dependencies.set(a, aState); if (nextAtomState.d.get(a) !== aState) { changed = true; } } else { console.warn("[Bug] atom state not found"); } }); if (changed || nextAtomState.d.size !== dependencies.size) { nextAtomState.d = dependencies; } }; const setAtomValue = (atom, value, nextDependencies) => { const prevAtomState = getAtomState(atom); const nextAtomState = { d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(), v: value }; if (nextDependencies) { updateDependencies(atom, nextAtomState, nextDependencies); } if (prevAtomState && isEqualAtomValue(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) { return prevAtomState; } if (prevAtomState && hasPromiseAtomValue(prevAtomState) && hasPromiseAtomValue(nextAtomState) && isEqualPromiseAtomValue(prevAtomState, nextAtomState)) { if (prevAtomState.d === nextAtomState.d) { return prevAtomState; } else { nextAtomState.v = prevAtomState.v; } } setAtomState(atom, nextAtomState); return nextAtomState; }; const setAtomValueOrPromise = (atom, valueOrPromise, nextDependencies, abortPromise) => { if (isPromiseLike(valueOrPromise)) { let continuePromise; const promise = new Promise((resolve, reject) => { let settled = false; valueOrPromise.then( (v) => { if (!settled) { settled = true; const prevAtomState = getAtomState(atom); const nextAtomState = setAtomValue( atom, promise, nextDependencies ); resolvePromise(promise, v); resolve(v); if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) { mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d); } } }, (e) => { if (!settled) { settled = true; const prevAtomState = getAtomState(atom); const nextAtomState = setAtomValue( atom, promise, nextDependencies ); rejectPromise(promise, e); reject(e); if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) { mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d); } } } ); continuePromise = (next) => { if (!settled) { settled = true; next.then( (v) => resolvePromise(promise, v), (e) => rejectPromise(promise, e) ); resolve(next); } }; }); promise.orig = valueOrPromise; promise.status = "pending"; registerCancelPromise(promise, (next) => { if (next) { continuePromise(next); } abortPromise == null ? void 0 : abortPromise(); }); return setAtomValue(atom, promise, nextDependencies); } return setAtomValue(atom, valueOrPromise, nextDependencies); }; const setAtomError = (atom, error, nextDependencies) => { const prevAtomState = getAtomState(atom); const nextAtomState = { d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(), e: error }; if (nextDependencies) { updateDependencies(atom, nextAtomState, nextDependencies); } if (prevAtomState && isEqualAtomError(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) { return prevAtomState; } setAtomState(atom, nextAtomState); return nextAtomState; }; const readAtomState = (atom) => { const atomState = getAtomState(atom); if (atomState) { atomState.d.forEach((_, a) => { if (a !== atom && !mountedMap.has(a)) { readAtomState(a); } }); if (Array.from(atomState.d).every( ([a, s]) => a === atom || getAtomState(a) === s )) { return atomState; } } const nextDependencies = /* @__PURE__ */ new Map(); let isSync = true; const getter = (a) => { if (a === atom) { const aState2 = getAtomState(a); if (aState2) { nextDependencies.set(a, aState2); return returnAtomValue(aState2); } if (hasInitialValue(a)) { nextDependencies.set(a, void 0); return a.init; } throw new Error("no atom init"); } const aState = readAtomState(a); nextDependencies.set(a, aState); return returnAtomValue(aState); }; let controller; let setSelf; const options = { get signal() { if (!controller) { controller = new AbortController(); } return controller.signal; }, get setSelf() { if (!isActuallyWritableAtom(atom)) { console.warn("setSelf function cannot be used with read-only atom"); } if (!setSelf && isActuallyWritableAtom(atom)) { setSelf = (...args) => { if (isSync) { console.warn("setSelf function cannot be called in sync"); } if (!isSync) { return writeAtom(atom, ...args); } }; } return setSelf; } }; try { const valueOrPromise = atom.read(getter, options); return setAtomValueOrPromise( atom, valueOrPromise, nextDependencies, () => controller == null ? void 0 : controller.abort() ); } catch (error) { return setAtomError(atom, error, nextDependencies); } finally { isSync = false; } }; const readAtom = (atom) => returnAtomValue(readAtomState(atom)); const addAtom = (atom) => { let mounted = mountedMap.get(atom); if (!mounted) { mounted = mountAtom(atom); } return mounted; }; const canUnmountAtom = (atom, mounted) => !mounted.l.size && (!mounted.t.size || mounted.t.size === 1 && mounted.t.has(atom)); const delAtom = (atom) => { const mounted = mountedMap.get(atom); if (mounted && canUnmountAtom(atom, mounted)) { unmountAtom(atom); } }; const recomputeDependents = (atom) => { const dependencyMap = /* @__PURE__ */ new Map(); const dirtyMap = /* @__PURE__ */ new WeakMap(); const loop1 = (a) => { const mounted = mountedMap.get(a); mounted == null ? void 0 : mounted.t.forEach((dependent) => { if (dependent !== a) { dependencyMap.set( dependent, (dependencyMap.get(dependent) || /* @__PURE__ */ new Set()).add(a) ); dirtyMap.set(dependent, (dirtyMap.get(dependent) || 0) + 1); loop1(dependent); } }); }; loop1(atom); const loop2 = (a) => { const mounted = mountedMap.get(a); mounted == null ? void 0 : mounted.t.forEach((dependent) => { var _a; if (dependent !== a) { let dirtyCount = dirtyMap.get(dependent); if (dirtyCount) { dirtyMap.set(dependent, --dirtyCount); } if (!dirtyCount) { let isChanged = !!((_a = dependencyMap.get(dependent)) == null ? void 0 : _a.size); if (isChanged) { const prevAtomState = getAtomState(dependent); const nextAtomState = readAtomState(dependent); isChanged = !prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState); } if (!isChanged) { dependencyMap.forEach((s) => s.delete(dependent)); } } loop2(dependent); } }); }; loop2(atom); }; const writeAtomState = (atom, ...args) => { let isSync = true; const getter = (a) => returnAtomValue(readAtomState(a)); const setter = (a, ...args2) => { let r; if (a === atom) { if (!hasInitialValue(a)) { throw new Error("atom not writable"); } const prevAtomState = getAtomState(a); const nextAtomState = setAtomValueOrPromise(a, args2[0]); if (!prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState)) { recomputeDependents(a); } } else { r = writeAtomState(a, ...args2); } if (!isSync) { const flushed = flushPending(); { storeListenersRev2.forEach( (l) => l({ type: "async-write", flushed }) ); } } return r; }; const result = atom.write(getter, setter, ...args); isSync = false; return result; }; const writeAtom = (atom, ...args) => { const result = writeAtomState(atom, ...args); const flushed = flushPending(); { storeListenersRev2.forEach( (l) => l({ type: "write", flushed }) ); } return result; }; const mountAtom = (atom, initialDependent) => { const mounted = { t: new Set(initialDependent && [initialDependent]), l: /* @__PURE__ */ new Set() }; mountedMap.set(atom, mounted); { mountedAtoms.add(atom); } readAtomState(atom).d.forEach((_, a) => { const aMounted = mountedMap.get(a); if (aMounted) { aMounted.t.add(atom); } else { if (a !== atom) { mountAtom(a, atom); } } }); readAtomState(atom); if (isActuallyWritableAtom(atom) && atom.onMount) { const onUnmount = atom.onMount((...args) => writeAtom(atom, ...args)); if (onUnmount) { mounted.u = onUnmount; } } return mounted; }; const unmountAtom = (atom) => { var _a; const onUnmount = (_a = mountedMap.get(atom)) == null ? void 0 : _a.u; if (onUnmount) { onUnmount(); } mountedMap.delete(atom); { mountedAtoms.delete(atom); } const atomState = getAtomState(atom); if (atomState) { if (hasPromiseAtomValue(atomState)) { cancelPromise(atomState.v); } atomState.d.forEach((_, a) => { if (a !== atom) { const mounted = mountedMap.get(a); if (mounted) { mounted.t.delete(atom); if (canUnmountAtom(a, mounted)) { unmountAtom(a); } } } }); } else { console.warn("[Bug] could not find atom state to unmount", atom); } }; const mountDependencies = (atom, atomState, prevDependencies) => { const depSet = new Set(atomState.d.keys()); prevDependencies == null ? void 0 : prevDependencies.forEach((_, a) => { if (depSet.has(a)) { depSet.delete(a); return; } const mounted = mountedMap.get(a); if (mounted) { mounted.t.delete(atom); if (canUnmountAtom(a, mounted)) { unmountAtom(a); } } }); depSet.forEach((a) => { const mounted = mountedMap.get(a); if (mounted) { mounted.t.add(atom); } else if (mountedMap.has(atom)) { mountAtom(a, atom); } }); }; const flushPending = () => { let flushed; { flushed = /* @__PURE__ */ new Set(); } while (pendingMap.size) { const pending = Array.from(pendingMap); pendingMap.clear(); pending.forEach(([atom, prevAtomState]) => { const atomState = getAtomState(atom); if (atomState) { if (atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) { mountDependencies(atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d); } const mounted = mountedMap.get(atom); if (mounted && !// TODO This seems pretty hacky. Hope to fix it. // Maybe we could `mountDependencies` in `setAtomState`? (prevAtomState && !hasPromiseAtomValue(prevAtomState) && (isEqualAtomValue(prevAtomState, atomState) || isEqualAtomError(prevAtomState, atomState)))) { mounted.l.forEach((listener) => listener()); { flushed.add(atom); } } } else { console.warn("[Bug] no atom state to flush"); } }); } { storeListenersRev1.forEach((l) => l("state")); return flushed; } }; const subscribeAtom = (atom, listener) => { const mounted = addAtom(atom); const flushed = flushPending(); const listeners = mounted.l; listeners.add(listener); { storeListenersRev1.forEach((l) => l("sub")); storeListenersRev2.forEach( (l) => l({ type: "sub", flushed }) ); } return () => { listeners.delete(listener); delAtom(atom); { storeListenersRev1.forEach((l) => l("unsub")); storeListenersRev2.forEach((l) => l({ type: "unsub" })); } }; }; { return { get: readAtom, set: writeAtom, sub: subscribeAtom, // store dev methods (these are tentative and subject to change without notice) dev_subscribe_store: (l, rev) => { if (rev !== 2) { console.warn( "The current StoreListener revision is 2. The older ones are deprecated." ); storeListenersRev1.add(l); return () => { storeListenersRev1.delete(l); }; } storeListenersRev2.add(l); return () => { storeListenersRev2.delete(l); }; }, dev_get_mounted_atoms: () => mountedAtoms.values(), dev_get_atom_state: (a) => atomStateMap.get(a), dev_get_mounted: (a) => mountedMap.get(a), dev_restore_atoms: (values) => { for (const [atom, valueOrPromise] of values) { if (hasInitialValue(atom)) { setAtomValueOrPromise(atom, valueOrPromise); recomputeDependents(atom); } } const flushed = flushPending(); storeListenersRev2.forEach( (l) => l({ type: "restore", flushed }) ); } }; } }); let defaultStore; const getDefaultStore = exports('getDefaultStore', () => { if (!defaultStore) { defaultStore = createStore(); } return defaultStore; }); }) }; }));