@buoy-gg/zustand 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +98 -1
- package/lib/commonjs/preset.js +102 -1
- package/lib/commonjs/zustand/components/ZustandActionButton.js +116 -1
- package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +134 -1
- package/lib/commonjs/zustand/components/ZustandEventFilterView.js +291 -1
- package/lib/commonjs/zustand/components/ZustandIcon.js +35 -1
- package/lib/commonjs/zustand/components/ZustandModal.js +603 -1
- package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +165 -1
- package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +352 -1
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +508 -1
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +323 -1
- package/lib/commonjs/zustand/components/index.js +73 -1
- package/lib/commonjs/zustand/hooks/index.js +12 -1
- package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +92 -1
- package/lib/commonjs/zustand/index.js +99 -1
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +48 -0
- package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +220 -1
- package/lib/commonjs/zustand/utils/index.js +31 -1
- package/lib/commonjs/zustand/utils/zustandStateStore.js +457 -1
- package/lib/module/index.js +85 -1
- package/lib/module/preset.js +98 -1
- package/lib/module/zustand/components/ZustandActionButton.js +112 -1
- package/lib/module/zustand/components/ZustandDetailViewToggle.js +129 -1
- package/lib/module/zustand/components/ZustandEventFilterView.js +287 -1
- package/lib/module/zustand/components/ZustandIcon.js +32 -1
- package/lib/module/zustand/components/ZustandModal.js +599 -1
- package/lib/module/zustand/components/ZustandStateChangeItem.js +161 -1
- package/lib/module/zustand/components/ZustandStateDetailContent.js +348 -1
- package/lib/module/zustand/components/ZustandStateInfoView.js +503 -1
- package/lib/module/zustand/components/ZustandStoreBrowser.js +319 -1
- package/lib/module/zustand/components/index.js +10 -1
- package/lib/module/zustand/hooks/index.js +3 -1
- package/lib/module/zustand/hooks/useZustandStateChanges.js +88 -1
- package/lib/module/zustand/index.js +12 -1
- package/lib/module/zustand/sync/zustandSyncAdapter.js +44 -0
- package/lib/module/zustand/utils/buoyZustandMiddleware.js +214 -1
- package/lib/module/zustand/utils/index.js +4 -1
- package/lib/module/zustand/utils/zustandStateStore.js +453 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts +8 -1
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -0
- package/lib/typescript/zustand/components/index.d.ts.map +1 -0
- package/lib/typescript/zustand/hooks/index.d.ts.map +1 -0
- package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -0
- package/lib/typescript/zustand/index.d.ts.map +1 -0
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +26 -0
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/zustand/types/index.d.ts +12 -0
- package/lib/typescript/zustand/types/index.d.ts.map +1 -0
- package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +1 -0
- package/lib/typescript/zustand/utils/index.d.ts.map +1 -0
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts +35 -1
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1,319 @@
|
|
|
1
|
-
"use strict";
|
|
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, ActivityIndicator } 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 LoadingBrowserState() {
|
|
112
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
113
|
+
style: styles.emptyState,
|
|
114
|
+
children: [/*#__PURE__*/_jsx(ActivityIndicator, {
|
|
115
|
+
size: "small",
|
|
116
|
+
color: macOSColors.text.muted
|
|
117
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
118
|
+
style: styles.emptyTitle,
|
|
119
|
+
children: "Loading stores\u2026"
|
|
120
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
121
|
+
style: styles.emptyText,
|
|
122
|
+
children: "Waiting for the device to send its registered stores."
|
|
123
|
+
})]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function EmptyBrowserState() {
|
|
127
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
128
|
+
style: styles.emptyState,
|
|
129
|
+
children: [/*#__PURE__*/_jsx(Box, {
|
|
130
|
+
size: 32,
|
|
131
|
+
color: macOSColors.text.muted
|
|
132
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
133
|
+
style: styles.emptyTitle,
|
|
134
|
+
children: "No stores registered"
|
|
135
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
136
|
+
style: styles.emptyText,
|
|
137
|
+
children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
|
|
138
|
+
})]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
export function ZustandStoreBrowser({
|
|
142
|
+
stores,
|
|
143
|
+
searchQuery,
|
|
144
|
+
onViewHistory,
|
|
145
|
+
isLoading = false
|
|
146
|
+
}) {
|
|
147
|
+
const [expandedStore, setExpandedStore] = useState(null);
|
|
148
|
+
|
|
149
|
+
// Filter stores by search query
|
|
150
|
+
const filteredStores = useMemo(() => {
|
|
151
|
+
if (!searchQuery) return stores;
|
|
152
|
+
const search = searchQuery.toLowerCase();
|
|
153
|
+
return stores.filter(s => s.name.toLowerCase().includes(search) || s.persistName && s.persistName.toLowerCase().includes(search));
|
|
154
|
+
}, [stores, searchQuery]);
|
|
155
|
+
const handleStorePress = useCallback(store => {
|
|
156
|
+
setExpandedStore(prev => prev === store.name ? null : store.name);
|
|
157
|
+
}, []);
|
|
158
|
+
if (filteredStores.length === 0 && !searchQuery) {
|
|
159
|
+
return isLoading ? /*#__PURE__*/_jsx(LoadingBrowserState, {}) : /*#__PURE__*/_jsx(EmptyBrowserState, {});
|
|
160
|
+
}
|
|
161
|
+
if (filteredStores.length === 0 && searchQuery) {
|
|
162
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
163
|
+
style: styles.emptyState,
|
|
164
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
165
|
+
style: styles.emptyTitle,
|
|
166
|
+
children: "No matching stores"
|
|
167
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
168
|
+
style: styles.emptyText,
|
|
169
|
+
children: ["No stores match \"", searchQuery, "\""]
|
|
170
|
+
})]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return /*#__PURE__*/_jsxs(ScrollView, {
|
|
174
|
+
style: styles.container,
|
|
175
|
+
contentContainerStyle: styles.scrollContent,
|
|
176
|
+
showsVerticalScrollIndicator: true,
|
|
177
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
178
|
+
style: styles.sectionHeader,
|
|
179
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
180
|
+
style: styles.sectionTitle,
|
|
181
|
+
children: "STORES"
|
|
182
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
183
|
+
style: styles.sectionCountBadge,
|
|
184
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
185
|
+
style: styles.sectionCountText,
|
|
186
|
+
children: filteredStores.length
|
|
187
|
+
})
|
|
188
|
+
})]
|
|
189
|
+
}), filteredStores.map(store => {
|
|
190
|
+
const isExpanded = expandedStore === store.name;
|
|
191
|
+
const keysPreview = getKeysPreview(store);
|
|
192
|
+
const storeColor = zustandStateStore.getStoreColor(store.name);
|
|
193
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
194
|
+
style: styles.storeRowWrapper,
|
|
195
|
+
children: [/*#__PURE__*/_jsx(CompactRow, {
|
|
196
|
+
statusDotColor: storeColor,
|
|
197
|
+
statusLabel: store.name,
|
|
198
|
+
statusSublabel: store.isPersisted ? "persisted" : "in-memory",
|
|
199
|
+
primaryText: keysPreview,
|
|
200
|
+
showChevron: true,
|
|
201
|
+
isExpanded: isExpanded,
|
|
202
|
+
onPress: () => handleStorePress(store),
|
|
203
|
+
expandedContent: isExpanded ? /*#__PURE__*/_jsx(StoreExpandedContent, {
|
|
204
|
+
store: store,
|
|
205
|
+
onViewHistory: onViewHistory
|
|
206
|
+
}) : undefined
|
|
207
|
+
}), store.stateChangeCount > 0 && /*#__PURE__*/_jsx(View, {
|
|
208
|
+
style: [styles.absCountBadge, {
|
|
209
|
+
backgroundColor: storeColor + "22",
|
|
210
|
+
borderColor: storeColor + "55"
|
|
211
|
+
}],
|
|
212
|
+
pointerEvents: "none",
|
|
213
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
214
|
+
style: [styles.absCountText, {
|
|
215
|
+
color: storeColor
|
|
216
|
+
}],
|
|
217
|
+
children: String(store.stateChangeCount)
|
|
218
|
+
})
|
|
219
|
+
})]
|
|
220
|
+
}, store.name);
|
|
221
|
+
})]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const styles = StyleSheet.create({
|
|
225
|
+
container: {
|
|
226
|
+
flex: 1
|
|
227
|
+
},
|
|
228
|
+
storeRowWrapper: {
|
|
229
|
+
position: "relative"
|
|
230
|
+
},
|
|
231
|
+
absCountBadge: {
|
|
232
|
+
position: "absolute",
|
|
233
|
+
top: 4,
|
|
234
|
+
right: 10,
|
|
235
|
+
paddingHorizontal: 5,
|
|
236
|
+
paddingVertical: 1,
|
|
237
|
+
borderRadius: 4,
|
|
238
|
+
borderWidth: 1,
|
|
239
|
+
zIndex: 1
|
|
240
|
+
},
|
|
241
|
+
absCountText: {
|
|
242
|
+
fontSize: 9,
|
|
243
|
+
fontWeight: "700",
|
|
244
|
+
fontFamily: "monospace"
|
|
245
|
+
},
|
|
246
|
+
scrollContent: {
|
|
247
|
+
paddingTop: 8,
|
|
248
|
+
paddingBottom: 20
|
|
249
|
+
},
|
|
250
|
+
sectionHeader: {
|
|
251
|
+
flexDirection: "row",
|
|
252
|
+
alignItems: "center",
|
|
253
|
+
paddingHorizontal: 16,
|
|
254
|
+
paddingVertical: 8,
|
|
255
|
+
gap: 8
|
|
256
|
+
},
|
|
257
|
+
sectionTitle: {
|
|
258
|
+
fontSize: 11,
|
|
259
|
+
fontWeight: "700",
|
|
260
|
+
letterSpacing: 0.5,
|
|
261
|
+
color: macOSColors.text.muted
|
|
262
|
+
},
|
|
263
|
+
sectionCountBadge: {
|
|
264
|
+
backgroundColor: buoyColors.primary + "26",
|
|
265
|
+
paddingHorizontal: 8,
|
|
266
|
+
paddingVertical: 2,
|
|
267
|
+
borderRadius: 4
|
|
268
|
+
},
|
|
269
|
+
sectionCountText: {
|
|
270
|
+
fontSize: 10,
|
|
271
|
+
fontWeight: "700",
|
|
272
|
+
color: buoyColors.primary,
|
|
273
|
+
fontFamily: "monospace"
|
|
274
|
+
},
|
|
275
|
+
emptyState: {
|
|
276
|
+
alignItems: "center",
|
|
277
|
+
paddingVertical: 40
|
|
278
|
+
},
|
|
279
|
+
emptyTitle: {
|
|
280
|
+
color: macOSColors.text.primary,
|
|
281
|
+
fontSize: 14,
|
|
282
|
+
fontWeight: "600",
|
|
283
|
+
marginTop: 12,
|
|
284
|
+
marginBottom: 6
|
|
285
|
+
},
|
|
286
|
+
emptyText: {
|
|
287
|
+
color: macOSColors.text.muted,
|
|
288
|
+
fontSize: 12,
|
|
289
|
+
textAlign: "center",
|
|
290
|
+
lineHeight: 18
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const expandedStyles = StyleSheet.create({
|
|
294
|
+
container: {
|
|
295
|
+
gap: 10
|
|
296
|
+
},
|
|
297
|
+
viewHistoryButton: {
|
|
298
|
+
marginLeft: 4
|
|
299
|
+
},
|
|
300
|
+
viewHistoryText: {
|
|
301
|
+
fontSize: 10,
|
|
302
|
+
fontWeight: "600",
|
|
303
|
+
fontFamily: "monospace"
|
|
304
|
+
},
|
|
305
|
+
dataContainer: {
|
|
306
|
+
backgroundColor: buoyColors.base,
|
|
307
|
+
borderRadius: 6,
|
|
308
|
+
borderWidth: 1,
|
|
309
|
+
borderColor: buoyColors.border,
|
|
310
|
+
overflow: "hidden",
|
|
311
|
+
minHeight: 60
|
|
312
|
+
},
|
|
313
|
+
emptyText: {
|
|
314
|
+
color: buoyColors.textMuted,
|
|
315
|
+
fontSize: 12,
|
|
316
|
+
padding: 14,
|
|
317
|
+
fontStyle: "italic"
|
|
318
|
+
}
|
|
319
|
+
});
|
|
@@ -1 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
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,88 @@
|
|
|
1
|
-
"use strict";
|
|
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";
|
|
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
|
+
};
|