@buoy-gg/zustand 7.0.39 โ 7.0.41
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/README.md +4 -0
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +37 -1
- package/lib/module/zustand/sync/zustandSyncAdapter.js +36 -1
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
- package/lib/web/index.mjs +3 -0
- package/package.json +35 -12
- package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +0 -393
- package/lib/module/zustand/components/ZustandStoreEditorScreen.js +0 -389
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts +0 -42
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -86,3 +86,7 @@ Use a Free or Pro Buoy account. History limits and paid features vary by tool; s
|
|
|
86
86
|
๐ [Full docs](https://buoy.gg/buoy/latest/docs/tools/zustand) ยท [All Buoy tools](https://github.com/Buoy-gg/buoy)
|
|
87
87
|
|
|
88
88
|
Proprietary software. ยฉ Buoy LLC. [Terms](https://buoy.gg/terms)
|
|
89
|
+
|
|
90
|
+
## Web support (unreleased)
|
|
91
|
+
|
|
92
|
+
Register live stores with watchStores. The shared browser panel edits the same store objects used by the app. The browser build is available in this checkout and has not been published yet. See the [web setup guide](https://buoy.gg/buoy/latest/docs/web-preview) for registration, dependencies, and browser boundaries.
|
|
@@ -6,6 +6,22 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.zustandSyncAdapter = exports.STATE_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = void 0;
|
|
7
7
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
8
|
var _zustandStateStore = require("../utils/zustandStateStore");
|
|
9
|
+
const isPlainRecord = v => typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Merge `patch` into `base` object by object. Anything that is not a plain object
|
|
13
|
+
* on both sides (arrays, primitives, class instances, functions) is taken from
|
|
14
|
+
* the patch as is.
|
|
15
|
+
*/
|
|
16
|
+
function deepMergeInto(base, patch) {
|
|
17
|
+
if (!isPlainRecord(base) || !isPlainRecord(patch)) return patch;
|
|
18
|
+
const out = {
|
|
19
|
+
...base
|
|
20
|
+
};
|
|
21
|
+
for (const [k, v] of Object.entries(patch)) out[k] = k in base ? deepMergeInto(base[k], v) : v;
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
|
|
11
27
|
* Same hazard as redux: up to 200 changes ร 2 full trees on every snapshot
|
|
@@ -309,7 +325,27 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
309
325
|
}
|
|
310
326
|
// A pathed write is a merge by construction โ it names one value, so it
|
|
311
327
|
// must never fall through to zustand's default whole-state replace.
|
|
312
|
-
|
|
328
|
+
const merging = pathed || replace === false;
|
|
329
|
+
if (merging) {
|
|
330
|
+
// zustand's own merge is shallow: `setState({ member: { crowns } }, false)`
|
|
331
|
+
// swaps the whole `member` object for `{ crowns }`. A path write always
|
|
332
|
+
// arrives in that nested form, and so does any merge that edits inside a
|
|
333
|
+
// top-level object, so fold each nested object into what the store holds
|
|
334
|
+
// now before handing it over. Arrays still replace (keyed edits were
|
|
335
|
+
// already expanded to whole arrays above).
|
|
336
|
+
let current;
|
|
337
|
+
try {
|
|
338
|
+
current = store.api.getState();
|
|
339
|
+
} catch {
|
|
340
|
+
current = undefined;
|
|
341
|
+
}
|
|
342
|
+
if (isPlainRecord(current) && isPlainRecord(effective)) {
|
|
343
|
+
const folded = {};
|
|
344
|
+
for (const [k, v] of Object.entries(effective)) folded[k] = k in current ? deepMergeInto(current[k], v) : v;
|
|
345
|
+
effective = folded;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
store.api.setState(effective, merging ? false : replace ?? true);
|
|
313
349
|
// Say what an addressed edit resolved to. A caller that meant to change one
|
|
314
350
|
// line and sees "17 item(s) remain" has caught its own mistake.
|
|
315
351
|
return {
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
import { expandKeyedArrays, patchFromPath, readPath, refusalText, shapeSummary, suggestedPatch, typedEditViolations, unshapedLists } from "@buoy-gg/shared-ui";
|
|
4
4
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
5
5
|
import { WIRE_DETAIL_LIMIT_BYTES, createSyncAdapter, detailByIdAction, isJsonSerializable, isOverWireBudget, memoByIdentity, onDeviceMarker, readParams, toWire, truncatedNote } from "@buoy-gg/shared-ui";
|
|
6
|
+
const isPlainRecord = v => typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Merge `patch` into `base` object by object. Anything that is not a plain object
|
|
10
|
+
* on both sides (arrays, primitives, class instances, functions) is taken from
|
|
11
|
+
* the patch as is.
|
|
12
|
+
*/
|
|
13
|
+
function deepMergeInto(base, patch) {
|
|
14
|
+
if (!isPlainRecord(base) || !isPlainRecord(patch)) return patch;
|
|
15
|
+
const out = {
|
|
16
|
+
...base
|
|
17
|
+
};
|
|
18
|
+
for (const [k, v] of Object.entries(patch)) out[k] = k in base ? deepMergeInto(base[k], v) : v;
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
/**
|
|
8
23
|
* Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
|
|
@@ -307,7 +322,27 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
307
322
|
}
|
|
308
323
|
// A pathed write is a merge by construction โ it names one value, so it
|
|
309
324
|
// must never fall through to zustand's default whole-state replace.
|
|
310
|
-
|
|
325
|
+
const merging = pathed || replace === false;
|
|
326
|
+
if (merging) {
|
|
327
|
+
// zustand's own merge is shallow: `setState({ member: { crowns } }, false)`
|
|
328
|
+
// swaps the whole `member` object for `{ crowns }`. A path write always
|
|
329
|
+
// arrives in that nested form, and so does any merge that edits inside a
|
|
330
|
+
// top-level object, so fold each nested object into what the store holds
|
|
331
|
+
// now before handing it over. Arrays still replace (keyed edits were
|
|
332
|
+
// already expanded to whole arrays above).
|
|
333
|
+
let current;
|
|
334
|
+
try {
|
|
335
|
+
current = store.api.getState();
|
|
336
|
+
} catch {
|
|
337
|
+
current = undefined;
|
|
338
|
+
}
|
|
339
|
+
if (isPlainRecord(current) && isPlainRecord(effective)) {
|
|
340
|
+
const folded = {};
|
|
341
|
+
for (const [k, v] of Object.entries(effective)) folded[k] = k in current ? deepMergeInto(current[k], v) : v;
|
|
342
|
+
effective = folded;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
store.api.setState(effective, merging ? false : replace ?? true);
|
|
311
346
|
// Say what an addressed edit resolved to. A caller that meant to change one
|
|
312
347
|
// line and sees "17 item(s) remain" has caught its own mistake.
|
|
313
348
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zustandSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/zustand/sync/zustandSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"zustandSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/zustand/sync/zustandSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AA8BnD;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;EAG3B,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,iBAAiB;;;;EAG7B,CAAC;AAuEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;IAiB3B;;;;OAIG;;;;;;IASH;;;OAGG;6BACsB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsChC;;;;;OAKG;0BACmB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0BT,OAAO;;;;;;;;;;;;;;;IA4H3B;;;;;;;;;;;;;;;;;;;;OAoBG;yBACwB,OAAO;;;;;;;;;;;EAqBV,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var Bt=Object.defineProperty;var _o=Object.getOwnPropertyDescriptor;var jo=Object.getOwnPropertyNames;var Ko=Object.prototype.hasOwnProperty;var Uo=(e,t)=>{for(var o in t)Bt(e,o,{get:t[o],enumerable:!0})},Et=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of jo(t))!Ko.call(e,r)&&r!==o&&Bt(e,r,{get:()=>t[r],enumerable:!(n=_o(t,r))||n.enumerable});return e},R=(e,t,o)=>(Et(e,t,"default"),o&&Et(o,t,"default"));typeof window<"u"&&typeof globalThis.global>"u"&&(globalThis.global=globalThis);import{classifyPreviewItem as tn,createRestoreScope as on,emptyRestoreResult as nn,failedRestore as rn,registerStateSnapshotProvider as an,unionKeys as sn}from"@buoy-gg/shared-ui/web";import{ChangeLog as Go,entityColor as Jo,formatValuePreview as Yo,shallowDiffSummary as Xo}from"@buoy-gg/shared-ui/web";var qo={counter:"#10B981",auth:"#8B5CF6",user:"#3B82F6",cart:"#EC4899",app:"#6366F1",ui:"#F59E0B",settings:"#14B8A6",theme:"#06B6D4",navigation:"#F97316",form:"#EF4444"};function At(e){return Jo(e,qo)}function Qo(e,t=40){return Yo(e,{maxLength:t,undefinedLabel:"",functionLabel:"(updater fn)"})}var et=class extends Go{constructor(){super(...arguments);this.stores=new Map;this.storeListeners=new Set;this.remoteSetStateHandler=null}addStateChange(o){if(!this.canRecord())return;let{storeName:n,partial:r,replace:a,prevState:i,nextState:s,duration:l,category:g,isPersisted:S=!1}=o,T=i!==s,{summary:f,changedKeys:p,changedCount:y}=Xo(i,s),B=Qo(r),V=(l??0)>16,K=g??(a?"replace":"setState"),H={id:this.nextId(),storeName:n,timestamp:Date.now(),prevState:i,nextState:s,partial:r,duration:l,hasStateChange:T,replace:a,category:K,changedKeys:p,changedKeysCount:y,diffSummary:f,partialPreview:B,isSlowUpdate:V,isPersisted:S};this.push(H);let se=this.stores.get(n);se&&se.stateChangeCount++,this.notifyListeners()}getStateChanges(){return this.getAll()}getStateChangeById(o){return this.getById(o)}clearStateChanges(){for(let o of this.stores.values())o.stateChangeCount=0;this.clear(),this.notifyStoreListeners()}replaceStateChanges(o){if(!this.getEnabled())return;for(let r of this.stores.values())r.stateChangeCount=0;let n=o.slice(0,this.max);for(let r of n){let a=this.stores.get(r.storeName);a&&a.stateChangeCount++}this.setItems(n),this.notifyStoreListeners()}getStoreSnapshots(){return Array.from(this.stores.values()).map(o=>{let n;try{n=o.api.getState()}catch{n=void 0}return{name:o.name,stateChangeCount:o.stateChangeCount,isPersisted:o.isPersisted,persistName:o.persistName,canRehydrate:typeof o.rehydrate=="function",color:o.color,currentState:n}})}setRemoteSetStateHandler(o){this.remoteSetStateHandler=o}replaceFromSnapshot(o,n){if(!this.getEnabled())return;let r=new Map;for(let s of this.items){let l=s.prevState;(!l||l.__buoyStateOnDevice!==!0)&&r.set(s.id,s)}let a=o.slice(0,this.max).map(s=>{let l=r.get(s.id),g=s.prevState;return l&&g?.__buoyStateOnDevice===!0?{...s,prevState:l.prevState,nextState:l.nextState,partial:l.partial}:s}),i=new Map;for(let[s,l]of this.stores)try{let g=l.api.getState(),S=g;(!S||S.__buoyStateOnDevice!==!0)&&i.set(s,g)}catch{}this.stores=new Map(n.map(s=>{let l=s.currentState,g=i.get(s.name),S=g!==void 0&&l?.__buoyStateOnDevice===!0?g:s.currentState;return[s.name,{name:s.name,api:{getState:()=>S,setState:(T,f)=>{this.remoteSetStateHandler?.(s.name,T,f)},subscribe:()=>()=>{}},stateChangeCount:s.stateChangeCount,isPersisted:s.isPersisted,persistName:s.persistName,color:s.color}]})),this.setItems(a),this.notifyStoreListeners()}hydrateChange(o,n){this.hydrateById(o,n)}hydrateStoreState(o,n){let r=this.stores.get(o);r&&(this.stores.set(o,{...r,api:{...r.api,getState:()=>n}}),this.notifyStoreListeners())}registerStore(o,n,r){if(this.stores.has(o))return;let a={name:o,api:n,stateChangeCount:0,isPersisted:r?.isPersisted??!1,persistName:r?.persistName,rehydrate:r?.rehydrate,color:At(o)};this.stores.set(o,a),this.notifyStoreListeners()}unregisterStore(o){this.stores.delete(o),this.notifyStoreListeners()}getStores(){return Array.from(this.stores.values())}getStore(o){return this.stores.get(o)}getStoreColor(o){return this.stores.get(o)?.color??At(o)}filterStateChanges(o){let n=this.getAll();if(o.searchText){let r=o.searchText.toLowerCase();n=n.filter(a=>a.storeName.toLowerCase().includes(r)||a.partialPreview.toLowerCase().includes(r)||a.changedKeys.some(i=>i.toLowerCase().includes(r)))}return o.storeNames&&o.storeNames.length>0&&(n=n.filter(r=>o.storeNames.includes(r.storeName))),o.onlyWithChanges&&(n=n.filter(r=>r.hasStateChange)),n}getStats(){let o=this.items.length,n=this.items.filter(s=>s.hasStateChange).length,r=this.stores.size,a=this.items.filter(s=>s.duration!==void 0).map(s=>s.duration),i=a.length>0?a.reduce((s,l)=>s+l,0)/a.length:0;return{totalChanges:o,changesWithStateChange:n,changesWithoutStateChange:o-n,storeCount:r,averageDuration:Math.round(i*100)/100}}getUniqueStoreNames(){return Array.from(this.stores.keys()).sort()}setMaxChanges(o){this.setMax(o)}subscribeToStores(o){return this.storeListeners.add(o),()=>{this.storeListeners.delete(o)}}notifyStoreListeners(){let o=this.getStores();this.storeListeners.forEach(n=>n(o))}},h=new et;function U(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function me(e,t,o,n,r){if(typeof e=="function"){o.push(t);return}if(typeof e!="object"||e===null||r>=8)return e;if(n.has(e))return;if(n.add(e),Array.isArray(e))return e.map((i,s)=>typeof i=="function"?(o.push(`${t}[${s}]`),null):me(i,`${t}[${s}]`,o,n,r+1));if(!U(e))return e;let a={};for(let[i,s]of Object.entries(e)){if(typeof s=="function"){o.push(t?`${t}.${i}`:i);continue}a[i]=me(s,t?`${t}.${i}`:i,o,n,r+1)}return a}function tt(e){if(U(e)&&e.__buoyStateOnDevice===!0)return{data:null,fnPaths:[],blockedReason:"Store state hasn't synced from the device yet."};if(!U(e))return{data:null,fnPaths:[],blockedReason:"Only stores whose state is a plain object can be edited \u2014 a merge write has nothing to address in "+(Array.isArray(e)?"an array":`a ${typeof e}`)+"."};let t=[],o=me(e,"",t,new WeakSet,0),n=t.filter(r=>r.includes(".")||r.includes("["));return n.length>0?{data:null,fnPaths:t,blockedReason:`State keeps a function inside "${n[0]}" \u2014 a merge write of that key would drop it, so this store can't be edited here.`}:{data:o,fnPaths:t,blockedReason:null}}function Nt(e,t){if(!U(t))throw new Error("The store's state must stay an object.");let o=Object.keys(e).filter(r=>!(r in t));if(o.length>0)throw new Error(`Can't remove top-level ${o.length===1?"key":"keys"} ${o.map(r=>`"${r}"`).join(", ")} \u2014 zustand merge writes can't delete. Edit the value instead.`);let n={};for(let r of Object.keys(t))(!(r in e)||JSON.stringify(t[r])!==JSON.stringify(e[r]))&&(n[r]=t[r]);return n}var $t=8;function Lt(e,t,o,n){if(!(!U(t)||!U(e))&&!(n>=$t||o.has(t))){o.add(t);for(let[r,a]of Object.entries(t))if(typeof a=="function")e[r]=a;else if(U(a)){if(!U(e[r])){if(!Ot(a,new WeakSet,n+1))continue;e[r]={}}Lt(e[r],a,o,n+1)}}}function Ot(e,t,o){if(o>=$t||t.has(e))return!1;t.add(e);for(let n of Object.values(e))if(typeof n=="function"||U(n)&&Ot(n,t,o+1))return!0;return!1}function ln(){return h.getStores().length===0?{canCapture:!1,canRestore:!1,detail:"No Zustand stores registered \u2014 pass `zustandStores` to FloatingDevTools or add the buoyDevTools middleware"}:{canCapture:!0,canRestore:!0}}async function dn(){let e=[],t={};for(let o of h.getStores()){let n=[],r;try{r=me(o.api.getState(),"",n,new WeakSet,0)}catch(a){e.push(`store "${o.name}": capture failed (${String(a)})`);continue}t[o.name]={state:r,fnPaths:n}}return{data:{stores:t},warnings:e}}async function cn(e,t){let o=e,n=on(t),r=nn();if(!o?.stores)return rn("empty zustand snapshot");for(let[i,s]of Object.entries(o.stores)){if(!n.allows(i))continue;let l=h.getStore(i);if(!l){r.skipped.push({id:i,reason:"store not registered (renamed, or created after snapshot?)"});continue}try{let g=s.state;Lt(g,l.api.getState(),new WeakSet,0),l.api.setState(g,!0),l.api.getState()===g?r.applied+=1:(r.warnings.push(`store "${i}": setState did not take effect`),r.ok=!1)}catch(g){r.skipped.push({id:i,reason:String(g)}),r.ok=!1}}let a=n.excludedWarning("store(s)");return a&&r.warnings.push(a),r}async function un(e,t){let o=e?.stores??{},n=t===void 0,r={};if(n)for(let l of h.getStores()){let g=[];try{r[l.name]={state:me(l.api.getState(),"",g,new WeakSet,0),fnPaths:g}}catch{}}else Object.assign(r,t?.stores??{});let a=[],i=[],s=0;for(let l of sn(r,o)){let g=l in o,S=l in r;if(!g){n?i.push(`store "${l}" not in snapshot \u2014 untouched`):a.push({key:l,label:l,verdict:"removed",oldValue:r[l].state});continue}if(s+=o[l].fnPaths?.length??0,n&&!h.getStore(l)){a.push({key:l,label:l,verdict:"wont-apply",reason:"store not registered (renamed, or created after snapshot?)"});continue}a.push(tn({key:l,label:l,oldValue:r[l]?.state,newValue:o[l].state,inOld:S,inNew:!0},{tooLargeReason:"store too large to render a diff \u2014 restore still applies it"}))}return s>0&&i.push(`${s} store action function${s===1?"":"s"} preserved on restore (not diffed)`),{items:a,notes:i}}an({id:"zustand",label:"Zustand",status:ln,capture:dn,restore:cn,previewItems:un});import{useState as ae,useMemo as de,useRef as _e,useCallback as L,useEffect as ao}from"react";var v={};Uo(v,{AccessibilityInfo:()=>In,ActivityIndicator:()=>wn,Alert:()=>xn,Animated:()=>Dn,AppRegistry:()=>Vn,AppState:()=>Zn,Appearance:()=>Pn,BackHandler:()=>Rn,Button:()=>Cn,CheckBox:()=>Un,Clipboard:()=>En,DeviceEventEmitter:()=>lr,Dimensions:()=>Bn,Easing:()=>An,FlatList:()=>Be,I18nManager:()=>Nn,Image:()=>Gn,ImageBackground:()=>Jn,InteractionManager:()=>Ln,Keyboard:()=>$n,KeyboardAvoidingView:()=>Yn,LayoutAnimation:()=>On,Linking:()=>zn,LogBox:()=>ir,Modal:()=>yn,NativeEventEmitter:()=>Wn,NativeModules:()=>kn,PanResponder:()=>Mn,Picker:()=>Xn,PixelRatio:()=>Hn,Platform:()=>Fn,Pressable:()=>mn,ProgressBar:()=>qn,RefreshControl:()=>Qn,SafeAreaView:()=>er,ScrollView:()=>ne,SectionList:()=>tr,Share:()=>_n,StatusBar:()=>or,StyleSheet:()=>G,Switch:()=>bn,Text:()=>b,TextInput:()=>pn,Touchable:()=>nr,TouchableHighlight:()=>hn,TouchableNativeFeedback:()=>rr,TouchableOpacity:()=>q,TouchableWithoutFeedback:()=>Sn,UIManager:()=>jn,Vibration:()=>Kn,View:()=>c,VirtualizedList:()=>ar,YellowBox:()=>sr,findNodeHandle:()=>Tn,unstable_createElement:()=>vn,useColorScheme:()=>dr,useLocaleContext:()=>cr,useWindowDimensions:()=>ur});R(v,Es);import*as Es from"react-native-web";import{unstable_createElement as vn,NativeModules as kn,AccessibilityInfo as In,Animated as Dn,Appearance as Pn,AppRegistry as Vn,AppState as Zn,BackHandler as Rn,Clipboard as En,Dimensions as Bn,Easing as An,I18nManager as Nn,Keyboard as $n,InteractionManager as Ln,LayoutAnimation as On,Linking as zn,NativeEventEmitter as Wn,PanResponder as Mn,PixelRatio as Hn,Platform as Fn,Share as _n,StyleSheet as G,UIManager as jn,Vibration as Kn,CheckBox as Un,FlatList as Be,Image as Gn,ImageBackground as Jn,KeyboardAvoidingView as Yn,Picker as Xn,ProgressBar as qn,RefreshControl as Qn,SafeAreaView as er,SectionList as tr,StatusBar as or,Touchable as nr,TouchableNativeFeedback as rr,VirtualizedList as ar,YellowBox as sr,LogBox as ir,DeviceEventEmitter as lr,useColorScheme as dr,useLocaleContext as cr,useWindowDimensions as ur}from"react-native-web";import*as I from"react-native-web";import{createElement as gn,forwardRef as fn}from"react";function J(e){let t=fn((n,r)=>{let{accessibilityState:a,accessibilityValue:i,...s}=n;for(let l of["busy","checked","disabled","expanded","selected"])a?.[l]!==void 0&&s[`aria-${l}`]===void 0&&(s[`aria-${l}`]=a[l]);for(let[l,g]of Object.entries({min:"valuemin",max:"valuemax",now:"valuenow",text:"valuetext"}))i?.[l]!==void 0&&s[`aria-${g}`]===void 0&&(s[`aria-${g}`]=i[l]);return gn(e,{...s,ref:r})}),o=e.State;return o&&Object.assign(t,{State:o}),t.displayName=e.displayName??e.name??"NativeWebView",t}var c=J(I.View),b=J(I.Text),pn=J(I.TextInput),q=J(I.TouchableOpacity),hn=J(I.TouchableHighlight),Sn=J(I.TouchableWithoutFeedback),mn=J(I.Pressable),ne=J(I.ScrollView),yn=J(I.Modal),bn=J(I.Switch),wn=J(I.ActivityIndicator),Cn=J(I.Button),Tn=I.findNodeHandle,xn={alert(e,t,o,n){if(typeof document>"u")return;let r=document.createElement("dialog");r.dataset.buoyRoot="",r.setAttribute("aria-label",e),r.style.cssText="position:fixed;inset:0;margin:auto;width:min(440px,90vw);padding:24px;border:1px solid #475569;border-radius:14px;background:#17212e;color:#f1f5f9;font:14px system-ui;box-shadow:0 20px 80px #0008;z-index:2147483647";let a=document.createElement("h2");if(a.textContent=e,a.style.cssText="font-size:18px;margin:0 0 12px",r.appendChild(a),t){let g=document.createElement("p");g.textContent=t,g.style.cssText="line-height:1.5;white-space:pre-wrap;margin:0 0 20px",r.appendChild(g)}let i=document.createElement("div");i.style.cssText="display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px";let s=o?.length?o:[{text:"OK"}],l=()=>{r.close?.(),r.remove()};for(let g of s){let S=document.createElement("button");S.type="button",S.textContent=g.text??"OK",S.style.cssText=`padding:9px 14px;border:1px solid #64748b;border-radius:7px;color:white;background:${g.style==="destructive"?"#a52b38":"#283a4d"};cursor:pointer;font:inherit`,S.onclick=()=>{l(),g.onPress?.()},i.appendChild(S)}r.addEventListener("cancel",g=>{g.preventDefault(),n?.cancelable!==!1&&(l(),n?.onDismiss?.())}),r.appendChild(i),document.body.appendChild(r),r.showModal?r.showModal():r.setAttribute("open","")}};import{JsModal as Za,ModalHeader as O,TabSelector as Ra,macOSColors as Ea,night as M,ToolEmptyState as St,resolveListEmptyState as co,Search as Ba,Filter as Aa,Power as Na,Lock as $a,devToolsStorageKeys as La,ProUpgradeModal as Oa,PowerToggleButton as za,ToolbarCopyButton as ht,ToolbarClearButton as Wa,truncatePayload as we,TickProvider as so,useLimit as Ma,windowDragProps as Ha,EVENT_LIST_PRESET as io}from"@buoy-gg/shared-ui/web";import{useIsPro as Fa}from"@buoy-gg/license/web";import{useCallback as gr,useMemo as zt}from"react";import{useChangeLog as fr,useSubscribedValue as pr}from"@buoy-gg/shared-ui/web";function hr(e,t){let o=e;if(t.searchText){let n=t.searchText.toLowerCase();o=o.filter(r=>r.storeName.toLowerCase().includes(n)||r.partialPreview.toLowerCase().includes(n)||r.changedKeys.some(a=>a.toLowerCase().includes(n)))}return t.storeNames&&t.storeNames.length>0&&(o=o.filter(n=>t.storeNames.includes(n.storeName))),t.onlyWithChanges&&(o=o.filter(n=>n.hasStateChange)),o}var Sr=()=>h.getStores(),mr=e=>h.subscribeToStores(e);function ot(){let e=fr(h,{filterItems:hr}),t=pr(Sr,mr),o=zt(()=>{let a=e.items.length,i=e.items.filter(s=>s.hasStateChange).length;return{totalChanges:a,changesWithStateChange:i,changesWithoutStateChange:a-i,storeCount:t.length,averageDuration:0}},[e.items,t]),n=zt(()=>h.getUniqueStoreNames(),[t]),r=gr(a=>h.getStateChangeById(a),[]);return{stateChanges:e.items,filteredChanges:e.filteredItems,filter:e.filter,setFilter:e.setFilter,stats:o,stores:t,clearChanges:h.clearStateChanges.bind(h),isEnabled:e.isEnabled,toggleCapture:e.toggleCapture,storeNames:n,getChangeById:r}}import{memo as yr}from"react";import{ChangeRow as br,buoyColors as Wt,night as wr}from"@buoy-gg/shared-ui/web";import{jsx as Ir}from"react/jsx-runtime";function Cr(e,t){switch(e){case"replace":return"Replace";case"persist":return"Hydrate";case"initial":return"Initial";default:return t.charAt(0).toUpperCase()+t.slice(1)}}function Tr(e,t){switch(e){case"replace":return Wt.warning;case"persist":return Wt.info;case"initial":return wr.color.textTertiary;default:return h.getStoreColor(t)}}function xr(e){let t=[];return e.duration!==void 0&&e.duration>=.1&&t.push(`${e.duration.toFixed(1)}ms`),e.hasStateChange?e.changedKeysCount>0?t.push(`${e.changedKeysCount} key${e.changedKeysCount>1?"s":""} changed`):t.push(e.diffSummary||"state changed"):t.push("no state change"),t.join(" \xB7 ")}function vr(e){return e.category==="replace"?"REPLACE":e.category==="persist"?"HYDRATE":e.category==="initial"?"INIT":"SET"}function kr(e){return e.changedKeys.length>0&&e.changedKeys.length<=3?e.changedKeys.join(", "):e.changedKeys.length>3?`${e.changedKeys.slice(0,2).join(", ")} +${e.changedKeys.length-2}`:e.partialPreview||"setState()"}var nt=yr(function({change:t,onPress:o,testID:n}){let r=Tr(t.category,t.storeName),a=Cr(t.category,t.storeName),i=xr(t),s=kr(t),l=vr(t);return Ir(br,{statusColor:r,statusLabel:a,sublabel:i,primaryText:s,timestamp:t.timestamp,badgeText:l,changed:t.hasStateChange,slow:t.isSlowUpdate,onPress:()=>o(t),testID:n})});import{useState as Gt,useCallback as Oe,useMemo as Jt}from"react";import{night as le,EventStepperFooter as qt,formatRelativeTime as We,parseValue as ze,monoFont as Pe}from"@buoy-gg/shared-ui/web";import{DataViewer as ea,SplitDiffViewer as ta,TreeDiffViewer as oa,diffThemes as na}from"@buoy-gg/shared-ui/web/dataViewer";import{memo as Dr}from"react";import{DetailViewToggle as Pr,FileText as Vr,Database as Zr,GitBranch as Rr}from"@buoy-gg/shared-ui/web";import{jsx as Ar}from"react/jsx-runtime";var Er=[{key:"change",label:"CHANGE",description:"View change details",icon:Vr},{key:"state",label:"STATE",description:"View current state",icon:Zr},{key:"diff",label:"DIFF",description:"Compare changes",icon:Rr}],Br=["diff"],Mt=Dr(function({activeView:t,onViewChange:o,diffDisabled:n=!1}){return Ar(Pr,{views:Er,activeView:t,onViewChange:o,disabledKeys:n?Br:void 0,testIDPrefix:"buoy-zustand"})});import{memo as zr,useCallback as at,useState as Wr,useEffect as Mr}from"react";import{buoyColors as F,night as P,Clock as Hr,AlertTriangle as Fr,Zap as _t,Info as _r,Box as jt,Database as jr,parseValue as Kr,ProUpgradeModal as Ur,copyToClipboard as Gr,InfoRow as Kt,monoFont as ye}from"@buoy-gg/shared-ui/web";import{useIsPro as Jr}from"@buoy-gg/license/web";import{DataViewer as Yr}from"@buoy-gg/shared-ui/web/dataViewer";import{night as E,monoFont as Nr}from"@buoy-gg/shared-ui/web";import{jsx as Ht,jsxs as Lr}from"react/jsx-runtime";var $r={copy:{color:E.color.accent,backgroundColor:E.color.accentSoft,borderColor:E.color.accentBorder,textColor:E.color.accent},reset:{color:E.color.warning,backgroundColor:E.color.warningSoft,borderColor:E.color.warning+"40",textColor:E.color.warning},clear:{color:E.color.danger,backgroundColor:E.color.dangerSoft,borderColor:E.color.danger+"40",textColor:E.color.danger},jump:{color:E.color.warning,backgroundColor:E.color.warningSoft,borderColor:E.color.warning+"40",textColor:E.color.warning}};function Ae({onPress:e,text:t,type:o,disabled:n=!1,testID:r}){let a=$r[o];return Lr(q,{disabled:n,onPress:e,style:[rt.button,{backgroundColor:n?E.color.textTertiary+"1A":a.backgroundColor,borderColor:n?E.color.textTertiary+"33":a.borderColor,opacity:n?.5:1}],activeOpacity:.7,testID:r,accessibilityRole:"button",accessibilityLabel:t,accessibilityState:{disabled:n},children:[Ht(c,{style:[rt.dot,{backgroundColor:n?E.color.textTertiary:a.color}]}),Ht(b,{style:[rt.text,{color:n?E.color.textTertiary:a.textColor}],children:t})]})}var rt=G.create({button:{flexDirection:"row",alignItems:"center",justifyContent:"center",borderRadius:6,borderWidth:1,paddingHorizontal:12,paddingVertical:6,height:25,minWidth:80,shadowOffset:{width:0,height:1},shadowOpacity:.2,shadowRadius:2,elevation:2},dot:{width:5,height:5,borderRadius:3,marginRight:6,shadowColor:E.color.text,shadowOffset:{width:0,height:0},shadowOpacity:.6,shadowRadius:2},text:{fontSize:10,fontWeight:"600",letterSpacing:.5,textTransform:"uppercase",fontFamily:Nr,height:12}});var u="buoy-zustand";function Ne(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}function Or(e,t){return`${u}-row-${Ne(e)}-${t}`}function $e(e){return`${u}-store-${Ne(e)}`}function Ft(e){let t=new Map,o=new Map;for(let n of e){let r=(t.get(n.storeName)??0)+1;t.set(n.storeName,r),o.set(n.id,Or(n.storeName,r))}return o}import{jsx as C,jsxs as _}from"react/jsx-runtime";function Xr(e){switch(e){case"replace":return{label:"REPLACE",color:F.warning,bgColor:F.warning+"26"};case"persist":return{label:"PERSIST",color:F.info,bgColor:F.info+"26"};case"initial":return{label:"INITIAL",color:P.color.textTertiary,bgColor:P.color.textTertiary+"26"};default:return{label:"SET STATE",color:F.success,bgColor:F.success+"26"}}}function qr(e){return e===void 0?"N/A":e<1?"<1ms":`${e.toFixed(1)}ms`}function Le({icon:e,title:t,badge:o,testID:n}){return _(c,{style:w.sectionHeader,testID:n,children:[_(c,{style:w.sectionHeaderLeft,children:[C(e,{size:14,color:P.color.accent}),C(b,{style:w.sectionTitle,children:t})]}),o]})}function Qr(e){let t=new Date(e),o=t.getHours().toString().padStart(2,"0"),n=t.getMinutes().toString().padStart(2,"0"),r=t.getSeconds().toString().padStart(2,"0"),a=t.getMilliseconds().toString().padStart(3,"0");return`${o}:${n}:${r}.${a}`}var Ut=zr(function({change:t}){let o=Jr(),[n,r]=Wr(!1);Mr(()=>{n&&o&&r(!1)},[n,o]);let a=Xr(t.category),i=t.partial!==void 0&&t.partial!=="(updater fn)",s=t.changedKeys.length>0,l=h.getStoreColor(t.storeName),g=at(async()=>{if(!o){r(!0);return}await Gr(t.nextState)},[t.nextState,o]),S=at(()=>{let f=h.getStore(t.storeName);if(f?.api.getInitialState){let p=f.api.getInitialState();f.api.setState(p,!0)}},[t.storeName]),T=at(()=>{let f=h.getStore(t.storeName);f&&f.api.setState(t.nextState,!0)},[t.storeName,t.nextState]);return _(ne,{style:w.container,contentContainerStyle:w.contentContainer,showsVerticalScrollIndicator:!1,testID:`${u}-detail-scroll`,children:[_(c,{style:w.card,testID:`${u}-info-card`,children:[C(Le,{icon:_r,title:"CHANGE INFO"}),_(c,{style:w.cardContent,children:[C(Kt,{label:"Store",value:t.storeName,mono:!0,valueColor:l,testID:`${u}-info-store`}),C(Kt,{label:"Time",value:Qr(t.timestamp),mono:!0,testID:`${u}-info-time`}),_(c,{style:w.badgeRow,children:[C(c,{style:[w.categoryBadge,{backgroundColor:a.bgColor}],children:C(b,{style:[w.categoryText,{color:a.color}],testID:`${u}-info-category`,children:a.label})}),_(c,{style:[w.durationBadge,t.isSlowUpdate&&w.durationBadgeSlow],children:[C(Hr,{size:10,color:t.isSlowUpdate?F.error:P.color.textSecondary}),C(b,{style:[w.durationText,t.isSlowUpdate&&w.durationTextSlow],testID:`${u}-info-duration`,children:qr(t.duration)}),t.isSlowUpdate&&C(Fr,{size:10,color:F.error})]}),t.hasStateChange?_(c,{style:w.changesBadge,children:[C(_t,{size:10,color:F.success}),C(b,{style:w.changesText,testID:`${u}-info-changes`,children:t.diffSummary||"changed"})]}):C(c,{style:w.noChangesBadge,children:C(b,{style:w.noChangesText,testID:`${u}-info-no-changes`,children:"no change"})}),t.isPersisted&&_(c,{style:w.persistBadge,children:[C(jr,{size:10,color:F.info}),C(b,{style:w.persistText,testID:`${u}-info-persisted`,children:"PERSISTED"})]})]})]})]}),s&&_(c,{style:w.card,testID:`${u}-keys-card`,children:[C(Le,{icon:_t,title:"CHANGED KEYS",badge:C(c,{style:w.countBadge,children:C(b,{style:w.countText,testID:`${u}-keys-count`,children:t.changedKeys.length})})}),C(c,{style:w.cardContent,children:C(c,{style:w.keysContainer,children:t.changedKeys.map(f=>C(c,{style:w.keyBadge,children:C(b,{style:w.keyText,testID:`${u}-key-${f}`,children:f})},f))})})]}),i&&_(c,{style:w.card,testID:`${u}-partial-card`,children:[C(Le,{icon:jt,title:"PARTIAL (setState argument)",badge:C(c,{style:w.typeBadge,children:C(b,{style:w.typeText,testID:`${u}-partial-type`,children:Array.isArray(t.partial)?"ARRAY":typeof t.partial=="object"?"OBJECT":(typeof t.partial).toUpperCase()})})}),C(c,{style:w.dataViewerContainer,children:C(Yr,{title:"",data:Kr(t.partial),showTypeFilter:!0,rawMode:!0,initialExpanded:!0})})]}),t.partial==="(updater fn)"&&_(c,{style:w.card,testID:`${u}-updater-card`,children:[C(Le,{icon:jt,title:"SET STATE CALL"}),_(c,{style:w.cardContent,children:[C(b,{style:w.updaterText,children:"Called with an updater function: set((state) => ...)"}),C(b,{style:w.updaterSubtext,children:"The partial value is computed at runtime from the previous state. See the DIFF tab to view the resulting changes."})]})]}),_(c,{style:w.actionsGrid,children:[C(Ae,{type:"jump",text:"JUMP",onPress:T,testID:`${u}-jump`}),C(Ae,{type:"reset",text:"RESET STORE",onPress:S,testID:`${u}-reset`}),C(Ae,{type:"copy",text:"COPY STATE",onPress:g,testID:`${u}-copy-state`})]}),C(Ur,{visible:n,onClose:()=>r(!1),featureName:"Copy"})]})}),w=G.create({container:{flex:1},contentContainer:{padding:8,paddingBottom:100,gap:16},card:{backgroundColor:P.color.surface,borderRadius:6,borderWidth:1,borderColor:P.color.accentBorderStrong,overflow:"hidden",shadowColor:P.color.accent,shadowOffset:{width:0,height:0},shadowOpacity:.1,shadowRadius:6},sectionHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingVertical:10,borderBottomWidth:1,borderBottomColor:P.color.accentBorder,backgroundColor:P.color.accentSoft},sectionHeaderLeft:{flexDirection:"row",alignItems:"center",gap:6},sectionTitle:{fontSize:12,fontWeight:"600",letterSpacing:.5,color:P.color.accent,fontFamily:ye},cardContent:{padding:14,gap:10},badgeRow:{flexDirection:"row",flexWrap:"wrap",gap:8,marginTop:6},categoryBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4},categoryText:{fontSize:9,fontWeight:"700",letterSpacing:.3},durationBadge:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:P.color.surfaceElevated},durationBadgeSlow:{backgroundColor:F.error+"26"},durationText:{fontSize:9,fontWeight:"600",color:P.color.textSecondary,fontFamily:ye},durationTextSlow:{color:F.error},changesBadge:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:F.success+"26"},changesText:{fontSize:9,fontWeight:"600",color:F.success},noChangesBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:P.color.surfaceElevated},noChangesText:{fontSize:9,fontWeight:"600",color:P.color.textTertiary},persistBadge:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:F.info+"26"},persistText:{fontSize:9,fontWeight:"700",color:F.info,letterSpacing:.3},keysContainer:{flexDirection:"row",flexWrap:"wrap",gap:6},keyBadge:{paddingHorizontal:8,paddingVertical:4,borderRadius:4,backgroundColor:P.color.accentSoft,borderWidth:1,borderColor:P.color.accentBorder},keyText:{fontSize:11,fontWeight:"600",color:P.color.accent,fontFamily:ye},countBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:P.color.accentFill},countText:{fontSize:9,fontWeight:"700",color:P.color.accent,fontFamily:ye},typeBadge:{paddingHorizontal:6,paddingVertical:2,borderRadius:3,backgroundColor:P.color.surfaceElevated},typeText:{fontSize:8,fontWeight:"600",color:P.color.textTertiary,fontFamily:ye},dataViewerContainer:{backgroundColor:P.color.bg,minHeight:60},updaterText:{fontSize:12,color:P.color.text,fontFamily:ye},updaterSubtext:{fontSize:11,color:P.color.textTertiary,lineHeight:16,marginTop:4},actionsGrid:{flexDirection:"row",flexWrap:"wrap",gap:12,justifyContent:"flex-start"}});import{DiffModeTabs as ra,CompareBar as aa}from"@buoy-gg/shared-ui/web";import{jsx as Z,jsxs as be}from"react/jsx-runtime";function st(e){return h.getStoreColor(e.storeName)}function Yt(e){switch(e.category){case"replace":return"REPLACE";case"persist":return"HYDRATE";case"initial":return"INIT";default:return"SET"}}function sa(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}function Xt(e){return new Date(e).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}var ia=[{key:"tree",label:"TREE VIEW"},{key:"split",label:"SPLIT VIEW"}];function it({change:e,changes:t,selectedIndex:o,onIndexChange:n,disableInternalFooter:r=!1,initialView:a="change"}){let[i,s]=Gt(a),[l,g]=Gt("tree"),S=t.length,T=st(e),f=Yt(e),p=sa(ze(e.nextState)),y=S-o,B=y-1,V=o<S-1?o+1:null,K=V!==null?t[V]:null,H=Jt(()=>({index:V??0,label:B>0?`#${B}`:"Initial",timestamp:K?Xt(K.timestamp):"",relativeTime:K?We(new Date(K.timestamp)):"state",badge:K?Z(c,{style:[N.changeBadgeSmall,{backgroundColor:`${st(K)}20`}],children:Z(b,{style:[N.changeTextSmall,{color:st(K)}],children:Yt(K)})}):void 0}),[V,K,B]),se=Jt(()=>({index:o,label:`#${y}`,timestamp:Xt(e.timestamp),relativeTime:We(new Date(e.timestamp)),badge:Z(c,{style:[N.changeBadgeSmall,{backgroundColor:`${T}20`}],children:Z(b,{style:[N.changeTextSmall,{color:T}],children:f})})}),[o,e.timestamp,T,f,y]),ce=Oe(()=>{let ue=ze(e.nextState);return be(c,{style:N.contentCard,testID:`${u}-state-card`,children:[be(c,{style:N.valueHeader,children:[Z(b,{style:N.valueLabel,children:"STATE AFTER CHANGE"}),be(c,{style:N.valueHeaderBadges,children:[Z(c,{style:[N.changeBadge,{backgroundColor:`${T}20`}],children:Z(b,{style:[N.changeText,{color:T}],children:f})}),Z(c,{style:N.typeBadge,children:Z(b,{style:N.typeText,testID:`${u}-state-type`,children:p.toUpperCase()})})]})]}),Z(c,{style:N.dataViewerContainer,children:Z(ea,{title:"",data:ue,showTypeFilter:!0,rawMode:!0,initialExpanded:!0})})]})},[e.nextState,T,f,p]),Te=Oe(()=>{let ue=ze(e.prevState),he=ze(e.nextState);return l==="split"?Z(ne,{style:{flex:1},showsVerticalScrollIndicator:!0,children:Z(ta,{oldValue:ue,newValue:he,theme:na.devToolsDefault,options:{hideLineNumbers:!1,disableWordDiff:!1,showDiffOnly:!1,compareMethod:"words",contextLines:3},showThemeName:!1})}):Z(oa,{oldValue:ue,newValue:he})},[e.prevState,e.nextState,l]),xe=Oe(()=>{n(Math.min(S-1,o+1))},[o,S,n]),ve=Oe(()=>{n(Math.max(0,o-1))},[o,n]),ee=e.prevState===void 0,ke=!r&&S>1;return be(c,{style:N.container,children:[Z(Mt,{activeView:i,onViewChange:s,diffDisabled:ee}),be(c,{style:[N.contentArea,ke&&N.contentAreaFooterPad],children:[i==="change"&&Z(Ut,{change:e}),i==="state"&&Z(c,{style:N.stateContainer,children:ce()}),i==="diff"&&be(c,{style:N.diffContainer,testID:`${u}-diff`,children:[Z(ra,{tabs:ia,activeTab:l,onTabChange:g,testIDPrefix:`${u}-diff-tab`}),Z(aa,{leftEvent:H,rightEvent:se}),Z(c,{style:N.diffContent,testID:`${u}-diff-content`,children:Te()})]})]}),ke&&Z(qt,{currentIndex:y-1,totalItems:S,onPrevious:xe,onNext:ve,itemLabel:"Change",subtitle:We(new Date(e.timestamp)),absolute:!0})]})}function lt({change:e,changes:t,selectedIndex:o,onIndexChange:n}){let r=t.length;if(r<=1)return null;let a=r-o;return Z(qt,{currentIndex:a-1,totalItems:r,onPrevious:()=>n(Math.min(r-1,o+1)),onNext:()=>n(Math.max(0,o-1)),itemLabel:"Change",subtitle:We(new Date(e.timestamp))})}var N=G.create({container:{flex:1,backgroundColor:"transparent"},contentArea:{flex:1},contentAreaFooterPad:{paddingBottom:96},stateContainer:{flex:1,padding:14},contentCard:{backgroundColor:le.color.surface,borderRadius:14,padding:14,borderWidth:1,borderColor:le.color.border},valueHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:8},valueLabel:{fontSize:10,color:le.color.textSecondary,fontFamily:Pe,letterSpacing:.5,fontWeight:"600"},valueHeaderBadges:{flexDirection:"row",alignItems:"center",gap:6},changeBadge:{paddingHorizontal:8,paddingVertical:2,borderRadius:4},changeText:{fontSize:9,fontWeight:"700",fontFamily:Pe,letterSpacing:.3},typeBadge:{paddingHorizontal:8,paddingVertical:2,borderRadius:4,backgroundColor:le.color.surfaceElevated},typeText:{fontSize:9,fontWeight:"600",color:le.color.textTertiary,fontFamily:Pe},valueBox:{backgroundColor:le.color.bg,borderRadius:6,borderWidth:1,borderColor:le.color.border,padding:8},valueText:{fontSize:12,color:le.color.text,fontFamily:Pe,lineHeight:18},diffContainer:{flex:1},diffContent:{flex:1,paddingHorizontal:14},changeBadgeSmall:{paddingHorizontal:6,paddingVertical:1,borderRadius:3},changeTextSmall:{fontSize:8,fontWeight:"700",fontFamily:Pe},dataViewerContainer:{marginTop:-12,marginHorizontal:-12,marginBottom:-12}});import{useMemo as la}from"react";import{CompactRow as da,night as dt,ToolEmptyState as ut,resolveListEmptyState as ca,monoFont as Qt}from"@buoy-gg/shared-ui/web";import{jsx as Q,jsxs as ct}from"react/jsx-runtime";function ua(e){try{let t=e.api.getState();if(t&&typeof t=="object"&&t.__buoyStateOnDevice===!0)return"on device";if(t&&typeof t=="object"){let n=Object.keys(t).filter(r=>typeof t[r]!="function");return n.length===0?"no data keys":n.length<=3?n.join(", "):`${n.slice(0,2).join(", ")} +${n.length-2}`}return""}catch{return""}}function ga(){return Q(ut,{testID:`${u}-stores-loading`,otto:"loading",title:"Loading stores\u2026",message:"Waiting for the device to send its registered stores."})}function fa(){return Q(ut,{testID:`${u}-stores-empty`,otto:"scanning",title:"No stores registered",message:`Use watchStores() to register your Zustand stores.
|
|
3
|
+
They will appear here with their current state.`})}function eo({stores:e,searchQuery:t,isLoading:o=!1,onOpenStore:n}){let r=la(()=>{if(!t)return e;let a=t.toLowerCase();return e.filter(i=>i.name.toLowerCase().includes(a)||i.persistName&&i.persistName.toLowerCase().includes(a))},[e,t]);return r.length===0&&!t?o?Q(ga,{}):Q(fa,{}):r.length===0&&t?Q(ut,{testID:`${u}-stores-no-match`,...ca({subject:"stores",verb:"registered",search:t,total:e.length,waiting:{otto:"scanning",title:"No stores registered",message:"Use watchStores() to register your Zustand stores."}})}):ct(ne,{style:re.container,contentContainerStyle:re.scrollContent,showsVerticalScrollIndicator:!0,testID:`${u}-stores-scroll`,children:[ct(c,{style:re.sectionHeader,children:[Q(b,{style:re.sectionTitle,children:"STORES"}),Q(c,{style:re.sectionCountBadge,children:Q(b,{style:re.sectionCountText,testID:`${u}-stores-count`,children:r.length})})]}),r.map(a=>{let i=ua(a),s=h.getStoreColor(a.name),l=$e(a.name);return ct(c,{style:re.storeRowWrapper,children:[Q(da,{statusDotColor:s,statusLabel:a.isPersisted?"persisted":"in-memory",primaryText:a.name,secondaryText:i||void 0,showChevron:!0,onPress:()=>n(a.name),testID:l,statusTestID:`${l}-backing`,primaryTestID:`${l}-name`,secondaryTestID:`${l}-keys`}),a.stateChangeCount>0&&Q(c,{style:[re.absCountBadge,{backgroundColor:s+"22",borderColor:s+"55"}],pointerEvents:"none",children:Q(b,{style:[re.absCountText,{color:s}],testID:`${l}-count`,children:String(a.stateChangeCount)})})]},a.name)})]})}var re=G.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:Qt},scrollContent:{paddingTop:8,paddingBottom:20},sectionHeader:{flexDirection:"row",alignItems:"center",paddingHorizontal:16,paddingVertical:8,gap:8},sectionTitle:{fontSize:11,fontWeight:"700",letterSpacing:.5,color:dt.color.textTertiary},sectionCountBadge:{backgroundColor:dt.color.accentFill,paddingHorizontal:8,paddingVertical:2,borderRadius:4},sectionCountText:{fontSize:10,fontWeight:"700",color:dt.color.accent,fontFamily:Qt}});import{useCallback as pa,useEffect as ha,useMemo as gt,useState as Fe}from"react";import{buoyColors as ft,Database as Sa,ExpandedInfoRow as Me,FileJson as ma,night as $,PillBadge as pt,parseValue as to,monoFont as He}from"@buoy-gg/shared-ui/web";import{DataViewer as ya,LiveExplorer as ba,ValueEditModal as wa,updateNestedDataByPath as Ca,deleteNestedDataByPath as Ta}from"@buoy-gg/shared-ui/web/dataViewer";import{Fragment as no,jsx as x,jsxs as Y}from"react/jsx-runtime";var xa=300,pe=`${u}-detail`;function va(e){let t=o=>o.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return e.length===0?`${pe}-explorer-root`:`${pe}-explorer-${e.map(t).filter(Boolean).join("-")}`}function oo({store:e,onViewHistory:t}){let o=$e(e.name),[n,r]=Fe(0);ha(()=>e.api.subscribe(()=>r(f=>f+1)),[e.api]);let a=e.api.getState(),i=gt(()=>tt(a),[a,n]),[s,l]=Fe(!1),g=gt(()=>{if(i.data!==null)return{update:(f,p)=>{let y=e.api.getState();if(!U(y)||f.length===0)return;let B=f[0],V=f.length===1?p:Ca(y[B],f.slice(1),p);e.api.setState({[B]:V},!1)},remove:f=>{let p=e.api.getState();if(!U(p)||f.length<=1)return;let y=f[0],B=Ta(p[y],f.slice(1));e.api.setState({[y]:B},!1)},canRemove:f=>f.length>1}},[e.api,i.data===null]),S=pa(f=>{let p=tt(e.api.getState());if(p.data===null)return;let y=Nt(p.data,f);Object.keys(y).length>0&&e.api.setState(y,!1),l(!1)},[e.api]),T=gt(()=>{if(a&&typeof a=="object"){let f={};for(let[p,y]of Object.entries(a))typeof y!="function"&&(f[p]=y);return to(f)}return to(a)},[a,n]);return Y(c,{style:k.root,children:[Y(ne,{testID:`${pe}-scroll`,style:k.scroll,contentContainerStyle:k.scrollContent,keyboardShouldPersistTaps:"handled",children:[x(c,{style:k.section,children:Y(c,{style:k.editorContainer,children:[Y(c,{style:k.sectionHeaderRow,children:[x(b,{testID:`${pe}-editor-header`,style:k.sectionHeaderText,children:"State Editor"}),i.data!==null?Y(q,{onPress:()=>l(!0),testID:`${pe}-raw`,accessibilityRole:"button",accessibilityLabel:"Edit raw JSON",style:k.rawButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:[x(ma,{size:13,color:$.color.accent}),x(b,{style:k.rawButtonText,children:"Raw"})]}):null]}),x(c,{style:k.sectionBody,children:i.data!==null?Y(no,{children:[x(ba,{editable:!0,label:"State",value:i.data,defaultExpanded:["State"],writer:g,dataVersion:n,testIDForPath:va,debounceMs:xa}),x(b,{style:k.editorHint,children:"Edits write into the live store as you make them \u2014 merged, so the store's actions and untouched keys survive."})]}):x(b,{style:k.blockedText,testID:`${pe}-edit-blocked`,children:i.blockedReason})})]})}),x(c,{style:k.section,children:Y(c,{style:k.infoContainer,children:[x(b,{style:k.sectionHeaderTextPlain,children:"Store Details"}),Y(c,{style:k.infoBody,children:[x(Me,{label:"Type",children:x(pt,{color:e.color,testID:`${o}-type`,children:"ZUSTAND"})}),Y(Me,{label:"Backing",children:[x(pt,{color:ft.info,icon:e.isPersisted?x(Sa,{size:9,color:ft.info}):void 0,testID:`${o}-persist`,children:e.isPersisted?e.persistName||"persisted":"in-memory"}),e.isPersisted?x(ka,{store:e,rowID:o}):null]}),e.stateChangeCount>0&&Y(Me,{label:"Changes",children:[x(pt,{color:ft.warning,testID:`${o}-changes`,children:String(e.stateChangeCount)}),x(q,{onPress:()=>t(e.name),style:k.inlineLinkButton,hitSlop:{top:6,bottom:6,left:6,right:6},testID:`${o}-history`,accessibilityRole:"button",accessibilityLabel:`View ${e.name} history`,children:x(b,{style:[k.inlineLinkText,{color:e.color}],children:"view history \u2192"})})]}),i.fnPaths.length>0&&x(Me,{label:"Actions",children:x(b,{style:k.actionsText,testID:`${pe}-actions`,numberOfLines:2,children:i.fnPaths.join(" \xB7 ")})})]})]})}),x(c,{style:k.section,children:Y(c,{style:k.infoContainer,children:[x(b,{style:k.sectionHeaderTextPlain,children:"State Explorer"}),x(c,{style:k.viewerBody,testID:`${o}-state`,children:T&&typeof T=="object"?x(ya,{title:"",data:T,showTypeFilter:!0,rawMode:!0,initialExpanded:!1}):x(b,{style:k.blockedText,children:"Empty state"})})]})})]}),s&&i.data!==null?x(wa,{path:[],value:i.data,onSave:f=>{try{S(f)}catch{}},onCancel:()=>l(!1),saveLabel:"Apply",testIDPrefix:`${u}-value`}):null]})}function ka({store:e,rowID:t}){let[o,n]=Fe(!1),[r,a]=Fe(null);return e.rehydrate?Y(no,{children:[x(q,{onPress:()=>{n(!0),a(null),(async()=>{try{await e.rehydrate?.()}catch(s){a(s instanceof Error?s.message:String(s))}finally{n(!1)}})()},disabled:o,style:k.inlineLinkButton,hitSlop:{top:6,bottom:6,left:6,right:6},testID:`${t}-reread`,accessibilityRole:"button",accessibilityLabel:`Re-read ${e.name} from storage`,children:x(b,{style:[k.inlineLinkText,{color:e.color}],children:o?"re-reading\u2026":"re-read saved value \u2192"})}),r?x(b,{style:[k.inlineLinkText,{color:$.color.danger}],children:r}):null]}):null}var k=G.create({root:{flex:1,minHeight:0},scroll:{flex:1},scrollContent:{paddingHorizontal:8,paddingTop:8,paddingBottom:24},section:{marginBottom:16},editorContainer:{minWidth:200,backgroundColor:$.color.surface,borderRadius:6,borderWidth:1,borderColor:$.color.accent+"4D",overflow:"hidden",shadowColor:$.color.accent,shadowOffset:{width:0,height:0},shadowOpacity:.1,shadowRadius:6},sectionHeaderRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:$.color.accent+"1A",paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:$.color.accent+"33"},sectionHeaderText:{fontWeight:"600",fontSize:12,color:$.color.accent,letterSpacing:.5,textTransform:"uppercase",fontFamily:He},rawButton:{flexDirection:"row",alignItems:"center",gap:4},rawButtonText:{color:$.color.accent,fontSize:11,fontWeight:"700"},sectionBody:{padding:8},editorHint:{color:$.color.textTertiary,fontSize:10,paddingTop:8,paddingHorizontal:4,lineHeight:14},blockedText:{color:$.color.textSecondary,fontSize:12,padding:12,lineHeight:18},infoContainer:{minWidth:200,backgroundColor:$.color.surface,borderRadius:6,borderWidth:1,borderColor:$.color.border,overflow:"hidden"},sectionHeaderTextPlain:{backgroundColor:$.color.surfaceElevated,paddingHorizontal:12,paddingVertical:8,fontWeight:"600",fontSize:12,color:$.color.textSecondary,borderBottomWidth:1,borderBottomColor:$.color.border,letterSpacing:.5,textTransform:"uppercase",fontFamily:He},infoBody:{padding:12,gap:10},viewerBody:{backgroundColor:$.color.bg,minHeight:60},inlineLinkButton:{marginLeft:4},inlineLinkText:{fontSize:10,fontWeight:"600",fontFamily:He},actionsText:{flex:1,color:$.color.textSecondary,fontSize:10,fontFamily:He,lineHeight:15}});import{useMemo as Ia}from"react";import{EntityFilterView as Da}from"@buoy-gg/shared-ui/web";import{jsx as Va}from"react/jsx-runtime";var Pa=["\u2022 auth \u2192 hides authStore from stores & events","\u2022 temp \u2192 hides tempStore, tempUserStore","\u2022 cart \u2192 hides cartStore from both tabs"];function ro({ignoredPatterns:e,onTogglePattern:t,onAddPattern:o,stores:n}){let r=Ia(()=>n.map(a=>({name:a.name,color:h.getStoreColor(a.name)})),[n]);return Va(Da,{entities:r,ignoredPatterns:e,onTogglePattern:t,onAddPattern:o,entityNoun:"STORES",entitySingular:"store",entityKey:"store",testIDPrefix:u,slug:Ne,howItWorks:"Patterns hide matching stores from both the Stores tab and the Events tab. Tap a store above to quickly add it as a filter.",examples:Pa})}import{Fragment as lo,jsx as m,jsxs as j}from"react/jsx-runtime";function _a({isEnabled:e,searchText:t,totalChanges:o}){let n=co({subject:"changes",search:t,total:o,capturing:e,waiting:{otto:"waiting",title:"No state changes",message:"State changes will appear here as stores update."},paused:{title:"No state changes",message:"Enable capture to start recording state changes"}});return m(St,{...n,testID:`${u}-empty`})}function Ve({visible:e,onClose:t,onBack:o,onMinimize:n,enableSharedModalDimensions:r=!1,inline:a=!1,storesLoading:i=!1,initialTab:s="stores",initialSelectedChangeId:l,initialDetailView:g,onChangeSelect:S,onStoreExpand:T}){let f=Fa(),p=Ma("zustand.changes"),[y,B]=ae(!1),[V,K]=ae(s),[H,se]=ae(null),[ce,Te]=ae(null),[xe,ve]=ae(!1),[ee,ke]=ae(new Set),{stateChanges:ue,filteredChanges:he,filter:ms,setFilter:Ie,stats:mo,stores:De,clearChanges:yo,isEnabled:Ze,toggleCapture:bo}=ot(),te=de(()=>ee.size===0?he:he.filter(d=>!Array.from(ee).some(D=>d.storeName.toLowerCase().includes(D.toLowerCase()))),[he,ee]),Se=de(()=>ee.size===0?De:De.filter(d=>!Array.from(ee).some(D=>d.name.toLowerCase().includes(D.toLowerCase()))),[De,ee]),wo=de(()=>H?ue.filter(d=>d.storeName===H).length:0,[ue,H]),Re=de(()=>H?te.filter(d=>d.storeName===H):te,[te,H]),Ye=de(()=>Re.slice(0,p),[Re,p]),Xe=de(()=>Math.max(0,Re.length-p),[Re.length,p]),Co=Xe>0,W=Ye,[qe,ge]=ae(null),fe=de(()=>{if(qe===null)return null;let d=W.findIndex(D=>D.id===qe);return d>=0?d:null},[qe,W]),X=fe!==null?W[fe]:null,yt=_e(null);ao(()=>{if(!S)return;let d=X?.id??null;d===null||d===yt.current||(yt.current=d,S(X))},[X,S]);let bt=_e(null);ao(()=>{!l||bt.current===l||!W.some(D=>D.id===l)||(bt.current=l,ge(l))},[l,W]);let[ie,Qe]=ae(""),[To,Ee]=ae(!1),ys=_e(null),xo=_e(null),vo=L(d=>{},[]),ko=d=>{Qe(d),V==="events"&&Ie(D=>({...D,searchText:d}))},Io=L(()=>{Qe(""),Ie(d=>({...d,searchText:""})),Ee(!1)},[Ie]),wt=L(d=>{ge(d.id)},[]),Do=L(()=>{B(!0)},[]),Po=L(()=>{ge(null)},[]),Ct=L(d=>{let D=W[d];D&&ge(D.id)},[W]),Vo=L(d=>{se(d),ge(null)},[]),Zo=L(()=>{se(null),ge(null)},[]),Ro=L(d=>{Te(d),T?.(d)},[T]),Eo=L(()=>{Te(null)},[]),Bo=L(()=>Se.reduce((d,D)=>{try{let oe=D.api.getState();if(oe&&typeof oe=="object"){let Zt={};for(let[Fo,Rt]of Object.entries(oe))typeof Rt!="function"&&(Zt[Fo]=we(Rt));d[D.name]=Zt}else d[D.name]=we(oe)}catch{d[D.name]=null}return d},{}),[Se]),Ao=L(()=>te.map(d=>({id:d.id,storeName:d.storeName,timestamp:d.timestamp,prevState:we(d.prevState),nextState:we(d.nextState),hasStateChange:d.hasStateChange,changedKeys:d.changedKeys})),[te]),No=L(()=>W.map(d=>({id:d.id,storeName:d.storeName,timestamp:d.timestamp,prevState:we(d.prevState),nextState:we(d.nextState),hasStateChange:d.hasStateChange,changedKeys:d.changedKeys})),[W]),$o=L(d=>{ke(D=>{let oe=new Set(D);return oe.has(d)?oe.delete(d):oe.add(d),oe})},[]),Lo=L(d=>{ke(D=>new Set([...D,d]))},[]),Oo=L(d=>{K(d),se(null),Te(null),ge(null),ve(!1),ie&&(Qe(""),Ie(D=>({...D,searchText:""}))),Ee(!1)},[ie,Ie]),Tt=L(d=>d.id,[]),xt=de(()=>Ft(W),[W]),vt=L(({item:d})=>m(nt,{change:d,onPress:wt,testID:xt.get(d.id)}),[wt,xt]),kt=ee.size>0,It=()=>xe?j(O,{children:[m(O.Navigation,{onBack:()=>ve(!1)}),m(O.Content,{title:"Filters",centered:!0,testID:`${u}-filters-title`})]}):X?j(O,{children:[m(O.Navigation,{onBack:Po}),m(O.Content,{title:`${X.storeName}/setState`,centered:!0,testID:`${u}-detail-title`})]}):H?j(O,{children:[m(O.Navigation,{onBack:Zo}),m(O.Content,{title:`${H} History`,centered:!0,testID:`${u}-history-title`}),m(O.Actions,{children:m(ht,{value:No,disabled:W.length===0,buttonStyle:z.headerActionButton,testID:`${u}-copy`})})]}):ce?j(O,{children:[m(O.Navigation,{onBack:Eo}),m(O.Content,{title:ce,centered:!0,testID:`${u}-store-detail-title`})]}):j(O,{children:[o&&m(O.Navigation,{onBack:o}),m(O.Content,{title:"",children:To?m(O.Search,{value:ie,onChangeText:ko,onCancel:Io,placeholder:V==="stores"?"Search stores...":"Search events...",accessibilityLabel:"Search Zustand stores and events",iconSize:14,testID:`${u}-search-field`,containerTestID:`${u}-search`,clearTestID:`${u}-search-clear`,onBlur:()=>{ie.length===0&&Ee(!1)}}):m(Ra,{tabs:[{key:"stores",label:`Stores${Se.length>0?` (${Se.length})`:""}`},{key:"events",label:`Events${te.length>0?` (${te.length})`:""}`}],activeTab:V,onTabChange:Oo,testIDPrefix:`${u}-tab`})}),j(O.Actions,{children:[m(q,{onPress:()=>Ee(!0),style:z.headerActionButton,testID:`${u}-search-open`,accessibilityRole:"button",accessibilityLabel:"Search",children:m(Ba,{size:14,color:M.color.textSecondary})}),m(q,{onPress:()=>ve(!0),style:[z.headerActionButton,kt&&z.headerActionButtonActive],testID:`${u}-filters-open`,accessibilityRole:"button",accessibilityLabel:"Filters",children:m(Aa,{size:14,color:kt?M.color.accent:M.color.textSecondary})}),V==="stores"&&m(ht,{value:Bo,disabled:Se.length===0,buttonStyle:z.headerActionButton,testID:`${u}-copy`}),V==="events"&&j(lo,{children:[m(ht,{value:Ao,disabled:te.length===0,buttonStyle:z.headerActionButton,testID:`${u}-copy`}),m(za,{isEnabled:Ze,onToggle:bo,accessibilityLabel:"Toggle Zustand state capture",testID:`${u}-power`}),m(Wa,{onPress:yo,disabled:te.length===0,buttonStyle:z.headerActionButton,testID:`${u}-clear`})]})]})]}),zo=()=>{if(xe)return m(ro,{ignoredPatterns:ee,onTogglePattern:$o,onAddPattern:Lo,stores:De});if(X&&fe!==null)return m(it,{change:X,changes:W,selectedIndex:fe,onIndexChange:Ct,disableInternalFooter:!0,initialView:g});if(H)return W.length>0?m(Be,{testID:`${u}-history-list`,data:W,renderItem:vt,keyExtractor:Tt,contentContainerStyle:z.listContent,showsVerticalScrollIndicator:!0,...io}):m(St,{testID:`${u}-history-empty`,...co({subject:"changes",search:ie,total:ie.trim()?wo:0,capturing:Ze,waiting:{otto:"waiting",title:"No history yet",message:`State changes for ${H} will appear here.`},paused:{title:"No history yet",message:`Capture is off, so nothing is being recorded for ${H}.`}})});if(ce){let d=De.find(D=>D.name===ce)??null;return d?m(oo,{store:d,onViewHistory:Vo}):m(St,{testID:`${u}-store-detail-gone`,otto:"ghost",title:"Store no longer registered",message:`${ce} is not in the registry any more.`})}return V==="stores"?m(eo,{stores:Se,searchQuery:ie,isLoading:i,onOpenStore:Ro}):j(lo,{children:[!Ze&&j(c,{style:z.disabledBanner,testID:`${u}-banner-disabled`,children:[m(Na,{size:14,color:M.color.warning}),m(b,{style:z.disabledText,children:"State capture is disabled"})]}),Co&&j(q,{style:z.lockedBanner,onPress:Do,activeOpacity:.8,testID:`${u}-locked`,accessibilityRole:"button",children:[m($a,{size:14,color:M.color.accent}),j(b,{style:z.lockedText,testID:`${u}-locked-text`,children:[Xe," older"," ",Xe===1?"change":"changes"," locked"]}),m(c,{style:z.upgradeBadge,children:m(b,{style:z.upgradeBadgeText,children:"UPGRADE"})})]}),Ye.length>0?m(Be,{ref:xo,testID:`${u}-list`,data:Ye,renderItem:vt,keyExtractor:Tt,contentContainerStyle:z.listContent,showsVerticalScrollIndicator:!0,...io}):m(_a,{isEnabled:Ze,searchText:ie,totalChanges:mo.totalChanges})]})},Wo=r?La.modal.root():"buoy-zustand-modal";if(!e)return null;let Dt=X&&fe!==null?m(lt,{change:X,changes:W,selectedIndex:fe,onIndexChange:Ct}):null,Mo=X&&W.length>1?68:0,Ho=!xe&&!(X&&fe!==null)&&(H!=null||V==="events"||V==="stores"),Pt=m(c,{style:z.container,children:zo()}),Vt=m(Oa,{visible:y,onClose:()=>B(!1),featureName:"Full State History"});return a?j(so,{children:[j(c,{style:z.inlineRoot,children:[m(c,{style:z.inlineHeader,...Ha,children:It()}),Pt,Dt]}),Vt]}):j(so,{children:[m(Za,{visible:e,onClose:t,onMinimize:n,persistenceKey:Wo,header:{showToggleButton:!0,customContent:It()},onModeChange:vo,enablePersistence:!0,initialMode:"bottomSheet",enableGlitchEffects:!0,disableScrollWrapper:Ho,styles:{},footer:Dt,footerHeight:Mo,children:Pt}),Vt]})}function ja(e={}){return m(Ve,{...e,inline:!0,visible:!0,onClose:()=>{}})}var z=G.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:Ea.border.default,backgroundColor:"transparent"},container:{flex:1,backgroundColor:"transparent"},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:M.color.buttonSurface,borderWidth:1,borderColor:M.color.border,alignItems:"center",justifyContent:"center"},headerActionButtonDisabled:{opacity:.55},headerActionButtonActive:{backgroundColor:M.color.accentSoft,borderColor:M.color.accentBorderStrong},disabledBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:M.color.warningSoft,borderRadius:8,borderWidth:1,borderColor:M.color.warning+"20"},disabledText:{color:M.color.warning,fontSize:11,flex:1},listContent:{paddingTop:8,paddingBottom:20},lockedBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:M.color.accentSoft,borderRadius:8,borderWidth:1,borderColor:M.color.accentBorder},lockedText:{color:M.color.accent,fontSize:11,fontWeight:"500",flex:1},upgradeBadge:{backgroundColor:M.color.accent,paddingHorizontal:8,paddingVertical:3,borderRadius:4},upgradeBadgeText:{color:M.color.onAccent,fontSize:9,fontWeight:"700",letterSpacing:.5}});import{ZustandIcon as je,ZUSTAND_ICON_COLOR as Ka}from"@buoy-gg/shared-ui/web";import{jsx as uo}from"react/jsx-runtime";var Ua={id:"zustand",name:"ZUSTAND",description:"Zustand store & state inspector",slot:"both",icon:({size:e})=>uo(je,{size:e}),component:Ve,props:{enableSharedModalDimensions:!1}};function Ga(e){return{id:e?.id||"zustand",name:e?.name||"ZUSTAND",description:e?.description||"Zustand store & state inspector",slot:"both",icon:({size:t})=>uo(je,{size:t,color:e?.iconColor}),component:Ve,props:{enableSharedModalDimensions:e?.enableSharedModalDimensions!==void 0?e.enableSharedModalDimensions:!1}}}var Ce=Symbol.for("@@buoy-zustand/watched");function go(e){let t=e.persist;return t&&typeof t.getOptions=="function"?{isPersisted:!0,persistName:t.getOptions()?.name,persistApi:t}:{isPersisted:!1}}function Ja(e){let t=[];for(let[o,n]of Object.entries(e)){let r=n;if(r[Ce])continue;r[Ce]=!0;let{isPersisted:a,persistName:i,persistApi:s}=go(n);h.registerStore(o,{getState:n.getState,getInitialState:n.getInitialState,setState:n.setState,subscribe:n.subscribe},{isPersisted:a,persistName:i,rehydrate:typeof s?.rehydrate=="function"?()=>s.rehydrate():void 0});let l=!1,g=n.subscribe((S,T)=>{try{let f;if(!l&&a){let p=!1;try{p=s?.hasHydrated?.()===!1}catch{}p&&(l=!0,f="persist")}h.addStateChange({storeName:o,partial:void 0,replace:!1,prevState:T,nextState:S,duration:void 0,category:f,isPersisted:a})}catch{}});t.push(()=>{g(),delete n[Ce],h.unregisterStore(o)})}return()=>{t.forEach(o=>o())}}var Ya=0;function Xa(e,t){return(o,n,r)=>{let a=t?.name??`store-${++Ya}`,i=t?.enabled!==!1,s=o,l=(S,T)=>{if(!i)return s(S,T);let f=performance.now(),p=n();s(S,T);let y=n(),B=performance.now()-f,V=typeof S=="function"?"(updater fn)":S;h.addStateChange({storeName:a,partial:V,replace:T??!1,prevState:p,nextState:y,duration:B,isPersisted:h.getStore(a)?.isPersisted??!1})},g=e(l,n,r);return setTimeout(()=>{if(r[Ce])return;r[Ce]=!0;let{isPersisted:S,persistName:T}=go(r);h.registerStore(a,{getState:n,getInitialState:r.getInitialState,setState:l,subscribe:r.subscribe},{isPersisted:S,persistName:T})},0),g}}function qa(e){return e[Ce]===!0}import{expandKeyedArrays as Qa,patchFromPath as es,readPath as ts,refusalText as os,shapeSummary as Ke,suggestedPatch as mt,typedEditViolations as ns,unshapedLists as rs}from"@buoy-gg/shared-ui/web";import{WIRE_DETAIL_LIMIT_BYTES as fo,createSyncAdapter as as,detailByIdAction as ss,isJsonSerializable as is,isOverWireBudget as ls,memoByIdentity as ds,onDeviceMarker as po,readParams as Ue,toWire as ho,truncatedNote as cs}from"@buoy-gg/shared-ui/web";var Ge=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.getPrototypeOf(e)===Object.prototype;function So(e,t){if(!Ge(e)||!Ge(t))return t;let o={...e};for(let[n,r]of Object.entries(t))o[n]=n in e?So(e[n],r):r;return o}var Je=po("State","State trees stay on the device \u2014 fetched on demand via getChangeDetail / getStoreState."),us=po("Payload","Partial stays on the device \u2014 fetched on demand via getChangeDetail.");function gs(e,t){let o=e;for(let n of t.split(".")){if(!o||typeof o!="object"||Array.isArray(o)||!(n in o))return!1;o=o[n]}return Array.isArray(o)?o.length>0:!!o&&typeof o=="object"}function fs(e){return e&&typeof e=="object"&&!Array.isArray(e)?Object.keys(e):void 0}var ps=ds(e=>({...e,prevState:Je,nextState:Je,partial:ho(e.partial,us)})),hs=e=>ho(e,Je),Ss=as({version:2,getSnapshot:()=>({changes:h.getStateChanges().map(ps),stores:h.getStoreSnapshots().map(e=>({...e,currentState:hs(e.currentState)}))}),subscribe:[e=>h.subscribe(e),e=>h.subscribeToStores(e)],actions:{clearEvents:()=>{h.clearStateChanges()},getChangeDetail:ss(e=>h.getStateChangeById(e),(e,t)=>({prevState:t(e.prevState),nextState:t(e.nextState),partial:t(e.partial)})),getStoreState:e=>{let{storeName:t,path:o}=Ue(e);if(!t)return{found:!1,reason:"missing storeName"};let n=h.getStore(t);if(!n)return{found:!1,reason:"unknown storeName"};let r;try{r=n.api.getState()}catch{r=void 0}if(o){let i=ts(r,o);return i.ok?{found:!0,storeName:t,path:o,shape:Ke(i.value),currentState:i.value}:{found:!1,reason:i.error}}let a=Ke(r);return ls(r,fo)?{found:!0,storeName:t,...a?{shape:a}:{},currentState:is(r)?cs(fo):Je}:{found:!0,storeName:t,...a?{shape:a}:{},currentState:r}},listStores:e=>{let{includeValues:t=!1,limit:o}=Ue(e),n=h.getStoreSnapshots(),a=(typeof o=="number"&&o>0?n.slice(0,o):n).map(i=>({name:i.name,changes:i.stateChangeCount,isPersisted:i.isPersisted,persistName:i.persistName,canRehydrate:i.canRehydrate,...t?{currentState:i.currentState}:{keys:fs(i.currentState)},...Ke(i.currentState)?{shape:Ke(i.currentState)}:{}}));return{stores:a,total:n.length,returned:a.length,includedValues:t}},setState:e=>{let{storeName:t,state:o,replace:n,force:r,path:a,value:i}=Ue(e),s=o,l=[],g=[];if(!t)return{ok:!1,error:"Missing storeName."};let S=h.getStore(t);if(!S)return{ok:!1,error:`No store named "${t}".`};if(o===void 0&&a===void 0)return{ok:!1,error:"Refused: no change was described. Send `state` with the fields to merge, or `path` and `value` to set one value."};let T=!1;if(a!==void 0){if(i===void 0)return{ok:!1,error:`Refused: sent \`path\` without \`value\`. Say what to set "${a}" to; if you mean to clear it, send value:null and the field must already be nullable.`};let p;try{p=S.api.getState()}catch{p=void 0}let y=es(p,a,i);if(!y.ok)return{ok:!1,error:`Refused: ${y.error}`};s=y.patch,T=!0}if(!r&&(n===!1||T)){let p;try{p=S.api.getState()}catch{p=void 0}if(p!==void 0){let y=Qa(p,s);if(y.error)return{ok:!1,error:`Refused: ${y.error}`};s=y.patch,l=y.notes}if(p!==void 0&&(g=rs(p).filter(y=>gs(s,y))),p!==void 0){let y=ns(p,s,{noun:"store"});if(y.length>0)return{ok:!1,error:os(y,"store",mt(s,y)),...mt(s,y)?{suggestedState:mt(s,y)}:{},violations:y}}}let f=T||n===!1;if(f){let p;try{p=S.api.getState()}catch{p=void 0}if(Ge(p)&&Ge(s)){let y={};for(let[B,V]of Object.entries(s))y[B]=B in p?So(p[B],V):V;s=y}}return S.api.setState(s,f?!1:n??!0),{ok:!0,...a!==void 0?{path:a}:{},...l.length?{keyed:l}:{},...g.length?{unchecked:g.map(p=>`\`${p}\` was empty, so nothing in the app knows what its items look like and the shape of what you added was NOT checked. If the app renders a field you left out, it will render undefined \u2014 read the store back and compare against how the screen uses it.`)}:{}}},rehydrate:async e=>{let{storeName:t}=Ue(e);if(!t)return{ok:!1,error:"Missing storeName."};let o=h.getStore(t);return o?o.isPersisted?o.rehydrate?(await o.rehydrate(),{ok:!0,storeName:t,persistName:o.persistName}):{ok:!1,error:`"${t}" is persisted but Buoy has no rehydrate handle for it. It was probably registered before the app's persist middleware attached.`}:{ok:!1,error:`"${t}" is not a persisted store \u2014 it has no saved copy to re-read.`}:{ok:!1,error:`No store named "${t}".`}}}});export{Ka as ZUSTAND_ICON_COLOR,je as ZustandIcon,Ve as ZustandModal,nt as ZustandStateChangeItem,it as ZustandStateDetailContent,lt as ZustandStateDetailFooter,ja as ZustandToolView,Xa as buoyDevTools,Ga as createZustandTool,qa as isStoreInstrumented,ot as useZustandStateChanges,Ja as watchStores,h as zustandStateStore,Ss as zustandSyncAdapter,Ua as zustandToolPreset};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/zustand",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.41",
|
|
4
4
|
"description": "Inspect registered Zustand stores, compare captured state changes and use supported reset or state-update actions.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -10,32 +10,43 @@
|
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"react-native": "./lib/module/index.js",
|
|
13
|
+
"browser": {
|
|
14
|
+
"types": "./lib/typescript/index.d.ts",
|
|
15
|
+
"default": "./lib/web/index.mjs"
|
|
16
|
+
},
|
|
13
17
|
"import": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
18
|
+
"types": "./lib/typescript/index.d.ts",
|
|
19
|
+
"default": "./lib/module/index.js"
|
|
16
20
|
},
|
|
17
21
|
"require": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
22
|
+
"types": "./lib/typescript/index.d.ts",
|
|
23
|
+
"default": "./lib/commonjs/index.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
|
-
"./package.json": "./package.json"
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./web": {
|
|
28
|
+
"types": "./lib/typescript/index.d.ts",
|
|
29
|
+
"default": "./lib/web/index.mjs"
|
|
30
|
+
}
|
|
23
31
|
},
|
|
24
32
|
"files": [
|
|
25
33
|
"lib"
|
|
26
34
|
],
|
|
27
35
|
"sideEffects": [
|
|
28
36
|
"**/snapshotProvider.js",
|
|
29
|
-
"**/snapshotProvider.ts"
|
|
37
|
+
"**/snapshotProvider.ts",
|
|
38
|
+
"./lib/web/**/*.mjs"
|
|
30
39
|
],
|
|
31
40
|
"dependencies": {
|
|
32
|
-
"@buoy-gg/license": "^7.0.
|
|
33
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
41
|
+
"@buoy-gg/license": "^7.0.41",
|
|
42
|
+
"@buoy-gg/shared-ui": "^7.0.41"
|
|
34
43
|
},
|
|
35
44
|
"peerDependencies": {
|
|
36
45
|
"react": "*",
|
|
37
46
|
"react-native": "*",
|
|
38
|
-
"zustand": ">=4.0.0"
|
|
47
|
+
"zustand": ">=4.0.0",
|
|
48
|
+
"react-native-web": "^0.21.0",
|
|
49
|
+
"react-dom": ">=18"
|
|
39
50
|
},
|
|
40
51
|
"devDependencies": {
|
|
41
52
|
"@types/react": "^19.1.0",
|
|
@@ -90,10 +101,22 @@
|
|
|
90
101
|
"zustand",
|
|
91
102
|
"state-management"
|
|
92
103
|
],
|
|
104
|
+
"peerDependenciesMeta": {
|
|
105
|
+
"react-native": {
|
|
106
|
+
"optional": true
|
|
107
|
+
},
|
|
108
|
+
"react-native-web": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
111
|
+
"react-dom": {
|
|
112
|
+
"optional": true
|
|
113
|
+
}
|
|
114
|
+
},
|
|
93
115
|
"scripts": {
|
|
94
|
-
"build": "bob build",
|
|
116
|
+
"build": "bob build && pnpm run build:web",
|
|
95
117
|
"typecheck": "tsc --noEmit",
|
|
96
118
|
"clean": "rimraf lib",
|
|
97
|
-
"test": "pnpm run typecheck"
|
|
119
|
+
"test": "pnpm run typecheck",
|
|
120
|
+
"build:web": "node ../../scripts/build-web.mjs zustand"
|
|
98
121
|
}
|
|
99
122
|
}
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ZustandStoreEditorScreen = ZustandStoreEditorScreen;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var _reactNative = require("react-native");
|
|
9
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
-
var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
|
|
11
|
-
var _editableState = require("../utils/editableState");
|
|
12
|
-
var _testIDs = require("../utils/testIDs");
|
|
13
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
-
/**
|
|
15
|
-
* Editing a store, on a screen of its own โ the Storage key editor's shape,
|
|
16
|
-
* pointed at a live zustand store.
|
|
17
|
-
*
|
|
18
|
-
* Same reasoning as storage's editor screen: a tree, six structural actions
|
|
19
|
-
* and a keyboard don't fit inside an expanded card, so the card offers ONE
|
|
20
|
-
* edit button and the work happens here with the whole viewport. The tree,
|
|
21
|
-
* dock, draft and value modal are the shared-ui editor โ one implementation,
|
|
22
|
-
* two tools.
|
|
23
|
-
*
|
|
24
|
-
* What is zustand-specific is the WRITE. Storage saves by replacing one key's
|
|
25
|
-
* serialized value; a store has no such luxury โ its state holds the app's
|
|
26
|
-
* action functions, and a full `setState(draft, true)` replace would delete
|
|
27
|
-
* them (see `editableState.ts`). So Save diffs the draft against the state it
|
|
28
|
-
* was seeded from and writes ONE top-level merge:
|
|
29
|
-
*
|
|
30
|
-
* store.api.setState(changedTopLevelKeys, false)
|
|
31
|
-
*
|
|
32
|
-
* which by construction cannot remove a key or touch a function. The two
|
|
33
|
-
* edits a merge cannot express โ removing or renaming a top-level key โ are
|
|
34
|
-
* disabled in the dock (`rootEntriesLocked`) and refused at save if they
|
|
35
|
-
* arrive through the raw-JSON escape hatch.
|
|
36
|
-
*
|
|
37
|
-
* On a desktop mirror the same line goes over the wire: the mirrored store's
|
|
38
|
-
* `api.setState` forwards to the device's sync adapter (the time-travel
|
|
39
|
-
* path). That write has no ack, so a remote save resolves optimistically and
|
|
40
|
-
* the next device snapshot is the confirmation.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
const EDITOR_ID = `${_testIDs.ZUSTAND_TEST_ID}-editor`;
|
|
44
|
-
function ZustandStoreEditorScreen({
|
|
45
|
-
store,
|
|
46
|
-
onClose,
|
|
47
|
-
applySafeAreaInset
|
|
48
|
-
}) {
|
|
49
|
-
const insets = (0, _sharedUi.useSafeAreaInsets)();
|
|
50
|
-
const bottomInset = applySafeAreaInset ? insets.bottom : 0;
|
|
51
|
-
|
|
52
|
-
// Seeded ONCE, when the editor opens โ the draft is diffed against this at
|
|
53
|
-
// save, so the partial carries exactly what the user changed and a key the
|
|
54
|
-
// app wrote meanwhile (untouched here) keeps the app's value.
|
|
55
|
-
const [seed] = (0, _react.useState)(() => (0, _editableState.editableStateOf)(store.api.getState()));
|
|
56
|
-
const save = (0, _react.useCallback)(async draft => {
|
|
57
|
-
if (seed.data === null) throw new Error("Store is not editable.");
|
|
58
|
-
const partial = (0, _editableState.computeMergePartial)(seed.data, draft);
|
|
59
|
-
// A merge, never a replace โ the entire reason this editor can exist.
|
|
60
|
-
// Empty partial = the draft circled back to the seed; nothing to write.
|
|
61
|
-
if (Object.keys(partial).length > 0) {
|
|
62
|
-
store.api.setState(partial, false);
|
|
63
|
-
}
|
|
64
|
-
}, [seed, store]);
|
|
65
|
-
const editor = (0, _dataViewer.useTreeEditor)({
|
|
66
|
-
value: seed.data,
|
|
67
|
-
save,
|
|
68
|
-
onClose,
|
|
69
|
-
confirmDiscard: choices => _reactNative.Alert.alert("Unsaved changes", `"${store.name}" has edits that haven't been written to the store.`, [{
|
|
70
|
-
text: "Keep editing",
|
|
71
|
-
style: "cancel"
|
|
72
|
-
}, {
|
|
73
|
-
text: "Discard",
|
|
74
|
-
style: "destructive",
|
|
75
|
-
onPress: choices.discard
|
|
76
|
-
}, {
|
|
77
|
-
text: "Save",
|
|
78
|
-
onPress: choices.save
|
|
79
|
-
}])
|
|
80
|
-
});
|
|
81
|
-
const {
|
|
82
|
-
draft,
|
|
83
|
-
selection,
|
|
84
|
-
valuePath
|
|
85
|
-
} = editor;
|
|
86
|
-
const keyCount = (0, _react.useMemo)(() => {
|
|
87
|
-
const v = draft.value;
|
|
88
|
-
return v !== null && typeof v === "object" ? Object.keys(v).length : 0;
|
|
89
|
-
}, [draft.value]);
|
|
90
|
-
|
|
91
|
-
// The card's gating means this can't normally be reached un-editable, but a
|
|
92
|
-
// remote mirror can theoretically drop hydration between tap and mount.
|
|
93
|
-
if (seed.data === null) {
|
|
94
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
95
|
-
style: styles.screen,
|
|
96
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolBackground, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
97
|
-
style: styles.blockedText,
|
|
98
|
-
children: seed.blockedReason
|
|
99
|
-
})]
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
103
|
-
style: styles.screen,
|
|
104
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolBackground, {}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
105
|
-
style: styles.header,
|
|
106
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
107
|
-
onPress: editor.requestClose,
|
|
108
|
-
testID: `${EDITOR_ID}-close`,
|
|
109
|
-
accessibilityRole: "button",
|
|
110
|
-
accessibilityLabel: "Close editor",
|
|
111
|
-
style: styles.headerButton,
|
|
112
|
-
hitSlop: {
|
|
113
|
-
top: 8,
|
|
114
|
-
bottom: 8,
|
|
115
|
-
left: 8,
|
|
116
|
-
right: 8
|
|
117
|
-
},
|
|
118
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
|
|
119
|
-
size: 16,
|
|
120
|
-
color: _sharedUi.night.color.textSecondary
|
|
121
|
-
})
|
|
122
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
123
|
-
style: styles.headerTitle,
|
|
124
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text
|
|
125
|
-
// Which store this editor is pointed at โ opening it on the wrong
|
|
126
|
-
// one looks exactly like the right one until a write lands
|
|
127
|
-
// somewhere unexpected.
|
|
128
|
-
, {
|
|
129
|
-
testID: `${EDITOR_ID}-store`,
|
|
130
|
-
style: styles.headerKey,
|
|
131
|
-
numberOfLines: 1,
|
|
132
|
-
children: store.name
|
|
133
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
134
|
-
style: [styles.headerState, draft.isDirty && styles.headerDirty],
|
|
135
|
-
children: draft.isSaving ? "Savingโฆ" : draft.isDirty ? "Draft ยท unsaved" : "In sync with store"
|
|
136
|
-
})]
|
|
137
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
138
|
-
onPress: editor.openRawEditor,
|
|
139
|
-
testID: `${EDITOR_ID}-raw`,
|
|
140
|
-
accessibilityRole: "button",
|
|
141
|
-
accessibilityLabel: "Edit raw JSON",
|
|
142
|
-
style: styles.headerButton,
|
|
143
|
-
hitSlop: {
|
|
144
|
-
top: 8,
|
|
145
|
-
bottom: 8,
|
|
146
|
-
left: 8,
|
|
147
|
-
right: 8
|
|
148
|
-
},
|
|
149
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
|
|
150
|
-
size: 16,
|
|
151
|
-
color: _sharedUi.night.color.accent
|
|
152
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
153
|
-
style: styles.headerRawText,
|
|
154
|
-
children: "Raw"
|
|
155
|
-
})]
|
|
156
|
-
})]
|
|
157
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
158
|
-
style: styles.metaRow,
|
|
159
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
160
|
-
color: _sharedUi.buoyColors.info,
|
|
161
|
-
icon: store.isPersisted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
|
|
162
|
-
size: 9,
|
|
163
|
-
color: _sharedUi.buoyColors.info
|
|
164
|
-
}) : undefined,
|
|
165
|
-
children: store.isPersisted ? store.persistName || "persisted" : "in-memory"
|
|
166
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
167
|
-
style: styles.metaText,
|
|
168
|
-
children: [`object ยท ${keyCount} key${keyCount === 1 ? "" : "s"}`, seed.fnPaths.length > 0 ? ` ยท ${seed.fnPaths.length} action${seed.fnPaths.length === 1 ? "" : "s"} untouched` : ""]
|
|
169
|
-
})]
|
|
170
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
171
|
-
testID: `${EDITOR_ID}-scroll`,
|
|
172
|
-
style: styles.treeScroll,
|
|
173
|
-
contentContainerStyle: styles.treeContent,
|
|
174
|
-
keyboardShouldPersistTaps: "handled",
|
|
175
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
176
|
-
style: styles.treeCard,
|
|
177
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
|
|
178
|
-
data: draft.value,
|
|
179
|
-
title: "",
|
|
180
|
-
rawMode: true,
|
|
181
|
-
initialExpanded: true
|
|
182
|
-
// Type filtering is a READING aid: the filtered view is a
|
|
183
|
-
// projection whose row paths don't address the real document, so
|
|
184
|
-
// the explorer stops reporting selection while one is active. An
|
|
185
|
-
// editor whose actions silently go dead isn't worth the filter.
|
|
186
|
-
,
|
|
187
|
-
showTypeFilter: false,
|
|
188
|
-
onSelectionChange: editor.setSelection,
|
|
189
|
-
selectPath: draft.selectPath
|
|
190
|
-
// Its own handle root: the card that opened this editor is still
|
|
191
|
-
// mounted underneath, rendering the same value in its preview tree.
|
|
192
|
-
,
|
|
193
|
-
testIDPrefix: `${EDITOR_ID}-row`,
|
|
194
|
-
onRequestInlineEdit: sel => editor.openValueEditor(sel.path)
|
|
195
|
-
})
|
|
196
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
197
|
-
style: styles.treeHint,
|
|
198
|
-
children: "Tap a row to select it \xB7 double-tap to edit its value \xB7 saves merge into the live store, so actions and untouched keys survive"
|
|
199
|
-
})]
|
|
200
|
-
}), draft.error ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
201
|
-
style: styles.error,
|
|
202
|
-
numberOfLines: 3,
|
|
203
|
-
testID: `${EDITOR_ID}-error`,
|
|
204
|
-
children: draft.error
|
|
205
|
-
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
206
|
-
style: [styles.footer, {
|
|
207
|
-
paddingBottom: bottomInset
|
|
208
|
-
}],
|
|
209
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataTreeActionDock, {
|
|
210
|
-
root: draft.value,
|
|
211
|
-
selection: selection,
|
|
212
|
-
onEdit: draft.apply,
|
|
213
|
-
onSelectPath: draft.selectAt
|
|
214
|
-
// Booleans never reach this โ the dock applies their flip directly.
|
|
215
|
-
,
|
|
216
|
-
onEditValue: () => editor.openValueEditor(selection?.path ?? [])
|
|
217
|
-
// A merge write can rewrite a top-level key but never delete one, so
|
|
218
|
-
// Remove is disabled at depth 1 โ see `editableState.ts`.
|
|
219
|
-
,
|
|
220
|
-
rootEntriesLocked: true
|
|
221
|
-
}), draft.isDirty ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
222
|
-
style: styles.commitRow,
|
|
223
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
224
|
-
onPress: draft.discard,
|
|
225
|
-
disabled: draft.isSaving,
|
|
226
|
-
testID: `${EDITOR_ID}-discard`,
|
|
227
|
-
accessibilityRole: "button",
|
|
228
|
-
accessibilityLabel: "Discard changes",
|
|
229
|
-
style: styles.discardButton,
|
|
230
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
231
|
-
style: styles.discardText,
|
|
232
|
-
children: "Discard"
|
|
233
|
-
})
|
|
234
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
235
|
-
onPress: editor.saveAndClose,
|
|
236
|
-
disabled: draft.isSaving,
|
|
237
|
-
testID: `${EDITOR_ID}-save`,
|
|
238
|
-
accessibilityRole: "button",
|
|
239
|
-
accessibilityLabel: "Save changes",
|
|
240
|
-
style: [styles.saveButton, draft.isSaving && styles.saveDisabled],
|
|
241
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
242
|
-
style: styles.saveText,
|
|
243
|
-
children: draft.isSaving ? "Savingโฆ" : "Save changes"
|
|
244
|
-
})
|
|
245
|
-
})]
|
|
246
|
-
}) : null]
|
|
247
|
-
}), valuePath ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.ValueEditModal, {
|
|
248
|
-
path: valuePath,
|
|
249
|
-
value: (0, _dataViewer.getAtPath)(draft.value, valuePath),
|
|
250
|
-
onSave: editor.applyValue,
|
|
251
|
-
onCancel: editor.closeValueEditor
|
|
252
|
-
// It only reaches the draft here โ the single write happens on Save.
|
|
253
|
-
,
|
|
254
|
-
saveLabel: "Apply",
|
|
255
|
-
testIDPrefix: `${_testIDs.ZUSTAND_TEST_ID}-value`
|
|
256
|
-
}) : null]
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
const styles = _reactNative.StyleSheet.create({
|
|
260
|
-
screen: {
|
|
261
|
-
flex: 1,
|
|
262
|
-
// Opaque on purpose: transparency here showed the whole store list
|
|
263
|
-
// through the editor, not just the sheet's backdrop. The screen paints
|
|
264
|
-
// its own ToolBackground instead โ see the mount site above.
|
|
265
|
-
backgroundColor: _sharedUi.night.color.bg
|
|
266
|
-
},
|
|
267
|
-
blockedText: {
|
|
268
|
-
color: _sharedUi.night.color.textTertiary,
|
|
269
|
-
fontSize: 12,
|
|
270
|
-
padding: 20,
|
|
271
|
-
textAlign: "center",
|
|
272
|
-
lineHeight: 18
|
|
273
|
-
},
|
|
274
|
-
header: {
|
|
275
|
-
flexDirection: "row",
|
|
276
|
-
alignItems: "center",
|
|
277
|
-
gap: 8,
|
|
278
|
-
paddingHorizontal: 10,
|
|
279
|
-
paddingVertical: 8,
|
|
280
|
-
borderBottomWidth: 1,
|
|
281
|
-
borderBottomColor: _sharedUi.night.color.border
|
|
282
|
-
},
|
|
283
|
-
headerButton: {
|
|
284
|
-
flexDirection: "row",
|
|
285
|
-
alignItems: "center",
|
|
286
|
-
gap: 4,
|
|
287
|
-
paddingVertical: 6,
|
|
288
|
-
paddingHorizontal: 4
|
|
289
|
-
},
|
|
290
|
-
headerRawText: {
|
|
291
|
-
color: _sharedUi.night.color.accent,
|
|
292
|
-
fontSize: 12,
|
|
293
|
-
fontWeight: "700"
|
|
294
|
-
},
|
|
295
|
-
headerTitle: {
|
|
296
|
-
flex: 1,
|
|
297
|
-
alignItems: "center",
|
|
298
|
-
gap: 2
|
|
299
|
-
},
|
|
300
|
-
headerKey: {
|
|
301
|
-
color: _sharedUi.night.color.text,
|
|
302
|
-
fontSize: 13,
|
|
303
|
-
fontWeight: "600",
|
|
304
|
-
fontFamily: _sharedUi.monoFont
|
|
305
|
-
},
|
|
306
|
-
headerState: {
|
|
307
|
-
color: _sharedUi.night.color.textTertiary,
|
|
308
|
-
fontSize: 10
|
|
309
|
-
},
|
|
310
|
-
headerDirty: {
|
|
311
|
-
color: _sharedUi.night.color.warning
|
|
312
|
-
},
|
|
313
|
-
metaRow: {
|
|
314
|
-
flexDirection: "row",
|
|
315
|
-
alignItems: "center",
|
|
316
|
-
gap: 8,
|
|
317
|
-
paddingHorizontal: 12,
|
|
318
|
-
paddingVertical: 8
|
|
319
|
-
},
|
|
320
|
-
metaText: {
|
|
321
|
-
color: _sharedUi.night.color.textTertiary,
|
|
322
|
-
fontSize: 11,
|
|
323
|
-
fontFamily: _sharedUi.monoFont
|
|
324
|
-
},
|
|
325
|
-
treeScroll: {
|
|
326
|
-
flex: 1
|
|
327
|
-
},
|
|
328
|
-
treeContent: {
|
|
329
|
-
paddingHorizontal: 12,
|
|
330
|
-
paddingBottom: 16
|
|
331
|
-
},
|
|
332
|
-
treeCard: {
|
|
333
|
-
backgroundColor: _sharedUi.night.color.surface,
|
|
334
|
-
borderRadius: 10,
|
|
335
|
-
borderWidth: 1,
|
|
336
|
-
borderColor: _sharedUi.night.color.border,
|
|
337
|
-
padding: 6
|
|
338
|
-
},
|
|
339
|
-
treeHint: {
|
|
340
|
-
color: _sharedUi.night.color.textTertiary,
|
|
341
|
-
fontSize: 10,
|
|
342
|
-
paddingTop: 8,
|
|
343
|
-
paddingHorizontal: 4
|
|
344
|
-
},
|
|
345
|
-
error: {
|
|
346
|
-
color: _sharedUi.night.color.danger,
|
|
347
|
-
fontSize: 11,
|
|
348
|
-
fontFamily: _sharedUi.monoFont,
|
|
349
|
-
paddingHorizontal: 12,
|
|
350
|
-
paddingBottom: 6
|
|
351
|
-
},
|
|
352
|
-
footer: {
|
|
353
|
-
backgroundColor: _sharedUi.night.color.surface
|
|
354
|
-
},
|
|
355
|
-
commitRow: {
|
|
356
|
-
flexDirection: "row",
|
|
357
|
-
gap: 8,
|
|
358
|
-
paddingHorizontal: 12,
|
|
359
|
-
paddingVertical: 10,
|
|
360
|
-
borderTopWidth: 1,
|
|
361
|
-
borderTopColor: _sharedUi.night.color.border
|
|
362
|
-
},
|
|
363
|
-
discardButton: {
|
|
364
|
-
flex: 1,
|
|
365
|
-
minHeight: 44,
|
|
366
|
-
alignItems: "center",
|
|
367
|
-
justifyContent: "center",
|
|
368
|
-
borderRadius: 10,
|
|
369
|
-
borderWidth: 1,
|
|
370
|
-
borderColor: _sharedUi.night.color.border
|
|
371
|
-
},
|
|
372
|
-
discardText: {
|
|
373
|
-
color: _sharedUi.night.color.textSecondary,
|
|
374
|
-
fontSize: 14,
|
|
375
|
-
fontWeight: "600"
|
|
376
|
-
},
|
|
377
|
-
saveButton: {
|
|
378
|
-
flex: 1.4,
|
|
379
|
-
minHeight: 44,
|
|
380
|
-
alignItems: "center",
|
|
381
|
-
justifyContent: "center",
|
|
382
|
-
borderRadius: 10,
|
|
383
|
-
backgroundColor: _sharedUi.night.color.accent
|
|
384
|
-
},
|
|
385
|
-
saveDisabled: {
|
|
386
|
-
opacity: 0.5
|
|
387
|
-
},
|
|
388
|
-
saveText: {
|
|
389
|
-
color: _sharedUi.night.color.onAccent,
|
|
390
|
-
fontSize: 15,
|
|
391
|
-
fontWeight: "700"
|
|
392
|
-
}
|
|
393
|
-
});
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Editing a store, on a screen of its own โ the Storage key editor's shape,
|
|
5
|
-
* pointed at a live zustand store.
|
|
6
|
-
*
|
|
7
|
-
* Same reasoning as storage's editor screen: a tree, six structural actions
|
|
8
|
-
* and a keyboard don't fit inside an expanded card, so the card offers ONE
|
|
9
|
-
* edit button and the work happens here with the whole viewport. The tree,
|
|
10
|
-
* dock, draft and value modal are the shared-ui editor โ one implementation,
|
|
11
|
-
* two tools.
|
|
12
|
-
*
|
|
13
|
-
* What is zustand-specific is the WRITE. Storage saves by replacing one key's
|
|
14
|
-
* serialized value; a store has no such luxury โ its state holds the app's
|
|
15
|
-
* action functions, and a full `setState(draft, true)` replace would delete
|
|
16
|
-
* them (see `editableState.ts`). So Save diffs the draft against the state it
|
|
17
|
-
* was seeded from and writes ONE top-level merge:
|
|
18
|
-
*
|
|
19
|
-
* store.api.setState(changedTopLevelKeys, false)
|
|
20
|
-
*
|
|
21
|
-
* which by construction cannot remove a key or touch a function. The two
|
|
22
|
-
* edits a merge cannot express โ removing or renaming a top-level key โ are
|
|
23
|
-
* disabled in the dock (`rootEntriesLocked`) and refused at save if they
|
|
24
|
-
* arrive through the raw-JSON escape hatch.
|
|
25
|
-
*
|
|
26
|
-
* On a desktop mirror the same line goes over the wire: the mirrored store's
|
|
27
|
-
* `api.setState` forwards to the device's sync adapter (the time-travel
|
|
28
|
-
* path). That write has no ack, so a remote save resolves optimistically and
|
|
29
|
-
* the next device snapshot is the confirmation.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import { useCallback, useMemo, useState } from "react";
|
|
33
|
-
import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
34
|
-
import { buoyColors, Database, FileJson, night, PillBadge, ToolBackground, useSafeAreaInsets, X, monoFont } from "@buoy-gg/shared-ui";
|
|
35
|
-
import { DataTreeActionDock, DataViewer, getAtPath, useTreeEditor, ValueEditModal } from "@buoy-gg/shared-ui/dataViewer";
|
|
36
|
-
import { computeMergePartial, editableStateOf } from "../utils/editableState";
|
|
37
|
-
import { ZUSTAND_TEST_ID } from "../utils/testIDs";
|
|
38
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
39
|
-
const EDITOR_ID = `${ZUSTAND_TEST_ID}-editor`;
|
|
40
|
-
export function ZustandStoreEditorScreen({
|
|
41
|
-
store,
|
|
42
|
-
onClose,
|
|
43
|
-
applySafeAreaInset
|
|
44
|
-
}) {
|
|
45
|
-
const insets = useSafeAreaInsets();
|
|
46
|
-
const bottomInset = applySafeAreaInset ? insets.bottom : 0;
|
|
47
|
-
|
|
48
|
-
// Seeded ONCE, when the editor opens โ the draft is diffed against this at
|
|
49
|
-
// save, so the partial carries exactly what the user changed and a key the
|
|
50
|
-
// app wrote meanwhile (untouched here) keeps the app's value.
|
|
51
|
-
const [seed] = useState(() => editableStateOf(store.api.getState()));
|
|
52
|
-
const save = useCallback(async draft => {
|
|
53
|
-
if (seed.data === null) throw new Error("Store is not editable.");
|
|
54
|
-
const partial = computeMergePartial(seed.data, draft);
|
|
55
|
-
// A merge, never a replace โ the entire reason this editor can exist.
|
|
56
|
-
// Empty partial = the draft circled back to the seed; nothing to write.
|
|
57
|
-
if (Object.keys(partial).length > 0) {
|
|
58
|
-
store.api.setState(partial, false);
|
|
59
|
-
}
|
|
60
|
-
}, [seed, store]);
|
|
61
|
-
const editor = useTreeEditor({
|
|
62
|
-
value: seed.data,
|
|
63
|
-
save,
|
|
64
|
-
onClose,
|
|
65
|
-
confirmDiscard: choices => Alert.alert("Unsaved changes", `"${store.name}" has edits that haven't been written to the store.`, [{
|
|
66
|
-
text: "Keep editing",
|
|
67
|
-
style: "cancel"
|
|
68
|
-
}, {
|
|
69
|
-
text: "Discard",
|
|
70
|
-
style: "destructive",
|
|
71
|
-
onPress: choices.discard
|
|
72
|
-
}, {
|
|
73
|
-
text: "Save",
|
|
74
|
-
onPress: choices.save
|
|
75
|
-
}])
|
|
76
|
-
});
|
|
77
|
-
const {
|
|
78
|
-
draft,
|
|
79
|
-
selection,
|
|
80
|
-
valuePath
|
|
81
|
-
} = editor;
|
|
82
|
-
const keyCount = useMemo(() => {
|
|
83
|
-
const v = draft.value;
|
|
84
|
-
return v !== null && typeof v === "object" ? Object.keys(v).length : 0;
|
|
85
|
-
}, [draft.value]);
|
|
86
|
-
|
|
87
|
-
// The card's gating means this can't normally be reached un-editable, but a
|
|
88
|
-
// remote mirror can theoretically drop hydration between tap and mount.
|
|
89
|
-
if (seed.data === null) {
|
|
90
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
91
|
-
style: styles.screen,
|
|
92
|
-
children: [/*#__PURE__*/_jsx(ToolBackground, {}), /*#__PURE__*/_jsx(Text, {
|
|
93
|
-
style: styles.blockedText,
|
|
94
|
-
children: seed.blockedReason
|
|
95
|
-
})]
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
99
|
-
style: styles.screen,
|
|
100
|
-
children: [/*#__PURE__*/_jsx(ToolBackground, {}), /*#__PURE__*/_jsxs(View, {
|
|
101
|
-
style: styles.header,
|
|
102
|
-
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
103
|
-
onPress: editor.requestClose,
|
|
104
|
-
testID: `${EDITOR_ID}-close`,
|
|
105
|
-
accessibilityRole: "button",
|
|
106
|
-
accessibilityLabel: "Close editor",
|
|
107
|
-
style: styles.headerButton,
|
|
108
|
-
hitSlop: {
|
|
109
|
-
top: 8,
|
|
110
|
-
bottom: 8,
|
|
111
|
-
left: 8,
|
|
112
|
-
right: 8
|
|
113
|
-
},
|
|
114
|
-
children: /*#__PURE__*/_jsx(X, {
|
|
115
|
-
size: 16,
|
|
116
|
-
color: night.color.textSecondary
|
|
117
|
-
})
|
|
118
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
119
|
-
style: styles.headerTitle,
|
|
120
|
-
children: [/*#__PURE__*/_jsx(Text
|
|
121
|
-
// Which store this editor is pointed at โ opening it on the wrong
|
|
122
|
-
// one looks exactly like the right one until a write lands
|
|
123
|
-
// somewhere unexpected.
|
|
124
|
-
, {
|
|
125
|
-
testID: `${EDITOR_ID}-store`,
|
|
126
|
-
style: styles.headerKey,
|
|
127
|
-
numberOfLines: 1,
|
|
128
|
-
children: store.name
|
|
129
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
130
|
-
style: [styles.headerState, draft.isDirty && styles.headerDirty],
|
|
131
|
-
children: draft.isSaving ? "Savingโฆ" : draft.isDirty ? "Draft ยท unsaved" : "In sync with store"
|
|
132
|
-
})]
|
|
133
|
-
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
134
|
-
onPress: editor.openRawEditor,
|
|
135
|
-
testID: `${EDITOR_ID}-raw`,
|
|
136
|
-
accessibilityRole: "button",
|
|
137
|
-
accessibilityLabel: "Edit raw JSON",
|
|
138
|
-
style: styles.headerButton,
|
|
139
|
-
hitSlop: {
|
|
140
|
-
top: 8,
|
|
141
|
-
bottom: 8,
|
|
142
|
-
left: 8,
|
|
143
|
-
right: 8
|
|
144
|
-
},
|
|
145
|
-
children: [/*#__PURE__*/_jsx(FileJson, {
|
|
146
|
-
size: 16,
|
|
147
|
-
color: night.color.accent
|
|
148
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
149
|
-
style: styles.headerRawText,
|
|
150
|
-
children: "Raw"
|
|
151
|
-
})]
|
|
152
|
-
})]
|
|
153
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
154
|
-
style: styles.metaRow,
|
|
155
|
-
children: [/*#__PURE__*/_jsx(PillBadge, {
|
|
156
|
-
color: buoyColors.info,
|
|
157
|
-
icon: store.isPersisted ? /*#__PURE__*/_jsx(Database, {
|
|
158
|
-
size: 9,
|
|
159
|
-
color: buoyColors.info
|
|
160
|
-
}) : undefined,
|
|
161
|
-
children: store.isPersisted ? store.persistName || "persisted" : "in-memory"
|
|
162
|
-
}), /*#__PURE__*/_jsxs(Text, {
|
|
163
|
-
style: styles.metaText,
|
|
164
|
-
children: [`object ยท ${keyCount} key${keyCount === 1 ? "" : "s"}`, seed.fnPaths.length > 0 ? ` ยท ${seed.fnPaths.length} action${seed.fnPaths.length === 1 ? "" : "s"} untouched` : ""]
|
|
165
|
-
})]
|
|
166
|
-
}), /*#__PURE__*/_jsxs(ScrollView, {
|
|
167
|
-
testID: `${EDITOR_ID}-scroll`,
|
|
168
|
-
style: styles.treeScroll,
|
|
169
|
-
contentContainerStyle: styles.treeContent,
|
|
170
|
-
keyboardShouldPersistTaps: "handled",
|
|
171
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
172
|
-
style: styles.treeCard,
|
|
173
|
-
children: /*#__PURE__*/_jsx(DataViewer, {
|
|
174
|
-
data: draft.value,
|
|
175
|
-
title: "",
|
|
176
|
-
rawMode: true,
|
|
177
|
-
initialExpanded: true
|
|
178
|
-
// Type filtering is a READING aid: the filtered view is a
|
|
179
|
-
// projection whose row paths don't address the real document, so
|
|
180
|
-
// the explorer stops reporting selection while one is active. An
|
|
181
|
-
// editor whose actions silently go dead isn't worth the filter.
|
|
182
|
-
,
|
|
183
|
-
showTypeFilter: false,
|
|
184
|
-
onSelectionChange: editor.setSelection,
|
|
185
|
-
selectPath: draft.selectPath
|
|
186
|
-
// Its own handle root: the card that opened this editor is still
|
|
187
|
-
// mounted underneath, rendering the same value in its preview tree.
|
|
188
|
-
,
|
|
189
|
-
testIDPrefix: `${EDITOR_ID}-row`,
|
|
190
|
-
onRequestInlineEdit: sel => editor.openValueEditor(sel.path)
|
|
191
|
-
})
|
|
192
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
193
|
-
style: styles.treeHint,
|
|
194
|
-
children: "Tap a row to select it \xB7 double-tap to edit its value \xB7 saves merge into the live store, so actions and untouched keys survive"
|
|
195
|
-
})]
|
|
196
|
-
}), draft.error ? /*#__PURE__*/_jsx(Text, {
|
|
197
|
-
style: styles.error,
|
|
198
|
-
numberOfLines: 3,
|
|
199
|
-
testID: `${EDITOR_ID}-error`,
|
|
200
|
-
children: draft.error
|
|
201
|
-
}) : null, /*#__PURE__*/_jsxs(View, {
|
|
202
|
-
style: [styles.footer, {
|
|
203
|
-
paddingBottom: bottomInset
|
|
204
|
-
}],
|
|
205
|
-
children: [/*#__PURE__*/_jsx(DataTreeActionDock, {
|
|
206
|
-
root: draft.value,
|
|
207
|
-
selection: selection,
|
|
208
|
-
onEdit: draft.apply,
|
|
209
|
-
onSelectPath: draft.selectAt
|
|
210
|
-
// Booleans never reach this โ the dock applies their flip directly.
|
|
211
|
-
,
|
|
212
|
-
onEditValue: () => editor.openValueEditor(selection?.path ?? [])
|
|
213
|
-
// A merge write can rewrite a top-level key but never delete one, so
|
|
214
|
-
// Remove is disabled at depth 1 โ see `editableState.ts`.
|
|
215
|
-
,
|
|
216
|
-
rootEntriesLocked: true
|
|
217
|
-
}), draft.isDirty ? /*#__PURE__*/_jsxs(View, {
|
|
218
|
-
style: styles.commitRow,
|
|
219
|
-
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
220
|
-
onPress: draft.discard,
|
|
221
|
-
disabled: draft.isSaving,
|
|
222
|
-
testID: `${EDITOR_ID}-discard`,
|
|
223
|
-
accessibilityRole: "button",
|
|
224
|
-
accessibilityLabel: "Discard changes",
|
|
225
|
-
style: styles.discardButton,
|
|
226
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
227
|
-
style: styles.discardText,
|
|
228
|
-
children: "Discard"
|
|
229
|
-
})
|
|
230
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
231
|
-
onPress: editor.saveAndClose,
|
|
232
|
-
disabled: draft.isSaving,
|
|
233
|
-
testID: `${EDITOR_ID}-save`,
|
|
234
|
-
accessibilityRole: "button",
|
|
235
|
-
accessibilityLabel: "Save changes",
|
|
236
|
-
style: [styles.saveButton, draft.isSaving && styles.saveDisabled],
|
|
237
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
238
|
-
style: styles.saveText,
|
|
239
|
-
children: draft.isSaving ? "Savingโฆ" : "Save changes"
|
|
240
|
-
})
|
|
241
|
-
})]
|
|
242
|
-
}) : null]
|
|
243
|
-
}), valuePath ? /*#__PURE__*/_jsx(ValueEditModal, {
|
|
244
|
-
path: valuePath,
|
|
245
|
-
value: getAtPath(draft.value, valuePath),
|
|
246
|
-
onSave: editor.applyValue,
|
|
247
|
-
onCancel: editor.closeValueEditor
|
|
248
|
-
// It only reaches the draft here โ the single write happens on Save.
|
|
249
|
-
,
|
|
250
|
-
saveLabel: "Apply",
|
|
251
|
-
testIDPrefix: `${ZUSTAND_TEST_ID}-value`
|
|
252
|
-
}) : null]
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
const styles = StyleSheet.create({
|
|
256
|
-
screen: {
|
|
257
|
-
flex: 1,
|
|
258
|
-
// Opaque on purpose: transparency here showed the whole store list
|
|
259
|
-
// through the editor, not just the sheet's backdrop. The screen paints
|
|
260
|
-
// its own ToolBackground instead โ see the mount site above.
|
|
261
|
-
backgroundColor: night.color.bg
|
|
262
|
-
},
|
|
263
|
-
blockedText: {
|
|
264
|
-
color: night.color.textTertiary,
|
|
265
|
-
fontSize: 12,
|
|
266
|
-
padding: 20,
|
|
267
|
-
textAlign: "center",
|
|
268
|
-
lineHeight: 18
|
|
269
|
-
},
|
|
270
|
-
header: {
|
|
271
|
-
flexDirection: "row",
|
|
272
|
-
alignItems: "center",
|
|
273
|
-
gap: 8,
|
|
274
|
-
paddingHorizontal: 10,
|
|
275
|
-
paddingVertical: 8,
|
|
276
|
-
borderBottomWidth: 1,
|
|
277
|
-
borderBottomColor: night.color.border
|
|
278
|
-
},
|
|
279
|
-
headerButton: {
|
|
280
|
-
flexDirection: "row",
|
|
281
|
-
alignItems: "center",
|
|
282
|
-
gap: 4,
|
|
283
|
-
paddingVertical: 6,
|
|
284
|
-
paddingHorizontal: 4
|
|
285
|
-
},
|
|
286
|
-
headerRawText: {
|
|
287
|
-
color: night.color.accent,
|
|
288
|
-
fontSize: 12,
|
|
289
|
-
fontWeight: "700"
|
|
290
|
-
},
|
|
291
|
-
headerTitle: {
|
|
292
|
-
flex: 1,
|
|
293
|
-
alignItems: "center",
|
|
294
|
-
gap: 2
|
|
295
|
-
},
|
|
296
|
-
headerKey: {
|
|
297
|
-
color: night.color.text,
|
|
298
|
-
fontSize: 13,
|
|
299
|
-
fontWeight: "600",
|
|
300
|
-
fontFamily: monoFont
|
|
301
|
-
},
|
|
302
|
-
headerState: {
|
|
303
|
-
color: night.color.textTertiary,
|
|
304
|
-
fontSize: 10
|
|
305
|
-
},
|
|
306
|
-
headerDirty: {
|
|
307
|
-
color: night.color.warning
|
|
308
|
-
},
|
|
309
|
-
metaRow: {
|
|
310
|
-
flexDirection: "row",
|
|
311
|
-
alignItems: "center",
|
|
312
|
-
gap: 8,
|
|
313
|
-
paddingHorizontal: 12,
|
|
314
|
-
paddingVertical: 8
|
|
315
|
-
},
|
|
316
|
-
metaText: {
|
|
317
|
-
color: night.color.textTertiary,
|
|
318
|
-
fontSize: 11,
|
|
319
|
-
fontFamily: monoFont
|
|
320
|
-
},
|
|
321
|
-
treeScroll: {
|
|
322
|
-
flex: 1
|
|
323
|
-
},
|
|
324
|
-
treeContent: {
|
|
325
|
-
paddingHorizontal: 12,
|
|
326
|
-
paddingBottom: 16
|
|
327
|
-
},
|
|
328
|
-
treeCard: {
|
|
329
|
-
backgroundColor: night.color.surface,
|
|
330
|
-
borderRadius: 10,
|
|
331
|
-
borderWidth: 1,
|
|
332
|
-
borderColor: night.color.border,
|
|
333
|
-
padding: 6
|
|
334
|
-
},
|
|
335
|
-
treeHint: {
|
|
336
|
-
color: night.color.textTertiary,
|
|
337
|
-
fontSize: 10,
|
|
338
|
-
paddingTop: 8,
|
|
339
|
-
paddingHorizontal: 4
|
|
340
|
-
},
|
|
341
|
-
error: {
|
|
342
|
-
color: night.color.danger,
|
|
343
|
-
fontSize: 11,
|
|
344
|
-
fontFamily: monoFont,
|
|
345
|
-
paddingHorizontal: 12,
|
|
346
|
-
paddingBottom: 6
|
|
347
|
-
},
|
|
348
|
-
footer: {
|
|
349
|
-
backgroundColor: night.color.surface
|
|
350
|
-
},
|
|
351
|
-
commitRow: {
|
|
352
|
-
flexDirection: "row",
|
|
353
|
-
gap: 8,
|
|
354
|
-
paddingHorizontal: 12,
|
|
355
|
-
paddingVertical: 10,
|
|
356
|
-
borderTopWidth: 1,
|
|
357
|
-
borderTopColor: night.color.border
|
|
358
|
-
},
|
|
359
|
-
discardButton: {
|
|
360
|
-
flex: 1,
|
|
361
|
-
minHeight: 44,
|
|
362
|
-
alignItems: "center",
|
|
363
|
-
justifyContent: "center",
|
|
364
|
-
borderRadius: 10,
|
|
365
|
-
borderWidth: 1,
|
|
366
|
-
borderColor: night.color.border
|
|
367
|
-
},
|
|
368
|
-
discardText: {
|
|
369
|
-
color: night.color.textSecondary,
|
|
370
|
-
fontSize: 14,
|
|
371
|
-
fontWeight: "600"
|
|
372
|
-
},
|
|
373
|
-
saveButton: {
|
|
374
|
-
flex: 1.4,
|
|
375
|
-
minHeight: 44,
|
|
376
|
-
alignItems: "center",
|
|
377
|
-
justifyContent: "center",
|
|
378
|
-
borderRadius: 10,
|
|
379
|
-
backgroundColor: night.color.accent
|
|
380
|
-
},
|
|
381
|
-
saveDisabled: {
|
|
382
|
-
opacity: 0.5
|
|
383
|
-
},
|
|
384
|
-
saveText: {
|
|
385
|
-
color: night.color.onAccent,
|
|
386
|
-
fontSize: 15,
|
|
387
|
-
fontWeight: "700"
|
|
388
|
-
}
|
|
389
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Editing a store, on a screen of its own โ the Storage key editor's shape,
|
|
3
|
-
* pointed at a live zustand store.
|
|
4
|
-
*
|
|
5
|
-
* Same reasoning as storage's editor screen: a tree, six structural actions
|
|
6
|
-
* and a keyboard don't fit inside an expanded card, so the card offers ONE
|
|
7
|
-
* edit button and the work happens here with the whole viewport. The tree,
|
|
8
|
-
* dock, draft and value modal are the shared-ui editor โ one implementation,
|
|
9
|
-
* two tools.
|
|
10
|
-
*
|
|
11
|
-
* What is zustand-specific is the WRITE. Storage saves by replacing one key's
|
|
12
|
-
* serialized value; a store has no such luxury โ its state holds the app's
|
|
13
|
-
* action functions, and a full `setState(draft, true)` replace would delete
|
|
14
|
-
* them (see `editableState.ts`). So Save diffs the draft against the state it
|
|
15
|
-
* was seeded from and writes ONE top-level merge:
|
|
16
|
-
*
|
|
17
|
-
* store.api.setState(changedTopLevelKeys, false)
|
|
18
|
-
*
|
|
19
|
-
* which by construction cannot remove a key or touch a function. The two
|
|
20
|
-
* edits a merge cannot express โ removing or renaming a top-level key โ are
|
|
21
|
-
* disabled in the dock (`rootEntriesLocked`) and refused at save if they
|
|
22
|
-
* arrive through the raw-JSON escape hatch.
|
|
23
|
-
*
|
|
24
|
-
* On a desktop mirror the same line goes over the wire: the mirrored store's
|
|
25
|
-
* `api.setState` forwards to the device's sync adapter (the time-travel
|
|
26
|
-
* path). That write has no ack, so a remote save resolves optimistically and
|
|
27
|
-
* the next device snapshot is the confirmation.
|
|
28
|
-
*/
|
|
29
|
-
import type { ZustandStoreInfo } from "../types";
|
|
30
|
-
interface ZustandStoreEditorScreenProps {
|
|
31
|
-
store: ZustandStoreInfo;
|
|
32
|
-
/** Leave the editor. Called only once any unsaved work is resolved. */
|
|
33
|
-
onClose: () => void;
|
|
34
|
-
/**
|
|
35
|
-
* Pad the docked controls past the home indicator. On in a bottom sheet,
|
|
36
|
-
* whose edge is the screen's; off in a floating window, whose isn't.
|
|
37
|
-
*/
|
|
38
|
-
applySafeAreaInset?: boolean;
|
|
39
|
-
}
|
|
40
|
-
export declare function ZustandStoreEditorScreen({ store, onClose, applySafeAreaInset, }: ZustandStoreEditorScreenProps): import("react").JSX.Element;
|
|
41
|
-
export {};
|
|
42
|
-
//# sourceMappingURL=ZustandStoreEditorScreen.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ZustandStoreEditorScreen.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreEditorScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA6BH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAMjD,UAAU,6BAA6B;IACrC,KAAK,EAAE,gBAAgB,CAAC;IACxB,uEAAuE;IACvE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,OAAO,EACP,kBAAkB,GACnB,EAAE,6BAA6B,+BAsO/B"}
|