@buoy-gg/jotai 2.1.16 → 3.0.0

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,149 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isAtomWatched=isAtomWatched,exports.watchAtoms=watchAtoms,exports.watchDefaultStoreAtoms=watchDefaultStoreAtoms;var _jotaiStateStore=require("./jotaiStateStore");function watchAtoms(t,e,o){const a=!1!==o?.enabled,r=[];for(const[o,i]of Object.entries(e)){const e=i,c=Symbol.for(`@@buoy-jotai/watched/${o}`);if(e[c])continue;let s;e[c]=!0;try{s=t.get(i)}catch{s=void 0}_jotaiStateStore.jotaiStateStore.registerAtom(o,()=>{try{return t.get(i)}catch{return}}),a&&_jotaiStateStore.jotaiStateStore.addAtomChange({atomLabel:o,prevValue:void 0,nextValue:s,category:"initial"});const n=t.sub(i,()=>{try{const e=t.get(i);_jotaiStateStore.jotaiStateStore.addAtomChange({atomLabel:o,prevValue:s,nextValue:e,category:"write"}),s=e}catch{}});r.push(()=>{n(),delete e[c],_jotaiStateStore.jotaiStateStore.unregisterAtom(o)})}return()=>r.forEach(t=>t())}function watchDefaultStoreAtoms(t,e){let o;try{const{getDefaultStore:t}=require("jotai/vanilla");o=t()}catch{try{const{getDefaultStore:t}=require("jotai");o=t()}catch{return console.warn("[@buoy-gg/jotai] Could not find getDefaultStore from jotai. Pass the store explicitly via watchAtoms(store, atoms) instead."),()=>{}}}return watchAtoms(o,t,e)}function isAtomWatched(t){return void 0!==_jotaiStateStore.jotaiStateStore.getAtom(t)}
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isAtomWatched = isAtomWatched;
7
+ exports.watchAtoms = watchAtoms;
8
+ exports.watchDefaultStoreAtoms = watchDefaultStoreAtoms;
9
+ var _jotaiStateStore = require("./jotaiStateStore");
10
+ /**
11
+ * Buoy Jotai DevTools — Atom instrumentation
12
+ *
13
+ * watchAtoms() — RECOMMENDED. Pass a Jotai store and a named atom map.
14
+ * Uses store.sub() to observe each atom externally. Never touches atom internals.
15
+ *
16
+ * watchDefaultStoreAtoms() — Convenience wrapper that uses getDefaultStore().
17
+ */
18
+
19
+ /** Any Jotai atom — alias for Atom<unknown> for readability */
20
+
21
+ /** Minimal store shape — what Jotai's createStore() / getDefaultStore() returns */
22
+
23
+ /**
24
+ * Watch a set of Jotai atoms for value changes.
25
+ *
26
+ * Pass a Jotai store and an object mapping display names to atoms.
27
+ * Uses store.sub() to observe each atom — never modifies atom internals.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * import { getDefaultStore } from 'jotai';
32
+ * import { watchAtoms } from '@buoy-gg/jotai';
33
+ * import { countAtom } from './atoms/count';
34
+ * import { authAtom } from './atoms/auth';
35
+ *
36
+ * watchAtoms(getDefaultStore(), {
37
+ * countAtom,
38
+ * authAtom,
39
+ * });
40
+ * ```
41
+ *
42
+ * @param store - Jotai store (from createStore() or getDefaultStore())
43
+ * @param atoms - Object mapping display labels to Jotai atoms
44
+ * @param options - Optional configuration
45
+ * @returns Cleanup function that removes all subscriptions
46
+ */
47
+ function watchAtoms(store, atoms, options) {
48
+ const enabled = options?.enabled !== false;
49
+ const cleanups = [];
50
+ for (const [label, atom] of Object.entries(atoms)) {
51
+ // Skip if already watched under this label
52
+ const atomAny = atom;
53
+ const watchedKey = Symbol.for(`@@buoy-jotai/watched/${label}`);
54
+ if (atomAny[watchedKey]) continue;
55
+ atomAny[watchedKey] = true;
56
+ let prevValue;
57
+ try {
58
+ prevValue = store.get(atom);
59
+ } catch {
60
+ prevValue = undefined;
61
+ }
62
+
63
+ // Register the atom for the browser view
64
+ _jotaiStateStore.jotaiStateStore.registerAtom(label, () => {
65
+ try {
66
+ return store.get(atom);
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ });
71
+
72
+ // Record initial value
73
+ if (enabled) {
74
+ _jotaiStateStore.jotaiStateStore.addAtomChange({
75
+ atomLabel: label,
76
+ prevValue: undefined,
77
+ nextValue: prevValue,
78
+ category: "initial"
79
+ });
80
+ }
81
+
82
+ // Subscribe to future changes
83
+ const unsubscribe = store.sub(atom, () => {
84
+ try {
85
+ const nextValue = store.get(atom);
86
+ _jotaiStateStore.jotaiStateStore.addAtomChange({
87
+ atomLabel: label,
88
+ prevValue,
89
+ nextValue,
90
+ category: "write"
91
+ });
92
+ prevValue = nextValue;
93
+ } catch {
94
+ // Silently catch — our bug must never propagate into the store
95
+ }
96
+ });
97
+ cleanups.push(() => {
98
+ unsubscribe();
99
+ delete atomAny[watchedKey];
100
+ _jotaiStateStore.jotaiStateStore.unregisterAtom(label);
101
+ });
102
+ }
103
+ return () => cleanups.forEach(fn => fn());
104
+ }
105
+
106
+ /**
107
+ * Watch atoms using Jotai's default store (no Provider setup required).
108
+ *
109
+ * Convenience wrapper around watchAtoms() — auto-imports getDefaultStore.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * import { watchDefaultStoreAtoms } from '@buoy-gg/jotai';
114
+ * import { countAtom, authAtom } from './atoms';
115
+ *
116
+ * watchDefaultStoreAtoms({ countAtom, authAtom });
117
+ * ```
118
+ */
119
+ function watchDefaultStoreAtoms(atoms, options) {
120
+ // Dynamic import to avoid bundling jotai internals when not needed
121
+ let store;
122
+ try {
123
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
124
+ const {
125
+ getDefaultStore
126
+ } = require("jotai/vanilla");
127
+ store = getDefaultStore();
128
+ } catch {
129
+ // Try the main jotai entry (React Native)
130
+ try {
131
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
132
+ const {
133
+ getDefaultStore
134
+ } = require("jotai");
135
+ store = getDefaultStore();
136
+ } catch {
137
+ console.warn("[@buoy-gg/jotai] Could not find getDefaultStore from jotai. " + "Pass the store explicitly via watchAtoms(store, atoms) instead.");
138
+ return () => {};
139
+ }
140
+ }
141
+ return watchAtoms(store, atoms, options);
142
+ }
143
+
144
+ /**
145
+ * Check if an atom label is currently being watched
146
+ */
147
+ function isAtomWatched(label) {
148
+ return _jotaiStateStore.jotaiStateStore.getAtom(label) !== undefined;
149
+ }
@@ -1 +1,98 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createJotaiTool=createJotaiTool,exports.jotaiToolPreset=void 0;var _JotaiModal=require("./jotai/components/JotaiModal"),_JotaiIcon=require("./jotai/components/JotaiIcon"),_jsxRuntime=require("react/jsx-runtime");const jotaiToolPreset=exports.jotaiToolPreset={id:"jotai",name:"JOTAI",description:"Jotai atom & state inspector",slot:"both",icon:({size:o})=>(0,_jsxRuntime.jsx)(_JotaiIcon.JotaiIcon,{size:o}),component:_JotaiModal.JotaiModal,props:{enableSharedModalDimensions:!1}};function createJotaiTool(o){return{id:o?.id||"jotai",name:o?.name||"JOTAI",description:o?.description||"Jotai atom & state inspector",slot:"both",icon:({size:e})=>(0,_jsxRuntime.jsx)(_JotaiIcon.JotaiIcon,{size:e,color:o?.iconColor}),component:_JotaiModal.JotaiModal,props:{enableSharedModalDimensions:void 0!==o?.enableSharedModalDimensions&&o.enableSharedModalDimensions}}}
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createJotaiTool = createJotaiTool;
7
+ exports.jotaiToolPreset = void 0;
8
+ var _JotaiModal = require("./jotai/components/JotaiModal");
9
+ var _JotaiIcon = require("./jotai/components/JotaiIcon");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * Pre-configured Jotai DevTools preset for FloatingDevTools
13
+ *
14
+ * ZERO-CONFIG: This preset is auto-discovered by FloatingDevTools!
15
+ * Just install @buoy-gg/jotai and call watchAtoms() with your atoms.
16
+ *
17
+ * @example Automatic (recommended)
18
+ * ```tsx
19
+ * import { getDefaultStore } from 'jotai';
20
+ * import { watchAtoms } from '@buoy-gg/jotai';
21
+ * import { countAtom, authAtom } from './atoms';
22
+ *
23
+ * watchAtoms(getDefaultStore(), { countAtom, authAtom });
24
+ *
25
+ * // The Jotai tool appears automatically in FloatingDevTools!
26
+ * <FloatingDevTools />
27
+ * ```
28
+ *
29
+ * @example Manual (only for custom configuration)
30
+ * ```tsx
31
+ * import { createJotaiTool } from '@buoy-gg/jotai';
32
+ *
33
+ * const customJotaiTool = createJotaiTool({
34
+ * name: "ATOMS",
35
+ * iconColor: "#6C47FF",
36
+ * });
37
+ *
38
+ * <FloatingDevTools apps={[customJotaiTool]} />
39
+ * ```
40
+ */
41
+
42
+ /**
43
+ * Pre-configured Jotai DevTools preset for FloatingDevTools.
44
+ * Includes:
45
+ * - Live atom change monitoring
46
+ * - Atom value inspection (JSON viewer)
47
+ * - Value diff visualization (tree + split)
48
+ * - Filter by atom label
49
+ * - Changed keys tracking for object atoms
50
+ */
51
+ const jotaiToolPreset = exports.jotaiToolPreset = {
52
+ id: "jotai",
53
+ name: "JOTAI",
54
+ description: "Jotai atom & state inspector",
55
+ slot: "both",
56
+ icon: ({
57
+ size
58
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_JotaiIcon.JotaiIcon, {
59
+ size: size
60
+ }),
61
+ component: _JotaiModal.JotaiModal,
62
+ props: {
63
+ enableSharedModalDimensions: false
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Create a custom Jotai DevTools configuration.
69
+ * Use this if you want to override default settings.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { createJotaiTool } from '@buoy-gg/jotai';
74
+ *
75
+ * const myJotaiTool = createJotaiTool({
76
+ * name: "ATOMS",
77
+ * iconColor: "#6C47FF",
78
+ * });
79
+ * ```
80
+ */
81
+ function createJotaiTool(options) {
82
+ return {
83
+ id: options?.id || "jotai",
84
+ name: options?.name || "JOTAI",
85
+ description: options?.description || "Jotai atom & state inspector",
86
+ slot: "both",
87
+ icon: ({
88
+ size
89
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_JotaiIcon.JotaiIcon, {
90
+ size: size,
91
+ color: options?.iconColor
92
+ }),
93
+ component: _JotaiModal.JotaiModal,
94
+ props: {
95
+ enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
96
+ }
97
+ };
98
+ }
@@ -1 +1,79 @@
1
- "use strict";export{jotaiToolPreset,createJotaiTool}from"./preset";export{watchAtoms}from"./jotai/utils/watchAtoms";export{watchDefaultStoreAtoms}from"./jotai/utils/watchAtoms";export{isAtomWatched}from"./jotai/utils/watchAtoms";export{useJotaiAtomChanges}from"./jotai/hooks/useJotaiAtomChanges";export{JotaiModal}from"./jotai/components/JotaiModal";export{JotaiAtomChangeItem}from"./jotai/components/JotaiAtomChangeItem";export{JotaiAtomDetailContent,JotaiAtomDetailFooter}from"./jotai/components/JotaiAtomDetailContent";export{JotaiIcon,JOTAI_ICON_COLOR}from"./jotai/components/JotaiIcon";export{jotaiStateStore}from"./jotai/utils/jotaiStateStore";
1
+ "use strict";
2
+
3
+ /**
4
+ * @buoy-gg/jotai
5
+ *
6
+ * Jotai Atom DevTools for React Native
7
+ *
8
+ * PUBLIC API - Only these exports are supported for external use.
9
+ *
10
+ * @example Recommended setup (one line, pass your atoms)
11
+ * ```tsx
12
+ * import { getDefaultStore } from 'jotai';
13
+ * import { watchAtoms } from '@buoy-gg/jotai';
14
+ * import { countAtom } from './atoms/count';
15
+ * import { authAtom } from './atoms/auth';
16
+ *
17
+ * // One call, anywhere at module scope or in your root layout:
18
+ * watchAtoms(getDefaultStore(), {
19
+ * countAtom,
20
+ * authAtom,
21
+ * });
22
+ * ```
23
+ *
24
+ * @example With a custom store (Provider-based)
25
+ * ```tsx
26
+ * import { createStore } from 'jotai';
27
+ * import { watchAtoms } from '@buoy-gg/jotai';
28
+ *
29
+ * const myStore = createStore();
30
+ *
31
+ * watchAtoms(myStore, { countAtom, authAtom });
32
+ * ```
33
+ */
34
+
35
+ // =============================================================================
36
+ // PRESET (Primary entry point for FloatingDevTools)
37
+ // =============================================================================
38
+ export { jotaiToolPreset, createJotaiTool } from "./preset";
39
+
40
+ // =============================================================================
41
+ // WATCH ATOMS (Recommended — subscribe-only, non-intrusive)
42
+ // =============================================================================
43
+ export { watchAtoms } from "./jotai/utils/watchAtoms";
44
+
45
+ // =============================================================================
46
+ // CONVENIENCE (Default store shortcut)
47
+ // =============================================================================
48
+ export { watchDefaultStoreAtoms } from "./jotai/utils/watchAtoms";
49
+
50
+ // =============================================================================
51
+ // UTILITIES
52
+ // =============================================================================
53
+ export { isAtomWatched } from "./jotai/utils/watchAtoms";
54
+ // =============================================================================
55
+ // HOOKS (For consuming Jotai atom data)
56
+ // =============================================================================
57
+ export { useJotaiAtomChanges } from "./jotai/hooks/useJotaiAtomChanges";
58
+ // =============================================================================
59
+ // COMPONENTS (For custom UI implementations)
60
+ // =============================================================================
61
+ export { JotaiModal } from "./jotai/components/JotaiModal";
62
+ export { JotaiAtomChangeItem } from "./jotai/components/JotaiAtomChangeItem";
63
+ export { JotaiAtomDetailContent, JotaiAtomDetailFooter } from "./jotai/components/JotaiAtomDetailContent";
64
+ export { JotaiIcon, JOTAI_ICON_COLOR } from "./jotai/components/JotaiIcon";
65
+
66
+ // =============================================================================
67
+ // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
68
+ // =============================================================================
69
+ export { jotaiSyncAdapter } from "./jotai/sync/jotaiSyncAdapter";
70
+
71
+ // =============================================================================
72
+ // TYPES
73
+ // =============================================================================
74
+
75
+ // =============================================================================
76
+ // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
77
+ // =============================================================================
78
+ /** @internal */
79
+ export { jotaiStateStore } from "./jotai/utils/jotaiStateStore";
@@ -1 +1,296 @@
1
- "use strict";import{useState,useMemo,useCallback}from"react";import{View,Text,StyleSheet,ScrollView,TouchableOpacity}from"react-native";import{CompactRow,macOSColors,buoyColors,Box,ExpandedInfoRow,PillBadge,parseValue}from"@buoy-gg/shared-ui";import{DataViewer}from"@buoy-gg/shared-ui/dataViewer";import{jotaiStateStore}from"../utils/jotaiStateStore";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";function getValuePreview(e){try{const t=e.getValue();if(void 0===t)return"undefined";if(null===t)return"null";if(Array.isArray(t))return 0===t.length?"[]":`[${t.length} item${1===t.length?"":"s"}]`;if("object"==typeof t){const e=Object.keys(t);return 0===e.length?"{}":e.length<=3?e.join(", "):`${e.slice(0,2).join(", ")} +${e.length-2}`}return String(t).slice(0,40)}catch{return""}}function getValueType(e){try{const t=e.getValue();return null===t?"null":void 0===t?"undefined":Array.isArray(t)?`array · ${t.length}`:typeof t}catch{return"unknown"}}function AtomExpandedContent({atom:e,onViewHistory:t}){const o=e.getValue(),r=useMemo(()=>{if(o&&"object"==typeof o){const e={};for(const[t,r]of Object.entries(o))"function"!=typeof r&&(e[t]=r);return parseValue(e)}return parseValue(o)},[o]);return _jsxs(View,{style:expandedStyles.container,children:[_jsx(ExpandedInfoRow,{label:"Type",children:_jsx(PillBadge,{color:e.color,children:"JOTAI"})}),e.changeCount>0&&_jsxs(ExpandedInfoRow,{label:"Changes",children:[_jsx(PillBadge,{color:buoyColors.warning,children:String(e.changeCount)}),_jsx(TouchableOpacity,{onPress:()=>t(e.label),style:expandedStyles.viewHistoryButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:_jsx(Text,{style:[expandedStyles.viewHistoryText,{color:e.color}],children:"view history →"})})]}),_jsx(View,{style:expandedStyles.dataContainer,children:r&&"object"==typeof r?_jsx(DataViewer,{title:"",data:r,showTypeFilter:!0,rawMode:!0,initialExpanded:!0}):_jsx(Text,{style:expandedStyles.primitiveText,children:String(o??"undefined")})})]})}function EmptyBrowserState(){return _jsxs(View,{style:styles.emptyState,children:[_jsx(Box,{size:32,color:macOSColors.text.muted}),_jsx(Text,{style:styles.emptyTitle,children:"No atoms registered"}),_jsx(Text,{style:styles.emptyText,children:"Use watchAtoms(store, { atomName }) to register your Jotai atoms.\nThey will appear here with their current value."})]})}export function JotaiAtomBrowser({atoms:e,searchQuery:t,onViewHistory:o}){const[r,n]=useState(null),i=useMemo(()=>{if(!t)return e;const o=t.toLowerCase();return e.filter(e=>e.label.toLowerCase().includes(o))},[e,t]),l=useCallback(e=>{n(t=>t===e.label?null:e.label)},[]);return 0!==i.length||t?0===i.length&&t?_jsxs(View,{style:styles.emptyState,children:[_jsx(Text,{style:styles.emptyTitle,children:"No matching atoms"}),_jsxs(Text,{style:styles.emptyText,children:['No atoms match "',t,'"']})]}):_jsxs(ScrollView,{style:styles.container,contentContainerStyle:styles.scrollContent,showsVerticalScrollIndicator:!0,children:[_jsxs(View,{style:styles.sectionHeader,children:[_jsx(Text,{style:styles.sectionTitle,children:"ATOMS"}),_jsx(View,{style:styles.sectionCountBadge,children:_jsx(Text,{style:styles.sectionCountText,children:i.length})})]}),i.map(e=>{const t=r===e.label,n=getValuePreview(e),i=jotaiStateStore.getAtomColor(e.label);return _jsxs(View,{style:styles.atomRowWrapper,children:[_jsx(CompactRow,{statusDotColor:i,statusLabel:e.label,statusSublabel:getValueType(e),primaryText:n,showChevron:!0,isExpanded:t,onPress:()=>l(e),expandedContent:t?_jsx(AtomExpandedContent,{atom:e,onViewHistory:o}):void 0}),e.changeCount>0&&_jsx(View,{style:[styles.absCountBadge,{backgroundColor:i+"22",borderColor:i+"55"}],pointerEvents:"none",children:_jsx(Text,{style:[styles.absCountText,{color:i}],children:String(e.changeCount)})})]},e.label)})]}):_jsx(EmptyBrowserState,{})}const styles=StyleSheet.create({container:{flex:1},atomRowWrapper:{position:"relative"},absCountBadge:{position:"absolute",top:4,right:10,paddingHorizontal:5,paddingVertical:1,borderRadius:4,borderWidth:1,zIndex:1},absCountText:{fontSize:9,fontWeight:"700",fontFamily:"monospace"},scrollContent:{paddingTop:8,paddingBottom:20},sectionHeader:{flexDirection:"row",alignItems:"center",paddingHorizontal:16,paddingVertical:8,gap:8},sectionTitle:{fontSize:11,fontWeight:"700",letterSpacing:.5,color:macOSColors.text.muted},sectionCountBadge:{backgroundColor:buoyColors.primary+"26",paddingHorizontal:8,paddingVertical:2,borderRadius:4},sectionCountText:{fontSize:10,fontWeight:"700",color:buoyColors.primary,fontFamily:"monospace"},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}}),expandedStyles=StyleSheet.create({container:{gap:10},viewHistoryButton:{marginLeft:4},viewHistoryText:{fontSize:10,fontWeight:"600",fontFamily:"monospace"},dataContainer:{backgroundColor:buoyColors.base,borderRadius:6,borderWidth:1,borderColor:buoyColors.border,overflow:"hidden",minHeight:60},primitiveText:{color:buoyColors.text,fontSize:12,fontFamily:"monospace",padding:14}});
1
+ "use strict";
2
+
3
+ /**
4
+ * JotaiAtomBrowser
5
+ *
6
+ * Atoms tab — shows all registered Jotai atoms and their current value.
7
+ * Mirrors ZustandStoreBrowser.tsx from @buoy-gg/zustand.
8
+ */
9
+
10
+ import { useState, useMemo, useCallback } from "react";
11
+ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
12
+ import { CompactRow, macOSColors, buoyColors, Box, ExpandedInfoRow, PillBadge, parseValue } from "@buoy-gg/shared-ui";
13
+ import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
14
+ import { jotaiStateStore } from "../utils/jotaiStateStore";
15
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ function getValuePreview(atom) {
17
+ try {
18
+ const value = atom.getValue();
19
+ if (value === undefined) return "undefined";
20
+ if (value === null) return "null";
21
+ if (Array.isArray(value)) {
22
+ if (value.length === 0) return "[]";
23
+ return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
24
+ }
25
+ if (typeof value === "object") {
26
+ const keys = Object.keys(value);
27
+ if (keys.length === 0) return "{}";
28
+ if (keys.length <= 3) return keys.join(", ");
29
+ return `${keys.slice(0, 2).join(", ")} +${keys.length - 2}`;
30
+ }
31
+ return String(value).slice(0, 40);
32
+ } catch {
33
+ return "";
34
+ }
35
+ }
36
+ function getValueType(atom) {
37
+ try {
38
+ const value = atom.getValue();
39
+ if (value === null) return "null";
40
+ if (value === undefined) return "undefined";
41
+ if (Array.isArray(value)) return `array · ${value.length}`;
42
+ return typeof value;
43
+ } catch {
44
+ return "unknown";
45
+ }
46
+ }
47
+ function AtomExpandedContent({
48
+ atom,
49
+ onViewHistory
50
+ }) {
51
+ const value = atom.getValue();
52
+ const displayValue = useMemo(() => {
53
+ if (value && typeof value === "object") {
54
+ const filtered = {};
55
+ for (const [key, v] of Object.entries(value)) {
56
+ if (typeof v !== "function") filtered[key] = v;
57
+ }
58
+ return parseValue(filtered);
59
+ }
60
+ return parseValue(value);
61
+ }, [value]);
62
+ return /*#__PURE__*/_jsxs(View, {
63
+ style: expandedStyles.container,
64
+ children: [/*#__PURE__*/_jsx(ExpandedInfoRow, {
65
+ label: "Type",
66
+ children: /*#__PURE__*/_jsx(PillBadge, {
67
+ color: atom.color,
68
+ children: "JOTAI"
69
+ })
70
+ }), atom.changeCount > 0 && /*#__PURE__*/_jsxs(ExpandedInfoRow, {
71
+ label: "Changes",
72
+ children: [/*#__PURE__*/_jsx(PillBadge, {
73
+ color: buoyColors.warning,
74
+ children: String(atom.changeCount)
75
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
76
+ onPress: () => onViewHistory(atom.label),
77
+ style: expandedStyles.viewHistoryButton,
78
+ hitSlop: {
79
+ top: 6,
80
+ bottom: 6,
81
+ left: 6,
82
+ right: 6
83
+ },
84
+ children: /*#__PURE__*/_jsx(Text, {
85
+ style: [expandedStyles.viewHistoryText, {
86
+ color: atom.color
87
+ }],
88
+ children: "view history \u2192"
89
+ })
90
+ })]
91
+ }), /*#__PURE__*/_jsx(View, {
92
+ style: expandedStyles.dataContainer,
93
+ children: displayValue && typeof displayValue === "object" ? /*#__PURE__*/_jsx(DataViewer, {
94
+ title: "",
95
+ data: displayValue,
96
+ showTypeFilter: true,
97
+ rawMode: true,
98
+ initialExpanded: true
99
+ }) : /*#__PURE__*/_jsx(Text, {
100
+ style: expandedStyles.primitiveText,
101
+ children: String(value ?? "undefined")
102
+ })
103
+ })]
104
+ });
105
+ }
106
+ function EmptyBrowserState() {
107
+ return /*#__PURE__*/_jsxs(View, {
108
+ style: styles.emptyState,
109
+ children: [/*#__PURE__*/_jsx(Box, {
110
+ size: 32,
111
+ color: macOSColors.text.muted
112
+ }), /*#__PURE__*/_jsx(Text, {
113
+ style: styles.emptyTitle,
114
+ children: "No atoms registered"
115
+ }), /*#__PURE__*/_jsx(Text, {
116
+ style: styles.emptyText,
117
+ children: "Use watchAtoms(store, { atomName }) to register your Jotai atoms.\nThey will appear here with their current value."
118
+ })]
119
+ });
120
+ }
121
+ export function JotaiAtomBrowser({
122
+ atoms,
123
+ searchQuery,
124
+ onViewHistory
125
+ }) {
126
+ const [expandedAtom, setExpandedAtom] = useState(null);
127
+ const filteredAtoms = useMemo(() => {
128
+ if (!searchQuery) return atoms;
129
+ const search = searchQuery.toLowerCase();
130
+ return atoms.filter(a => a.label.toLowerCase().includes(search));
131
+ }, [atoms, searchQuery]);
132
+ const handleAtomPress = useCallback(atom => {
133
+ setExpandedAtom(prev => prev === atom.label ? null : atom.label);
134
+ }, []);
135
+ if (filteredAtoms.length === 0 && !searchQuery) {
136
+ return /*#__PURE__*/_jsx(EmptyBrowserState, {});
137
+ }
138
+ if (filteredAtoms.length === 0 && searchQuery) {
139
+ return /*#__PURE__*/_jsxs(View, {
140
+ style: styles.emptyState,
141
+ children: [/*#__PURE__*/_jsx(Text, {
142
+ style: styles.emptyTitle,
143
+ children: "No matching atoms"
144
+ }), /*#__PURE__*/_jsxs(Text, {
145
+ style: styles.emptyText,
146
+ children: ["No atoms match \"", searchQuery, "\""]
147
+ })]
148
+ });
149
+ }
150
+ return /*#__PURE__*/_jsxs(ScrollView, {
151
+ style: styles.container,
152
+ contentContainerStyle: styles.scrollContent,
153
+ showsVerticalScrollIndicator: true,
154
+ children: [/*#__PURE__*/_jsxs(View, {
155
+ style: styles.sectionHeader,
156
+ children: [/*#__PURE__*/_jsx(Text, {
157
+ style: styles.sectionTitle,
158
+ children: "ATOMS"
159
+ }), /*#__PURE__*/_jsx(View, {
160
+ style: styles.sectionCountBadge,
161
+ children: /*#__PURE__*/_jsx(Text, {
162
+ style: styles.sectionCountText,
163
+ children: filteredAtoms.length
164
+ })
165
+ })]
166
+ }), filteredAtoms.map(atom => {
167
+ const isExpanded = expandedAtom === atom.label;
168
+ const valuePreview = getValuePreview(atom);
169
+ const atomColor = jotaiStateStore.getAtomColor(atom.label);
170
+ return /*#__PURE__*/_jsxs(View, {
171
+ style: styles.atomRowWrapper,
172
+ children: [/*#__PURE__*/_jsx(CompactRow, {
173
+ statusDotColor: atomColor,
174
+ statusLabel: atom.label,
175
+ statusSublabel: getValueType(atom),
176
+ primaryText: valuePreview,
177
+ showChevron: true,
178
+ isExpanded: isExpanded,
179
+ onPress: () => handleAtomPress(atom),
180
+ expandedContent: isExpanded ? /*#__PURE__*/_jsx(AtomExpandedContent, {
181
+ atom: atom,
182
+ onViewHistory: onViewHistory
183
+ }) : undefined
184
+ }), atom.changeCount > 0 && /*#__PURE__*/_jsx(View, {
185
+ style: [styles.absCountBadge, {
186
+ backgroundColor: atomColor + "22",
187
+ borderColor: atomColor + "55"
188
+ }],
189
+ pointerEvents: "none",
190
+ children: /*#__PURE__*/_jsx(Text, {
191
+ style: [styles.absCountText, {
192
+ color: atomColor
193
+ }],
194
+ children: String(atom.changeCount)
195
+ })
196
+ })]
197
+ }, atom.label);
198
+ })]
199
+ });
200
+ }
201
+ const styles = StyleSheet.create({
202
+ container: {
203
+ flex: 1
204
+ },
205
+ atomRowWrapper: {
206
+ position: "relative"
207
+ },
208
+ absCountBadge: {
209
+ position: "absolute",
210
+ top: 4,
211
+ right: 10,
212
+ paddingHorizontal: 5,
213
+ paddingVertical: 1,
214
+ borderRadius: 4,
215
+ borderWidth: 1,
216
+ zIndex: 1
217
+ },
218
+ absCountText: {
219
+ fontSize: 9,
220
+ fontWeight: "700",
221
+ fontFamily: "monospace"
222
+ },
223
+ scrollContent: {
224
+ paddingTop: 8,
225
+ paddingBottom: 20
226
+ },
227
+ sectionHeader: {
228
+ flexDirection: "row",
229
+ alignItems: "center",
230
+ paddingHorizontal: 16,
231
+ paddingVertical: 8,
232
+ gap: 8
233
+ },
234
+ sectionTitle: {
235
+ fontSize: 11,
236
+ fontWeight: "700",
237
+ letterSpacing: 0.5,
238
+ color: macOSColors.text.muted
239
+ },
240
+ sectionCountBadge: {
241
+ backgroundColor: buoyColors.primary + "26",
242
+ paddingHorizontal: 8,
243
+ paddingVertical: 2,
244
+ borderRadius: 4
245
+ },
246
+ sectionCountText: {
247
+ fontSize: 10,
248
+ fontWeight: "700",
249
+ color: buoyColors.primary,
250
+ fontFamily: "monospace"
251
+ },
252
+ emptyState: {
253
+ alignItems: "center",
254
+ paddingVertical: 40
255
+ },
256
+ emptyTitle: {
257
+ color: macOSColors.text.primary,
258
+ fontSize: 14,
259
+ fontWeight: "600",
260
+ marginTop: 12,
261
+ marginBottom: 6
262
+ },
263
+ emptyText: {
264
+ color: macOSColors.text.muted,
265
+ fontSize: 12,
266
+ textAlign: "center",
267
+ lineHeight: 18
268
+ }
269
+ });
270
+ const expandedStyles = StyleSheet.create({
271
+ container: {
272
+ gap: 10
273
+ },
274
+ viewHistoryButton: {
275
+ marginLeft: 4
276
+ },
277
+ viewHistoryText: {
278
+ fontSize: 10,
279
+ fontWeight: "600",
280
+ fontFamily: "monospace"
281
+ },
282
+ dataContainer: {
283
+ backgroundColor: buoyColors.base,
284
+ borderRadius: 6,
285
+ borderWidth: 1,
286
+ borderColor: buoyColors.border,
287
+ overflow: "hidden",
288
+ minHeight: 60
289
+ },
290
+ primitiveText: {
291
+ color: buoyColors.text,
292
+ fontSize: 12,
293
+ fontFamily: "monospace",
294
+ padding: 14
295
+ }
296
+ });