@buoy-gg/storage 7.0.4 → 7.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +77 -1
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
- package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
- package/lib/commonjs/storage/components/StorageActions.js +6 -6
- package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
- package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
- package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
- package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
- package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
- package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
- package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
- package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
- package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
- package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
- package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
- package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
- package/lib/commonjs/storage/index.js +29 -0
- package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
- package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
- package/lib/commonjs/storage/utils/index.js +13 -0
- package/lib/commonjs/storage/utils/lineDiff.js +21 -354
- package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
- package/lib/commonjs/storage/utils/storageOps.js +53 -0
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
- package/lib/commonjs/storage/utils/valueType.js +24 -0
- package/lib/module/index.js +18 -1
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
- package/lib/module/storage/components/GameUIStorageStats.js +7 -7
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/module/storage/components/SelectionActionBar.js +2 -36
- package/lib/module/storage/components/StorageActions.js +7 -7
- package/lib/module/storage/components/StorageBrowserMode.js +16 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
- package/lib/module/storage/components/StorageEventListener.js +14 -14
- package/lib/module/storage/components/StorageFilterCards.js +13 -135
- package/lib/module/storage/components/StorageKeyCard.js +1 -1
- package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
- package/lib/module/storage/components/StorageKeyRow.js +55 -17
- package/lib/module/storage/components/StorageKeySection.js +11 -5
- package/lib/module/storage/components/StorageKeyStats.js +11 -10
- package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
- package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
- package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
- package/lib/module/storage/components/StorageValueEditModal.js +357 -0
- package/lib/module/storage/hooks/useKeyEditor.js +100 -0
- package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
- package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
- package/lib/module/storage/hooks/useTreeDraft.js +103 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/stores/storageEventStore.js +4 -2
- package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
- package/lib/module/storage/utils/MMKVListener.js +40 -4
- package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
- package/lib/module/storage/utils/index.js +3 -0
- package/lib/module/storage/utils/lineDiff.js +7 -354
- package/lib/module/storage/utils/setStorageValue.js +209 -0
- package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
- package/lib/module/storage/utils/storageOps.js +46 -0
- package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
- package/lib/module/storage/utils/valueType.js +23 -0
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/storage/index.d.ts +2 -0
- package/lib/typescript/storage/index.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
- package/lib/typescript/storage/utils/index.d.ts +1 -0
- package/lib/typescript/storage/utils/index.d.ts.map +1 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
- package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
- package/lib/typescript/storage/utils/valueType.d.ts +13 -0
- package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.setStorageValue = setStorageValue;
|
|
7
|
+
exports.storageEditBlockedReason = storageEditBlockedReason;
|
|
8
|
+
exports.storageValueToEditableText = storageValueToEditableText;
|
|
9
|
+
exports.structuredStorageValue = structuredStorageValue;
|
|
10
|
+
exports.validateStorageValue = validateStorageValue;
|
|
11
|
+
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
12
|
+
var _SecureStoreRegistry = require("./SecureStoreRegistry");
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
/**
|
|
15
|
+
* Writing a storage value back, in one place.
|
|
16
|
+
*
|
|
17
|
+
* The counterpart to `deleteStorageKeys` — and the same reasoning applies: each
|
|
18
|
+
* backend writes differently, and a second copy of that fan-out is how one
|
|
19
|
+
* backend quietly starts corrupting values.
|
|
20
|
+
*
|
|
21
|
+
* The hard part is NOT the write, it's the types:
|
|
22
|
+
*
|
|
23
|
+
* - **AsyncStorage** stores strings only. A value that was stored as JSON must
|
|
24
|
+
* stay valid JSON, or an object silently becomes a quoted string and the app
|
|
25
|
+
* that reads it breaks in a way that looks nothing like a devtools edit.
|
|
26
|
+
* - **MMKV** is natively typed (string / number / boolean / buffer). Writing
|
|
27
|
+
* `"42"` over a number turns it into a string, so the original type is
|
|
28
|
+
* restored from `valueType`. Buffers can't round-trip at all — the snapshot
|
|
29
|
+
* only ever carried a `<ArrayBuffer n bytes>` placeholder.
|
|
30
|
+
* - **SecureStore** stores strings, and biometric-protected keys are never
|
|
31
|
+
* touched (writing needs the same auth prompt reading does).
|
|
32
|
+
*
|
|
33
|
+
* Pro gating and confirmation are deliberately NOT here; they belong to the UI
|
|
34
|
+
* that has the user's attention. This assumes the caller already asked.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Why a key can't be edited, or null when it can. UI uses this both to hide the
|
|
39
|
+
* edit affordance and to explain itself, rather than failing at save time.
|
|
40
|
+
*/
|
|
41
|
+
function storageEditBlockedReason(storageKey, mmkvInstances = []) {
|
|
42
|
+
if (storageKey.storageType === "mmkv") {
|
|
43
|
+
if (storageKey.valueType === "buffer") {
|
|
44
|
+
return "Buffer values can't be edited — the tool only ever sees their byte length, not their contents.";
|
|
45
|
+
}
|
|
46
|
+
const instance = mmkvInstances.find(i => i.id === (storageKey.instanceId || "default"));
|
|
47
|
+
if (instance?.readOnly) {
|
|
48
|
+
return `MMKV instance "${instance.id}" is registered read-only.`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (storageKey.storageType === "secure") {
|
|
52
|
+
const entry = _SecureStoreRegistry.secureStoreRegistry.getAll().find(e => e.key === storageKey.key);
|
|
53
|
+
if (entry?.options.requireAuthentication) {
|
|
54
|
+
return "Biometric-protected keys can't be edited — writing one would fire an auth prompt.";
|
|
55
|
+
}
|
|
56
|
+
if (!_SecureStoreRegistry.secureStoreRegistry.getModule()) {
|
|
57
|
+
return "No SecureStore module is registered.";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Validate `raw` for this key WITHOUT writing. Returns an error message, or
|
|
65
|
+
* null when the text is acceptable. The editor calls this as you type.
|
|
66
|
+
*/
|
|
67
|
+
function validateStorageValue(storageKey, raw) {
|
|
68
|
+
if (storageKey.storageType === "mmkv") {
|
|
69
|
+
const type = mmkvTargetType(storageKey);
|
|
70
|
+
if (type === "number") {
|
|
71
|
+
if (raw.trim() === "" || Number.isNaN(Number(raw))) {
|
|
72
|
+
return "Must be a number — this MMKV key is stored as a number.";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (type === "boolean") {
|
|
76
|
+
const t = raw.trim().toLowerCase();
|
|
77
|
+
if (t !== "true" && t !== "false") {
|
|
78
|
+
return 'Must be "true" or "false" — this MMKV key is stored as a boolean.';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// AsyncStorage + SecureStore are string-valued. Only guard the case where
|
|
85
|
+
// the CURRENT value is structured: silently flattening an object into a
|
|
86
|
+
// string is the corruption worth preventing. A plain string stays free-form.
|
|
87
|
+
if (isStructured(storageKey.value)) {
|
|
88
|
+
try {
|
|
89
|
+
JSON.parse(raw);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return `This key holds JSON — ${e instanceof Error ? e.message : "invalid JSON"}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The text an editor should open with: JSON pretty-printed so it's actually
|
|
99
|
+
* editable, everything else verbatim.
|
|
100
|
+
*/
|
|
101
|
+
function storageValueToEditableText(value) {
|
|
102
|
+
if (value === undefined || value === null) return "";
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
// Values arrive already parsed when they were JSON, so a string here is a
|
|
105
|
+
// real string — except AsyncStorage hands back raw JSON text sometimes.
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.stringify(JSON.parse(value), null, 2);
|
|
110
|
+
} catch {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
if (typeof value === "object") return JSON.stringify(value, null, 2);
|
|
117
|
+
return String(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The value as a STRUCTURE, for the tree editor to apply ops against.
|
|
122
|
+
*
|
|
123
|
+
* Backends disagree on how far they've already parsed: `useAsyncStorageKeys`
|
|
124
|
+
* hands back a parsed object, while MMKV reports its native type so a JSON blob
|
|
125
|
+
* arrives as the raw string. Both have to end up as the same thing before an op
|
|
126
|
+
* can address a path inside them.
|
|
127
|
+
*
|
|
128
|
+
* Returns `undefined` when the value isn't a container — scalars are edited
|
|
129
|
+
* through the plain value editor, not the tree.
|
|
130
|
+
*/
|
|
131
|
+
function structuredStorageValue(storageKey) {
|
|
132
|
+
const {
|
|
133
|
+
value
|
|
134
|
+
} = storageKey;
|
|
135
|
+
if (value !== null && typeof value === "object") return value;
|
|
136
|
+
if (typeof value === "string") {
|
|
137
|
+
const trimmed = value.trim();
|
|
138
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return undefined;
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(value);
|
|
141
|
+
return parsed !== null && typeof parsed === "object" ? parsed : undefined;
|
|
142
|
+
} catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Write `raw` to whichever backend holds this key, restoring the value's
|
|
151
|
+
* original type. Rejects (rather than writing something lossy) when the key
|
|
152
|
+
* isn't editable or the text doesn't fit the type — callers surface that.
|
|
153
|
+
*/
|
|
154
|
+
async function setStorageValue(storageKey, raw, mmkvInstances = []) {
|
|
155
|
+
const blocked = storageEditBlockedReason(storageKey, mmkvInstances);
|
|
156
|
+
if (blocked) throw new Error(blocked);
|
|
157
|
+
const invalid = validateStorageValue(storageKey, raw);
|
|
158
|
+
if (invalid) throw new Error(invalid);
|
|
159
|
+
switch (storageKey.storageType) {
|
|
160
|
+
case "async":
|
|
161
|
+
{
|
|
162
|
+
await _asyncStorage.default.setItem(storageKey.key, normalizeJsonText(storageKey, raw));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
case "secure":
|
|
166
|
+
{
|
|
167
|
+
await _SecureStoreRegistry.secureStoreRegistry.setValue(storageKey.key, normalizeJsonText(storageKey, raw));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
case "mmkv":
|
|
171
|
+
{
|
|
172
|
+
const instanceId = storageKey.instanceId || "default";
|
|
173
|
+
const target = mmkvInstances.find(i => i.id === instanceId);
|
|
174
|
+
if (!target) {
|
|
175
|
+
throw new Error(`MMKV instance "${instanceId}" is not registered.`);
|
|
176
|
+
}
|
|
177
|
+
target.instance.set(storageKey.key, coerceMMKV(storageKey, raw));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
{
|
|
182
|
+
throw new Error(`Unsupported storage type "${String(storageKey.storageType)}"`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** True when the value is an object/array (so its text must stay valid JSON). */
|
|
188
|
+
function isStructured(value) {
|
|
189
|
+
return value !== null && typeof value === "object";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Re-serialize JSON so the stored string is compact and canonical, matching
|
|
194
|
+
* what the app itself would have written with `JSON.stringify`. Free-form
|
|
195
|
+
* strings pass through untouched.
|
|
196
|
+
*/
|
|
197
|
+
function normalizeJsonText(storageKey, raw) {
|
|
198
|
+
if (!isStructured(storageKey.value)) return raw;
|
|
199
|
+
return JSON.stringify(JSON.parse(raw));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The MMKV native type to write back, defaulting to the current JS type. */
|
|
203
|
+
function mmkvTargetType(storageKey) {
|
|
204
|
+
if (storageKey.valueType) return storageKey.valueType;
|
|
205
|
+
if (typeof storageKey.value === "number") return "number";
|
|
206
|
+
if (typeof storageKey.value === "boolean") return "boolean";
|
|
207
|
+
return "string";
|
|
208
|
+
}
|
|
209
|
+
function coerceMMKV(storageKey, raw) {
|
|
210
|
+
switch (mmkvTargetType(storageKey)) {
|
|
211
|
+
case "number":
|
|
212
|
+
return Number(raw);
|
|
213
|
+
case "boolean":
|
|
214
|
+
return raw.trim().toLowerCase() === "true";
|
|
215
|
+
default:
|
|
216
|
+
return raw;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.onStorageBrowserRefresh = onStorageBrowserRefresh;
|
|
7
|
+
exports.requestStorageBrowserRefresh = requestStorageBrowserRefresh;
|
|
8
|
+
/**
|
|
9
|
+
* A "the underlying storage changed, re-read it" signal for the storage
|
|
10
|
+
* browser.
|
|
11
|
+
*
|
|
12
|
+
* On device the browser refreshes itself from the AsyncStorage/MMKV listeners,
|
|
13
|
+
* but those never fire in remote-mirror mode (the desktop dashboard suppresses
|
|
14
|
+
* local capture and receives the device's events as snapshots instead). Without
|
|
15
|
+
* this seam the browse tab is a one-shot read at mount — values stay frozen
|
|
16
|
+
* until the panel remounts, which is exactly the "shows old data until I switch
|
|
17
|
+
* tools and come back" behaviour.
|
|
18
|
+
*
|
|
19
|
+
* The dashboard calls {@link requestStorageBrowserRefresh} whenever a fresh
|
|
20
|
+
* device snapshot lands; the browser re-reads all three backends (debounced).
|
|
21
|
+
* It is a no-op when nothing is listening.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Subscribe to external refresh requests. Returns an unsubscribe function.
|
|
28
|
+
* @internal — used by the storage browser.
|
|
29
|
+
*/
|
|
30
|
+
function onStorageBrowserRefresh(listener) {
|
|
31
|
+
listeners.add(listener);
|
|
32
|
+
return () => {
|
|
33
|
+
listeners.delete(listener);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ask any mounted storage browser to re-read AsyncStorage, MMKV and
|
|
39
|
+
* SecureStore. Safe to call at any rate — the browser debounces.
|
|
40
|
+
*/
|
|
41
|
+
function requestStorageBrowserRefresh() {
|
|
42
|
+
listeners.forEach(listener => {
|
|
43
|
+
try {
|
|
44
|
+
listener();
|
|
45
|
+
} catch {
|
|
46
|
+
// Listener errors are not ours
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.STORAGE_OPS = void 0;
|
|
7
|
+
exports.isInitialScan = isInitialScan;
|
|
8
|
+
exports.storageOpDot = storageOpDot;
|
|
9
|
+
exports.storageOpOf = storageOpOf;
|
|
10
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
11
|
+
/**
|
|
12
|
+
* The five operations a storage event can be, and how to recognise one.
|
|
13
|
+
*
|
|
14
|
+
* AsyncStorage and MMKV each have their own action union, and neither maps
|
|
15
|
+
* cleanly onto what a reader wants to see — `multiRemove`, `removeItem` and
|
|
16
|
+
* `delete` are one idea wearing three names. These collapse both onto a single
|
|
17
|
+
* vocabulary so every surface labels, filters and colours events the same way.
|
|
18
|
+
*
|
|
19
|
+
* This is semantics, not presentation: it lives in `utils` rather than beside
|
|
20
|
+
* any one view, so a view can be deleted without taking the vocabulary with it.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const STORAGE_OPS = exports.STORAGE_OPS = ["SET", "GET", "MERGE", "DELETE", "CLEAR"];
|
|
24
|
+
const OP_DOT = {
|
|
25
|
+
SET: _sharedUi.macOSColors.semantic.info,
|
|
26
|
+
GET: _sharedUi.macOSColors.text.muted,
|
|
27
|
+
MERGE: _sharedUi.macOSColors.semantic.debug,
|
|
28
|
+
DELETE: _sharedUi.macOSColors.semantic.error,
|
|
29
|
+
CLEAR: _sharedUi.macOSColors.semantic.warning
|
|
30
|
+
};
|
|
31
|
+
function storageOpDot(op) {
|
|
32
|
+
return OP_DOT[op];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Both backends' action unions, collapsed onto the five display operations. */
|
|
36
|
+
function storageOpOf(event) {
|
|
37
|
+
const action = event.action;
|
|
38
|
+
if (action === "clear" || action === "clearAll") return "CLEAR";
|
|
39
|
+
if (action.startsWith("get.")) return "GET";
|
|
40
|
+
if (action === "removeItem" || action === "multiRemove" || action === "delete") return "DELETE";
|
|
41
|
+
if (action === "mergeItem" || action === "multiMerge") return "MERGE";
|
|
42
|
+
return "SET";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The store synthesises one event per existing key when it first subscribes, so
|
|
47
|
+
* the list isn't empty on open. Those are a SNAPSHOT, not a write: the key
|
|
48
|
+
* already held that value, so there is no "before" to diff against and showing
|
|
49
|
+
* one would invent a change that never happened.
|
|
50
|
+
*/
|
|
51
|
+
function isInitialScan(event) {
|
|
52
|
+
return Boolean(event.data?.initialScan);
|
|
53
|
+
}
|
|
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.canUndo = canUndo;
|
|
7
7
|
exports.jumpToState = jumpToState;
|
|
8
8
|
exports.undoOperation = undoOperation;
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
10
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
10
11
|
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
11
|
-
var _AsyncStorageListener = require("./AsyncStorageListener");
|
|
12
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
13
|
/**
|
|
14
14
|
* Storage Time-Travel Utilities
|
|
@@ -20,7 +20,13 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
20
20
|
* Undo a single storage operation
|
|
21
21
|
*
|
|
22
22
|
* Restores the previous value(s) before the operation occurred.
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* The restore is deliberately NOT capture-paused. Pausing it meant the write
|
|
25
|
+
* landed but emitted no event, so the modal's event list and current-value
|
|
26
|
+
* panel never changed — UNDO looked like a dead button even though it had
|
|
27
|
+
* worked. Letting the restore emit normally makes it visible and keeps the
|
|
28
|
+
* history honest about what the storage actually did. There is no feedback
|
|
29
|
+
* loop: a write emits an event, and an event never triggers a write.
|
|
24
30
|
*
|
|
25
31
|
* @param event - The storage event to undo
|
|
26
32
|
*/
|
|
@@ -29,10 +35,7 @@ async function undoOperation(event) {
|
|
|
29
35
|
action,
|
|
30
36
|
data
|
|
31
37
|
} = event;
|
|
32
|
-
|
|
33
|
-
// Pause capture to avoid creating events during restoration
|
|
34
|
-
(0, _AsyncStorageListener.pauseCapture)();
|
|
35
|
-
try {
|
|
38
|
+
{
|
|
36
39
|
switch (action) {
|
|
37
40
|
case "setItem":
|
|
38
41
|
case "mergeItem":
|
|
@@ -87,20 +90,21 @@ async function undoOperation(event) {
|
|
|
87
90
|
default:
|
|
88
91
|
throw new Error(`Cannot undo unknown action type: ${action}`);
|
|
89
92
|
}
|
|
90
|
-
} finally {
|
|
91
|
-
// Always resume capture, even if an error occurred
|
|
92
|
-
(0, _AsyncStorageListener.resumeCapture)();
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Jump to a specific point in storage history
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* Reconstructs the state as of `targetEventIndex` by replaying `events` up to
|
|
100
|
+
* that point, then reconciling ONLY the keys that timeline governs.
|
|
101
101
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
102
|
+
* This used to begin with `AsyncStorage.clear()`, which was badly out of
|
|
103
|
+
* proportion: the modal's JUMP button passes one key's history, so clearing
|
|
104
|
+
* wiped every unrelated app key — and Buoy's own `@react_buoy*` settings with
|
|
105
|
+
* them — to restore a single value. Now a jump touches exactly the keys that
|
|
106
|
+
* appear in the events it was given, and never Buoy's own keys. Like
|
|
107
|
+
* [undoOperation], the writes emit normally so the change is visible.
|
|
104
108
|
*
|
|
105
109
|
* @param events - All events in chronological order (oldest first)
|
|
106
110
|
* @param targetEventIndex - The index of the event to jump to (0-based)
|
|
@@ -110,34 +114,52 @@ async function jumpToState(events, targetEventIndex) {
|
|
|
110
114
|
throw new Error(`Invalid event index: ${targetEventIndex}`);
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Build up the state by applying each event up to and including the target
|
|
120
|
-
// We use the final values from each event, not the operations themselves
|
|
121
|
-
const stateMap = new Map();
|
|
122
|
-
for (let i = 0; i <= targetEventIndex; i++) {
|
|
123
|
-
const event = events[i];
|
|
124
|
-
applyEventToStateMap(event, stateMap);
|
|
125
|
-
}
|
|
117
|
+
// Build up the state by applying each event up to and including the target.
|
|
118
|
+
// We use the final values from each event, not the operations themselves.
|
|
119
|
+
const stateMap = new Map();
|
|
120
|
+
for (let i = 0; i <= targetEventIndex; i++) {
|
|
121
|
+
applyEventToStateMap(events[i], stateMap);
|
|
122
|
+
}
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
// Every key the WHOLE timeline touches — including events after the target,
|
|
125
|
+
// so a key created later is removed rather than left behind.
|
|
126
|
+
const governedKeys = new Set();
|
|
127
|
+
for (const event of events) {
|
|
128
|
+
collectEventKeys(event, governedKeys);
|
|
129
|
+
}
|
|
130
|
+
const pairsToSet = [];
|
|
131
|
+
const keysToRemove = [];
|
|
132
|
+
for (const key of governedKeys) {
|
|
133
|
+
// Buoy's own settings are never app state to travel through.
|
|
134
|
+
if ((0, _sharedUi.isDevToolsStorageKey)(key)) continue;
|
|
135
|
+
const value = stateMap.get(key);
|
|
136
|
+
if (value === undefined || value === null) {
|
|
137
|
+
keysToRemove.push(key);
|
|
138
|
+
} else {
|
|
139
|
+
pairsToSet.push([key, value]);
|
|
136
140
|
}
|
|
137
|
-
} finally {
|
|
138
|
-
// Always resume capture
|
|
139
|
-
(0, _AsyncStorageListener.resumeCapture)();
|
|
140
141
|
}
|
|
142
|
+
if (keysToRemove.length > 0) {
|
|
143
|
+
await (0, _asyncStorageCompat.removeMany)(keysToRemove);
|
|
144
|
+
}
|
|
145
|
+
if (pairsToSet.length > 0) {
|
|
146
|
+
await (0, _asyncStorageCompat.writeMany)(pairsToSet);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Collect every key an event refers to, across all action shapes — including
|
|
152
|
+
* `prevPairs`, so keys a `clear` removed are still governed by the timeline.
|
|
153
|
+
*/
|
|
154
|
+
function collectEventKeys(event, into) {
|
|
155
|
+
const {
|
|
156
|
+
data
|
|
157
|
+
} = event;
|
|
158
|
+
if (!data) return;
|
|
159
|
+
if (data.key) into.add(data.key);
|
|
160
|
+
if (data.keys) for (const key of data.keys) into.add(key);
|
|
161
|
+
if (data.pairs) for (const [key] of data.pairs) into.add(key);
|
|
162
|
+
if (data.prevPairs) for (const [key] of data.prevPairs) into.add(key);
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
/**
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.getValuePreview = getValuePreview;
|
|
7
7
|
exports.getValueTypeLabel = getValueTypeLabel;
|
|
8
8
|
exports.getValueTypeWithPreview = getValueTypeWithPreview;
|
|
9
|
+
exports.parseStorageValue = parseStorageValue;
|
|
9
10
|
/** Return a concise string describing the JavaScript type of the provided value. */
|
|
10
11
|
function getValueTypeLabel(value) {
|
|
11
12
|
if (value === null) return "null";
|
|
@@ -19,6 +20,29 @@ function getValueTypeLabel(value) {
|
|
|
19
20
|
return "unknown";
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A stored value as a tree can show it.
|
|
25
|
+
*
|
|
26
|
+
* AsyncStorage and SecureStore hold strings, so an object arrives as the text
|
|
27
|
+
* `{"a":1}` — displaying that as a string is technically true and useless. Any
|
|
28
|
+
* surface that renders or edits a value structurally has to do this same
|
|
29
|
+
* un-stringify first, so it lives here rather than being re-derived (slightly
|
|
30
|
+
* differently) in each one.
|
|
31
|
+
*
|
|
32
|
+
* A string that doesn't parse comes back untouched: it was just a string that
|
|
33
|
+
* happened to start with a brace.
|
|
34
|
+
*/
|
|
35
|
+
function parseStorageValue(value) {
|
|
36
|
+
if (typeof value !== "string") return value;
|
|
37
|
+
const trimmed = value.trim();
|
|
38
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(value);
|
|
41
|
+
} catch {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
22
46
|
/** Longest value preview rendered inline next to the type label. */
|
|
23
47
|
const MAX_INLINE_PREVIEW = 40;
|
|
24
48
|
|
package/lib/module/index.js
CHANGED
|
@@ -20,12 +20,19 @@ export { storageToolPreset, createStorageTool } from "./preset";
|
|
|
20
20
|
// COMPONENTS (For custom UI implementations)
|
|
21
21
|
// =============================================================================
|
|
22
22
|
export { StorageSection } from "./storage/components/StorageSection";
|
|
23
|
-
|
|
23
|
+
// `StorageToolView` is the same tool rendered inline (header bar + body +
|
|
24
|
+
// footer) instead of inside a JsModal — for hosts that show tools in a panel.
|
|
25
|
+
export { StorageModalWithTabs, StorageToolView } from "./storage/components/StorageModalWithTabs";
|
|
24
26
|
export { StorageKeyCard } from "./storage/components/StorageKeyCard";
|
|
25
27
|
export { StorageKeyStatsSection } from "./storage/components/StorageKeyStats";
|
|
26
28
|
export { StorageKeySection } from "./storage/components/StorageKeySection";
|
|
27
29
|
export { StorageBrowserMode } from "./storage/components/StorageBrowserMode";
|
|
30
|
+
// The backend filter, shared by the Keys and Events tabs.
|
|
31
|
+
export { StorageTypeFilterBar, countByStorageType } from "./storage/components/StorageTypeFilterBar";
|
|
28
32
|
export { StorageEventsSection } from "./storage/components/StorageEventsSection";
|
|
33
|
+
// The five display operations a storage event collapses onto — semantics, not
|
|
34
|
+
// presentation, so hosts can filter or group by the same vocabulary the UI uses.
|
|
35
|
+
export { STORAGE_OPS, storageOpOf, storageOpDot, isInitialScan } from "./storage/utils/storageOps";
|
|
29
36
|
export { StorageEventCard, getValueType } from "./storage/components/StorageEventCard";
|
|
30
37
|
export { StorageEventDetailContent, StorageEventDetailFooter } from "./storage/components/StorageEventDetailContent";
|
|
31
38
|
export { MMKVInstanceSelector } from "./storage/components/MMKVInstanceSelector";
|
|
@@ -47,6 +54,9 @@ export { useStorageEvents } from "./storage/hooks/useStorageEvents";
|
|
|
47
54
|
// UTILITIES (Public helpers only)
|
|
48
55
|
// =============================================================================
|
|
49
56
|
export { clearAllAppStorage } from "./storage/utils/clearAllStorage";
|
|
57
|
+
// Writing a value back, with the per-backend type rules in one place. Exported
|
|
58
|
+
// so a host with its own storage UI edits through the same path the tool does.
|
|
59
|
+
export { setStorageValue, storageEditBlockedReason, validateStorageValue, storageValueToEditableText } from "./storage/utils/setStorageValue";
|
|
50
60
|
export { isMMKVAvailable, getMMKVClass, getMMKVUnavailableMessage } from "./storage/utils/mmkvAvailability";
|
|
51
61
|
export { detectMMKVType, formatMMKVValue, isTypeMatch } from "./storage/utils/mmkvTypeDetection";
|
|
52
62
|
export { undoOperation, jumpToState, canUndo } from "./storage/utils/storageTimeTravelUtils";
|
|
@@ -70,6 +80,13 @@ export { storageSyncAdapter } from "./storage/sync/storageSyncAdapter";
|
|
|
70
80
|
// =============================================================================
|
|
71
81
|
/** @internal */
|
|
72
82
|
export { storageEventStore } from "./storage/stores/storageEventStore";
|
|
83
|
+
/**
|
|
84
|
+
* Tell a mounted storage browser to re-read every backend. Used by the desktop
|
|
85
|
+
* dashboard, where local storage listeners are suppressed and device data
|
|
86
|
+
* arrives as snapshots instead.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
export { requestStorageBrowserRefresh } from "./storage/utils/storageBrowserRefresh";
|
|
73
90
|
|
|
74
91
|
// =============================================================================
|
|
75
92
|
// NOT EXPORTED (Internal capture controls)
|
|
@@ -6,14 +6,16 @@ import { TreeDiffViewer as SharedTreeDiffViewer } from "@buoy-gg/shared-ui/dataV
|
|
|
6
6
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
7
|
export function TreeDiffViewer({
|
|
8
8
|
oldValue,
|
|
9
|
-
newValue
|
|
9
|
+
newValue,
|
|
10
|
+
scrollEnabled = true
|
|
10
11
|
}) {
|
|
11
12
|
return /*#__PURE__*/_jsx(View, {
|
|
12
|
-
style: styles.container,
|
|
13
|
+
style: scrollEnabled ? styles.container : styles.containerInline,
|
|
13
14
|
children: /*#__PURE__*/_jsx(SharedTreeDiffViewer, {
|
|
14
15
|
oldValue: oldValue,
|
|
15
16
|
newValue: newValue,
|
|
16
|
-
theme: "dark"
|
|
17
|
+
theme: "dark",
|
|
18
|
+
scrollEnabled: scrollEnabled
|
|
17
19
|
})
|
|
18
20
|
});
|
|
19
21
|
}
|
|
@@ -21,5 +23,10 @@ const styles = StyleSheet.create({
|
|
|
21
23
|
container: {
|
|
22
24
|
flex: 1,
|
|
23
25
|
backgroundColor: gameUIColors.background
|
|
26
|
+
},
|
|
27
|
+
// Inline: size to the tree so the page scroll reveals it, rather than
|
|
28
|
+
// stretching to the pane and clipping the rows.
|
|
29
|
+
containerInline: {
|
|
30
|
+
backgroundColor: gameUIColors.background
|
|
24
31
|
}
|
|
25
32
|
});
|
|
@@ -18,9 +18,12 @@ export function ThemedSplitView({
|
|
|
18
18
|
newValue,
|
|
19
19
|
theme,
|
|
20
20
|
options = DEFAULT_OPTIONS,
|
|
21
|
-
showThemeName = false
|
|
21
|
+
showThemeName = false,
|
|
22
|
+
scrollEnabled = true
|
|
22
23
|
}) {
|
|
23
|
-
|
|
24
|
+
// Inline mode drops the fixed 400pt pane so the container grows with the
|
|
25
|
+
// rows; the ancestor ScrollView is what scrolls them.
|
|
26
|
+
const dynamicStyles = useMemo(() => createDynamicStyles(theme, scrollEnabled), [theme, scrollEnabled]);
|
|
24
27
|
const diffComputeOptions = useMemo(() => ({
|
|
25
28
|
compareMethod: options.compareMethod,
|
|
26
29
|
disableWordDiff: options.disableWordDiff,
|
|
@@ -69,9 +72,7 @@ export function ThemedSplitView({
|
|
|
69
72
|
});
|
|
70
73
|
}, []);
|
|
71
74
|
const keyExtractor = useCallback(item => item.kind === "row" ? `r:${item.diffIndex}` : `f:${item.foldId}`, []);
|
|
72
|
-
const
|
|
73
|
-
item
|
|
74
|
-
}) => {
|
|
75
|
+
const renderSplitItem = useCallback(item => {
|
|
75
76
|
if (item.kind === "fold") {
|
|
76
77
|
return /*#__PURE__*/_jsx(FoldHeader, {
|
|
77
78
|
foldId: item.foldId,
|
|
@@ -90,6 +91,9 @@ export function ThemedSplitView({
|
|
|
90
91
|
styles: dynamicStyles
|
|
91
92
|
});
|
|
92
93
|
}, [theme, options, dynamicStyles, toggleFold]);
|
|
94
|
+
const renderItem = useCallback(({
|
|
95
|
+
item
|
|
96
|
+
}) => renderSplitItem(item), [renderSplitItem]);
|
|
93
97
|
return /*#__PURE__*/_jsxs(View, {
|
|
94
98
|
style: [dynamicStyles.container, theme.glowColor && {
|
|
95
99
|
shadowColor: theme.glowColor,
|
|
@@ -137,7 +141,7 @@ export function ThemedSplitView({
|
|
|
137
141
|
style: dynamicStyles.emptyText,
|
|
138
142
|
children: options.showDiffOnly ? "No differences found" : "No content to display"
|
|
139
143
|
})
|
|
140
|
-
}) : /*#__PURE__*/_jsx(FlatList, {
|
|
144
|
+
}) : scrollEnabled ? /*#__PURE__*/_jsx(FlatList, {
|
|
141
145
|
style: dynamicStyles.scrollView,
|
|
142
146
|
contentContainerStyle: dynamicStyles.scrollContent,
|
|
143
147
|
data: items,
|
|
@@ -148,6 +152,16 @@ export function ThemedSplitView({
|
|
|
148
152
|
maxToRenderPerBatch: 20,
|
|
149
153
|
windowSize: 7,
|
|
150
154
|
removeClippedSubviews: true
|
|
155
|
+
}) :
|
|
156
|
+
/*#__PURE__*/
|
|
157
|
+
// A FlatList can't virtualize inside a page ScrollView — it has no
|
|
158
|
+
// viewport of its own to measure against — so render the rows directly.
|
|
159
|
+
// Folding already keeps this list short (unchanged runs collapse).
|
|
160
|
+
_jsx(View, {
|
|
161
|
+
style: dynamicStyles.scrollContent,
|
|
162
|
+
children: items.map(item => /*#__PURE__*/_jsx(Fragment, {
|
|
163
|
+
children: renderSplitItem(item)
|
|
164
|
+
}, keyExtractor(item)))
|
|
151
165
|
})]
|
|
152
166
|
});
|
|
153
167
|
}
|
|
@@ -399,10 +413,14 @@ function getDiffColors(type, theme) {
|
|
|
399
413
|
}
|
|
400
414
|
|
|
401
415
|
// Create dynamic styles based on theme
|
|
402
|
-
function createDynamicStyles(theme) {
|
|
416
|
+
function createDynamicStyles(theme, scrollEnabled) {
|
|
403
417
|
return StyleSheet.create({
|
|
404
418
|
container: {
|
|
405
|
-
|
|
419
|
+
// Fixed pane only when the rows scroll themselves; inline mode sizes to
|
|
420
|
+
// content so the page scroll reveals it.
|
|
421
|
+
...(scrollEnabled ? {
|
|
422
|
+
height: 400
|
|
423
|
+
} : null),
|
|
406
424
|
backgroundColor: theme.background,
|
|
407
425
|
borderRadius: 8,
|
|
408
426
|
overflow: "hidden",
|