@buoy-gg/jotai 3.0.1 → 4.0.1

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 (48) hide show
  1. package/lib/commonjs/index.js +98 -1
  2. package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +300 -1
  3. package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +113 -1
  4. package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +754 -1
  5. package/lib/commonjs/jotai/components/JotaiEventFilterView.js +305 -1
  6. package/lib/commonjs/jotai/components/JotaiIcon.js +35 -1
  7. package/lib/commonjs/jotai/components/JotaiModal.js +567 -1
  8. package/lib/commonjs/jotai/components/index.js +59 -1
  9. package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +83 -1
  10. package/lib/commonjs/jotai/index.js +85 -1
  11. package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +38 -0
  12. package/lib/commonjs/jotai/utils/jotaiStateStore.js +399 -1
  13. package/lib/commonjs/jotai/utils/watchAtoms.js +149 -1
  14. package/lib/commonjs/preset.js +98 -1
  15. package/lib/module/index.js +79 -1
  16. package/lib/module/jotai/components/JotaiAtomBrowser.js +296 -1
  17. package/lib/module/jotai/components/JotaiAtomChangeItem.js +109 -1
  18. package/lib/module/jotai/components/JotaiAtomDetailContent.js +748 -1
  19. package/lib/module/jotai/components/JotaiEventFilterView.js +301 -1
  20. package/lib/module/jotai/components/JotaiIcon.js +31 -1
  21. package/lib/module/jotai/components/JotaiModal.js +563 -1
  22. package/lib/module/jotai/components/index.js +8 -1
  23. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +79 -1
  24. package/lib/module/jotai/index.js +10 -1
  25. package/lib/module/jotai/sync/jotaiSyncAdapter.js +35 -0
  26. package/lib/module/jotai/utils/jotaiStateStore.js +395 -1
  27. package/lib/module/jotai/utils/watchAtoms.js +144 -1
  28. package/lib/module/preset.js +94 -1
  29. package/lib/typescript/index.d.ts +2 -1
  30. package/lib/typescript/index.d.ts.map +1 -0
  31. package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +1 -0
  32. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -0
  33. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +1 -0
  34. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +1 -0
  35. package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +1 -0
  36. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -0
  37. package/lib/typescript/jotai/components/index.d.ts.map +1 -0
  38. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +1 -0
  39. package/lib/typescript/jotai/index.d.ts.map +1 -0
  40. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts +23 -0
  41. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +1 -0
  42. package/lib/typescript/jotai/types/index.d.ts +11 -0
  43. package/lib/typescript/jotai/types/index.d.ts.map +1 -0
  44. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts +29 -1
  45. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +1 -0
  46. package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +1 -0
  47. package/lib/typescript/preset.d.ts.map +1 -0
  48. package/package.json +3 -3
@@ -1 +1,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
+ "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 +1,8 @@
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
+ "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 +1,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
+ "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 +1,10 @@
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";
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";