UNPKG

14.9 kBJavaScriptView Raw
1import { useContext, useState, useEffect, useDebugValue, useRef, useCallback } from 'react';
2import { SECRET_INTERNAL_getScopeContext, useAtom } from 'jotai';
3
4const RESTORE_ATOMS = "h";
5const DEV_SUBSCRIBE_STATE = "n";
6const DEV_GET_MOUNTED_ATOMS = "l";
7const DEV_GET_ATOM_STATE = "a";
8const DEV_GET_MOUNTED = "m";
9
10const atomToPrintable$1 = (atom) => atom.debugLabel || atom.toString();
11const stateToPrintable = ([store, atoms]) => Object.fromEntries(
12 atoms.flatMap((atom) => {
13 var _a, _b;
14 const mounted = (_a = store[DEV_GET_MOUNTED]) == null ? void 0 : _a.call(store, atom);
15 if (!mounted) {
16 return [];
17 }
18 const dependents = mounted.t;
19 const atomState = ((_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom)) || {};
20 return [
21 [
22 atomToPrintable$1(atom),
23 {
24 ..."e" in atomState && { error: atomState.e },
25 ..."p" in atomState && { promise: atomState.p },
26 ..."v" in atomState && { value: atomState.v },
27 dependents: Array.from(dependents).map(atomToPrintable$1)
28 }
29 ]
30 ];
31 })
32);
33const useAtomsDebugValue = (options) => {
34 var _a;
35 const enabled = (_a = options == null ? void 0 : options.enabled) != null ? _a : (import.meta.env && import.meta.env.MODE) !== "production";
36 const ScopeContext = SECRET_INTERNAL_getScopeContext(options == null ? void 0 : options.scope);
37 const { s: store } = useContext(ScopeContext);
38 const [atoms, setAtoms] = useState([]);
39 useEffect(() => {
40 var _a2;
41 if (!enabled) {
42 return;
43 }
44 const callback = () => {
45 var _a3;
46 setAtoms(Array.from(((_a3 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a3.call(store)) || []));
47 };
48 const unsubscribe = (_a2 = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a2.call(store, callback);
49 callback();
50 return unsubscribe;
51 }, [enabled, store]);
52 useDebugValue([store, atoms], stateToPrintable);
53};
54
55function useAtomDevtools(anAtom, options, deprecatedScope) {
56 if (typeof options === "string" || typeof deprecatedScope !== "undefined") {
57 console.warn("DEPRECATED [useAtomDevtools] use DevtoolOptions");
58 options = {
59 name: options,
60 scope: deprecatedScope
61 };
62 }
63 const { enabled, name, scope } = options || {};
64 let extension;
65 try {
66 extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
67 } catch {
68 }
69 if (!extension) {
70 if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
71 console.warn("Please install/enable Redux devtools extension");
72 }
73 }
74 const [value, setValue] = useAtom(anAtom, scope);
75 const lastValue = useRef(value);
76 const isTimeTraveling = useRef(false);
77 const devtools = useRef();
78 const atomName = name || anAtom.debugLabel || anAtom.toString();
79 useEffect(() => {
80 if (!extension) {
81 return;
82 }
83 const setValueIfWritable = (value2) => {
84 if (typeof setValue === "function") {
85 setValue(value2);
86 return;
87 }
88 console.warn(
89 "[Warn] you cannot do write operations (Time-travelling, etc) in read-only atoms\n",
90 anAtom
91 );
92 };
93 devtools.current = extension.connect({ name: atomName });
94 const unsubscribe = devtools.current.subscribe((message) => {
95 var _a, _b, _c, _d, _e, _f;
96 if (message.type === "ACTION" && message.payload) {
97 try {
98 setValueIfWritable(JSON.parse(message.payload));
99 } catch (e) {
100 console.error(
101 "please dispatch a serializable value that JSON.parse() support\n",
102 e
103 );
104 }
105 } else if (message.type === "DISPATCH" && message.state) {
106 if (((_a = message.payload) == null ? void 0 : _a.type) === "JUMP_TO_ACTION" || ((_b = message.payload) == null ? void 0 : _b.type) === "JUMP_TO_STATE") {
107 isTimeTraveling.current = true;
108 setValueIfWritable(JSON.parse(message.state));
109 }
110 } else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") {
111 (_d = devtools.current) == null ? void 0 : _d.init(lastValue.current);
112 } else if (message.type === "DISPATCH" && ((_e = message.payload) == null ? void 0 : _e.type) === "IMPORT_STATE") {
113 const computedStates = ((_f = message.payload.nextLiftedState) == null ? void 0 : _f.computedStates) || [];
114 computedStates.forEach(({ state }, index) => {
115 var _a2;
116 if (index === 0) {
117 (_a2 = devtools.current) == null ? void 0 : _a2.init(state);
118 } else {
119 setValueIfWritable(state);
120 }
121 });
122 }
123 });
124 devtools.current.shouldInit = true;
125 return unsubscribe;
126 }, [anAtom, extension, atomName, setValue]);
127 useEffect(() => {
128 if (!devtools.current) {
129 return;
130 }
131 lastValue.current = value;
132 if (devtools.current.shouldInit) {
133 devtools.current.init(value);
134 devtools.current.shouldInit = false;
135 } else if (isTimeTraveling.current) {
136 isTimeTraveling.current = false;
137 } else {
138 devtools.current.send(
139 `${atomName} - ${new Date().toLocaleString()}`,
140 value
141 );
142 }
143 }, [anAtom, extension, atomName, value]);
144}
145
146const isEqualAtomsValues$1 = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
147const isEqualAtomsDependents$1 = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => {
148 const dRight = right.get(a);
149 return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d));
150});
151function useAtomsSnapshot(scope) {
152 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
153 const scopeContainer = useContext(ScopeContext);
154 const store = scopeContainer.s;
155 if (!store[DEV_SUBSCRIBE_STATE]) {
156 throw new Error("useAtomsSnapshot can only be used in dev mode.");
157 }
158 const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
159 values: /* @__PURE__ */ new Map(),
160 dependents: /* @__PURE__ */ new Map()
161 }));
162 useEffect(() => {
163 var _a;
164 let prevValues = /* @__PURE__ */ new Map();
165 let prevDependents = /* @__PURE__ */ new Map();
166 const invalidatedAtoms = /* @__PURE__ */ new Set();
167 const callback = () => {
168 var _a2, _b, _c;
169 const values = /* @__PURE__ */ new Map();
170 const dependents = /* @__PURE__ */ new Map();
171 let hasNewInvalidatedAtoms = false;
172 for (const atom of ((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []) {
173 const atomState = (_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom);
174 if (atomState) {
175 if (!atomState.y) {
176 if ("p" in atomState) {
177 return;
178 }
179 if (!invalidatedAtoms.has(atom)) {
180 invalidatedAtoms.add(atom);
181 hasNewInvalidatedAtoms = true;
182 }
183 }
184 if ("v" in atomState) {
185 values.set(atom, atomState.v);
186 }
187 }
188 const mounted = (_c = store[DEV_GET_MOUNTED]) == null ? void 0 : _c.call(store, atom);
189 if (mounted) {
190 dependents.set(atom, mounted.t);
191 }
192 }
193 if (hasNewInvalidatedAtoms) {
194 return;
195 }
196 if (isEqualAtomsValues$1(prevValues, values) && isEqualAtomsDependents$1(prevDependents, dependents)) {
197 return;
198 }
199 prevValues = values;
200 prevDependents = dependents;
201 invalidatedAtoms.clear();
202 setAtomsSnapshot({ values, dependents });
203 };
204 const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
205 callback();
206 return unsubscribe;
207 }, [store]);
208 return atomsSnapshot;
209}
210
211function useGotoAtomsSnapshot(scope) {
212 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
213 const { s: store, w: versionedWrite } = useContext(ScopeContext);
214 if (!store[DEV_SUBSCRIBE_STATE]) {
215 throw new Error("useGotoAtomsSnapshot can only be used in dev mode.");
216 }
217 return useCallback(
218 (snapshot) => {
219 const restoreAtoms = (values) => {
220 if (versionedWrite) {
221 versionedWrite((version) => {
222 store[RESTORE_ATOMS](values, version);
223 });
224 } else {
225 store[RESTORE_ATOMS](values);
226 }
227 };
228 if (isIterable(snapshot)) {
229 if ((import.meta.env && import.meta.env.MODE) !== "production") {
230 console.warn(
231 "snapshot as iterable is deprecated. use an object instead."
232 );
233 }
234 restoreAtoms(snapshot);
235 return;
236 }
237 restoreAtoms(snapshot.values);
238 },
239 [store, versionedWrite]
240 );
241}
242const isIterable = (item) => {
243 return typeof item[Symbol.iterator] === "function";
244};
245
246const isEqualAtomsValues = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v));
247const isEqualAtomsDependents = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => {
248 const dRight = right.get(a);
249 return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d));
250});
251const atomToPrintable = (atom) => atom.debugLabel ? `${atom}:${atom.debugLabel}` : `${atom}`;
252const getDevtoolsState = (atomsSnapshot) => {
253 const values = {};
254 atomsSnapshot.values.forEach((v, atom) => {
255 values[atomToPrintable(atom)] = v;
256 });
257 const dependents = {};
258 atomsSnapshot.dependents.forEach((d, atom) => {
259 dependents[atomToPrintable(atom)] = Array.from(d).map(atomToPrintable);
260 });
261 return {
262 values,
263 dependents
264 };
265};
266function useAtomsDevtools(name, options) {
267 if (typeof options !== "undefined" && typeof options !== "object") {
268 console.warn("DEPRECATED [useAtomsDevtools] use DevtoolsOptions");
269 options = { scope: options };
270 }
271 const { enabled, scope } = options || {};
272 const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
273 const { s: store, w: versionedWrite } = useContext(ScopeContext);
274 let extension;
275 try {
276 extension = (enabled != null ? enabled : (import.meta.env && import.meta.env.MODE) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
277 } catch {
278 }
279 if (!extension) {
280 if ((import.meta.env && import.meta.env.MODE) !== "production" && enabled) {
281 console.warn("Please install/enable Redux devtools extension");
282 }
283 }
284 if (extension && !store[DEV_SUBSCRIBE_STATE]) {
285 throw new Error("useAtomsDevtools can only be used in dev mode.");
286 }
287 const [atomsSnapshot, setAtomsSnapshot] = useState(() => ({
288 values: /* @__PURE__ */ new Map(),
289 dependents: /* @__PURE__ */ new Map()
290 }));
291 useEffect(() => {
292 var _a;
293 if (!extension) {
294 return;
295 }
296 let prevValues = /* @__PURE__ */ new Map();
297 let prevDependents = /* @__PURE__ */ new Map();
298 const invalidatedAtoms = /* @__PURE__ */ new Set();
299 const callback = () => {
300 var _a2, _b, _c;
301 const values = /* @__PURE__ */ new Map();
302 const dependents = /* @__PURE__ */ new Map();
303 let hasNewInvalidatedAtoms = false;
304 for (const atom of ((_a2 = store[DEV_GET_MOUNTED_ATOMS]) == null ? void 0 : _a2.call(store)) || []) {
305 const atomState = (_b = store[DEV_GET_ATOM_STATE]) == null ? void 0 : _b.call(store, atom);
306 if (atomState) {
307 if (!atomState.y) {
308 if ("p" in atomState) {
309 return;
310 }
311 if (!invalidatedAtoms.has(atom)) {
312 invalidatedAtoms.add(atom);
313 hasNewInvalidatedAtoms = true;
314 }
315 }
316 if ("v" in atomState) {
317 values.set(atom, atomState.v);
318 }
319 }
320 const mounted = (_c = store[DEV_GET_MOUNTED]) == null ? void 0 : _c.call(store, atom);
321 if (mounted) {
322 dependents.set(atom, mounted.t);
323 }
324 }
325 if (hasNewInvalidatedAtoms) {
326 return;
327 }
328 if (isEqualAtomsValues(prevValues, values) && isEqualAtomsDependents(prevDependents, dependents)) {
329 return;
330 }
331 prevValues = values;
332 prevDependents = dependents;
333 invalidatedAtoms.clear();
334 setAtomsSnapshot({ values, dependents });
335 };
336 const unsubscribe = (_a = store[DEV_SUBSCRIBE_STATE]) == null ? void 0 : _a.call(store, callback);
337 callback();
338 return unsubscribe;
339 }, [extension, store]);
340 const goToSnapshot = useCallback(
341 (snapshot) => {
342 const { values } = snapshot;
343 if (versionedWrite) {
344 versionedWrite((version) => {
345 store[RESTORE_ATOMS](values, version);
346 });
347 } else {
348 store[RESTORE_ATOMS](values);
349 }
350 },
351 [store, versionedWrite]
352 );
353 const isTimeTraveling = useRef(false);
354 const isRecording = useRef(true);
355 const devtools = useRef();
356 const snapshots = useRef([]);
357 useEffect(() => {
358 if (!extension) {
359 return;
360 }
361 const getSnapshotAt = (index = snapshots.current.length - 1) => {
362 const snapshot = snapshots.current[index >= 0 ? index : 0];
363 if (!snapshot) {
364 throw new Error("snaphost index out of bounds");
365 }
366 return snapshot;
367 };
368 const connection = extension.connect({ name });
369 const devtoolsUnsubscribe = connection.subscribe((message) => {
370 var _a;
371 switch (message.type) {
372 case "DISPATCH":
373 switch ((_a = message.payload) == null ? void 0 : _a.type) {
374 case "RESET":
375 break;
376 case "COMMIT":
377 connection.init(getDevtoolsState(getSnapshotAt()));
378 snapshots.current = [];
379 break;
380 case "JUMP_TO_ACTION":
381 case "JUMP_TO_STATE":
382 isTimeTraveling.current = true;
383 goToSnapshot(getSnapshotAt(message.payload.actionId - 1));
384 break;
385 case "PAUSE_RECORDING":
386 isRecording.current = !isRecording.current;
387 break;
388 }
389 }
390 });
391 devtools.current = connection;
392 devtools.current.shouldInit = true;
393 return devtoolsUnsubscribe;
394 }, [extension, goToSnapshot, name]);
395 useEffect(() => {
396 if (!devtools.current) {
397 return;
398 }
399 if (devtools.current.shouldInit) {
400 devtools.current.init(void 0);
401 devtools.current.shouldInit = false;
402 return;
403 }
404 if (isTimeTraveling.current) {
405 isTimeTraveling.current = false;
406 } else if (isRecording.current) {
407 snapshots.current.push(atomsSnapshot);
408 devtools.current.send(
409 {
410 type: `${snapshots.current.length}`,
411 updatedAt: new Date().toLocaleString()
412 },
413 getDevtoolsState(atomsSnapshot)
414 );
415 }
416 }, [atomsSnapshot]);
417}
418
419export { useAtomDevtools, useAtomsDebugValue, useAtomsDevtools, useAtomsSnapshot, useGotoAtomsSnapshot };