@buoy-gg/zustand 7.0.41 → 7.0.43

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/README.md +11 -76
  2. package/lib/commonjs/index.js +1 -105
  3. package/lib/commonjs/preset.js +1 -102
  4. package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -118
  5. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +1 -48
  6. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +1 -43
  7. package/lib/commonjs/zustand/components/ZustandIcon.js +1 -18
  8. package/lib/commonjs/zustand/components/ZustandModal.js +1 -772
  9. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +1 -117
  10. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +1 -372
  11. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +1 -488
  12. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +1 -204
  13. package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +1 -471
  14. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +1 -74
  15. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +1 -418
  16. package/lib/commonjs/zustand/types/index.js +1 -1
  17. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +1 -284
  18. package/lib/commonjs/zustand/utils/editableState.js +1 -146
  19. package/lib/commonjs/zustand/utils/snapshotProvider.js +1 -209
  20. package/lib/commonjs/zustand/utils/testIDs.js +1 -82
  21. package/lib/commonjs/zustand/utils/zustandStateStore.js +1 -379
  22. package/lib/module/index.js +1 -90
  23. package/lib/module/preset.js +1 -98
  24. package/lib/module/zustand/components/ZustandActionButton.js +1 -114
  25. package/lib/module/zustand/components/ZustandDetailViewToggle.js +1 -43
  26. package/lib/module/zustand/components/ZustandEventFilterView.js +1 -39
  27. package/lib/module/zustand/components/ZustandIcon.js +1 -11
  28. package/lib/module/zustand/components/ZustandModal.js +1 -767
  29. package/lib/module/zustand/components/ZustandStateChangeItem.js +1 -113
  30. package/lib/module/zustand/components/ZustandStateDetailContent.js +1 -368
  31. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -483
  32. package/lib/module/zustand/components/ZustandStoreBrowser.js +1 -200
  33. package/lib/module/zustand/components/ZustandStoreDetailContent.js +1 -469
  34. package/lib/module/zustand/hooks/useZustandStateChanges.js +1 -70
  35. package/lib/module/zustand/sync/zustandSyncAdapter.js +1 -415
  36. package/lib/module/zustand/types/index.js +1 -1
  37. package/lib/module/zustand/utils/buoyZustandMiddleware.js +1 -278
  38. package/lib/module/zustand/utils/editableState.js +1 -138
  39. package/lib/module/zustand/utils/snapshotProvider.js +1 -209
  40. package/lib/module/zustand/utils/testIDs.js +1 -74
  41. package/lib/module/zustand/utils/zustandStateStore.js +1 -375
  42. package/lib/web/index.mjs +2 -3
  43. package/package.json +4 -7
  44. package/lib/typescript/index.d.ts.map +0 -1
  45. package/lib/typescript/preset.d.ts.map +0 -1
  46. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +0 -1
  47. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +0 -1
  48. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +0 -1
  49. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +0 -1
  50. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +0 -1
  51. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +0 -1
  52. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +0 -1
  53. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +0 -1
  54. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +0 -1
  55. package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +0 -1
  56. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +0 -1
  57. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +0 -1
  58. package/lib/typescript/zustand/types/index.d.ts.map +0 -1
  59. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +0 -1
  60. package/lib/typescript/zustand/utils/editableState.d.ts.map +0 -1
  61. package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +0 -1
  62. package/lib/typescript/zustand/utils/testIDs.d.ts.map +0 -1
  63. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +0 -1
@@ -1,82 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.ZUSTAND_TEST_ID = void 0;
7
- exports.buildChangeRowTestIDs = buildChangeRowTestIDs;
8
- exports.changeRowTestID = changeRowTestID;
9
- exports.slugForTestID = slugForTestID;
10
- exports.storeRowTestID = storeRowTestID;
11
- /**
12
- * Zustand tool test handles.
13
- *
14
- * Every tappable and every value the e2e suite reads back is addressed
15
- * `buoy-zustand-<thing>`. Two rules in here need explaining.
16
- *
17
- * **A change row's handle carries an ORDINAL, and it counts per STORE.** The
18
- * Events tool can address a row by what it is about, because an event is a
19
- * thing that happened to a named subject — one key, one URL, one atom. A
20
- * zustand state change is not: a store that a screen writes to fifty times
21
- * produces fifty separate, equally real rows. Addressing them by store name
22
- * alone would give fifty views one handle, and Detox fails a *matcher* that
23
- * hits more than one element — so a suite would break on `toExist()`, before
24
- * any assertion, with an error that says nothing about duplicate changes.
25
- *
26
- * So the handle is `buoy-zustand-row-<slug(storeName)>-<n>`, where n counts
27
- * back from the newest row of that store (1 = newest). Deliberately relative:
28
- * a state change has no identity a host could know in advance — `id` is a
29
- * timestamp plus a per-process counter whose value depends on how many
30
- * changes the app happened to make before the test looked. What a test CAN
31
- * know is "the set I just fired is the newest change to that store". Read and
32
- * tap in one beat: a row's ordinal moves the instant another change lands.
33
- *
34
- * **A store row's handle does NOT.** A store is registered once under a name
35
- * that is unique by construction (`zustandStateStore.registerStore` returns
36
- * early on a name it already holds), so `buoy-zustand-store-<slug(name)>` is
37
- * as stable as the store itself — and unlike a change, it is a thing the host
38
- * knows the name of before the app has booted.
39
- *
40
- * The suite mirrors these rules in CommonJS (`example-dev-build/utils/
41
- * zustandBench.js`) and asserts the two agree — Detox runs in Node on the host
42
- * and cannot import TypeScript.
43
- */
44
-
45
- /** Prefix every handle in this tool shares. */
46
- const ZUSTAND_TEST_ID = exports.ZUSTAND_TEST_ID = "buoy-zustand";
47
-
48
- /** `zbenchWatch` → `zbenchwatch`. Shared with every Buoy tool. */
49
- function slugForTestID(value) {
50
- return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
51
- }
52
-
53
- /**
54
- * Handle for one change row: `buoy-zustand-row-zbenchwatch-1`.
55
- *
56
- * `ordinal` is 1-based and counts back from the newest row of this store.
57
- */
58
- function changeRowTestID(storeName, ordinal) {
59
- return `${ZUSTAND_TEST_ID}-row-${slugForTestID(storeName)}-${ordinal}`;
60
- }
61
-
62
- /** Handle for one store row in the browser: `buoy-zustand-store-zbenchwatch`. */
63
- function storeRowTestID(storeName) {
64
- return `${ZUSTAND_TEST_ID}-store-${slugForTestID(storeName)}`;
65
- }
66
-
67
- /**
68
- * Row handle per change id for one rendered list, newest-first.
69
- *
70
- * Built once for the whole list rather than per row, because an ordinal is a
71
- * fact about the list and a row cannot compute its own.
72
- */
73
- function buildChangeRowTestIDs(changes) {
74
- const seen = new Map();
75
- const handles = new Map();
76
- for (const change of changes) {
77
- const ordinal = (seen.get(change.storeName) ?? 0) + 1;
78
- seen.set(change.storeName, ordinal);
79
- handles.set(change.id, changeRowTestID(change.storeName, ordinal));
80
- }
81
- return handles;
82
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ZUSTAND_TEST_ID=void 0;exports.buildChangeRowTestIDs=buildChangeRowTestIDs;exports.changeRowTestID=changeRowTestID;exports.slugForTestID=slugForTestID;exports.storeRowTestID=storeRowTestID;const ZUSTAND_TEST_ID=exports.ZUSTAND_TEST_ID="buoy-zustand";function slugForTestID(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}function changeRowTestID(e,t){return`${ZUSTAND_TEST_ID}-row-${slugForTestID(e)}-${t}`}function storeRowTestID(e){return`${ZUSTAND_TEST_ID}-store-${slugForTestID(e)}`}function buildChangeRowTestIDs(e){const t=new Map;const s=new Map;for(const o of e){const r=(t.get(o.storeName)??0)+1;t.set(o.storeName,r);s.set(o.id,changeRowTestID(o.storeName,r))}return s}
@@ -1,379 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.zustandStateStore = void 0;
7
- var _sharedUi = require("@buoy-gg/shared-ui");
8
- /**
9
- * Zustand state store - captures and stores Zustand state changes
10
- *
11
- * The generic event-log half (ring buffer, listeners, clear-forwarding for
12
- * the desktop mirror, pause/suppress flags, hydrate-by-id) is the shared
13
- * `ChangeLog`; this file owns the zustand-specific half — the store registry,
14
- * snapshots, mirror rebuild, filters and stats.
15
- */
16
-
17
- // ============================================
18
- // Store Color Palette
19
- // ============================================
20
-
21
- const STORE_COLORS = {
22
- counter: "#10B981",
23
- // emerald
24
- auth: "#8B5CF6",
25
- // purple
26
- user: "#3B82F6",
27
- // blue
28
- cart: "#EC4899",
29
- // pink
30
- app: "#6366F1",
31
- // indigo
32
- ui: "#F59E0B",
33
- // amber
34
- settings: "#14B8A6",
35
- // teal
36
- theme: "#06B6D4",
37
- // cyan
38
- navigation: "#F97316",
39
- // orange
40
- form: "#EF4444" // red
41
- };
42
-
43
- /**
44
- * Get consistent color for a store based on its name
45
- */
46
- function getStoreColor(storeName) {
47
- return (0, _sharedUi.entityColor)(storeName, STORE_COLORS);
48
- }
49
-
50
- /**
51
- * Format payload preview for display
52
- */
53
- function formatPartialPreview(partial, maxLength = 40) {
54
- return (0, _sharedUi.formatValuePreview)(partial, {
55
- maxLength,
56
- undefinedLabel: "",
57
- functionLabel: "(updater fn)"
58
- });
59
- }
60
-
61
- // ============================================
62
- // Zustand State Store
63
- // ============================================
64
-
65
- class ZustandStateStore extends _sharedUi.ChangeLog {
66
- stores = new Map();
67
- storeListeners = new Set();
68
- remoteSetStateHandler = null;
69
-
70
- // ---- State Change Tracking ----
71
-
72
- addStateChange(params) {
73
- if (!this.canRecord()) return;
74
- const {
75
- storeName,
76
- partial,
77
- replace,
78
- prevState,
79
- nextState,
80
- duration,
81
- category,
82
- isPersisted = false
83
- } = params;
84
- const hasStateChange = prevState !== nextState;
85
- // `changedCount` counts the keys in `changedKeys` and nothing else, so the
86
- // row's "N keys changed" and the detail page's CHANGED KEYS card agree —
87
- // including the persist-rehydrate case where the object is replaced
88
- // without any top-level value moving.
89
- const {
90
- summary,
91
- changedKeys,
92
- changedCount
93
- } = (0, _sharedUi.shallowDiffSummary)(prevState, nextState);
94
- const partialPreview = formatPartialPreview(partial);
95
- const isSlowUpdate = (duration ?? 0) > 16;
96
- const resolvedCategory = category ?? (replace ? "replace" : "setState");
97
- const stateChange = {
98
- id: this.nextId(),
99
- storeName,
100
- timestamp: Date.now(),
101
- prevState,
102
- nextState,
103
- partial,
104
- duration,
105
- hasStateChange,
106
- replace,
107
- category: resolvedCategory,
108
- changedKeys,
109
- changedKeysCount: changedCount,
110
- diffSummary: summary,
111
- partialPreview,
112
- isSlowUpdate,
113
- isPersisted
114
- };
115
- this.push(stateChange);
116
-
117
- // Update store change count
118
- const storeInfo = this.stores.get(storeName);
119
- if (storeInfo) {
120
- storeInfo.stateChangeCount++;
121
- }
122
- this.notifyListeners();
123
- }
124
- getStateChanges() {
125
- return this.getAll();
126
- }
127
- getStateChangeById(id) {
128
- return this.getById(id);
129
- }
130
- clearStateChanges() {
131
- // Reset counts
132
- for (const store of this.stores.values()) {
133
- store.stateChangeCount = 0;
134
- }
135
- this.clear();
136
- // The browser keeps its store list in React state and only re-reads it
137
- // on a store notification — `clear()` above only tells the change-log
138
- // listeners, so without this the cards kept their old counts after Clear.
139
- this.notifyStoreListeners();
140
- }
141
-
142
- // ---- Remote Mirror Mode ----
143
-
144
- /**
145
- * Replace the change log without touching the store registry. Used when a
146
- * host keeps real registered stores (JUMP / RESET stay wired) but needs to
147
- * reseed the event list — the docs live tour, or any mirror that already
148
- * called `registerStore`. Does not fire `onClear`. Respects the enabled
149
- * flag so the UI's pause button freezes the reseed.
150
- */
151
- replaceStateChanges(changes) {
152
- if (!this.getEnabled()) return;
153
- for (const store of this.stores.values()) {
154
- store.stateChangeCount = 0;
155
- }
156
- const kept = changes.slice(0, this.max);
157
- for (const change of kept) {
158
- const store = this.stores.get(change.storeName);
159
- if (store) store.stateChangeCount++;
160
- }
161
- this.setItems(kept);
162
- this.notifyStoreListeners();
163
- }
164
-
165
- /**
166
- * Serializable snapshot of the tracked stores (registry metadata plus each
167
- * store's current state). Used by the sync adapter on the device side —
168
- * the live `api` handles can't go over the wire.
169
- */
170
- getStoreSnapshots() {
171
- return Array.from(this.stores.values()).map(store => {
172
- let currentState;
173
- try {
174
- currentState = store.api.getState();
175
- } catch {
176
- currentState = undefined;
177
- }
178
- return {
179
- name: store.name,
180
- stateChangeCount: store.stateChangeCount,
181
- isPersisted: store.isPersisted,
182
- persistName: store.persistName,
183
- // Persisted is not the same as re-readable: a store registered before
184
- // its persist middleware attached has a name but no handle. The
185
- // Storage tool needs the difference to decide between offering "apply"
186
- // and telling the user to restart.
187
- canRehydrate: typeof store.rehydrate === "function",
188
- color: store.color,
189
- currentState
190
- };
191
- });
192
- }
193
-
194
- /**
195
- * Handler invoked when the UI calls setState on a mirrored store (e.g.
196
- * time-travel from the dashboard). Forward it to the synced device.
197
- */
198
- setRemoteSetStateHandler(handler) {
199
- this.remoteSetStateHandler = handler;
200
- }
201
-
202
- /**
203
- * Replace the entire mirror contents from a synced device snapshot. The
204
- * rebuilt registry entries get stub `api` handles: getState() serves the
205
- * snapshotted state, setState() forwards to the remote handler. Respects
206
- * the enabled flag so the UI's pause button freezes the mirror.
207
- */
208
- replaceFromSnapshot(changes, storeSnapshots) {
209
- if (!this.getEnabled()) return;
210
-
211
- // Preserve trees a remote mirror already hydrated — wire-form snapshots
212
- // always carry markers, and a later snapshot must not blank the detail pane.
213
- const hydrated = new Map();
214
- for (const existing of this.items) {
215
- const prev = existing.prevState;
216
- if (!prev || prev.__buoyStateOnDevice !== true) {
217
- hydrated.set(existing.id, existing);
218
- }
219
- }
220
- const merged = changes.slice(0, this.max).map(change => {
221
- const kept = hydrated.get(change.id);
222
- const incoming = change.prevState;
223
- return kept && incoming?.__buoyStateOnDevice === true ? {
224
- ...change,
225
- prevState: kept.prevState,
226
- nextState: kept.nextState,
227
- partial: kept.partial
228
- } : change;
229
- });
230
-
231
- // Preserve currentState a remote mirror already hydrated — oversized
232
- // stores arrive as STATE_ON_DEVICE markers on every snapshot.
233
- const hydratedStores = new Map();
234
- for (const [name, store] of this.stores) {
235
- try {
236
- const state = store.api.getState();
237
- const marker = state;
238
- if (!marker || marker.__buoyStateOnDevice !== true) {
239
- hydratedStores.set(name, state);
240
- }
241
- } catch {
242
- // ignore
243
- }
244
- }
245
- this.stores = new Map(storeSnapshots.map(snapshot => {
246
- const incoming = snapshot.currentState;
247
- const kept = hydratedStores.get(snapshot.name);
248
- const currentState = kept !== undefined && incoming?.__buoyStateOnDevice === true ? kept : snapshot.currentState;
249
- return [snapshot.name, {
250
- name: snapshot.name,
251
- api: {
252
- getState: () => currentState,
253
- setState: (partial, replace) => {
254
- this.remoteSetStateHandler?.(snapshot.name, partial, replace);
255
- },
256
- subscribe: () => () => {}
257
- },
258
- stateChangeCount: snapshot.stateChangeCount,
259
- isPersisted: snapshot.isPersisted,
260
- persistName: snapshot.persistName,
261
- color: snapshot.color
262
- }];
263
- }));
264
- this.setItems(merged);
265
- this.notifyStoreListeners();
266
- }
267
-
268
- /**
269
- * Hydrate one mirrored change with real trees fetched on demand from the
270
- * device via the adapter's `getChangeDetail`. Remote-mirror (desktop) only.
271
- */
272
- hydrateChange(id, patch) {
273
- this.hydrateById(id, patch);
274
- }
275
-
276
- /**
277
- * Hydrate one mirrored store's currentState fetched on demand from the
278
- * device via the adapter's `getStoreState`. Remote-mirror (desktop) only.
279
- */
280
- hydrateStoreState(storeName, currentState) {
281
- const store = this.stores.get(storeName);
282
- if (!store) return;
283
- this.stores.set(storeName, {
284
- ...store,
285
- api: {
286
- ...store.api,
287
- getState: () => currentState
288
- }
289
- });
290
- this.notifyStoreListeners();
291
- }
292
-
293
- // ---- Store Registry ----
294
-
295
- registerStore(name, api, options) {
296
- if (this.stores.has(name)) return;
297
- const storeInfo = {
298
- name,
299
- api,
300
- stateChangeCount: 0,
301
- isPersisted: options?.isPersisted ?? false,
302
- persistName: options?.persistName,
303
- rehydrate: options?.rehydrate,
304
- color: getStoreColor(name)
305
- };
306
- this.stores.set(name, storeInfo);
307
- this.notifyStoreListeners();
308
- }
309
- unregisterStore(name) {
310
- this.stores.delete(name);
311
- this.notifyStoreListeners();
312
- }
313
- getStores() {
314
- return Array.from(this.stores.values());
315
- }
316
- getStore(name) {
317
- return this.stores.get(name);
318
- }
319
- getStoreColor(name) {
320
- return this.stores.get(name)?.color ?? getStoreColor(name);
321
- }
322
-
323
- // ---- Filtering ----
324
-
325
- filterStateChanges(filter) {
326
- let filtered = this.getAll();
327
- if (filter.searchText) {
328
- const search = filter.searchText.toLowerCase();
329
- filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
330
- }
331
- if (filter.storeNames && filter.storeNames.length > 0) {
332
- filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
333
- }
334
- if (filter.onlyWithChanges) {
335
- filtered = filtered.filter(c => c.hasStateChange);
336
- }
337
- return filtered;
338
- }
339
-
340
- // ---- Stats ----
341
-
342
- getStats() {
343
- const total = this.items.length;
344
- const withChanges = this.items.filter(c => c.hasStateChange).length;
345
- const storeCount = this.stores.size;
346
- const durations = this.items.filter(c => c.duration !== undefined).map(c => c.duration);
347
- const avgDuration = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
348
- return {
349
- totalChanges: total,
350
- changesWithStateChange: withChanges,
351
- changesWithoutStateChange: total - withChanges,
352
- storeCount,
353
- averageDuration: Math.round(avgDuration * 100) / 100
354
- };
355
- }
356
- getUniqueStoreNames() {
357
- return Array.from(this.stores.keys()).sort();
358
- }
359
-
360
- // ---- Enable / Disable ----
361
-
362
- setMaxChanges(max) {
363
- this.setMax(max);
364
- }
365
-
366
- // ---- Subscriptions ----
367
-
368
- subscribeToStores(listener) {
369
- this.storeListeners.add(listener);
370
- return () => {
371
- this.storeListeners.delete(listener);
372
- };
373
- }
374
- notifyStoreListeners() {
375
- const stores = this.getStores();
376
- this.storeListeners.forEach(listener => listener(stores));
377
- }
378
- }
379
- const zustandStateStore = exports.zustandStateStore = new ZustandStateStore();
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.zustandStateStore=void 0;var _sharedUi=require("@buoy-gg/shared-ui");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(u){return(0,_sharedUi.entityColor)(u,STORE_COLORS)}function formatPartialPreview(u,t=40){return(0,_sharedUi.formatValuePreview)(u,{maxLength:t,undefinedLabel:"",functionLabel:"(updater fn)"})}class ZustandStateStore extends _sharedUi.ChangeLog{stores=new Map;storeListeners=new Set;remoteSetStateHandler=null;addStateChange(t){if(!this.canRecord())return;const{storeName:s,partial:r,replace:a,prevState:o,nextState:e,duration:n,category:i,isPersisted:h=false}=t;const c=o!==e;const{summary:g,changedKeys:d,changedCount:l}=(0,_sharedUi.shallowDiffSummary)(o,e);const f=formatPartialPreview(r);const m=(n??0)>16;const C=i??(a?"replace":"setState");const y={id:this.nextId(),storeName:s,timestamp:Date.now(),prevState:o,nextState:e,partial:r,duration:n,hasStateChange:c,replace:a,category:C,changedKeys:d,changedKeysCount:l,diffSummary:g,partialPreview:f,isSlowUpdate:m,isPersisted:h};this.push(y);const S=this.stores.get(s);if(S){S.stateChangeCount++}this.notifyListeners()}getStateChanges(){return this.getAll()}getStateChangeById(t){return this.getById(t)}clearStateChanges(){for(const t of this.stores.values()){t.stateChangeCount=0}this.clear();this.notifyStoreListeners()}replaceStateChanges(t){if(!this.getEnabled())return;for(const r of this.stores.values()){r.stateChangeCount=0}const s=t.slice(0,this.max);for(const r of s){const a=this.stores.get(r.storeName);if(a)a.stateChangeCount++}this.setItems(s);this.notifyStoreListeners()}getStoreSnapshots(){return Array.from(this.stores.values()).map(t=>{let s;try{s=t.api.getState()}catch{s=void 0}return{name:t.name,stateChangeCount:t.stateChangeCount,isPersisted:t.isPersisted,persistName:t.persistName,canRehydrate:typeof t.rehydrate==="function",color:t.color,currentState:s}})}setRemoteSetStateHandler(t){this.remoteSetStateHandler=t}replaceFromSnapshot(t,s){if(!this.getEnabled())return;const r=new Map;for(const e of this.items){const n=e.prevState;if(!n||n.__buoyStateOnDevice!==true){r.set(e.id,e)}}const a=t.slice(0,this.max).map(e=>{const n=r.get(e.id);const i=e.prevState;return n&&i?.__buoyStateOnDevice===true?{...e,prevState:n.prevState,nextState:n.nextState,partial:n.partial}:e});const o=new Map;for(const[e,n]of this.stores){try{const i=n.api.getState();const h=i;if(!h||h.__buoyStateOnDevice!==true){o.set(e,i)}}catch{}}this.stores=new Map(s.map(e=>{const n=e.currentState;const i=o.get(e.name);const h=i!==void 0&&n?.__buoyStateOnDevice===true?i:e.currentState;return[e.name,{name:e.name,api:{getState:()=>h,setState:(c,g)=>{this.remoteSetStateHandler?.(e.name,c,g)},subscribe:()=>()=>{}},stateChangeCount:e.stateChangeCount,isPersisted:e.isPersisted,persistName:e.persistName,color:e.color}]}));this.setItems(a);this.notifyStoreListeners()}hydrateChange(t,s){this.hydrateById(t,s)}hydrateStoreState(t,s){const r=this.stores.get(t);if(!r)return;this.stores.set(t,{...r,api:{...r.api,getState:()=>s}});this.notifyStoreListeners()}registerStore(t,s,r){if(this.stores.has(t))return;const a={name:t,api:s,stateChangeCount:0,isPersisted:r?.isPersisted??false,persistName:r?.persistName,rehydrate:r?.rehydrate,color:getStoreColor(t)};this.stores.set(t,a);this.notifyStoreListeners()}unregisterStore(t){this.stores.delete(t);this.notifyStoreListeners()}getStores(){return Array.from(this.stores.values())}getStore(t){return this.stores.get(t)}getStoreColor(t){return this.stores.get(t)?.color??getStoreColor(t)}filterStateChanges(t){let s=this.getAll();if(t.searchText){const r=t.searchText.toLowerCase();s=s.filter(a=>a.storeName.toLowerCase().includes(r)||a.partialPreview.toLowerCase().includes(r)||a.changedKeys.some(o=>o.toLowerCase().includes(r)))}if(t.storeNames&&t.storeNames.length>0){s=s.filter(r=>t.storeNames.includes(r.storeName))}if(t.onlyWithChanges){s=s.filter(r=>r.hasStateChange)}return s}getStats(){const t=this.items.length;const s=this.items.filter(e=>e.hasStateChange).length;const r=this.stores.size;const a=this.items.filter(e=>e.duration!==void 0).map(e=>e.duration);const o=a.length>0?a.reduce((e,n)=>e+n,0)/a.length:0;return{totalChanges:t,changesWithStateChange:s,changesWithoutStateChange:t-s,storeCount:r,averageDuration:Math.round(o*100)/100}}getUniqueStoreNames(){return Array.from(this.stores.keys()).sort()}setMaxChanges(t){this.setMax(t)}subscribeToStores(t){this.storeListeners.add(t);return()=>{this.storeListeners.delete(t)}}notifyStoreListeners(){const t=this.getStores();this.storeListeners.forEach(s=>s(t))}}const zustandStateStore=exports.zustandStateStore=new ZustandStateStore;
@@ -1,90 +1 @@
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
- // Side-effect: registers the Time Machine snapshot provider
41
- import "./zustand/utils/snapshotProvider";
42
-
43
- // =============================================================================
44
- // PRESET (Primary entry point for FloatingDevTools)
45
- // =============================================================================
46
- export { zustandToolPreset, createZustandTool } from "./preset";
47
-
48
- // =============================================================================
49
- // WATCH STORES (Recommended — non-intrusive, subscribe-only)
50
- // =============================================================================
51
- export { watchStores } from "./zustand/utils/buoyZustandMiddleware";
52
-
53
- // =============================================================================
54
- // MIDDLEWARE (Advanced — wraps setState for partial + timing capture)
55
- // =============================================================================
56
- export { buoyDevTools } from "./zustand/utils/buoyZustandMiddleware";
57
-
58
- // =============================================================================
59
- // UTILITIES
60
- // =============================================================================
61
- export { isStoreInstrumented } from "./zustand/utils/buoyZustandMiddleware";
62
-
63
- // =============================================================================
64
- // HOOKS (For consuming Zustand state data)
65
- // =============================================================================
66
- export { useZustandStateChanges } from "./zustand/hooks/useZustandStateChanges";
67
- // =============================================================================
68
- // COMPONENTS (For custom UI implementations)
69
- // =============================================================================
70
- // `ZustandToolView` is the same tool rendered inline (header bar + body + footer)
71
- // instead of inside a JsModal — for hosts that show tools in a panel.
72
- export { ZustandModal, ZustandToolView } from "./zustand/components/ZustandModal";
73
- export { ZustandStateChangeItem } from "./zustand/components/ZustandStateChangeItem";
74
- export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./zustand/components/ZustandStateDetailContent";
75
- export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./zustand/components/ZustandIcon";
76
-
77
- // =============================================================================
78
- // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
79
- // =============================================================================
80
- export { zustandSyncAdapter } from "./zustand/sync/zustandSyncAdapter";
81
-
82
- // =============================================================================
83
- // TYPES
84
- // =============================================================================
85
-
86
- // =============================================================================
87
- // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
88
- // =============================================================================
89
- /** @internal */
90
- export { zustandStateStore } from "./zustand/utils/zustandStateStore";
1
+ "use strict";import"./zustand/utils/snapshotProvider";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,ZustandToolView}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{zustandSyncAdapter}from"./zustand/sync/zustandSyncAdapter";export{zustandStateStore}from"./zustand/utils/zustandStateStore";
@@ -1,98 +1 @@
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
- }
1
+ "use strict";import{ZustandModal as o}from"./zustand/components/ZustandModal";import{ZustandIcon as n}from"./zustand/components/ZustandIcon";import{jsx as s}from"react/jsx-runtime";export const zustandToolPreset={id:"zustand",name:"ZUSTAND",description:"Zustand store & state inspector",slot:"both",icon:({size:e})=>s(n,{size:e}),component:o,props:{enableSharedModalDimensions:false}};export function createZustandTool(e){return{id:e?.id||"zustand",name:e?.name||"ZUSTAND",description:e?.description||"Zustand store & state inspector",slot:"both",icon:({size:t})=>s(n,{size:t,color:e?.iconColor}),component:o,props:{enableSharedModalDimensions:e?.enableSharedModalDimensions!==void 0?e.enableSharedModalDimensions:false}}}