@buoy-gg/zustand 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 (63) hide show
  1. package/lib/commonjs/index.js +98 -1
  2. package/lib/commonjs/preset.js +102 -1
  3. package/lib/commonjs/zustand/components/ZustandActionButton.js +116 -1
  4. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +134 -1
  5. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +291 -1
  6. package/lib/commonjs/zustand/components/ZustandIcon.js +35 -1
  7. package/lib/commonjs/zustand/components/ZustandModal.js +603 -1
  8. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +165 -1
  9. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +352 -1
  10. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +508 -1
  11. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +307 -1
  12. package/lib/commonjs/zustand/components/index.js +73 -1
  13. package/lib/commonjs/zustand/hooks/index.js +12 -1
  14. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +92 -1
  15. package/lib/commonjs/zustand/index.js +99 -1
  16. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +48 -0
  17. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +220 -1
  18. package/lib/commonjs/zustand/utils/index.js +31 -1
  19. package/lib/commonjs/zustand/utils/zustandStateStore.js +457 -1
  20. package/lib/module/index.js +85 -1
  21. package/lib/module/preset.js +98 -1
  22. package/lib/module/zustand/components/ZustandActionButton.js +112 -1
  23. package/lib/module/zustand/components/ZustandDetailViewToggle.js +129 -1
  24. package/lib/module/zustand/components/ZustandEventFilterView.js +287 -1
  25. package/lib/module/zustand/components/ZustandIcon.js +32 -1
  26. package/lib/module/zustand/components/ZustandModal.js +599 -1
  27. package/lib/module/zustand/components/ZustandStateChangeItem.js +161 -1
  28. package/lib/module/zustand/components/ZustandStateDetailContent.js +348 -1
  29. package/lib/module/zustand/components/ZustandStateInfoView.js +503 -1
  30. package/lib/module/zustand/components/ZustandStoreBrowser.js +303 -1
  31. package/lib/module/zustand/components/index.js +10 -1
  32. package/lib/module/zustand/hooks/index.js +3 -1
  33. package/lib/module/zustand/hooks/useZustandStateChanges.js +88 -1
  34. package/lib/module/zustand/index.js +12 -1
  35. package/lib/module/zustand/sync/zustandSyncAdapter.js +44 -0
  36. package/lib/module/zustand/utils/buoyZustandMiddleware.js +214 -1
  37. package/lib/module/zustand/utils/index.js +4 -1
  38. package/lib/module/zustand/utils/zustandStateStore.js +453 -1
  39. package/lib/typescript/index.d.ts +2 -1
  40. package/lib/typescript/index.d.ts.map +1 -0
  41. package/lib/typescript/preset.d.ts.map +1 -0
  42. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +1 -0
  43. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -0
  44. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -0
  45. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +1 -0
  46. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -0
  47. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -0
  48. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +1 -0
  49. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -0
  50. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -0
  51. package/lib/typescript/zustand/components/index.d.ts.map +1 -0
  52. package/lib/typescript/zustand/hooks/index.d.ts.map +1 -0
  53. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -0
  54. package/lib/typescript/zustand/index.d.ts.map +1 -0
  55. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +26 -0
  56. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -0
  57. package/lib/typescript/zustand/types/index.d.ts +12 -0
  58. package/lib/typescript/zustand/types/index.d.ts.map +1 -0
  59. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +1 -0
  60. package/lib/typescript/zustand/utils/index.d.ts.map +1 -0
  61. package/lib/typescript/zustand/utils/zustandStateStore.d.ts +35 -1
  62. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -0
  63. package/package.json +3 -3
@@ -1 +1,303 @@
1
- "use strict";import{useState,useMemo,useCallback}from"react";import{View,Text,StyleSheet,ScrollView,TouchableOpacity}from"react-native";import{CompactRow,macOSColors,buoyColors,Database,parseValue,Box,ExpandedInfoRow,PillBadge}from"@buoy-gg/shared-ui";import{DataViewer}from"@buoy-gg/shared-ui/dataViewer";import{zustandStateStore}from"../utils/zustandStateStore";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";function getKeysPreview(e){try{const t=e.api.getState();if(t&&"object"==typeof t){const e=Object.keys(t).filter(e=>"function"!=typeof t[e]);return 0===e.length?"no data keys":e.length<=3?e.join(", "):`${e.slice(0,2).join(", ")} +${e.length-2}`}return""}catch{return""}}function StoreExpandedContent({store:e,onViewHistory:t}){const o=e.api.getState(),s=useMemo(()=>{if(o&&"object"==typeof o){const e={};for(const[t,s]of Object.entries(o))"function"!=typeof s&&(e[t]=s);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:"ZUSTAND"})}),e.stateChangeCount>0&&_jsxs(ExpandedInfoRow,{label:"Changes",children:[_jsx(PillBadge,{color:buoyColors.warning,children:String(e.stateChangeCount)}),_jsx(TouchableOpacity,{onPress:()=>t(e.name),style:expandedStyles.viewHistoryButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:_jsx(Text,{style:[expandedStyles.viewHistoryText,{color:e.color}],children:"view history →"})})]}),e.isPersisted&&_jsx(ExpandedInfoRow,{label:"Persist",children:_jsx(PillBadge,{color:buoyColors.info,icon:_jsx(Database,{size:9,color:buoyColors.info}),children:e.persistName||"persisted"})}),_jsx(View,{style:expandedStyles.dataContainer,children:s&&"object"==typeof s?_jsx(DataViewer,{title:"",data:s,showTypeFilter:!0,rawMode:!0,initialExpanded:!0}):_jsx(Text,{style:expandedStyles.emptyText,children:"Empty state"})})]})}function EmptyBrowserState(){return _jsxs(View,{style:styles.emptyState,children:[_jsx(Box,{size:32,color:macOSColors.text.muted}),_jsx(Text,{style:styles.emptyTitle,children:"No stores registered"}),_jsx(Text,{style:styles.emptyText,children:"Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."})]})}export function ZustandStoreBrowser({stores:e,searchQuery:t,onViewHistory:o}){const[s,r]=useState(null),n=useMemo(()=>{if(!t)return e;const o=t.toLowerCase();return e.filter(e=>e.name.toLowerCase().includes(o)||e.persistName&&e.persistName.toLowerCase().includes(o))},[e,t]),i=useCallback(e=>{r(t=>t===e.name?null:e.name)},[]);return 0!==n.length||t?0===n.length&&t?_jsxs(View,{style:styles.emptyState,children:[_jsx(Text,{style:styles.emptyTitle,children:"No matching stores"}),_jsxs(Text,{style:styles.emptyText,children:['No stores 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:"STORES"}),_jsx(View,{style:styles.sectionCountBadge,children:_jsx(Text,{style:styles.sectionCountText,children:n.length})})]}),n.map(e=>{const t=s===e.name,r=getKeysPreview(e),n=zustandStateStore.getStoreColor(e.name);return _jsxs(View,{style:styles.storeRowWrapper,children:[_jsx(CompactRow,{statusDotColor:n,statusLabel:e.name,statusSublabel:e.isPersisted?"persisted":"in-memory",primaryText:r,showChevron:!0,isExpanded:t,onPress:()=>i(e),expandedContent:t?_jsx(StoreExpandedContent,{store:e,onViewHistory:o}):void 0}),e.stateChangeCount>0&&_jsx(View,{style:[styles.absCountBadge,{backgroundColor:n+"22",borderColor:n+"55"}],pointerEvents:"none",children:_jsx(Text,{style:[styles.absCountText,{color:n}],children:String(e.stateChangeCount)})})]},e.name)})]}):_jsx(EmptyBrowserState,{})}const styles=StyleSheet.create({container:{flex:1},storeRowWrapper:{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},emptyText:{color:buoyColors.textMuted,fontSize:12,padding:14,fontStyle:"italic"}});
1
+ "use strict";
2
+
3
+ /**
4
+ * ZustandStoreBrowser
5
+ *
6
+ * Browse tab — shows all registered Zustand stores and their current state.
7
+ * Mirrors the Storage Browser pattern from @buoy-gg/storage.
8
+ *
9
+ * Each store is shown as an expandable row with its current state viewable
10
+ * via the shared DataViewer component.
11
+ */
12
+
13
+ import { useState, useMemo, useCallback } from "react";
14
+ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
15
+ import { CompactRow, macOSColors, buoyColors, Database, parseValue, Box, ExpandedInfoRow, PillBadge } from "@buoy-gg/shared-ui";
16
+ import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
17
+ import { zustandStateStore } from "../utils/zustandStateStore";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ /**
20
+ * Get a preview of the store's top-level keys
21
+ */
22
+ function getKeysPreview(store) {
23
+ try {
24
+ const state = store.api.getState();
25
+ if (state && typeof state === "object") {
26
+ const keys = Object.keys(state);
27
+ const dataKeys = keys.filter(k => {
28
+ const val = state[k];
29
+ return typeof val !== "function";
30
+ });
31
+ if (dataKeys.length === 0) return "no data keys";
32
+ if (dataKeys.length <= 3) return dataKeys.join(", ");
33
+ return `${dataKeys.slice(0, 2).join(", ")} +${dataKeys.length - 2}`;
34
+ }
35
+ return "";
36
+ } catch {
37
+ return "";
38
+ }
39
+ }
40
+ function StoreExpandedContent({
41
+ store,
42
+ onViewHistory
43
+ }) {
44
+ const state = store.api.getState();
45
+ const displayState = useMemo(() => {
46
+ if (state && typeof state === "object") {
47
+ const filtered = {};
48
+ for (const [key, value] of Object.entries(state)) {
49
+ if (typeof value !== "function") {
50
+ filtered[key] = value;
51
+ }
52
+ }
53
+ return parseValue(filtered);
54
+ }
55
+ return parseValue(state);
56
+ }, [state]);
57
+ return /*#__PURE__*/_jsxs(View, {
58
+ style: expandedStyles.container,
59
+ children: [/*#__PURE__*/_jsx(ExpandedInfoRow, {
60
+ label: "Type",
61
+ children: /*#__PURE__*/_jsx(PillBadge, {
62
+ color: store.color,
63
+ children: "ZUSTAND"
64
+ })
65
+ }), store.stateChangeCount > 0 && /*#__PURE__*/_jsxs(ExpandedInfoRow, {
66
+ label: "Changes",
67
+ children: [/*#__PURE__*/_jsx(PillBadge, {
68
+ color: buoyColors.warning,
69
+ children: String(store.stateChangeCount)
70
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
71
+ onPress: () => onViewHistory(store.name),
72
+ style: expandedStyles.viewHistoryButton,
73
+ hitSlop: {
74
+ top: 6,
75
+ bottom: 6,
76
+ left: 6,
77
+ right: 6
78
+ },
79
+ children: /*#__PURE__*/_jsx(Text, {
80
+ style: [expandedStyles.viewHistoryText, {
81
+ color: store.color
82
+ }],
83
+ children: "view history \u2192"
84
+ })
85
+ })]
86
+ }), store.isPersisted && /*#__PURE__*/_jsx(ExpandedInfoRow, {
87
+ label: "Persist",
88
+ children: /*#__PURE__*/_jsx(PillBadge, {
89
+ color: buoyColors.info,
90
+ icon: /*#__PURE__*/_jsx(Database, {
91
+ size: 9,
92
+ color: buoyColors.info
93
+ }),
94
+ children: store.persistName || "persisted"
95
+ })
96
+ }), /*#__PURE__*/_jsx(View, {
97
+ style: expandedStyles.dataContainer,
98
+ children: displayState && typeof displayState === "object" ? /*#__PURE__*/_jsx(DataViewer, {
99
+ title: "",
100
+ data: displayState,
101
+ showTypeFilter: true,
102
+ rawMode: true,
103
+ initialExpanded: true
104
+ }) : /*#__PURE__*/_jsx(Text, {
105
+ style: expandedStyles.emptyText,
106
+ children: "Empty state"
107
+ })
108
+ })]
109
+ });
110
+ }
111
+ function EmptyBrowserState() {
112
+ return /*#__PURE__*/_jsxs(View, {
113
+ style: styles.emptyState,
114
+ children: [/*#__PURE__*/_jsx(Box, {
115
+ size: 32,
116
+ color: macOSColors.text.muted
117
+ }), /*#__PURE__*/_jsx(Text, {
118
+ style: styles.emptyTitle,
119
+ children: "No stores registered"
120
+ }), /*#__PURE__*/_jsx(Text, {
121
+ style: styles.emptyText,
122
+ children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
123
+ })]
124
+ });
125
+ }
126
+ export function ZustandStoreBrowser({
127
+ stores,
128
+ searchQuery,
129
+ onViewHistory
130
+ }) {
131
+ const [expandedStore, setExpandedStore] = useState(null);
132
+
133
+ // Filter stores by search query
134
+ const filteredStores = useMemo(() => {
135
+ if (!searchQuery) return stores;
136
+ const search = searchQuery.toLowerCase();
137
+ return stores.filter(s => s.name.toLowerCase().includes(search) || s.persistName && s.persistName.toLowerCase().includes(search));
138
+ }, [stores, searchQuery]);
139
+ const handleStorePress = useCallback(store => {
140
+ setExpandedStore(prev => prev === store.name ? null : store.name);
141
+ }, []);
142
+ if (filteredStores.length === 0 && !searchQuery) {
143
+ return /*#__PURE__*/_jsx(EmptyBrowserState, {});
144
+ }
145
+ if (filteredStores.length === 0 && searchQuery) {
146
+ return /*#__PURE__*/_jsxs(View, {
147
+ style: styles.emptyState,
148
+ children: [/*#__PURE__*/_jsx(Text, {
149
+ style: styles.emptyTitle,
150
+ children: "No matching stores"
151
+ }), /*#__PURE__*/_jsxs(Text, {
152
+ style: styles.emptyText,
153
+ children: ["No stores match \"", searchQuery, "\""]
154
+ })]
155
+ });
156
+ }
157
+ return /*#__PURE__*/_jsxs(ScrollView, {
158
+ style: styles.container,
159
+ contentContainerStyle: styles.scrollContent,
160
+ showsVerticalScrollIndicator: true,
161
+ children: [/*#__PURE__*/_jsxs(View, {
162
+ style: styles.sectionHeader,
163
+ children: [/*#__PURE__*/_jsx(Text, {
164
+ style: styles.sectionTitle,
165
+ children: "STORES"
166
+ }), /*#__PURE__*/_jsx(View, {
167
+ style: styles.sectionCountBadge,
168
+ children: /*#__PURE__*/_jsx(Text, {
169
+ style: styles.sectionCountText,
170
+ children: filteredStores.length
171
+ })
172
+ })]
173
+ }), filteredStores.map(store => {
174
+ const isExpanded = expandedStore === store.name;
175
+ const keysPreview = getKeysPreview(store);
176
+ const storeColor = zustandStateStore.getStoreColor(store.name);
177
+ return /*#__PURE__*/_jsxs(View, {
178
+ style: styles.storeRowWrapper,
179
+ children: [/*#__PURE__*/_jsx(CompactRow, {
180
+ statusDotColor: storeColor,
181
+ statusLabel: store.name,
182
+ statusSublabel: store.isPersisted ? "persisted" : "in-memory",
183
+ primaryText: keysPreview,
184
+ showChevron: true,
185
+ isExpanded: isExpanded,
186
+ onPress: () => handleStorePress(store),
187
+ expandedContent: isExpanded ? /*#__PURE__*/_jsx(StoreExpandedContent, {
188
+ store: store,
189
+ onViewHistory: onViewHistory
190
+ }) : undefined
191
+ }), store.stateChangeCount > 0 && /*#__PURE__*/_jsx(View, {
192
+ style: [styles.absCountBadge, {
193
+ backgroundColor: storeColor + "22",
194
+ borderColor: storeColor + "55"
195
+ }],
196
+ pointerEvents: "none",
197
+ children: /*#__PURE__*/_jsx(Text, {
198
+ style: [styles.absCountText, {
199
+ color: storeColor
200
+ }],
201
+ children: String(store.stateChangeCount)
202
+ })
203
+ })]
204
+ }, store.name);
205
+ })]
206
+ });
207
+ }
208
+ const styles = StyleSheet.create({
209
+ container: {
210
+ flex: 1
211
+ },
212
+ storeRowWrapper: {
213
+ position: "relative"
214
+ },
215
+ absCountBadge: {
216
+ position: "absolute",
217
+ top: 4,
218
+ right: 10,
219
+ paddingHorizontal: 5,
220
+ paddingVertical: 1,
221
+ borderRadius: 4,
222
+ borderWidth: 1,
223
+ zIndex: 1
224
+ },
225
+ absCountText: {
226
+ fontSize: 9,
227
+ fontWeight: "700",
228
+ fontFamily: "monospace"
229
+ },
230
+ scrollContent: {
231
+ paddingTop: 8,
232
+ paddingBottom: 20
233
+ },
234
+ sectionHeader: {
235
+ flexDirection: "row",
236
+ alignItems: "center",
237
+ paddingHorizontal: 16,
238
+ paddingVertical: 8,
239
+ gap: 8
240
+ },
241
+ sectionTitle: {
242
+ fontSize: 11,
243
+ fontWeight: "700",
244
+ letterSpacing: 0.5,
245
+ color: macOSColors.text.muted
246
+ },
247
+ sectionCountBadge: {
248
+ backgroundColor: buoyColors.primary + "26",
249
+ paddingHorizontal: 8,
250
+ paddingVertical: 2,
251
+ borderRadius: 4
252
+ },
253
+ sectionCountText: {
254
+ fontSize: 10,
255
+ fontWeight: "700",
256
+ color: buoyColors.primary,
257
+ fontFamily: "monospace"
258
+ },
259
+ emptyState: {
260
+ alignItems: "center",
261
+ paddingVertical: 40
262
+ },
263
+ emptyTitle: {
264
+ color: macOSColors.text.primary,
265
+ fontSize: 14,
266
+ fontWeight: "600",
267
+ marginTop: 12,
268
+ marginBottom: 6
269
+ },
270
+ emptyText: {
271
+ color: macOSColors.text.muted,
272
+ fontSize: 12,
273
+ textAlign: "center",
274
+ lineHeight: 18
275
+ }
276
+ });
277
+ const expandedStyles = StyleSheet.create({
278
+ container: {
279
+ gap: 10
280
+ },
281
+ viewHistoryButton: {
282
+ marginLeft: 4
283
+ },
284
+ viewHistoryText: {
285
+ fontSize: 10,
286
+ fontWeight: "600",
287
+ fontFamily: "monospace"
288
+ },
289
+ dataContainer: {
290
+ backgroundColor: buoyColors.base,
291
+ borderRadius: 6,
292
+ borderWidth: 1,
293
+ borderColor: buoyColors.border,
294
+ overflow: "hidden",
295
+ minHeight: 60
296
+ },
297
+ emptyText: {
298
+ color: buoyColors.textMuted,
299
+ fontSize: 12,
300
+ padding: 14,
301
+ fontStyle: "italic"
302
+ }
303
+ });
@@ -1 +1,10 @@
1
- "use strict";export{ZustandModal}from"./ZustandModal";export{ZustandIcon,ZUSTAND_ICON_COLOR}from"./ZustandIcon";export{ZustandStateChangeItem}from"./ZustandStateChangeItem";export{ZustandStateDetailContent,ZustandStateDetailFooter}from"./ZustandStateDetailContent";export{ZustandStateInfoView}from"./ZustandStateInfoView";export{ZustandDetailViewToggle}from"./ZustandDetailViewToggle";export{ZustandActionButton}from"./ZustandActionButton";export{ZustandStoreBrowser}from"./ZustandStoreBrowser";
1
+ "use strict";
2
+
3
+ export { ZustandModal } from "./ZustandModal";
4
+ export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./ZustandIcon";
5
+ export { ZustandStateChangeItem } from "./ZustandStateChangeItem";
6
+ export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./ZustandStateDetailContent";
7
+ export { ZustandStateInfoView } from "./ZustandStateInfoView";
8
+ export { ZustandDetailViewToggle } from "./ZustandDetailViewToggle";
9
+ export { ZustandActionButton } from "./ZustandActionButton";
10
+ export { ZustandStoreBrowser } from "./ZustandStoreBrowser";
@@ -1 +1,3 @@
1
- "use strict";export{useZustandStateChanges}from"./useZustandStateChanges";
1
+ "use strict";
2
+
3
+ export { useZustandStateChanges } from "./useZustandStateChanges";
@@ -1 +1,88 @@
1
- "use strict";import{useState,useEffect,useMemo,useCallback}from"react";import{zustandStateStore}from"../utils/zustandStateStore";export function useZustandStateChanges(){const[e,t]=useState(()=>zustandStateStore.getStateChanges()),[a,s]=useState(()=>zustandStateStore.getStores()),[r,n]=useState({}),[o,u]=useState(()=>zustandStateStore.getEnabled());useEffect(()=>{const e=zustandStateStore.subscribe(e=>{t(e)}),a=zustandStateStore.subscribeToStores(e=>{s(e)});return()=>{e(),a()}},[]);const S=useMemo(()=>{let t=e;if(r.searchText){const e=r.searchText.toLowerCase();t=t.filter(t=>t.storeName.toLowerCase().includes(e)||t.partialPreview.toLowerCase().includes(e)||t.changedKeys.some(t=>t.toLowerCase().includes(e)))}return r.storeNames&&r.storeNames.length>0&&(t=t.filter(e=>r.storeNames.includes(e.storeName))),r.onlyWithChanges&&(t=t.filter(e=>e.hasStateChange)),t},[e,r]),l=useMemo(()=>{const t=e.length,s=e.filter(e=>e.hasStateChange).length;return{totalChanges:t,changesWithStateChange:s,changesWithoutStateChange:t-s,storeCount:a.length,averageDuration:0}},[e,a]),g=useMemo(()=>zustandStateStore.getUniqueStoreNames(),[a]),c=useCallback(()=>{zustandStateStore.clearStateChanges()},[]),h=useCallback(()=>{const e=!o;zustandStateStore.setEnabled(e),u(e)},[o]),i=useCallback(e=>zustandStateStore.getStateChangeById(e),[]);return{stateChanges:e,filteredChanges:S,filter:r,setFilter:n,stats:l,stores:a,clearChanges:c,isEnabled:o,toggleCapture:h,storeNames:g,getChangeById:i}}
1
+ "use strict";
2
+
3
+ /**
4
+ * Hook for consuming Zustand state changes from the store
5
+ *
6
+ * Mirrors useReduxActions.ts from @buoy-gg/redux
7
+ */
8
+
9
+ import { useState, useEffect, useMemo, useCallback } from "react";
10
+ import { zustandStateStore } from "../utils/zustandStateStore";
11
+ export function useZustandStateChanges() {
12
+ const [stateChanges, setStateChanges] = useState(() => zustandStateStore.getStateChanges());
13
+ const [stores, setStores] = useState(() => zustandStateStore.getStores());
14
+ const [filter, setFilter] = useState({});
15
+ const [isEnabled, setIsEnabled] = useState(() => zustandStateStore.getEnabled());
16
+
17
+ // Subscribe to state changes
18
+ useEffect(() => {
19
+ const unsubChanges = zustandStateStore.subscribe(newChanges => {
20
+ setStateChanges(newChanges);
21
+ });
22
+ const unsubStores = zustandStateStore.subscribeToStores(newStores => {
23
+ setStores(newStores);
24
+ });
25
+ return () => {
26
+ unsubChanges();
27
+ unsubStores();
28
+ };
29
+ }, []);
30
+
31
+ // Filter state changes — derive directly from state to avoid stale reads
32
+ const filteredChanges = useMemo(() => {
33
+ let filtered = stateChanges;
34
+ if (filter.searchText) {
35
+ const search = filter.searchText.toLowerCase();
36
+ filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
37
+ }
38
+ if (filter.storeNames && filter.storeNames.length > 0) {
39
+ filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
40
+ }
41
+ if (filter.onlyWithChanges) {
42
+ filtered = filtered.filter(c => c.hasStateChange);
43
+ }
44
+ return filtered;
45
+ }, [stateChanges, filter]);
46
+
47
+ // Get stats — derive directly from state
48
+ const stats = useMemo(() => {
49
+ const total = stateChanges.length;
50
+ const withChanges = stateChanges.filter(c => c.hasStateChange).length;
51
+ return {
52
+ totalChanges: total,
53
+ changesWithStateChange: withChanges,
54
+ changesWithoutStateChange: total - withChanges,
55
+ storeCount: stores.length,
56
+ averageDuration: 0
57
+ };
58
+ }, [stateChanges, stores]);
59
+
60
+ // Get unique store names
61
+ const storeNames = useMemo(() => {
62
+ return zustandStateStore.getUniqueStoreNames();
63
+ }, [stores]);
64
+ const clearChanges = useCallback(() => {
65
+ zustandStateStore.clearStateChanges();
66
+ }, []);
67
+ const toggleCapture = useCallback(() => {
68
+ const newEnabled = !isEnabled;
69
+ zustandStateStore.setEnabled(newEnabled);
70
+ setIsEnabled(newEnabled);
71
+ }, [isEnabled]);
72
+ const getChangeById = useCallback(id => {
73
+ return zustandStateStore.getStateChangeById(id);
74
+ }, []);
75
+ return {
76
+ stateChanges,
77
+ filteredChanges,
78
+ filter,
79
+ setFilter,
80
+ stats,
81
+ stores,
82
+ clearChanges,
83
+ isEnabled,
84
+ toggleCapture,
85
+ storeNames,
86
+ getChangeById
87
+ };
88
+ }
@@ -1 +1,12 @@
1
- "use strict";export{ZustandModal}from"./components/ZustandModal";export{ZustandIcon,ZUSTAND_ICON_COLOR}from"./components/ZustandIcon";export{ZustandStateChangeItem}from"./components/ZustandStateChangeItem";export{ZustandStateDetailContent,ZustandStateDetailFooter}from"./components/ZustandStateDetailContent";export{ZustandStateInfoView}from"./components/ZustandStateInfoView";export{ZustandDetailViewToggle}from"./components/ZustandDetailViewToggle";export{ZustandActionButton}from"./components/ZustandActionButton";export{zustandStateStore}from"./utils/zustandStateStore";export{watchStores,buoyDevTools,isStoreInstrumented}from"./utils/buoyZustandMiddleware";export{useZustandStateChanges}from"./hooks/useZustandStateChanges";
1
+ "use strict";
2
+
3
+ export { ZustandModal } from "./components/ZustandModal";
4
+ export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./components/ZustandIcon";
5
+ export { ZustandStateChangeItem } from "./components/ZustandStateChangeItem";
6
+ export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./components/ZustandStateDetailContent";
7
+ export { ZustandStateInfoView } from "./components/ZustandStateInfoView";
8
+ export { ZustandDetailViewToggle } from "./components/ZustandDetailViewToggle";
9
+ export { ZustandActionButton } from "./components/ZustandActionButton";
10
+ export { zustandStateStore } from "./utils/zustandStateStore";
11
+ export { watchStores, buoyDevTools, isStoreInstrumented } from "./utils/buoyZustandMiddleware";
12
+ export { useZustandStateChanges } from "./hooks/useZustandStateChanges";
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ import { zustandStateStore } from "../utils/zustandStateStore";
4
+ /**
5
+ * Sync adapter for the zustand tool, consumed by @buoy-gg/external-sync's
6
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
7
+ * this package doesn't need a dependency on it).
8
+ *
9
+ * State changes are captured by the middleware (watchStores/buoyDevTools),
10
+ * independent of whether a dashboard is watching — subscribing here only
11
+ * streams what the store already records. The snapshot carries both the
12
+ * change timeline and the store registry (with each store's current state)
13
+ * so the dashboard can render the store browser.
14
+ *
15
+ * The `setState` action powers remote time-travel/reset from the dashboard.
16
+ */
17
+ export const zustandSyncAdapter = {
18
+ version: 1,
19
+ getSnapshot: () => ({
20
+ changes: zustandStateStore.getStateChanges(),
21
+ stores: zustandStateStore.getStoreSnapshots()
22
+ }),
23
+ subscribe: onChange => {
24
+ const unsubscribeChanges = zustandStateStore.subscribe(onChange);
25
+ const unsubscribeStores = zustandStateStore.subscribeToStores(onChange);
26
+ return () => {
27
+ unsubscribeChanges();
28
+ unsubscribeStores();
29
+ };
30
+ },
31
+ actions: {
32
+ clearEvents: () => {
33
+ zustandStateStore.clearStateChanges();
34
+ },
35
+ setState: params => {
36
+ const {
37
+ storeName,
38
+ state,
39
+ replace
40
+ } = params;
41
+ zustandStateStore.getStore(storeName)?.api.setState(state, replace ?? true);
42
+ }
43
+ }
44
+ };