@buoy-gg/jotai 2.1.11 → 2.1.13

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.
Files changed (42) hide show
  1. package/LICENSE +58 -0
  2. package/lib/commonjs/index.js +1 -91
  3. package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +1 -300
  4. package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +1 -113
  5. package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +1 -754
  6. package/lib/commonjs/jotai/components/JotaiEventFilterView.js +1 -305
  7. package/lib/commonjs/jotai/components/JotaiIcon.js +1 -35
  8. package/lib/commonjs/jotai/components/JotaiModal.js +1 -567
  9. package/lib/commonjs/jotai/components/index.js +1 -59
  10. package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +1 -83
  11. package/lib/commonjs/jotai/index.js +1 -85
  12. package/lib/commonjs/jotai/utils/jotaiStateStore.js +1 -322
  13. package/lib/commonjs/jotai/utils/watchAtoms.js +1 -149
  14. package/lib/commonjs/preset.js +1 -98
  15. package/lib/module/index.js +1 -74
  16. package/lib/module/jotai/components/JotaiAtomBrowser.js +1 -296
  17. package/lib/module/jotai/components/JotaiAtomChangeItem.js +1 -109
  18. package/lib/module/jotai/components/JotaiAtomDetailContent.js +1 -748
  19. package/lib/module/jotai/components/JotaiEventFilterView.js +1 -301
  20. package/lib/module/jotai/components/JotaiIcon.js +1 -31
  21. package/lib/module/jotai/components/JotaiModal.js +1 -563
  22. package/lib/module/jotai/components/index.js +1 -8
  23. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +1 -79
  24. package/lib/module/jotai/index.js +1 -10
  25. package/lib/module/jotai/utils/jotaiStateStore.js +1 -318
  26. package/lib/module/jotai/utils/watchAtoms.js +1 -144
  27. package/lib/module/preset.js +1 -94
  28. package/package.json +10 -10
  29. package/lib/typescript/index.d.ts.map +0 -1
  30. package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +0 -1
  31. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +0 -1
  32. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +0 -1
  33. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +0 -1
  34. package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +0 -1
  35. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +0 -1
  36. package/lib/typescript/jotai/components/index.d.ts.map +0 -1
  37. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +0 -1
  38. package/lib/typescript/jotai/index.d.ts.map +0 -1
  39. package/lib/typescript/jotai/types/index.d.ts.map +0 -1
  40. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +0 -1
  41. package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +0 -1
  42. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,563 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Main Jotai DevTools modal
5
- *
6
- * Two tabs mirroring the Zustand DevTools pattern:
7
- * - Atoms tab: Browse all registered atoms and their current value
8
- * - Events tab: Live atom change monitoring with diffs
9
- */
10
-
11
- import { useState, useMemo, useRef, useCallback } from "react";
12
- import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity } from "react-native";
13
- import { JsModal, ModalHeader, TabSelector, macOSColors, buoyColors, Search, Filter, Power, X, Box, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider } from "@buoy-gg/shared-ui";
14
- import { useIsPro } from "@buoy-gg/license";
15
- import { useJotaiAtomChanges } from "../hooks/useJotaiAtomChanges";
16
- import { JotaiAtomChangeItem } from "./JotaiAtomChangeItem";
17
- import { JotaiAtomDetailContent, JotaiAtomDetailFooter } from "./JotaiAtomDetailContent";
18
- import { JotaiAtomBrowser } from "./JotaiAtomBrowser";
19
- import { JotaiEventFilterView } from "./JotaiEventFilterView";
20
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
21
- const FREE_TIER_CHANGE_LIMIT = 25;
22
- function EventsEmptyState({
23
- isEnabled
24
- }) {
25
- return /*#__PURE__*/_jsxs(View, {
26
- style: styles.emptyState,
27
- children: [/*#__PURE__*/_jsx(Box, {
28
- size: 32,
29
- color: macOSColors.text.muted
30
- }), /*#__PURE__*/_jsx(Text, {
31
- style: styles.emptyTitle,
32
- children: "No atom changes"
33
- }), /*#__PURE__*/_jsx(Text, {
34
- style: styles.emptyText,
35
- children: isEnabled ? "Atom changes will appear here as values update." : "Enable capture to start recording atom changes"
36
- })]
37
- });
38
- }
39
- export function JotaiModal({
40
- visible,
41
- onClose,
42
- onBack,
43
- onMinimize,
44
- enableSharedModalDimensions = false
45
- }) {
46
- const isPro = useIsPro();
47
- const [showProModal, setShowProModal] = useState(false);
48
- const [activeTab, setActiveTab] = useState("atoms");
49
- const [selectedHistoryAtom, setSelectedHistoryAtom] = useState(null);
50
- const [showFilters, setShowFilters] = useState(false);
51
- const [ignoredPatterns, setIgnoredPatterns] = useState(new Set());
52
- const {
53
- filteredChanges,
54
- filter,
55
- setFilter,
56
- atoms,
57
- clearChanges,
58
- isEnabled,
59
- toggleCapture
60
- } = useJotaiAtomChanges();
61
-
62
- // Apply ignored patterns on top of filteredChanges
63
- const displayedChanges = useMemo(() => {
64
- if (ignoredPatterns.size === 0) return filteredChanges;
65
- return filteredChanges.filter(c => !Array.from(ignoredPatterns).some(p => c.atomLabel.toLowerCase().includes(p.toLowerCase())));
66
- }, [filteredChanges, ignoredPatterns]);
67
-
68
- // Apply ignored patterns to atoms list
69
- const displayedAtoms = useMemo(() => {
70
- if (ignoredPatterns.size === 0) return atoms;
71
- return atoms.filter(a => !Array.from(ignoredPatterns).some(p => a.label.toLowerCase().includes(p.toLowerCase())));
72
- }, [atoms, ignoredPatterns]);
73
-
74
- // Free-tier gate
75
- const visibleChanges = useMemo(() => {
76
- if (isPro) return displayedChanges;
77
- return displayedChanges.slice(0, FREE_TIER_CHANGE_LIMIT);
78
- }, [displayedChanges, isPro]);
79
- const lockedChangeCount = useMemo(() => {
80
- if (isPro) return 0;
81
- return Math.max(0, displayedChanges.length - FREE_TIER_CHANGE_LIMIT);
82
- }, [displayedChanges.length, isPro]);
83
- const hasLockedChanges = lockedChangeCount > 0;
84
-
85
- // When viewing a single atom's history, scope the list
86
- const activeChanges = useMemo(() => {
87
- if (selectedHistoryAtom) {
88
- return visibleChanges.filter(c => c.atomLabel === selectedHistoryAtom);
89
- }
90
- return visibleChanges;
91
- }, [visibleChanges, selectedHistoryAtom]);
92
- const [selectedChangeId, setSelectedChangeId] = useState(null);
93
- const selectedChangeIndex = useMemo(() => {
94
- if (selectedChangeId === null) return null;
95
- const idx = activeChanges.findIndex(c => c.id === selectedChangeId);
96
- return idx >= 0 ? idx : null;
97
- }, [selectedChangeId, activeChanges]);
98
- const selectedChange = selectedChangeIndex !== null ? activeChanges[selectedChangeIndex] : null;
99
-
100
- // Search state
101
- const [searchText, setSearchText] = useState("");
102
- const [isSearchActive, setIsSearchActive] = useState(false);
103
- const searchInputRef = useRef(null);
104
- const flatListRef = useRef(null);
105
- const handleModeChange = useCallback(_mode => {}, []);
106
- const handleSearch = text => {
107
- setSearchText(text);
108
- if (activeTab === "events") {
109
- setFilter(prev => ({
110
- ...prev,
111
- searchText: text
112
- }));
113
- }
114
- };
115
- const handleChangePress = useCallback(change => {
116
- setSelectedChangeId(change.id);
117
- }, []);
118
- const handleUpgradePress = useCallback(() => {
119
- setShowProModal(true);
120
- }, []);
121
- const handleBack = useCallback(() => {
122
- setSelectedChangeId(null);
123
- }, []);
124
- const handleIndexChange = useCallback(newIndex => {
125
- const change = activeChanges[newIndex];
126
- if (change) setSelectedChangeId(change.id);
127
- }, [activeChanges]);
128
- const handleViewHistory = useCallback(atomLabel => {
129
- setSelectedHistoryAtom(atomLabel);
130
- setSelectedChangeId(null);
131
- }, []);
132
- const handleBackFromHistory = useCallback(() => {
133
- setSelectedHistoryAtom(null);
134
- setSelectedChangeId(null);
135
- }, []);
136
- const getAtomsSnapshot = useCallback(() => {
137
- return displayedAtoms.reduce((acc, atom) => {
138
- try {
139
- const value = atom.getValue();
140
- acc[atom.label] = truncatePayload(value);
141
- } catch {
142
- acc[atom.label] = null;
143
- }
144
- return acc;
145
- }, {});
146
- }, [displayedAtoms]);
147
- const getEventsSnapshot = useCallback(() => {
148
- return displayedChanges.map(change => ({
149
- id: change.id,
150
- atomLabel: change.atomLabel,
151
- timestamp: change.timestamp,
152
- prevValue: truncatePayload(change.prevValue),
153
- nextValue: truncatePayload(change.nextValue),
154
- hasValueChange: change.hasValueChange,
155
- changedKeys: change.changedKeys
156
- }));
157
- }, [displayedChanges]);
158
- const getHistorySnapshot = useCallback(() => {
159
- return activeChanges.map(change => ({
160
- id: change.id,
161
- atomLabel: change.atomLabel,
162
- timestamp: change.timestamp,
163
- prevValue: truncatePayload(change.prevValue),
164
- nextValue: truncatePayload(change.nextValue),
165
- hasValueChange: change.hasValueChange,
166
- changedKeys: change.changedKeys
167
- }));
168
- }, [activeChanges]);
169
- const handleTogglePattern = useCallback(pattern => {
170
- setIgnoredPatterns(prev => {
171
- const next = new Set(prev);
172
- if (next.has(pattern)) next.delete(pattern);else next.add(pattern);
173
- return next;
174
- });
175
- }, []);
176
- const handleAddPattern = useCallback(pattern => {
177
- setIgnoredPatterns(prev => new Set([...prev, pattern]));
178
- }, []);
179
- const handleTabChange = useCallback(tab => {
180
- setActiveTab(tab);
181
- setSelectedHistoryAtom(null);
182
- setSelectedChangeId(null);
183
- setShowFilters(false);
184
- if (searchText) {
185
- setSearchText("");
186
- setFilter(prev => ({
187
- ...prev,
188
- searchText: ""
189
- }));
190
- }
191
- setIsSearchActive(false);
192
- }, [searchText, setFilter]);
193
- const keyExtractor = useCallback(item => item.id, []);
194
- const renderItem = useCallback(({
195
- item
196
- }) => /*#__PURE__*/_jsx(JotaiAtomChangeItem, {
197
- change: item,
198
- onPress: handleChangePress
199
- }), [handleChangePress]);
200
-
201
- // ---- Header ----
202
-
203
- const hasActiveFilters = ignoredPatterns.size > 0;
204
- const renderHeaderContent = () => {
205
- if (showFilters) {
206
- return /*#__PURE__*/_jsxs(ModalHeader, {
207
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
208
- onBack: () => setShowFilters(false)
209
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
210
- title: "Filters",
211
- centered: true
212
- })]
213
- });
214
- }
215
- if (selectedChange) {
216
- return /*#__PURE__*/_jsxs(ModalHeader, {
217
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
218
- onBack: handleBack
219
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
220
- title: `${selectedChange.atomLabel}/${selectedChange.category}`,
221
- centered: true
222
- })]
223
- });
224
- }
225
- if (selectedHistoryAtom) {
226
- return /*#__PURE__*/_jsxs(ModalHeader, {
227
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
228
- onBack: handleBackFromHistory
229
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
230
- title: `${selectedHistoryAtom} History`,
231
- centered: true
232
- }), /*#__PURE__*/_jsx(ModalHeader.Actions, {
233
- children: /*#__PURE__*/_jsx(ToolbarCopyButton, {
234
- value: getHistorySnapshot,
235
- disabled: activeChanges.length === 0,
236
- buttonStyle: styles.headerActionButton
237
- })
238
- })]
239
- });
240
- }
241
- return /*#__PURE__*/_jsxs(ModalHeader, {
242
- children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
243
- onBack: onBack
244
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
245
- title: "",
246
- children: isSearchActive ? /*#__PURE__*/_jsxs(View, {
247
- style: styles.headerSearchContainer,
248
- children: [/*#__PURE__*/_jsx(Search, {
249
- size: 14,
250
- color: macOSColors.text.secondary
251
- }), /*#__PURE__*/_jsx(TextInput, {
252
- ref: searchInputRef,
253
- style: styles.headerSearchInput,
254
- placeholder: activeTab === "atoms" ? "Search atoms..." : "Search events...",
255
- placeholderTextColor: macOSColors.text.muted,
256
- value: searchText,
257
- onChangeText: handleSearch,
258
- onSubmitEditing: () => setIsSearchActive(false),
259
- onBlur: () => setIsSearchActive(false),
260
- autoCapitalize: "none",
261
- autoCorrect: false,
262
- returnKeyType: "search"
263
- }), searchText.length > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
264
- onPress: () => {
265
- handleSearch("");
266
- setIsSearchActive(false);
267
- },
268
- style: styles.headerSearchClear,
269
- children: /*#__PURE__*/_jsx(X, {
270
- size: 14,
271
- color: macOSColors.text.secondary
272
- })
273
- })]
274
- }) : /*#__PURE__*/_jsx(TabSelector, {
275
- tabs: [{
276
- key: "atoms",
277
- label: `Atoms${displayedAtoms.length > 0 ? ` (${displayedAtoms.length})` : ""}`
278
- }, {
279
- key: "events",
280
- label: `Events${displayedChanges.length > 0 ? ` (${displayedChanges.length})` : ""}`
281
- }],
282
- activeTab: activeTab,
283
- onTabChange: handleTabChange
284
- })
285
- }), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
286
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
287
- onPress: () => setIsSearchActive(true),
288
- style: styles.headerActionButton,
289
- children: /*#__PURE__*/_jsx(Search, {
290
- size: 14,
291
- color: macOSColors.text.secondary
292
- })
293
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
294
- onPress: () => setShowFilters(true),
295
- style: [styles.headerActionButton, hasActiveFilters && styles.headerActionButtonActive],
296
- children: /*#__PURE__*/_jsx(Filter, {
297
- size: 14,
298
- color: hasActiveFilters ? macOSColors.semantic.debug : macOSColors.text.secondary
299
- })
300
- }), activeTab === "atoms" && /*#__PURE__*/_jsx(ToolbarCopyButton, {
301
- value: getAtomsSnapshot,
302
- disabled: displayedAtoms.length === 0,
303
- buttonStyle: styles.headerActionButton
304
- }), activeTab === "events" && /*#__PURE__*/_jsxs(_Fragment, {
305
- children: [/*#__PURE__*/_jsx(ToolbarCopyButton, {
306
- value: getEventsSnapshot,
307
- disabled: displayedChanges.length === 0,
308
- buttonStyle: styles.headerActionButton
309
- }), /*#__PURE__*/_jsx(PowerToggleButton, {
310
- isEnabled: isEnabled,
311
- onToggle: toggleCapture,
312
- accessibilityLabel: "Toggle Jotai atom capture"
313
- }), /*#__PURE__*/_jsx(ToolbarClearButton, {
314
- onPress: clearChanges,
315
- disabled: displayedChanges.length === 0,
316
- buttonStyle: styles.headerActionButton
317
- })]
318
- })]
319
- })]
320
- });
321
- };
322
-
323
- // ---- Content ----
324
-
325
- const renderContent = () => {
326
- if (showFilters) {
327
- return /*#__PURE__*/_jsx(JotaiEventFilterView, {
328
- ignoredPatterns: ignoredPatterns,
329
- onTogglePattern: handleTogglePattern,
330
- onAddPattern: handleAddPattern,
331
- atoms: atoms
332
- });
333
- }
334
- if (selectedChange && selectedChangeIndex !== null) {
335
- return /*#__PURE__*/_jsx(JotaiAtomDetailContent, {
336
- change: selectedChange,
337
- changes: activeChanges,
338
- selectedIndex: selectedChangeIndex,
339
- onIndexChange: handleIndexChange,
340
- disableInternalFooter: true
341
- });
342
- }
343
- if (selectedHistoryAtom) {
344
- return activeChanges.length > 0 ? /*#__PURE__*/_jsx(FlatList, {
345
- data: activeChanges,
346
- renderItem: renderItem,
347
- keyExtractor: keyExtractor,
348
- contentContainerStyle: styles.listContent,
349
- showsVerticalScrollIndicator: true,
350
- removeClippedSubviews: true,
351
- initialNumToRender: 15,
352
- maxToRenderPerBatch: 10,
353
- windowSize: 10,
354
- scrollEnabled: false
355
- }) : /*#__PURE__*/_jsxs(View, {
356
- style: styles.emptyState,
357
- children: [/*#__PURE__*/_jsx(Box, {
358
- size: 32,
359
- color: macOSColors.text.muted
360
- }), /*#__PURE__*/_jsx(Text, {
361
- style: styles.emptyTitle,
362
- children: "No history yet"
363
- }), /*#__PURE__*/_jsxs(Text, {
364
- style: styles.emptyText,
365
- children: ["Changes for ", selectedHistoryAtom, " will appear here."]
366
- })]
367
- });
368
- }
369
- if (activeTab === "atoms") {
370
- return /*#__PURE__*/_jsx(JotaiAtomBrowser, {
371
- atoms: displayedAtoms,
372
- searchQuery: searchText,
373
- onViewHistory: handleViewHistory
374
- });
375
- }
376
-
377
- // Events tab
378
- return /*#__PURE__*/_jsxs(_Fragment, {
379
- children: [!isEnabled && /*#__PURE__*/_jsxs(View, {
380
- style: styles.disabledBanner,
381
- children: [/*#__PURE__*/_jsx(Power, {
382
- size: 14,
383
- color: macOSColors.semantic.warning
384
- }), /*#__PURE__*/_jsx(Text, {
385
- style: styles.disabledText,
386
- children: "Atom capture is disabled"
387
- })]
388
- }), hasLockedChanges && /*#__PURE__*/_jsxs(TouchableOpacity, {
389
- style: styles.lockedBanner,
390
- onPress: handleUpgradePress,
391
- activeOpacity: 0.8,
392
- children: [/*#__PURE__*/_jsxs(Text, {
393
- style: styles.lockedText,
394
- children: [lockedChangeCount, " older", " ", lockedChangeCount === 1 ? "change" : "changes", " locked"]
395
- }), /*#__PURE__*/_jsx(View, {
396
- style: styles.upgradeBadge,
397
- children: /*#__PURE__*/_jsx(Text, {
398
- style: styles.upgradeBadgeText,
399
- children: "UPGRADE"
400
- })
401
- })]
402
- }), visibleChanges.length > 0 ? /*#__PURE__*/_jsx(FlatList, {
403
- ref: flatListRef,
404
- data: visibleChanges,
405
- renderItem: renderItem,
406
- keyExtractor: keyExtractor,
407
- contentContainerStyle: styles.listContent,
408
- showsVerticalScrollIndicator: true,
409
- removeClippedSubviews: true,
410
- initialNumToRender: 15,
411
- maxToRenderPerBatch: 10,
412
- windowSize: 10,
413
- scrollEnabled: false
414
- }) : /*#__PURE__*/_jsx(EventsEmptyState, {
415
- isEnabled: isEnabled
416
- })]
417
- });
418
- };
419
- const persistenceKey = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : "buoy-jotai-modal";
420
- if (!visible) return null;
421
- const footerNode = selectedChange && selectedChangeIndex !== null ? /*#__PURE__*/_jsx(JotaiAtomDetailFooter, {
422
- change: selectedChange,
423
- changes: activeChanges,
424
- selectedIndex: selectedChangeIndex,
425
- onIndexChange: handleIndexChange
426
- }) : null;
427
- const footerHeight = selectedChange && activeChanges.length > 1 ? 68 : 0;
428
- return /*#__PURE__*/_jsxs(TickProvider, {
429
- children: [/*#__PURE__*/_jsx(JsModal, {
430
- visible: visible,
431
- onClose: onClose,
432
- onMinimize: onMinimize,
433
- persistenceKey: persistenceKey,
434
- header: {
435
- showToggleButton: true,
436
- customContent: renderHeaderContent()
437
- },
438
- onModeChange: handleModeChange,
439
- enablePersistence: true,
440
- initialMode: "bottomSheet",
441
- enableGlitchEffects: true,
442
- styles: {},
443
- footer: footerNode,
444
- footerHeight: footerHeight,
445
- children: /*#__PURE__*/_jsx(View, {
446
- style: styles.container,
447
- children: renderContent()
448
- })
449
- }), /*#__PURE__*/_jsx(ProUpgradeModal, {
450
- visible: showProModal,
451
- onClose: () => setShowProModal(false),
452
- featureName: "Full Atom History"
453
- })]
454
- });
455
- }
456
- const styles = StyleSheet.create({
457
- container: {
458
- flex: 1,
459
- backgroundColor: macOSColors.background.base
460
- },
461
- headerSearchContainer: {
462
- flexDirection: "row",
463
- alignItems: "center",
464
- backgroundColor: macOSColors.background.input,
465
- borderRadius: 10,
466
- borderWidth: 1,
467
- borderColor: macOSColors.border.default,
468
- paddingHorizontal: 12,
469
- paddingVertical: 5
470
- },
471
- headerSearchInput: {
472
- flex: 1,
473
- color: macOSColors.text.primary,
474
- fontSize: 13,
475
- marginLeft: 6,
476
- paddingVertical: 2
477
- },
478
- headerSearchClear: {
479
- marginLeft: 6,
480
- padding: 4
481
- },
482
- headerActionButton: {
483
- width: 32,
484
- height: 32,
485
- borderRadius: 8,
486
- backgroundColor: macOSColors.background.hover,
487
- borderWidth: 1,
488
- borderColor: macOSColors.border.default,
489
- alignItems: "center",
490
- justifyContent: "center"
491
- },
492
- headerActionButtonActive: {
493
- backgroundColor: macOSColors.semantic.infoBackground
494
- },
495
- disabledBanner: {
496
- flexDirection: "row",
497
- alignItems: "center",
498
- gap: 8,
499
- padding: 10,
500
- marginHorizontal: 12,
501
- marginTop: 8,
502
- backgroundColor: macOSColors.semantic.warningBackground,
503
- borderRadius: 8,
504
- borderWidth: 1,
505
- borderColor: macOSColors.semantic.warning + "20"
506
- },
507
- disabledText: {
508
- color: macOSColors.semantic.warning,
509
- fontSize: 11,
510
- flex: 1
511
- },
512
- listContent: {
513
- paddingTop: 8,
514
- paddingBottom: 20
515
- },
516
- emptyState: {
517
- alignItems: "center",
518
- paddingVertical: 40
519
- },
520
- emptyTitle: {
521
- color: macOSColors.text.primary,
522
- fontSize: 14,
523
- fontWeight: "600",
524
- marginTop: 12,
525
- marginBottom: 6
526
- },
527
- emptyText: {
528
- color: macOSColors.text.muted,
529
- fontSize: 12,
530
- textAlign: "center",
531
- lineHeight: 18
532
- },
533
- lockedBanner: {
534
- flexDirection: "row",
535
- alignItems: "center",
536
- gap: 8,
537
- padding: 10,
538
- marginHorizontal: 12,
539
- marginTop: 8,
540
- backgroundColor: buoyColors.primary + "15",
541
- borderRadius: 8,
542
- borderWidth: 1,
543
- borderColor: buoyColors.primary + "33"
544
- },
545
- lockedText: {
546
- color: buoyColors.primary,
547
- fontSize: 11,
548
- fontWeight: "500",
549
- flex: 1
550
- },
551
- upgradeBadge: {
552
- backgroundColor: buoyColors.primary,
553
- paddingHorizontal: 8,
554
- paddingVertical: 3,
555
- borderRadius: 4
556
- },
557
- upgradeBadgeText: {
558
- color: "#fff",
559
- fontSize: 9,
560
- fontWeight: "700",
561
- letterSpacing: 0.5
562
- }
563
- });
1
+ "use strict";import{useState,useMemo,useRef,useCallback}from"react";import{View,Text,StyleSheet,FlatList,TextInput,TouchableOpacity}from"react-native";import{JsModal,ModalHeader,TabSelector,macOSColors,buoyColors,Search,Filter,Power,X,Box,devToolsStorageKeys,ProUpgradeModal,PowerToggleButton,ToolbarCopyButton,ToolbarClearButton,truncatePayload,TickProvider}from"@buoy-gg/shared-ui";import{useIsPro}from"@buoy-gg/license";import{useJotaiAtomChanges}from"../hooks/useJotaiAtomChanges";import{JotaiAtomChangeItem}from"./JotaiAtomChangeItem";import{JotaiAtomDetailContent,JotaiAtomDetailFooter}from"./JotaiAtomDetailContent";import{JotaiAtomBrowser}from"./JotaiAtomBrowser";import{JotaiEventFilterView}from"./JotaiEventFilterView";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";const FREE_TIER_CHANGE_LIMIT=25;function EventsEmptyState({isEnabled:e}){return _jsxs(View,{style:styles.emptyState,children:[_jsx(Box,{size:32,color:macOSColors.text.muted}),_jsx(Text,{style:styles.emptyTitle,children:"No atom changes"}),_jsx(Text,{style:styles.emptyText,children:e?"Atom changes will appear here as values update.":"Enable capture to start recording atom changes"})]})}export function JotaiModal({visible:e,onClose:t,onBack:o,onMinimize:a,enableSharedModalDimensions:l=!1}){const r=useIsPro(),[s,n]=useState(!1),[i,d]=useState("atoms"),[c,u]=useState(null),[m,h]=useState(!1),[g,y]=useState(new Set),{filteredChanges:x,filter:b,setFilter:C,atoms:p,clearChanges:S,isEnabled:_,toggleCapture:j}=useJotaiAtomChanges(),T=useMemo(()=>0===g.size?x:x.filter(e=>!Array.from(g).some(t=>e.atomLabel.toLowerCase().includes(t.toLowerCase()))),[x,g]),f=useMemo(()=>0===g.size?p:p.filter(e=>!Array.from(g).some(t=>e.label.toLowerCase().includes(t.toLowerCase()))),[p,g]),k=useMemo(()=>r?T:T.slice(0,25),[T,r]),w=useMemo(()=>r?0:Math.max(0,T.length-25),[T.length,r]),B=w>0,v=useMemo(()=>c?k.filter(e=>e.atomLabel===c):k,[k,c]),[A,M]=useState(null),V=useMemo(()=>{if(null===A)return null;const e=v.findIndex(e=>e.id===A);return e>=0?e:null},[A,v]),P=null!==V?v[V]:null,[O,I]=useState(""),[z,E]=useState(!1),H=useRef(null),J=useRef(null),F=useCallback(e=>{},[]),L=e=>{I(e),"events"===i&&C(t=>({...t,searchText:e}))},R=useCallback(e=>{M(e.id)},[]),D=useCallback(()=>{n(!0)},[]),N=useCallback(()=>{M(null)},[]),K=useCallback(e=>{const t=v[e];t&&M(t.id)},[v]),W=useCallback(e=>{u(e),M(null)},[]),$=useCallback(()=>{u(null),M(null)},[]),G=useCallback(()=>f.reduce((e,t)=>{try{const o=t.getValue();e[t.label]=truncatePayload(o)}catch{e[t.label]=null}return e},{}),[f]),U=useCallback(()=>T.map(e=>({id:e.id,atomLabel:e.atomLabel,timestamp:e.timestamp,prevValue:truncatePayload(e.prevValue),nextValue:truncatePayload(e.nextValue),hasValueChange:e.hasValueChange,changedKeys:e.changedKeys})),[T]),Q=useCallback(()=>v.map(e=>({id:e.id,atomLabel:e.atomLabel,timestamp:e.timestamp,prevValue:truncatePayload(e.prevValue),nextValue:truncatePayload(e.nextValue),hasValueChange:e.hasValueChange,changedKeys:e.changedKeys})),[v]),q=useCallback(e=>{y(t=>{const o=new Set(t);return o.has(e)?o.delete(e):o.add(e),o})},[]),Y=useCallback(e=>{y(t=>new Set([...t,e]))},[]),Z=useCallback(e=>{d(e),u(null),M(null),h(!1),O&&(I(""),C(e=>({...e,searchText:""}))),E(!1)},[O,C]),ee=useCallback(e=>e.id,[]),te=useCallback(({item:e})=>_jsx(JotaiAtomChangeItem,{change:e,onPress:R}),[R]),oe=g.size>0,ae=l?devToolsStorageKeys.modal.root():"buoy-jotai-modal";if(!e)return null;const le=P&&null!==V?_jsx(JotaiAtomDetailFooter,{change:P,changes:v,selectedIndex:V,onIndexChange:K}):null,re=P&&v.length>1?68:0;return _jsxs(TickProvider,{children:[_jsx(JsModal,{visible:e,onClose:t,onMinimize:a,persistenceKey:ae,header:{showToggleButton:!0,customContent:_jsxs(ModalHeader,m?{children:[_jsx(ModalHeader.Navigation,{onBack:()=>h(!1)}),_jsx(ModalHeader.Content,{title:"Filters",centered:!0})]}:P?{children:[_jsx(ModalHeader.Navigation,{onBack:N}),_jsx(ModalHeader.Content,{title:`${P.atomLabel}/${P.category}`,centered:!0})]}:c?{children:[_jsx(ModalHeader.Navigation,{onBack:$}),_jsx(ModalHeader.Content,{title:`${c} History`,centered:!0}),_jsx(ModalHeader.Actions,{children:_jsx(ToolbarCopyButton,{value:Q,disabled:0===v.length,buttonStyle:styles.headerActionButton})})]}:{children:[o&&_jsx(ModalHeader.Navigation,{onBack:o}),_jsx(ModalHeader.Content,{title:"",children:z?_jsxs(View,{style:styles.headerSearchContainer,children:[_jsx(Search,{size:14,color:macOSColors.text.secondary}),_jsx(TextInput,{ref:H,style:styles.headerSearchInput,placeholder:"atoms"===i?"Search atoms...":"Search events...",placeholderTextColor:macOSColors.text.muted,value:O,onChangeText:L,onSubmitEditing:()=>E(!1),onBlur:()=>E(!1),autoCapitalize:"none",autoCorrect:!1,returnKeyType:"search"}),O.length>0&&_jsx(TouchableOpacity,{onPress:()=>{L(""),E(!1)},style:styles.headerSearchClear,children:_jsx(X,{size:14,color:macOSColors.text.secondary})})]}):_jsx(TabSelector,{tabs:[{key:"atoms",label:"Atoms"+(f.length>0?` (${f.length})`:"")},{key:"events",label:"Events"+(T.length>0?` (${T.length})`:"")}],activeTab:i,onTabChange:Z})}),_jsxs(ModalHeader.Actions,{children:[_jsx(TouchableOpacity,{onPress:()=>E(!0),style:styles.headerActionButton,children:_jsx(Search,{size:14,color:macOSColors.text.secondary})}),_jsx(TouchableOpacity,{onPress:()=>h(!0),style:[styles.headerActionButton,oe&&styles.headerActionButtonActive],children:_jsx(Filter,{size:14,color:oe?macOSColors.semantic.debug:macOSColors.text.secondary})}),"atoms"===i&&_jsx(ToolbarCopyButton,{value:G,disabled:0===f.length,buttonStyle:styles.headerActionButton}),"events"===i&&_jsxs(_Fragment,{children:[_jsx(ToolbarCopyButton,{value:U,disabled:0===T.length,buttonStyle:styles.headerActionButton}),_jsx(PowerToggleButton,{isEnabled:_,onToggle:j,accessibilityLabel:"Toggle Jotai atom capture"}),_jsx(ToolbarClearButton,{onPress:S,disabled:0===T.length,buttonStyle:styles.headerActionButton})]})]})]})},onModeChange:F,enablePersistence:!0,initialMode:"bottomSheet",enableGlitchEffects:!0,styles:{},footer:le,footerHeight:re,children:_jsx(View,{style:styles.container,children:m?_jsx(JotaiEventFilterView,{ignoredPatterns:g,onTogglePattern:q,onAddPattern:Y,atoms:p}):P&&null!==V?_jsx(JotaiAtomDetailContent,{change:P,changes:v,selectedIndex:V,onIndexChange:K,disableInternalFooter:!0}):c?v.length>0?_jsx(FlatList,{data:v,renderItem:te,keyExtractor:ee,contentContainerStyle:styles.listContent,showsVerticalScrollIndicator:!0,removeClippedSubviews:!0,initialNumToRender:15,maxToRenderPerBatch:10,windowSize:10,scrollEnabled:!1}):_jsxs(View,{style:styles.emptyState,children:[_jsx(Box,{size:32,color:macOSColors.text.muted}),_jsx(Text,{style:styles.emptyTitle,children:"No history yet"}),_jsxs(Text,{style:styles.emptyText,children:["Changes for ",c," will appear here."]})]}):"atoms"===i?_jsx(JotaiAtomBrowser,{atoms:f,searchQuery:O,onViewHistory:W}):_jsxs(_Fragment,{children:[!_&&_jsxs(View,{style:styles.disabledBanner,children:[_jsx(Power,{size:14,color:macOSColors.semantic.warning}),_jsx(Text,{style:styles.disabledText,children:"Atom capture is disabled"})]}),B&&_jsxs(TouchableOpacity,{style:styles.lockedBanner,onPress:D,activeOpacity:.8,children:[_jsxs(Text,{style:styles.lockedText,children:[w," older"," ",1===w?"change":"changes"," locked"]}),_jsx(View,{style:styles.upgradeBadge,children:_jsx(Text,{style:styles.upgradeBadgeText,children:"UPGRADE"})})]}),k.length>0?_jsx(FlatList,{ref:J,data:k,renderItem:te,keyExtractor:ee,contentContainerStyle:styles.listContent,showsVerticalScrollIndicator:!0,removeClippedSubviews:!0,initialNumToRender:15,maxToRenderPerBatch:10,windowSize:10,scrollEnabled:!1}):_jsx(EventsEmptyState,{isEnabled:_})]})})}),_jsx(ProUpgradeModal,{visible:s,onClose:()=>n(!1),featureName:"Full Atom History"})]})}const styles=StyleSheet.create({container:{flex:1,backgroundColor:macOSColors.background.base},headerSearchContainer:{flexDirection:"row",alignItems:"center",backgroundColor:macOSColors.background.input,borderRadius:10,borderWidth:1,borderColor:macOSColors.border.default,paddingHorizontal:12,paddingVertical:5},headerSearchInput:{flex:1,color:macOSColors.text.primary,fontSize:13,marginLeft:6,paddingVertical:2},headerSearchClear:{marginLeft:6,padding:4},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:macOSColors.background.hover,borderWidth:1,borderColor:macOSColors.border.default,alignItems:"center",justifyContent:"center"},headerActionButtonActive:{backgroundColor:macOSColors.semantic.infoBackground},disabledBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:macOSColors.semantic.warningBackground,borderRadius:8,borderWidth:1,borderColor:macOSColors.semantic.warning+"20"},disabledText:{color:macOSColors.semantic.warning,fontSize:11,flex:1},listContent:{paddingTop:8,paddingBottom:20},emptyState:{alignItems:"center",paddingVertical:40},emptyTitle:{color:macOSColors.text.primary,fontSize:14,fontWeight:"600",marginTop:12,marginBottom:6},emptyText:{color:macOSColors.text.muted,fontSize:12,textAlign:"center",lineHeight:18},lockedBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:buoyColors.primary+"15",borderRadius:8,borderWidth:1,borderColor:buoyColors.primary+"33"},lockedText:{color:buoyColors.primary,fontSize:11,fontWeight:"500",flex:1},upgradeBadge:{backgroundColor:buoyColors.primary,paddingHorizontal:8,paddingVertical:3,borderRadius:4},upgradeBadgeText:{color:"#fff",fontSize:9,fontWeight:"700",letterSpacing:.5}});
@@ -1,8 +1 @@
1
- "use strict";
2
-
3
- export { JotaiModal } from "./JotaiModal";
4
- export { JotaiIcon, JOTAI_ICON_COLOR } from "./JotaiIcon";
5
- export { JotaiAtomChangeItem } from "./JotaiAtomChangeItem";
6
- export { JotaiAtomDetailContent, JotaiAtomDetailFooter } from "./JotaiAtomDetailContent";
7
- export { JotaiAtomBrowser } from "./JotaiAtomBrowser";
8
- export { JotaiEventFilterView } from "./JotaiEventFilterView";
1
+ "use strict";export{JotaiModal}from"./JotaiModal";export{JotaiIcon,JOTAI_ICON_COLOR}from"./JotaiIcon";export{JotaiAtomChangeItem}from"./JotaiAtomChangeItem";export{JotaiAtomDetailContent,JotaiAtomDetailFooter}from"./JotaiAtomDetailContent";export{JotaiAtomBrowser}from"./JotaiAtomBrowser";export{JotaiEventFilterView}from"./JotaiEventFilterView";
@@ -1,79 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Hook for consuming Jotai atom changes from the store
5
- *
6
- * Mirrors useZustandStateChanges.ts from @buoy-gg/zustand
7
- */
8
-
9
- import { useState, useEffect, useMemo, useCallback } from "react";
10
- import { jotaiStateStore } from "../utils/jotaiStateStore";
11
- export function useJotaiAtomChanges() {
12
- const [atomChanges, setAtomChanges] = useState(() => jotaiStateStore.getAtomChanges());
13
- const [atoms, setAtoms] = useState(() => jotaiStateStore.getAtoms());
14
- const [filter, setFilter] = useState({});
15
- const [isEnabled, setIsEnabled] = useState(() => jotaiStateStore.getEnabled());
16
- useEffect(() => {
17
- const unsubChanges = jotaiStateStore.subscribe(newChanges => {
18
- setAtomChanges(newChanges);
19
- });
20
- const unsubAtoms = jotaiStateStore.subscribeToAtoms(newAtoms => {
21
- setAtoms(newAtoms);
22
- });
23
- return () => {
24
- unsubChanges();
25
- unsubAtoms();
26
- };
27
- }, []);
28
- const filteredChanges = useMemo(() => {
29
- let filtered = atomChanges;
30
- if (filter.searchText) {
31
- const search = filter.searchText.toLowerCase();
32
- filtered = filtered.filter(c => c.atomLabel.toLowerCase().includes(search) || c.valuePreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
33
- }
34
- if (filter.atomLabels && filter.atomLabels.length > 0) {
35
- filtered = filtered.filter(c => filter.atomLabels.includes(c.atomLabel));
36
- }
37
- if (filter.onlyWithChanges) {
38
- filtered = filtered.filter(c => c.hasValueChange);
39
- }
40
- return filtered;
41
- }, [atomChanges, filter]);
42
- const stats = useMemo(() => {
43
- const total = atomChanges.length;
44
- const withChanges = atomChanges.filter(c => c.hasValueChange).length;
45
- return {
46
- totalChanges: total,
47
- changesWithValueChange: withChanges,
48
- changesWithoutValueChange: total - withChanges,
49
- atomCount: atoms.length
50
- };
51
- }, [atomChanges, atoms]);
52
- const atomLabels = useMemo(() => {
53
- return jotaiStateStore.getUniqueAtomLabels();
54
- }, [atoms]);
55
- const clearChanges = useCallback(() => {
56
- jotaiStateStore.clearAtomChanges();
57
- }, []);
58
- const toggleCapture = useCallback(() => {
59
- const newEnabled = !isEnabled;
60
- jotaiStateStore.setEnabled(newEnabled);
61
- setIsEnabled(newEnabled);
62
- }, [isEnabled]);
63
- const getChangeById = useCallback(id => {
64
- return jotaiStateStore.getAtomChangeById(id);
65
- }, []);
66
- return {
67
- atomChanges,
68
- filteredChanges,
69
- filter,
70
- setFilter,
71
- stats,
72
- atoms,
73
- clearChanges,
74
- isEnabled,
75
- toggleCapture,
76
- atomLabels,
77
- getChangeById
78
- };
79
- }
1
+ "use strict";import{useState,useEffect,useMemo,useCallback}from"react";import{jotaiStateStore}from"../utils/jotaiStateStore";export function useJotaiAtomChanges(){const[e,t]=useState(()=>jotaiStateStore.getAtomChanges()),[a,o]=useState(()=>jotaiStateStore.getAtoms()),[s,l]=useState({}),[r,n]=useState(()=>jotaiStateStore.getEnabled());useEffect(()=>{const e=jotaiStateStore.subscribe(e=>{t(e)}),a=jotaiStateStore.subscribeToAtoms(e=>{o(e)});return()=>{e(),a()}},[]);const u=useMemo(()=>{let t=e;if(s.searchText){const e=s.searchText.toLowerCase();t=t.filter(t=>t.atomLabel.toLowerCase().includes(e)||t.valuePreview.toLowerCase().includes(e)||t.changedKeys.some(t=>t.toLowerCase().includes(e)))}return s.atomLabels&&s.atomLabels.length>0&&(t=t.filter(e=>s.atomLabels.includes(e.atomLabel))),s.onlyWithChanges&&(t=t.filter(e=>e.hasValueChange)),t},[e,s]),i=useMemo(()=>{const t=e.length,o=e.filter(e=>e.hasValueChange).length;return{totalChanges:t,changesWithValueChange:o,changesWithoutValueChange:t-o,atomCount:a.length}},[e,a]),g=useMemo(()=>jotaiStateStore.getUniqueAtomLabels(),[a]),c=useCallback(()=>{jotaiStateStore.clearAtomChanges()},[]),h=useCallback(()=>{const e=!r;jotaiStateStore.setEnabled(e),n(e)},[r]),S=useCallback(e=>jotaiStateStore.getAtomChangeById(e),[]);return{atomChanges:e,filteredChanges:u,filter:s,setFilter:l,stats:i,atoms:a,clearChanges:c,isEnabled:r,toggleCapture:h,atomLabels:g,getChangeById:S}}
@@ -1,10 +1 @@
1
- "use strict";
2
-
3
- export { JotaiModal } from "./components/JotaiModal";
4
- export { JotaiIcon, JOTAI_ICON_COLOR } from "./components/JotaiIcon";
5
- export { JotaiAtomChangeItem } from "./components/JotaiAtomChangeItem";
6
- export { JotaiAtomDetailContent, JotaiAtomDetailFooter } from "./components/JotaiAtomDetailContent";
7
- export { JotaiAtomBrowser } from "./components/JotaiAtomBrowser";
8
- export { jotaiStateStore } from "./utils/jotaiStateStore";
9
- export { watchAtoms, watchDefaultStoreAtoms, isAtomWatched } from "./utils/watchAtoms";
10
- export { useJotaiAtomChanges } from "./hooks/useJotaiAtomChanges";
1
+ "use strict";export{JotaiModal}from"./components/JotaiModal";export{JotaiIcon,JOTAI_ICON_COLOR}from"./components/JotaiIcon";export{JotaiAtomChangeItem}from"./components/JotaiAtomChangeItem";export{JotaiAtomDetailContent,JotaiAtomDetailFooter}from"./components/JotaiAtomDetailContent";export{JotaiAtomBrowser}from"./components/JotaiAtomBrowser";export{jotaiStateStore}from"./utils/jotaiStateStore";export{watchAtoms,watchDefaultStoreAtoms,isAtomWatched}from"./utils/watchAtoms";export{useJotaiAtomChanges}from"./hooks/useJotaiAtomChanges";