@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,457 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.zustandStateStore=void 0;const STORE_COLORS={counter:"#10B981",auth:"#8B5CF6",user:"#3B82F6",cart:"#EC4899",app:"#6366F1",ui:"#F59E0B",settings:"#14B8A6",theme:"#06B6D4",navigation:"#F97316",form:"#EF4444"};function getStoreColor(e){const t=e.toLowerCase();if(STORE_COLORS[t])return STORE_COLORS[t];for(const[e,s]of Object.entries(STORE_COLORS))if(t.includes(e))return s;const s=137*e.split("").reduce((e,t)=>e+t.charCodeAt(0),0)%360,r=.7*(1-Math.abs(1.2-1)),n=r*(1-Math.abs(s/60%2-1)),a=.6-r/2;let i=0,o=0,h=0;s<60?(i=r,o=n):s<120?(i=n,o=r):s<180?(o=r,h=n):s<240?(o=n,h=r):s<300?(i=n,h=r):(i=r,h=n);const g=e=>Math.round(255*(e+a)).toString(16).padStart(2,"0");return`#${g(i)}${g(o)}${g(h)}`}function formatPartialPreview(e,t=40){if(void 0===e)return"";if(null===e)return"null";try{if("function"==typeof e)return"(updater fn)";if("string"==typeof e)return e.length>t?`"${e.slice(0,t-3)}..."`:`"${e}"`;if("number"==typeof e||"boolean"==typeof e)return String(e);if(Array.isArray(e)){if(0===e.length)return"[]";const s=JSON.stringify(e);return s.length>t?`[${e.length} items]`:s}if("object"==typeof e){const s=Object.keys(e);if(0===s.length)return"{}";const r=JSON.stringify(e);return r.length<=t?r:`{ ${s.length} keys }`}return String(e).slice(0,t)}catch{return"[complex]"}}function getStateDiffSummary(e,t){if(e===t)return{summary:"no change",changedKeys:[],changedCount:0};if("object"!=typeof e||"object"!=typeof t||null===e||null===t)return{summary:"changed",changedKeys:[],changedCount:1};const s=Object.keys(e),r=Object.keys(t),n=r.filter(e=>!s.includes(e)),a=s.filter(e=>!r.includes(e)),i=[];for(const n of s)r.includes(n)&&e[n]!==t[n]&&i.push(n);const o=[...n,...a,...i],h=[];n.length>0&&h.push(`+${n.length}`),a.length>0&&h.push(`-${a.length}`),i.length>0&&h.push(`~${i.length}`);const g=o.length;return 0===h.length?{summary:"nested change",changedKeys:[],changedCount:1}:{summary:`${h.join(" ")} ${1===g?"key":"keys"}`,changedKeys:o,changedCount:g}}class ZustandStateStore{stateChanges=[];stores=new Map;listeners=new Set;storeListeners=new Set;maxChanges=200;idCounter=0;isEnabled=!0;addStateChange(e){if(!this.isEnabled)return;const{storeName:t,partial:s,replace:r,prevState:n,nextState:a,duration:i,category:o,isPersisted:h=!1}=e,g=n!==a,{summary:u,changedKeys:l,changedCount:c}=getStateDiffSummary(n,a),d=formatPartialPreview(s),f=(i??0)>16,S=o??(r?"replace":"setState"),C={id:`${Date.now()}-${++this.idCounter}`,storeName:t,timestamp:Date.now(),prevState:n,nextState:a,partial:s,duration:i,hasStateChange:g,replace:r,category:S,changedKeys:l,changedKeysCount:c,diffSummary:u,partialPreview:d,isSlowUpdate:f,isPersisted:h};this.stateChanges=[C,...this.stateChanges].slice(0,this.maxChanges);const y=this.stores.get(t);y&&y.stateChangeCount++,this.notifyListeners()}getStateChanges(){return[...this.stateChanges]}getStateChangeById(e){return this.stateChanges.find(t=>t.id===e)}clearStateChanges(){this.stateChanges=[];for(const e of this.stores.values())e.stateChangeCount=0;this.notifyListeners()}registerStore(e,t,s){if(this.stores.has(e))return;const r={name:e,api:t,stateChangeCount:0,isPersisted:s?.isPersisted??!1,persistName:s?.persistName,color:getStoreColor(e)};this.stores.set(e,r),this.notifyStoreListeners()}unregisterStore(e){this.stores.delete(e),this.notifyStoreListeners()}getStores(){return Array.from(this.stores.values())}getStore(e){return this.stores.get(e)}getStoreColor(e){return this.stores.get(e)?.color??getStoreColor(e)}filterStateChanges(e){let t=[...this.stateChanges];if(e.searchText){const s=e.searchText.toLowerCase();t=t.filter(e=>e.storeName.toLowerCase().includes(s)||e.partialPreview.toLowerCase().includes(s)||e.changedKeys.some(e=>e.toLowerCase().includes(s)))}return e.storeNames&&e.storeNames.length>0&&(t=t.filter(t=>e.storeNames.includes(t.storeName))),e.onlyWithChanges&&(t=t.filter(e=>e.hasStateChange)),t}getStats(){const e=this.stateChanges.length,t=this.stateChanges.filter(e=>e.hasStateChange).length,s=this.stores.size,r=this.stateChanges.filter(e=>void 0!==e.duration).map(e=>e.duration),n=r.length>0?r.reduce((e,t)=>e+t,0)/r.length:0;return{totalChanges:e,changesWithStateChange:t,changesWithoutStateChange:e-t,storeCount:s,averageDuration:Math.round(100*n)/100}}getUniqueStoreNames(){return Array.from(this.stores.keys()).sort()}setEnabled(e){this.isEnabled=e}getEnabled(){return this.isEnabled}setMaxChanges(e){this.maxChanges=e,this.stateChanges.length>e&&(this.stateChanges=this.stateChanges.slice(0,e),this.notifyListeners())}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}subscribeToStores(e){return this.storeListeners.add(e),()=>{this.storeListeners.delete(e)}}notifyListeners(){const e=this.getStateChanges();this.listeners.forEach(t=>t(e))}notifyStoreListeners(){const e=this.getStores();this.storeListeners.forEach(t=>t(e))}}const zustandStateStore=exports.zustandStateStore=new ZustandStateStore;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.zustandStateStore = void 0;
7
+ /**
8
+ * Zustand state store - captures and stores Zustand state changes
9
+ *
10
+ * Mirrors the architecture of reduxActionStore.ts from @buoy-gg/redux
11
+ */
12
+
13
+ // ============================================
14
+ // Store Color Palette
15
+ // ============================================
16
+
17
+ const STORE_COLORS = {
18
+ counter: "#10B981",
19
+ // emerald
20
+ auth: "#8B5CF6",
21
+ // purple
22
+ user: "#3B82F6",
23
+ // blue
24
+ cart: "#EC4899",
25
+ // pink
26
+ app: "#6366F1",
27
+ // indigo
28
+ ui: "#F59E0B",
29
+ // amber
30
+ settings: "#14B8A6",
31
+ // teal
32
+ theme: "#06B6D4",
33
+ // cyan
34
+ navigation: "#F97316",
35
+ // orange
36
+ form: "#EF4444" // red
37
+ };
38
+
39
+ /**
40
+ * Get consistent color for a store based on its name
41
+ */
42
+ function getStoreColor(storeName) {
43
+ const lowerName = storeName.toLowerCase();
44
+
45
+ // Check for exact match
46
+ if (STORE_COLORS[lowerName]) {
47
+ return STORE_COLORS[lowerName];
48
+ }
49
+
50
+ // Check for partial matches
51
+ for (const [key, color] of Object.entries(STORE_COLORS)) {
52
+ if (lowerName.includes(key)) {
53
+ return color;
54
+ }
55
+ }
56
+
57
+ // Generate consistent hex color from name hash
58
+ const hash = storeName.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
59
+ const hue = hash * 137 % 360;
60
+ // Convert HSL to hex so "color + alpha_hex" pattern works everywhere
61
+ const s = 0.7;
62
+ const l = 0.6;
63
+ const c = (1 - Math.abs(2 * l - 1)) * s;
64
+ const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
65
+ const m = l - c / 2;
66
+ let r = 0,
67
+ g = 0,
68
+ b = 0;
69
+ if (hue < 60) {
70
+ r = c;
71
+ g = x;
72
+ } else if (hue < 120) {
73
+ r = x;
74
+ g = c;
75
+ } else if (hue < 180) {
76
+ g = c;
77
+ b = x;
78
+ } else if (hue < 240) {
79
+ g = x;
80
+ b = c;
81
+ } else if (hue < 300) {
82
+ r = x;
83
+ b = c;
84
+ } else {
85
+ r = c;
86
+ b = x;
87
+ }
88
+ const toHex = v => Math.round((v + m) * 255).toString(16).padStart(2, "0");
89
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
90
+ }
91
+
92
+ // ============================================
93
+ // Helper Functions
94
+ // ============================================
95
+
96
+ /**
97
+ * Format payload preview for display
98
+ */
99
+ function formatPartialPreview(partial, maxLength = 40) {
100
+ if (partial === undefined) return "";
101
+ if (partial === null) return "null";
102
+ try {
103
+ if (typeof partial === "function") {
104
+ return "(updater fn)";
105
+ }
106
+ if (typeof partial === "string") {
107
+ return partial.length > maxLength ? `"${partial.slice(0, maxLength - 3)}..."` : `"${partial}"`;
108
+ }
109
+ if (typeof partial === "number" || typeof partial === "boolean") {
110
+ return String(partial);
111
+ }
112
+ if (Array.isArray(partial)) {
113
+ if (partial.length === 0) return "[]";
114
+ const preview = JSON.stringify(partial);
115
+ return preview.length > maxLength ? `[${partial.length} items]` : preview;
116
+ }
117
+ if (typeof partial === "object") {
118
+ const keys = Object.keys(partial);
119
+ if (keys.length === 0) return "{}";
120
+ const preview = JSON.stringify(partial);
121
+ if (preview.length <= maxLength) return preview;
122
+ return `{ ${keys.length} keys }`;
123
+ }
124
+ return String(partial).slice(0, maxLength);
125
+ } catch {
126
+ return "[complex]";
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Calculate state diff summary
132
+ */
133
+ function getStateDiffSummary(prevState, nextState) {
134
+ if (prevState === nextState) {
135
+ return {
136
+ summary: "no change",
137
+ changedKeys: [],
138
+ changedCount: 0
139
+ };
140
+ }
141
+ if (typeof prevState !== "object" || typeof nextState !== "object" || prevState === null || nextState === null) {
142
+ return {
143
+ summary: "changed",
144
+ changedKeys: [],
145
+ changedCount: 1
146
+ };
147
+ }
148
+ const prevKeys = Object.keys(prevState);
149
+ const nextKeys = Object.keys(nextState);
150
+ const added = nextKeys.filter(k => !prevKeys.includes(k));
151
+ const removed = prevKeys.filter(k => !nextKeys.includes(k));
152
+ const changed = [];
153
+ for (const key of prevKeys) {
154
+ if (nextKeys.includes(key) && prevState[key] !== nextState[key]) {
155
+ changed.push(key);
156
+ }
157
+ }
158
+ const allChangedKeys = [...added, ...removed, ...changed];
159
+ const parts = [];
160
+ if (added.length > 0) parts.push(`+${added.length}`);
161
+ if (removed.length > 0) parts.push(`-${removed.length}`);
162
+ if (changed.length > 0) parts.push(`~${changed.length}`);
163
+ const totalChanged = allChangedKeys.length;
164
+ if (parts.length === 0) {
165
+ return {
166
+ summary: "nested change",
167
+ changedKeys: [],
168
+ changedCount: 1
169
+ };
170
+ }
171
+ return {
172
+ summary: `${parts.join(" ")} ${totalChanged === 1 ? "key" : "keys"}`,
173
+ changedKeys: allChangedKeys,
174
+ changedCount: totalChanged
175
+ };
176
+ }
177
+
178
+ // ============================================
179
+ // Zustand State Store
180
+ // ============================================
181
+
182
+ class ZustandStateStore {
183
+ stateChanges = [];
184
+ stores = new Map();
185
+ listeners = new Set();
186
+ storeListeners = new Set();
187
+ clearListeners = new Set();
188
+ maxChanges = 200;
189
+ idCounter = 0;
190
+ isEnabled = true;
191
+ captureSuppressed = false;
192
+ remoteSetStateHandler = null;
193
+
194
+ // ---- State Change Tracking ----
195
+
196
+ addStateChange(params) {
197
+ if (!this.isEnabled || this.captureSuppressed) return;
198
+ const {
199
+ storeName,
200
+ partial,
201
+ replace,
202
+ prevState,
203
+ nextState,
204
+ duration,
205
+ category,
206
+ isPersisted = false
207
+ } = params;
208
+ const hasStateChange = prevState !== nextState;
209
+ const {
210
+ summary,
211
+ changedKeys,
212
+ changedCount
213
+ } = getStateDiffSummary(prevState, nextState);
214
+ const partialPreview = formatPartialPreview(partial);
215
+ const isSlowUpdate = (duration ?? 0) > 16;
216
+ const resolvedCategory = category ?? (replace ? "replace" : "setState");
217
+ const stateChange = {
218
+ id: `${Date.now()}-${++this.idCounter}`,
219
+ storeName,
220
+ timestamp: Date.now(),
221
+ prevState,
222
+ nextState,
223
+ partial,
224
+ duration,
225
+ hasStateChange,
226
+ replace,
227
+ category: resolvedCategory,
228
+ changedKeys,
229
+ changedKeysCount: changedCount,
230
+ diffSummary: summary,
231
+ partialPreview,
232
+ isSlowUpdate,
233
+ isPersisted
234
+ };
235
+ this.stateChanges = [stateChange, ...this.stateChanges].slice(0, this.maxChanges);
236
+
237
+ // Update store change count
238
+ const storeInfo = this.stores.get(storeName);
239
+ if (storeInfo) {
240
+ storeInfo.stateChangeCount++;
241
+ }
242
+ this.notifyListeners();
243
+ }
244
+ getStateChanges() {
245
+ return [...this.stateChanges];
246
+ }
247
+ getStateChangeById(id) {
248
+ return this.stateChanges.find(c => c.id === id);
249
+ }
250
+ clearStateChanges() {
251
+ this.stateChanges = [];
252
+ // Reset counts
253
+ for (const store of this.stores.values()) {
254
+ store.stateChangeCount = 0;
255
+ }
256
+ this.notifyListeners();
257
+ this.clearListeners.forEach(listener => {
258
+ try {
259
+ listener();
260
+ } catch {
261
+ // Ignore listener errors
262
+ }
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Listen for clearStateChanges() calls. Used in remote mirror mode to
268
+ * forward a clear performed in the dashboard UI to the synced device.
269
+ */
270
+ onClear(listener) {
271
+ this.clearListeners.add(listener);
272
+ return () => {
273
+ this.clearListeners.delete(listener);
274
+ };
275
+ }
276
+
277
+ // ---- Remote Mirror Mode ----
278
+
279
+ /**
280
+ * Permanently suppress local capture. Use when this store acts as a mirror
281
+ * of a remote device's state changes (e.g. the desktop dashboard): data
282
+ * arrives via replaceFromSnapshot() and local middleware must never record.
283
+ */
284
+ disableCapture() {
285
+ this.captureSuppressed = true;
286
+ }
287
+
288
+ /** Whether the store is in remote mirror mode (capture suppressed). */
289
+ isCaptureSuppressed() {
290
+ return this.captureSuppressed;
291
+ }
292
+
293
+ /**
294
+ * Serializable snapshot of the tracked stores (registry metadata plus each
295
+ * store's current state). Used by the sync adapter on the device side —
296
+ * the live `api` handles can't go over the wire.
297
+ */
298
+ getStoreSnapshots() {
299
+ return Array.from(this.stores.values()).map(store => {
300
+ let currentState;
301
+ try {
302
+ currentState = store.api.getState();
303
+ } catch {
304
+ currentState = undefined;
305
+ }
306
+ return {
307
+ name: store.name,
308
+ stateChangeCount: store.stateChangeCount,
309
+ isPersisted: store.isPersisted,
310
+ persistName: store.persistName,
311
+ color: store.color,
312
+ currentState
313
+ };
314
+ });
315
+ }
316
+
317
+ /**
318
+ * Handler invoked when the UI calls setState on a mirrored store (e.g.
319
+ * time-travel from the dashboard). Forward it to the synced device.
320
+ */
321
+ setRemoteSetStateHandler(handler) {
322
+ this.remoteSetStateHandler = handler;
323
+ }
324
+
325
+ /**
326
+ * Replace the entire mirror contents from a synced device snapshot. The
327
+ * rebuilt registry entries get stub `api` handles: getState() serves the
328
+ * snapshotted state, setState() forwards to the remote handler. Respects
329
+ * the enabled flag so the UI's pause button freezes the mirror.
330
+ */
331
+ replaceFromSnapshot(changes, storeSnapshots) {
332
+ if (!this.isEnabled) return;
333
+ this.stateChanges = changes.slice(0, this.maxChanges);
334
+ this.stores = new Map(storeSnapshots.map(snapshot => [snapshot.name, {
335
+ name: snapshot.name,
336
+ api: {
337
+ getState: () => snapshot.currentState,
338
+ setState: (partial, replace) => {
339
+ this.remoteSetStateHandler?.(snapshot.name, partial, replace);
340
+ },
341
+ subscribe: () => () => {}
342
+ },
343
+ stateChangeCount: snapshot.stateChangeCount,
344
+ isPersisted: snapshot.isPersisted,
345
+ persistName: snapshot.persistName,
346
+ color: snapshot.color
347
+ }]));
348
+ this.notifyListeners();
349
+ this.notifyStoreListeners();
350
+ }
351
+
352
+ // ---- Store Registry ----
353
+
354
+ registerStore(name, api, options) {
355
+ if (this.stores.has(name)) return;
356
+ const storeInfo = {
357
+ name,
358
+ api,
359
+ stateChangeCount: 0,
360
+ isPersisted: options?.isPersisted ?? false,
361
+ persistName: options?.persistName,
362
+ color: getStoreColor(name)
363
+ };
364
+ this.stores.set(name, storeInfo);
365
+ this.notifyStoreListeners();
366
+ }
367
+ unregisterStore(name) {
368
+ this.stores.delete(name);
369
+ this.notifyStoreListeners();
370
+ }
371
+ getStores() {
372
+ return Array.from(this.stores.values());
373
+ }
374
+ getStore(name) {
375
+ return this.stores.get(name);
376
+ }
377
+ getStoreColor(name) {
378
+ return this.stores.get(name)?.color ?? getStoreColor(name);
379
+ }
380
+
381
+ // ---- Filtering ----
382
+
383
+ filterStateChanges(filter) {
384
+ let filtered = [...this.stateChanges];
385
+ if (filter.searchText) {
386
+ const search = filter.searchText.toLowerCase();
387
+ filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
388
+ }
389
+ if (filter.storeNames && filter.storeNames.length > 0) {
390
+ filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
391
+ }
392
+ if (filter.onlyWithChanges) {
393
+ filtered = filtered.filter(c => c.hasStateChange);
394
+ }
395
+ return filtered;
396
+ }
397
+
398
+ // ---- Stats ----
399
+
400
+ getStats() {
401
+ const total = this.stateChanges.length;
402
+ const withChanges = this.stateChanges.filter(c => c.hasStateChange).length;
403
+ const storeCount = this.stores.size;
404
+ const durations = this.stateChanges.filter(c => c.duration !== undefined).map(c => c.duration);
405
+ const avgDuration = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
406
+ return {
407
+ totalChanges: total,
408
+ changesWithStateChange: withChanges,
409
+ changesWithoutStateChange: total - withChanges,
410
+ storeCount,
411
+ averageDuration: Math.round(avgDuration * 100) / 100
412
+ };
413
+ }
414
+ getUniqueStoreNames() {
415
+ return Array.from(this.stores.keys()).sort();
416
+ }
417
+
418
+ // ---- Enable / Disable ----
419
+
420
+ setEnabled(enabled) {
421
+ this.isEnabled = enabled;
422
+ }
423
+ getEnabled() {
424
+ return this.isEnabled;
425
+ }
426
+ setMaxChanges(max) {
427
+ this.maxChanges = max;
428
+ if (this.stateChanges.length > max) {
429
+ this.stateChanges = this.stateChanges.slice(0, max);
430
+ this.notifyListeners();
431
+ }
432
+ }
433
+
434
+ // ---- Subscriptions ----
435
+
436
+ subscribe(listener) {
437
+ this.listeners.add(listener);
438
+ return () => {
439
+ this.listeners.delete(listener);
440
+ };
441
+ }
442
+ subscribeToStores(listener) {
443
+ this.storeListeners.add(listener);
444
+ return () => {
445
+ this.storeListeners.delete(listener);
446
+ };
447
+ }
448
+ notifyListeners() {
449
+ const changes = this.getStateChanges();
450
+ this.listeners.forEach(listener => listener(changes));
451
+ }
452
+ notifyStoreListeners() {
453
+ const stores = this.getStores();
454
+ this.storeListeners.forEach(listener => listener(stores));
455
+ }
456
+ }
457
+ const zustandStateStore = exports.zustandStateStore = new ZustandStateStore();
@@ -1 +1,85 @@
1
- "use strict";export{zustandToolPreset,createZustandTool}from"./preset";export{watchStores}from"./zustand/utils/buoyZustandMiddleware";export{buoyDevTools}from"./zustand/utils/buoyZustandMiddleware";export{isStoreInstrumented}from"./zustand/utils/buoyZustandMiddleware";export{useZustandStateChanges}from"./zustand/hooks/useZustandStateChanges";export{ZustandModal}from"./zustand/components/ZustandModal";export{ZustandStateChangeItem}from"./zustand/components/ZustandStateChangeItem";export{ZustandStateDetailContent,ZustandStateDetailFooter}from"./zustand/components/ZustandStateDetailContent";export{ZustandIcon,ZUSTAND_ICON_COLOR}from"./zustand/components/ZustandIcon";export{zustandStateStore}from"./zustand/utils/zustandStateStore";
1
+ "use strict";
2
+
3
+ /**
4
+ * @buoy-gg/zustand
5
+ *
6
+ * Zustand Store DevTools for React Native
7
+ *
8
+ * PUBLIC API - Only these exports are supported for external use.
9
+ *
10
+ * @example Recommended setup (one line, zero store modifications)
11
+ * ```tsx
12
+ * import { watchStores } from '@buoy-gg/zustand';
13
+ * import { useCounterStore } from './stores/counter';
14
+ * import { useAuthStore } from './stores/auth';
15
+ *
16
+ * // One call, anywhere at module scope or in your root layout:
17
+ * watchStores({
18
+ * counterStore: useCounterStore,
19
+ * authStore: useAuthStore,
20
+ * });
21
+ * ```
22
+ *
23
+ * @example Advanced: middleware for extra detail (partial + timing)
24
+ * ```tsx
25
+ * import { create } from 'zustand';
26
+ * import { buoyDevTools } from '@buoy-gg/zustand';
27
+ *
28
+ * const useCounterStore = create(
29
+ * buoyDevTools(
30
+ * (set) => ({
31
+ * count: 0,
32
+ * increment: () => set((s) => ({ count: s.count + 1 })),
33
+ * }),
34
+ * { name: 'counterStore' }
35
+ * )
36
+ * );
37
+ * ```
38
+ */
39
+
40
+ // =============================================================================
41
+ // PRESET (Primary entry point for FloatingDevTools)
42
+ // =============================================================================
43
+ export { zustandToolPreset, createZustandTool } from "./preset";
44
+
45
+ // =============================================================================
46
+ // WATCH STORES (Recommended — non-intrusive, subscribe-only)
47
+ // =============================================================================
48
+ export { watchStores } from "./zustand/utils/buoyZustandMiddleware";
49
+
50
+ // =============================================================================
51
+ // MIDDLEWARE (Advanced — wraps setState for partial + timing capture)
52
+ // =============================================================================
53
+ export { buoyDevTools } from "./zustand/utils/buoyZustandMiddleware";
54
+
55
+ // =============================================================================
56
+ // UTILITIES
57
+ // =============================================================================
58
+ export { isStoreInstrumented } from "./zustand/utils/buoyZustandMiddleware";
59
+
60
+ // =============================================================================
61
+ // HOOKS (For consuming Zustand state data)
62
+ // =============================================================================
63
+ export { useZustandStateChanges } from "./zustand/hooks/useZustandStateChanges";
64
+ // =============================================================================
65
+ // COMPONENTS (For custom UI implementations)
66
+ // =============================================================================
67
+ export { ZustandModal } from "./zustand/components/ZustandModal";
68
+ export { ZustandStateChangeItem } from "./zustand/components/ZustandStateChangeItem";
69
+ export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./zustand/components/ZustandStateDetailContent";
70
+ export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./zustand/components/ZustandIcon";
71
+
72
+ // =============================================================================
73
+ // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
74
+ // =============================================================================
75
+ export { zustandSyncAdapter } from "./zustand/sync/zustandSyncAdapter";
76
+
77
+ // =============================================================================
78
+ // TYPES
79
+ // =============================================================================
80
+
81
+ // =============================================================================
82
+ // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
83
+ // =============================================================================
84
+ /** @internal */
85
+ export { zustandStateStore } from "./zustand/utils/zustandStateStore";
@@ -1 +1,98 @@
1
- "use strict";import{ZustandModal}from"./zustand/components/ZustandModal";import{ZustandIcon}from"./zustand/components/ZustandIcon";import{jsx as _jsx}from"react/jsx-runtime";export const zustandToolPreset={id:"zustand",name:"ZUSTAND",description:"Zustand store & state inspector",slot:"both",icon:({size:o})=>_jsx(ZustandIcon,{size:o}),component:ZustandModal,props:{enableSharedModalDimensions:!1}};export function createZustandTool(o){return{id:o?.id||"zustand",name:o?.name||"ZUSTAND",description:o?.description||"Zustand store & state inspector",slot:"both",icon:({size:n})=>_jsx(ZustandIcon,{size:n,color:o?.iconColor}),component:ZustandModal,props:{enableSharedModalDimensions:void 0!==o?.enableSharedModalDimensions&&o.enableSharedModalDimensions}}}
1
+ "use strict";
2
+
3
+ /**
4
+ * Pre-configured Zustand DevTools preset for FloatingDevTools
5
+ *
6
+ * ZERO-CONFIG: This preset is auto-discovered by FloatingDevTools!
7
+ * Just install @buoy-gg/zustand and wrap your stores with buoyDevTools().
8
+ *
9
+ * @example Automatic (recommended)
10
+ * ```tsx
11
+ * import { create } from 'zustand';
12
+ * import { buoyDevTools } from '@buoy-gg/zustand';
13
+ *
14
+ * const useCounterStore = create(
15
+ * buoyDevTools(
16
+ * (set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }),
17
+ * { name: 'counterStore' }
18
+ * )
19
+ * );
20
+ *
21
+ * // The Zustand tool appears automatically in FloatingDevTools!
22
+ * <FloatingDevTools />
23
+ * ```
24
+ *
25
+ * @example Manual (only for custom configuration)
26
+ * ```tsx
27
+ * import { createZustandTool } from '@buoy-gg/zustand';
28
+ *
29
+ * const customZustandTool = createZustandTool({
30
+ * name: "STATE",
31
+ * iconColor: "#463B3F",
32
+ * });
33
+ *
34
+ * <FloatingDevTools apps={[customZustandTool]} />
35
+ * ```
36
+ */
37
+
38
+ import { ZustandModal } from "./zustand/components/ZustandModal";
39
+ import { ZustandIcon } from "./zustand/components/ZustandIcon";
40
+
41
+ /**
42
+ * Pre-configured Zustand DevTools preset for FloatingDevTools.
43
+ * Includes:
44
+ * - Live state change monitoring
45
+ * - State inspection (JSON viewer)
46
+ * - State diff visualization (tree + split)
47
+ * - Filter by store name
48
+ * - Changed keys tracking
49
+ */
50
+ import { jsx as _jsx } from "react/jsx-runtime";
51
+ export const zustandToolPreset = {
52
+ id: "zustand",
53
+ name: "ZUSTAND",
54
+ description: "Zustand store & state inspector",
55
+ slot: "both",
56
+ icon: ({
57
+ size
58
+ }) => /*#__PURE__*/_jsx(ZustandIcon, {
59
+ size: size
60
+ }),
61
+ component: ZustandModal,
62
+ props: {
63
+ enableSharedModalDimensions: false
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Create a custom Zustand DevTools configuration.
69
+ * Use this if you want to override default settings.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { createZustandTool } from '@buoy-gg/zustand';
74
+ *
75
+ * const myZustandTool = createZustandTool({
76
+ * name: "STATE",
77
+ * iconColor: "#463B3F",
78
+ * });
79
+ * ```
80
+ */
81
+ export function createZustandTool(options) {
82
+ return {
83
+ id: options?.id || "zustand",
84
+ name: options?.name || "ZUSTAND",
85
+ description: options?.description || "Zustand store & state inspector",
86
+ slot: "both",
87
+ icon: ({
88
+ size
89
+ }) => /*#__PURE__*/_jsx(ZustandIcon, {
90
+ size: size,
91
+ color: options?.iconColor
92
+ }),
93
+ component: ZustandModal,
94
+ props: {
95
+ enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
96
+ }
97
+ };
98
+ }