@buoy-gg/jotai 7.0.41 → 7.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -80
- package/lib/commonjs/index.js +1 -112
- package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +1 -199
- package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +1 -95
- package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +1 -715
- package/lib/commonjs/jotai/components/JotaiAtomValueDetailContent.js +1 -444
- package/lib/commonjs/jotai/components/JotaiEventFilterView.js +1 -43
- package/lib/commonjs/jotai/components/JotaiIcon.js +1 -18
- package/lib/commonjs/jotai/components/JotaiModal.js +1 -743
- package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +1 -69
- package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +1 -306
- package/lib/commonjs/jotai/types/index.js +1 -1
- package/lib/commonjs/jotai/utils/jotaiStateStore.js +1 -321
- package/lib/commonjs/jotai/utils/snapshotProvider.js +1 -215
- package/lib/commonjs/jotai/utils/testIDs.js +1 -90
- package/lib/commonjs/jotai/utils/watchAtoms.js +1 -143
- package/lib/commonjs/jotai/utils/writeSemantics.js +1 -47
- package/lib/commonjs/preset.js +1 -90
- package/lib/module/index.js +1 -85
- package/lib/module/jotai/components/JotaiAtomBrowser.js +1 -195
- package/lib/module/jotai/components/JotaiAtomChangeItem.js +1 -91
- package/lib/module/jotai/components/JotaiAtomDetailContent.js +1 -710
- package/lib/module/jotai/components/JotaiAtomValueDetailContent.js +1 -442
- package/lib/module/jotai/components/JotaiEventFilterView.js +1 -39
- package/lib/module/jotai/components/JotaiIcon.js +1 -11
- package/lib/module/jotai/components/JotaiModal.js +1 -738
- package/lib/module/jotai/hooks/useJotaiAtomChanges.js +1 -65
- package/lib/module/jotai/sync/jotaiSyncAdapter.js +1 -303
- package/lib/module/jotai/types/index.js +1 -1
- package/lib/module/jotai/utils/jotaiStateStore.js +1 -317
- package/lib/module/jotai/utils/snapshotProvider.js +1 -214
- package/lib/module/jotai/utils/testIDs.js +1 -82
- package/lib/module/jotai/utils/watchAtoms.js +1 -138
- package/lib/module/jotai/utils/writeSemantics.js +1 -43
- package/lib/module/preset.js +1 -87
- package/lib/web/index.mjs +2 -3
- package/package.json +4 -7
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +0 -1
- package/lib/typescript/jotai/components/JotaiModal.d.ts.map +0 -1
- package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +0 -1
- package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/jotai/types/index.d.ts.map +0 -1
- package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +0 -1
- package/lib/typescript/jotai/utils/snapshotProvider.d.ts.map +0 -1
- package/lib/typescript/jotai/utils/testIDs.d.ts.map +0 -1
- package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +0 -1
- package/lib/typescript/jotai/utils/writeSemantics.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
|
@@ -1,442 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* One atom, on a screen of its own — the React Query detail-screen shape
|
|
5
|
-
* pointed at a live jotai atom. The jotai counterpart of zustand's
|
|
6
|
-
* `ZustandStoreDetailContent`; tapping an atom card opens this instead of
|
|
7
|
-
* expanding in place.
|
|
8
|
-
*
|
|
9
|
-
* 1. VALUE EDITOR — the shared LiveExplorer. No draft, no Save: an edit
|
|
10
|
-
* writes into the atom as you make it and the app reacts immediately.
|
|
11
|
-
* Typing commits on a short debounce (~300ms, flushed on blur) so a word
|
|
12
|
-
* is one recorded change, not one per keystroke — atom writes land in
|
|
13
|
-
* the change log and count against the free tier's cap.
|
|
14
|
-
* 2. ATOM DETAILS — what the expanded card used to say: type, write
|
|
15
|
-
* semantics, change count (with the road to history).
|
|
16
|
-
* 3. VALUE EXPLORER — the read-only tree of the same value.
|
|
17
|
-
*
|
|
18
|
-
* THE WRITE RULE — whole-value replace, jotai's own semantics. An atom is one
|
|
19
|
-
* value; there is no merge. However deep the edit, the writer rebuilds the
|
|
20
|
-
* whole value (`updateNestedDataByPath`) and hands it to `store.set(atom, …)`
|
|
21
|
-
* — so nested deletes are fine and only the ROOT itself can't be removed.
|
|
22
|
-
*
|
|
23
|
-
* Write-kind honesty (classified from jotai's own `defaultWrite`, see
|
|
24
|
-
* writeSemantics.ts):
|
|
25
|
-
* - "value" — primitive atom: `store.set` stores the edited value. Clean.
|
|
26
|
-
* - "action" — custom write (`atomWithStorage`, `atomWithReducer`, …): the
|
|
27
|
-
* edited value is passed to that write as its argument. For most
|
|
28
|
-
* (`atomWithStorage`) that IS a set; for a reducer it's an action. Still
|
|
29
|
-
* editable — the MCP's `setAtom` and Time Machine restore already write
|
|
30
|
-
* these — but the editor says what the write really does.
|
|
31
|
-
* - read-only derived — no setter exists; the editor section explains and
|
|
32
|
-
* the info/explorer sections still render.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
36
|
-
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
37
|
-
import { buoyColors, ExpandedInfoRow, FileJson, night, PillBadge, parseValue, monoFont } from "@buoy-gg/shared-ui";
|
|
38
|
-
import { DataViewer, LiveExplorer, ValueEditModal, updateNestedDataByPath, deleteNestedDataByPath } from "@buoy-gg/shared-ui/dataViewer";
|
|
39
|
-
import { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
40
|
-
import { JOTAI_TEST_ID, atomRowTestID } from "../utils/testIDs";
|
|
41
|
-
|
|
42
|
-
/** How long typed edits coalesce before the single write fires. */
|
|
43
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
44
|
-
const TYPING_COMMIT_MS = 300;
|
|
45
|
-
|
|
46
|
-
/** `buoy-jotai-atom-detail-…` — this screen's own handle root. */
|
|
47
|
-
const DETAIL_ID = `${JOTAI_TEST_ID}-atom-detail`;
|
|
48
|
-
function detailExplorerTestID(dataPath) {
|
|
49
|
-
const slug = part => part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
50
|
-
return dataPath.length === 0 ? `${DETAIL_ID}-explorer-root` : `${DETAIL_ID}-explorer-${dataPath.map(slug).filter(Boolean).join("-")}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Why this atom's value can't be live-edited, or null when it can.
|
|
55
|
-
*
|
|
56
|
-
* The path utils handle scalars, arrays, plain objects, Map and Set. What
|
|
57
|
-
* they can't faithfully rebuild is a class instance (a leaf edit would spread
|
|
58
|
-
* it into a plain object, dropping its prototype) — and a mirror's
|
|
59
|
-
* on-device marker or a function value aren't editable data at all.
|
|
60
|
-
*/
|
|
61
|
-
function editBlockedReasonFor(atom, value) {
|
|
62
|
-
if (!atom.setValue) {
|
|
63
|
-
return "Derived atom — its value recomputes from other atoms and has no setter. Edit the atoms it reads from instead.";
|
|
64
|
-
}
|
|
65
|
-
if (typeof value === "function") {
|
|
66
|
-
return "This atom holds a function — nothing here to edit as data.";
|
|
67
|
-
}
|
|
68
|
-
if (value && typeof value === "object" && value.__buoyValueOnDevice === true) {
|
|
69
|
-
return "The value stayed on the device (too large for the wire) — open the tool on-device to edit it.";
|
|
70
|
-
}
|
|
71
|
-
if (value && typeof value === "object") {
|
|
72
|
-
const proto = Object.getPrototypeOf(value);
|
|
73
|
-
const isPlain = proto === Object.prototype || proto === null || Array.isArray(value) || value instanceof Map || value instanceof Set;
|
|
74
|
-
if (!isPlain) {
|
|
75
|
-
const name = value.constructor?.name ?? "class";
|
|
76
|
-
return `This atom holds a ${name} instance — rebuilding it from edited fields would drop its prototype. Edit via app code.`;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
function valueTypeLabel(value) {
|
|
82
|
-
if (value === null) return "null";
|
|
83
|
-
if (value === undefined) return "undefined";
|
|
84
|
-
if (Array.isArray(value)) return `array · ${value.length}`;
|
|
85
|
-
return typeof value;
|
|
86
|
-
}
|
|
87
|
-
export function JotaiAtomValueDetailContent({
|
|
88
|
-
atom,
|
|
89
|
-
onViewHistory
|
|
90
|
-
}) {
|
|
91
|
-
const rowID = atomRowTestID(atom.label);
|
|
92
|
-
|
|
93
|
-
// Re-render on every recorded change — our own edits included — so the
|
|
94
|
-
// editor, the read-only explorer, and the change count all track the live
|
|
95
|
-
// atom. Changes and registry updates arrive on separate listener sets, so
|
|
96
|
-
// subscribe to both. On a desktop mirror the snapshot refresh drives the
|
|
97
|
-
// same listeners.
|
|
98
|
-
const [version, setVersion] = useState(0);
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
const bump = () => setVersion(v => v + 1);
|
|
101
|
-
const unsubChanges = jotaiStateStore.subscribe(bump);
|
|
102
|
-
const unsubAtoms = jotaiStateStore.subscribeToAtoms(bump);
|
|
103
|
-
return () => {
|
|
104
|
-
unsubChanges();
|
|
105
|
-
unsubAtoms();
|
|
106
|
-
};
|
|
107
|
-
}, []);
|
|
108
|
-
|
|
109
|
-
// Recomputed per change on purpose — this screen edits the LIVE atom, so
|
|
110
|
-
// every view of it derives from the atom's current answer, never a seed.
|
|
111
|
-
const value = useMemo(() => {
|
|
112
|
-
try {
|
|
113
|
-
return atom.getValue();
|
|
114
|
-
} catch {
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
[atom, version]);
|
|
120
|
-
const blockedReason = editBlockedReasonFor(atom, value);
|
|
121
|
-
const [rawOpen, setRawOpen] = useState(false);
|
|
122
|
-
const writer = useMemo(() => {
|
|
123
|
-
if (blockedReason !== null) return undefined;
|
|
124
|
-
return {
|
|
125
|
-
update: (path, next) => {
|
|
126
|
-
const current = atom.getValue();
|
|
127
|
-
atom.setValue?.(path.length === 0 ? next : updateNestedDataByPath(current, path, next));
|
|
128
|
-
},
|
|
129
|
-
remove: path => {
|
|
130
|
-
// Depth 0 is vetoed by canRemove; an atom is one value — you can
|
|
131
|
-
// empty it, not remove it.
|
|
132
|
-
if (path.length === 0) return;
|
|
133
|
-
const current = atom.getValue();
|
|
134
|
-
atom.setValue?.(deleteNestedDataByPath(current, path));
|
|
135
|
-
},
|
|
136
|
-
canRemove: path => path.length > 0
|
|
137
|
-
};
|
|
138
|
-
}, [atom, blockedReason]);
|
|
139
|
-
const displayValue = useMemo(() => {
|
|
140
|
-
if (value && typeof value === "object") {
|
|
141
|
-
const filtered = {};
|
|
142
|
-
for (const [key, v] of Object.entries(value)) {
|
|
143
|
-
if (typeof v !== "function") filtered[key] = v;
|
|
144
|
-
}
|
|
145
|
-
return parseValue(filtered);
|
|
146
|
-
}
|
|
147
|
-
return parseValue(value);
|
|
148
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
149
|
-
}, [value, version]);
|
|
150
|
-
|
|
151
|
-
/** Raw-hatch save: whole-value edit, one write. */
|
|
152
|
-
const applyRaw = useCallback(next => {
|
|
153
|
-
atom.setValue?.(next);
|
|
154
|
-
setRawOpen(false);
|
|
155
|
-
}, [atom]);
|
|
156
|
-
const writePill = atom.setValue === undefined ? {
|
|
157
|
-
color: night.color.textTertiary,
|
|
158
|
-
label: "READ-ONLY (DERIVED)"
|
|
159
|
-
} : atom.writeKind === "action" ? {
|
|
160
|
-
color: buoyColors.warning,
|
|
161
|
-
label: "CUSTOM WRITE"
|
|
162
|
-
} : {
|
|
163
|
-
color: buoyColors.success,
|
|
164
|
-
label: "PRIMITIVE"
|
|
165
|
-
};
|
|
166
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
167
|
-
style: styles.root,
|
|
168
|
-
children: [/*#__PURE__*/_jsxs(ScrollView, {
|
|
169
|
-
testID: `${DETAIL_ID}-scroll`,
|
|
170
|
-
style: styles.scroll,
|
|
171
|
-
contentContainerStyle: styles.scrollContent,
|
|
172
|
-
keyboardShouldPersistTaps: "handled",
|
|
173
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
174
|
-
style: styles.section,
|
|
175
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
176
|
-
style: styles.editorContainer,
|
|
177
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
178
|
-
style: styles.sectionHeaderRow,
|
|
179
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
180
|
-
testID: `${DETAIL_ID}-editor-header`,
|
|
181
|
-
style: styles.sectionHeaderText,
|
|
182
|
-
children: "Value Editor"
|
|
183
|
-
}), blockedReason === null ? /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
184
|
-
onPress: () => setRawOpen(true),
|
|
185
|
-
testID: `${DETAIL_ID}-raw`,
|
|
186
|
-
accessibilityRole: "button",
|
|
187
|
-
accessibilityLabel: "Edit raw JSON",
|
|
188
|
-
style: styles.rawButton,
|
|
189
|
-
hitSlop: {
|
|
190
|
-
top: 6,
|
|
191
|
-
bottom: 6,
|
|
192
|
-
left: 6,
|
|
193
|
-
right: 6
|
|
194
|
-
},
|
|
195
|
-
children: [/*#__PURE__*/_jsx(FileJson, {
|
|
196
|
-
size: 13,
|
|
197
|
-
color: night.color.accent
|
|
198
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
199
|
-
style: styles.rawButtonText,
|
|
200
|
-
children: "Raw"
|
|
201
|
-
})]
|
|
202
|
-
}) : null]
|
|
203
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
204
|
-
style: styles.sectionBody,
|
|
205
|
-
children: blockedReason === null ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
206
|
-
children: [/*#__PURE__*/_jsx(LiveExplorer, {
|
|
207
|
-
editable: true,
|
|
208
|
-
label: atom.label,
|
|
209
|
-
value: value,
|
|
210
|
-
defaultExpanded: [atom.label],
|
|
211
|
-
writer: writer,
|
|
212
|
-
dataVersion: version,
|
|
213
|
-
testIDForPath: detailExplorerTestID,
|
|
214
|
-
debounceMs: TYPING_COMMIT_MS
|
|
215
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
216
|
-
style: styles.editorHint,
|
|
217
|
-
children: atom.writeKind === "action" ? "This atom has a custom write — the edited value is handed to it as its argument (a set for atomWithStorage, an ACTION for a reducer atom)." : "Edits write into the live atom as you make them — the whole value is replaced, jotai's own semantics."
|
|
218
|
-
})]
|
|
219
|
-
}) : /*#__PURE__*/_jsx(Text, {
|
|
220
|
-
style: styles.blockedText,
|
|
221
|
-
testID: `${DETAIL_ID}-edit-blocked`,
|
|
222
|
-
children: blockedReason
|
|
223
|
-
})
|
|
224
|
-
})]
|
|
225
|
-
})
|
|
226
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
227
|
-
style: styles.section,
|
|
228
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
229
|
-
style: styles.infoContainer,
|
|
230
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
231
|
-
style: styles.sectionHeaderTextPlain,
|
|
232
|
-
children: "Atom Details"
|
|
233
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
234
|
-
style: styles.infoBody,
|
|
235
|
-
children: [/*#__PURE__*/_jsxs(ExpandedInfoRow, {
|
|
236
|
-
label: "Type",
|
|
237
|
-
children: [/*#__PURE__*/_jsx(PillBadge, {
|
|
238
|
-
color: atom.color,
|
|
239
|
-
testID: `${rowID}-type`,
|
|
240
|
-
children: "JOTAI"
|
|
241
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
242
|
-
style: styles.valueTypeText,
|
|
243
|
-
testID: `${rowID}-valuetype`,
|
|
244
|
-
children: valueTypeLabel(value)
|
|
245
|
-
})]
|
|
246
|
-
}), /*#__PURE__*/_jsx(ExpandedInfoRow, {
|
|
247
|
-
label: "Write",
|
|
248
|
-
children: /*#__PURE__*/_jsx(PillBadge, {
|
|
249
|
-
color: writePill.color,
|
|
250
|
-
testID: `${DETAIL_ID}-write`,
|
|
251
|
-
children: writePill.label
|
|
252
|
-
})
|
|
253
|
-
}), atom.changeCount > 0 && /*#__PURE__*/_jsxs(ExpandedInfoRow, {
|
|
254
|
-
label: "Changes",
|
|
255
|
-
children: [/*#__PURE__*/_jsx(PillBadge, {
|
|
256
|
-
color: buoyColors.warning,
|
|
257
|
-
testID: `${rowID}-changes`,
|
|
258
|
-
children: String(atom.changeCount)
|
|
259
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
260
|
-
onPress: () => onViewHistory(atom.label),
|
|
261
|
-
style: styles.inlineLinkButton,
|
|
262
|
-
hitSlop: {
|
|
263
|
-
top: 6,
|
|
264
|
-
bottom: 6,
|
|
265
|
-
left: 6,
|
|
266
|
-
right: 6
|
|
267
|
-
},
|
|
268
|
-
testID: `${rowID}-history`,
|
|
269
|
-
accessibilityRole: "button",
|
|
270
|
-
accessibilityLabel: `View ${atom.label} history`,
|
|
271
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
272
|
-
style: [styles.inlineLinkText, {
|
|
273
|
-
color: atom.color
|
|
274
|
-
}],
|
|
275
|
-
children: "view history \u2192"
|
|
276
|
-
})
|
|
277
|
-
})]
|
|
278
|
-
})]
|
|
279
|
-
})]
|
|
280
|
-
})
|
|
281
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
282
|
-
style: styles.section,
|
|
283
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
284
|
-
style: styles.infoContainer,
|
|
285
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
286
|
-
style: styles.sectionHeaderTextPlain,
|
|
287
|
-
children: "Value Explorer"
|
|
288
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
289
|
-
style: styles.viewerBody,
|
|
290
|
-
testID: `${rowID}-value`,
|
|
291
|
-
children: displayValue && typeof displayValue === "object" ? /*#__PURE__*/_jsx(DataViewer, {
|
|
292
|
-
title: "",
|
|
293
|
-
data: displayValue,
|
|
294
|
-
showTypeFilter: true,
|
|
295
|
-
rawMode: true,
|
|
296
|
-
initialExpanded: false
|
|
297
|
-
}) : /*#__PURE__*/_jsx(Text, {
|
|
298
|
-
style: styles.primitiveText,
|
|
299
|
-
testID: `${rowID}-primitive`,
|
|
300
|
-
children: String(value ?? "undefined")
|
|
301
|
-
})
|
|
302
|
-
})]
|
|
303
|
-
})
|
|
304
|
-
})]
|
|
305
|
-
}), rawOpen && blockedReason === null ? /*#__PURE__*/_jsx(ValueEditModal, {
|
|
306
|
-
path: [],
|
|
307
|
-
value: value,
|
|
308
|
-
onSave: applyRaw,
|
|
309
|
-
onCancel: () => setRawOpen(false),
|
|
310
|
-
saveLabel: "Apply",
|
|
311
|
-
testIDPrefix: `${JOTAI_TEST_ID}-value`
|
|
312
|
-
}) : null]
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
const styles = StyleSheet.create({
|
|
316
|
-
root: {
|
|
317
|
-
flex: 1,
|
|
318
|
-
minHeight: 0
|
|
319
|
-
},
|
|
320
|
-
scroll: {
|
|
321
|
-
flex: 1
|
|
322
|
-
},
|
|
323
|
-
scrollContent: {
|
|
324
|
-
paddingHorizontal: 8,
|
|
325
|
-
paddingTop: 8,
|
|
326
|
-
paddingBottom: 24
|
|
327
|
-
},
|
|
328
|
-
section: {
|
|
329
|
-
marginBottom: 16
|
|
330
|
-
},
|
|
331
|
-
// The editable section wears the accent, like RQ's Data Editor — the one
|
|
332
|
-
// box on this screen that changes things should read differently from the
|
|
333
|
-
// two that don't.
|
|
334
|
-
editorContainer: {
|
|
335
|
-
minWidth: 200,
|
|
336
|
-
backgroundColor: night.color.surface,
|
|
337
|
-
borderRadius: 6,
|
|
338
|
-
borderWidth: 1,
|
|
339
|
-
borderColor: night.color.accent + "4D",
|
|
340
|
-
overflow: "hidden",
|
|
341
|
-
shadowColor: night.color.accent,
|
|
342
|
-
shadowOffset: {
|
|
343
|
-
width: 0,
|
|
344
|
-
height: 0
|
|
345
|
-
},
|
|
346
|
-
shadowOpacity: 0.1,
|
|
347
|
-
shadowRadius: 6
|
|
348
|
-
},
|
|
349
|
-
sectionHeaderRow: {
|
|
350
|
-
flexDirection: "row",
|
|
351
|
-
alignItems: "center",
|
|
352
|
-
justifyContent: "space-between",
|
|
353
|
-
backgroundColor: night.color.accent + "1A",
|
|
354
|
-
paddingHorizontal: 12,
|
|
355
|
-
paddingVertical: 8,
|
|
356
|
-
borderBottomWidth: 1,
|
|
357
|
-
borderBottomColor: night.color.accent + "33"
|
|
358
|
-
},
|
|
359
|
-
sectionHeaderText: {
|
|
360
|
-
fontWeight: "600",
|
|
361
|
-
fontSize: 12,
|
|
362
|
-
color: night.color.accent,
|
|
363
|
-
letterSpacing: 0.5,
|
|
364
|
-
textTransform: "uppercase",
|
|
365
|
-
fontFamily: monoFont
|
|
366
|
-
},
|
|
367
|
-
rawButton: {
|
|
368
|
-
flexDirection: "row",
|
|
369
|
-
alignItems: "center",
|
|
370
|
-
gap: 4
|
|
371
|
-
},
|
|
372
|
-
rawButtonText: {
|
|
373
|
-
color: night.color.accent,
|
|
374
|
-
fontSize: 11,
|
|
375
|
-
fontWeight: "700"
|
|
376
|
-
},
|
|
377
|
-
sectionBody: {
|
|
378
|
-
padding: 8
|
|
379
|
-
},
|
|
380
|
-
editorHint: {
|
|
381
|
-
color: night.color.textTertiary,
|
|
382
|
-
fontSize: 10,
|
|
383
|
-
paddingTop: 8,
|
|
384
|
-
paddingHorizontal: 4,
|
|
385
|
-
lineHeight: 14
|
|
386
|
-
},
|
|
387
|
-
blockedText: {
|
|
388
|
-
color: night.color.textSecondary,
|
|
389
|
-
fontSize: 12,
|
|
390
|
-
padding: 12,
|
|
391
|
-
lineHeight: 18
|
|
392
|
-
},
|
|
393
|
-
infoContainer: {
|
|
394
|
-
minWidth: 200,
|
|
395
|
-
backgroundColor: night.color.surface,
|
|
396
|
-
borderRadius: 6,
|
|
397
|
-
borderWidth: 1,
|
|
398
|
-
borderColor: night.color.border,
|
|
399
|
-
overflow: "hidden"
|
|
400
|
-
},
|
|
401
|
-
sectionHeaderTextPlain: {
|
|
402
|
-
backgroundColor: night.color.surfaceElevated,
|
|
403
|
-
paddingHorizontal: 12,
|
|
404
|
-
paddingVertical: 8,
|
|
405
|
-
fontWeight: "600",
|
|
406
|
-
fontSize: 12,
|
|
407
|
-
color: night.color.textSecondary,
|
|
408
|
-
borderBottomWidth: 1,
|
|
409
|
-
borderBottomColor: night.color.border,
|
|
410
|
-
letterSpacing: 0.5,
|
|
411
|
-
textTransform: "uppercase",
|
|
412
|
-
fontFamily: monoFont
|
|
413
|
-
},
|
|
414
|
-
infoBody: {
|
|
415
|
-
padding: 12,
|
|
416
|
-
gap: 10
|
|
417
|
-
},
|
|
418
|
-
viewerBody: {
|
|
419
|
-
backgroundColor: night.color.bg,
|
|
420
|
-
minHeight: 60
|
|
421
|
-
},
|
|
422
|
-
primitiveText: {
|
|
423
|
-
color: night.color.text,
|
|
424
|
-
fontSize: 12,
|
|
425
|
-
fontFamily: monoFont,
|
|
426
|
-
padding: 14
|
|
427
|
-
},
|
|
428
|
-
valueTypeText: {
|
|
429
|
-
color: night.color.textTertiary,
|
|
430
|
-
fontSize: 10,
|
|
431
|
-
fontFamily: monoFont,
|
|
432
|
-
marginLeft: 6
|
|
433
|
-
},
|
|
434
|
-
inlineLinkButton: {
|
|
435
|
-
marginLeft: 4
|
|
436
|
-
},
|
|
437
|
-
inlineLinkText: {
|
|
438
|
-
fontSize: 10,
|
|
439
|
-
fontWeight: "600",
|
|
440
|
-
fontFamily: monoFont
|
|
441
|
-
}
|
|
442
|
-
});
|
|
1
|
+
"use strict";import{useCallback as O,useEffect as j,useMemo as T,useState as S}from"react";import{ScrollView as A,StyleSheet as P,Text as d,TouchableOpacity as V,View as a}from"react-native";import{buoyColors as x,ExpandedInfoRow as D,FileJson as z,night as r,PillBadge as I,parseValue as B,monoFont as b}from"@buoy-gg/shared-ui";import{DataViewer as L,LiveExplorer as W,ValueEditModal as F,updateNestedDataByPath as M,deleteNestedDataByPath as _}from"@buoy-gg/shared-ui/dataViewer";import{jotaiStateStore as v}from"../utils/jotaiStateStore";import{JOTAI_TEST_ID as E,atomRowTestID as N}from"../utils/testIDs";import{jsx as o,jsxs as s,Fragment as J}from"react/jsx-runtime";const K=300;const f=`${E}-atom-detail`;function Y(e){const n=l=>l.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return e.length===0?`${f}-explorer-root`:`${f}-explorer-${e.map(n).filter(Boolean).join("-")}`}function G(e,n){if(!e.setValue){return"Derived atom \u2014 its value recomputes from other atoms and has no setter. Edit the atoms it reads from instead."}if(typeof n==="function"){return"This atom holds a function \u2014 nothing here to edit as data."}if(n&&typeof n==="object"&&n.__buoyValueOnDevice===true){return"The value stayed on the device (too large for the wire) \u2014 open the tool on-device to edit it."}if(n&&typeof n==="object"){const l=Object.getPrototypeOf(n);const y=l===Object.prototype||l===null||Array.isArray(n)||n instanceof Map||n instanceof Set;if(!y){const g=n.constructor?.name??"class";return`This atom holds a ${g} instance \u2014 rebuilding it from edited fields would drop its prototype. Edit via app code.`}}return null}function U(e){if(e===null)return"null";if(e===void 0)return"undefined";if(Array.isArray(e))return`array \xB7 ${e.length}`;return typeof e}export function JotaiAtomValueDetailContent({atom:e,onViewHistory:n}){const l=N(e.label);const[y,g]=S(0);j(()=>{const i=()=>g(H=>H+1);const u=v.subscribe(i);const p=v.subscribeToAtoms(i);return()=>{u();p()}},[]);const c=T(()=>{try{return e.getValue()}catch{return void 0}},[e,y]);const h=G(e,c);const[$,m]=S(false);const R=T(()=>{if(h!==null)return void 0;return{update:(i,u)=>{const p=e.getValue();e.setValue?.(i.length===0?u:M(p,i,u))},remove:i=>{if(i.length===0)return;const u=e.getValue();e.setValue?.(_(u,i))},canRemove:i=>i.length>0}},[e,h]);const w=T(()=>{if(c&&typeof c==="object"){const i={};for(const[u,p]of Object.entries(c)){if(typeof p!=="function")i[u]=p}return B(i)}return B(c)},[c,y]);const k=O(i=>{e.setValue?.(i);m(false)},[e]);const C=e.setValue===void 0?{color:r.color.textTertiary,label:"READ-ONLY (DERIVED)"}:e.writeKind==="action"?{color:x.warning,label:"CUSTOM WRITE"}:{color:x.success,label:"PRIMITIVE"};return s(a,{style:t.root,children:[s(A,{testID:`${f}-scroll`,style:t.scroll,contentContainerStyle:t.scrollContent,keyboardShouldPersistTaps:"handled",children:[o(a,{style:t.section,children:s(a,{style:t.editorContainer,children:[s(a,{style:t.sectionHeaderRow,children:[o(d,{testID:`${f}-editor-header`,style:t.sectionHeaderText,children:"Value Editor"}),h===null?s(V,{onPress:()=>m(true),testID:`${f}-raw`,accessibilityRole:"button",accessibilityLabel:"Edit raw JSON",style:t.rawButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:[o(z,{size:13,color:r.color.accent}),o(d,{style:t.rawButtonText,children:"Raw"})]}):null]}),o(a,{style:t.sectionBody,children:h===null?s(J,{children:[o(W,{editable:true,label:e.label,value:c,defaultExpanded:[e.label],writer:R,dataVersion:y,testIDForPath:Y,debounceMs:K}),o(d,{style:t.editorHint,children:e.writeKind==="action"?"This atom has a custom write \u2014 the edited value is handed to it as its argument (a set for atomWithStorage, an ACTION for a reducer atom).":"Edits write into the live atom as you make them \u2014 the whole value is replaced, jotai's own semantics."})]}):o(d,{style:t.blockedText,testID:`${f}-edit-blocked`,children:h})})]})}),o(a,{style:t.section,children:s(a,{style:t.infoContainer,children:[o(d,{style:t.sectionHeaderTextPlain,children:"Atom Details"}),s(a,{style:t.infoBody,children:[s(D,{label:"Type",children:[o(I,{color:e.color,testID:`${l}-type`,children:"JOTAI"}),o(d,{style:t.valueTypeText,testID:`${l}-valuetype`,children:U(c)})]}),o(D,{label:"Write",children:o(I,{color:C.color,testID:`${f}-write`,children:C.label})}),e.changeCount>0&&s(D,{label:"Changes",children:[o(I,{color:x.warning,testID:`${l}-changes`,children:String(e.changeCount)}),o(V,{onPress:()=>n(e.label),style:t.inlineLinkButton,hitSlop:{top:6,bottom:6,left:6,right:6},testID:`${l}-history`,accessibilityRole:"button",accessibilityLabel:`View ${e.label} history`,children:o(d,{style:[t.inlineLinkText,{color:e.color}],children:"view history \u2192"})})]})]})]})}),o(a,{style:t.section,children:s(a,{style:t.infoContainer,children:[o(d,{style:t.sectionHeaderTextPlain,children:"Value Explorer"}),o(a,{style:t.viewerBody,testID:`${l}-value`,children:w&&typeof w==="object"?o(L,{title:"",data:w,showTypeFilter:true,rawMode:true,initialExpanded:false}):o(d,{style:t.primitiveText,testID:`${l}-primitive`,children:String(c??"undefined")})})]})})]}),$&&h===null?o(F,{path:[],value:c,onSave:k,onCancel:()=>m(false),saveLabel:"Apply",testIDPrefix:`${E}-value`}):null]})}const t=P.create({root:{flex:1,minHeight:0},scroll:{flex:1},scrollContent:{paddingHorizontal:8,paddingTop:8,paddingBottom:24},section:{marginBottom:16},editorContainer:{minWidth:200,backgroundColor:r.color.surface,borderRadius:6,borderWidth:1,borderColor:r.color.accent+"4D",overflow:"hidden",shadowColor:r.color.accent,shadowOffset:{width:0,height:0},shadowOpacity:.1,shadowRadius:6},sectionHeaderRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:r.color.accent+"1A",paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:r.color.accent+"33"},sectionHeaderText:{fontWeight:"600",fontSize:12,color:r.color.accent,letterSpacing:.5,textTransform:"uppercase",fontFamily:b},rawButton:{flexDirection:"row",alignItems:"center",gap:4},rawButtonText:{color:r.color.accent,fontSize:11,fontWeight:"700"},sectionBody:{padding:8},editorHint:{color:r.color.textTertiary,fontSize:10,paddingTop:8,paddingHorizontal:4,lineHeight:14},blockedText:{color:r.color.textSecondary,fontSize:12,padding:12,lineHeight:18},infoContainer:{minWidth:200,backgroundColor:r.color.surface,borderRadius:6,borderWidth:1,borderColor:r.color.border,overflow:"hidden"},sectionHeaderTextPlain:{backgroundColor:r.color.surfaceElevated,paddingHorizontal:12,paddingVertical:8,fontWeight:"600",fontSize:12,color:r.color.textSecondary,borderBottomWidth:1,borderBottomColor:r.color.border,letterSpacing:.5,textTransform:"uppercase",fontFamily:b},infoBody:{padding:12,gap:10},viewerBody:{backgroundColor:r.color.bg,minHeight:60},primitiveText:{color:r.color.text,fontSize:12,fontFamily:b,padding:14},valueTypeText:{color:r.color.textTertiary,fontSize:10,fontFamily:b,marginLeft:6},inlineLinkButton:{marginLeft:4},inlineLinkText:{fontSize:10,fontWeight:"600",fontFamily:b}});
|
|
@@ -1,39 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* JotaiEventFilterView
|
|
5
|
-
*
|
|
6
|
-
* The shared `EntityFilterView` with jotai's nouns: watched atoms you can
|
|
7
|
-
* tap to hide, active atom-name patterns, and how it works.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useMemo } from "react";
|
|
11
|
-
import { EntityFilterView } from "@buoy-gg/shared-ui";
|
|
12
|
-
import { JOTAI_TEST_ID, slugForTestID } from "../utils/testIDs";
|
|
13
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
-
const EXAMPLES = ["• count -> hides countAtom from atoms & events", "• auth -> hides authAtom, authStatusAtom", "• user -> hides userAtom from both tabs"];
|
|
15
|
-
export function JotaiEventFilterView({
|
|
16
|
-
ignoredPatterns,
|
|
17
|
-
onTogglePattern,
|
|
18
|
-
onAddPattern,
|
|
19
|
-
atoms
|
|
20
|
-
}) {
|
|
21
|
-
const entities = useMemo(() => atoms.map(atom => ({
|
|
22
|
-
name: atom.label,
|
|
23
|
-
color: atom.color
|
|
24
|
-
})), [atoms]);
|
|
25
|
-
return /*#__PURE__*/_jsx(EntityFilterView, {
|
|
26
|
-
entities: entities,
|
|
27
|
-
ignoredPatterns: ignoredPatterns,
|
|
28
|
-
onTogglePattern: onTogglePattern,
|
|
29
|
-
onAddPattern: onAddPattern,
|
|
30
|
-
entityNoun: "ATOMS",
|
|
31
|
-
entitySingular: "atom",
|
|
32
|
-
entityKey: "atom",
|
|
33
|
-
testIDPrefix: JOTAI_TEST_ID,
|
|
34
|
-
slug: slugForTestID,
|
|
35
|
-
howItWorks: "Patterns hide matching atoms from both the Atoms tab and the Events tab. Tap an atom above to quickly add it as a filter.",
|
|
36
|
-
examples: EXAMPLES,
|
|
37
|
-
emptyMessage: "No atoms are being watched yet."
|
|
38
|
-
});
|
|
39
|
-
}
|
|
1
|
+
"use strict";import{useMemo as i}from"react";import{EntityFilterView as n}from"@buoy-gg/shared-ui";import{JOTAI_TEST_ID as m,slugForTestID as u}from"../utils/testIDs";import{jsx as h}from"react/jsx-runtime";const l=["\u2022 count -> hides countAtom from atoms & events","\u2022 auth -> hides authAtom, authStatusAtom","\u2022 user -> hides userAtom from both tabs"];export function JotaiEventFilterView({ignoredPatterns:o,onTogglePattern:a,onAddPattern:s,atoms:t}){const r=i(()=>t.map(e=>({name:e.label,color:e.color})),[t]);return h(n,{entities:r,ignoredPatterns:o,onTogglePattern:a,onAddPattern:s,entityNoun:"ATOMS",entitySingular:"atom",entityKey:"atom",testIDPrefix:m,slug:u,howItWorks:"Patterns hide matching atoms from both the Atoms tab and the Events tab. Tap an atom above to quickly add it as a filter.",examples:l,emptyMessage:"No atoms are being watched yet."})}
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* JotaiIcon — re-exported from the Buoy Icon Format.
|
|
5
|
-
*
|
|
6
|
-
* The artwork lives in shared/icons/jotai.json and is generated into
|
|
7
|
-
* @buoy-gg/floating-tools-core, so React Native, the desktop dashboard and
|
|
8
|
-
* Flutter all render the same drawing. Edit the JSON, then `pnpm icons`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export { JotaiIcon, JOTAI_ICON_COLOR } from "@buoy-gg/shared-ui";
|
|
1
|
+
"use strict";export{JotaiIcon,JOTAI_ICON_COLOR}from"@buoy-gg/shared-ui";
|